Recon Coverage Report 65% coverage
Files 33 files
    Files
    33
    Lines
    3388
    Coverage
    65%
    677 / 1030
    Actions
    66% lib/chimera/src/CryticAsserts.sol
    Lines covered: 8 / 12 (66%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Asserts} from "./Asserts.sol";
    5
    6 0
    contract CryticAsserts is Asserts {
    7
        event Log(string);
    8
    9
        function gt(uint256 a, uint256 b, string memory reason) internal virtual override {
    10
            if (!(a > b)) {
    11
                emit Log(reason);
    12
                assert(false);
    13
            }
    14
        }
    15
    16
        function gte(uint256 a, uint256 b, string memory reason) internal virtual override {
    17
            if (!(a >= b)) {
    18
                emit Log(reason);
    19
                assert(false);
    20
            }
    21
        }
    22
    23
        function lt(uint256 a, uint256 b, string memory reason) internal virtual override {
    24
            if (!(a < b)) {
    25
                emit Log(reason);
    26
                assert(false);
    27
            }
    28
        }
    29
    30 0
        function lte(uint256 a, uint256 b, string memory reason) internal virtual override {
    31 0
            if (!(a <= b)) {
    32 0
                emit Log(reason);
    33
                assert(false);
    34
            }
    35
        }
    36
    37
        function eq(uint256 a, uint256 b, string memory reason) internal virtual override {
    38
            if (!(a == b)) {
    39 17×
                emit Log(reason);
    40
                assert(false);
    41
            }
    42
        }
    43
    44
        function t(bool b, string memory reason) internal virtual override {
    45
            if (!b) {
    46 17×
                emit Log(reason);
    47
                assert(false);
    48
            }
    49
        }
    50
    51
        function between(uint256 value, uint256 low, uint256 high) internal virtual override returns (uint256) {
    52
            if (value < low || value > high) {
    53
                uint256 ans = low + (value % (high - low + 1));
    54
                return ans;
    55
            }
    56
            return value;
    57
        }
    58
    59
        function between(int256 value, int256 low, int256 high) internal virtual override returns (int256) {
    60
            if (value < low || value > high) {
    61
                int256 range = high - low + 1;
    62
                int256 clamped = (value - low) % (range);
    63
                if (clamped < 0) clamped += range;
    64
                int256 ans = low + clamped;
    65
                return ans;
    66
            }
    67
            return value;
    68
        }
    69
    70
        function precondition(bool p) internal virtual override {
    71
            require(p);
    72
        }
    73
    }
    100% lib/chimera/src/Hevm.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.0;
    3
    4
    // slither-disable-start shadowing-local
    5
    6
    interface IHevm {
    7
        // Set block.timestamp to newTimestamp
    8
        function warp(uint256 newTimestamp) external;
    9
    10
        // Set block.number to newNumber
    11
        function roll(uint256 newNumber) external;
    12
    13
        // Add the condition b to the assumption base for the current branch
    14
        // This function is almost identical to require
    15
        function assume(bool b) external;
    16
    17
        // Sets the eth balance of usr to amt
    18
        function deal(address usr, uint256 amt) external;
    19
    20
        // Loads a storage slot from an address
    21
        function load(address where, bytes32 slot) external returns (bytes32);
    22
    23
        // Stores a value to an address' storage slot
    24
        function store(address where, bytes32 slot, bytes32 value) external;
    25
    26
        // Signs data (privateKey, digest) => (v, r, s)
    27
        function sign(uint256 privateKey, bytes32 digest) external returns (uint8 v, bytes32 r, bytes32 s);
    28
    29
        // Gets address for a given private key
    30
        function addr(uint256 privateKey) external returns (address addr);
    31
    32
        // Performs a foreign function call via terminal
    33
        function ffi(string[] calldata inputs) external returns (bytes memory result);
    34
    35
        // Performs the next smart contract call with specified `msg.sender`
    36
        function prank(address newSender) external;
    37
    38
        // Creates a new fork with the given endpoint and the latest block and returns the identifier of the fork
    39
        function createFork(string calldata urlOrAlias) external returns (uint256);
    40
    41
        // Takes a fork identifier created by createFork and sets the corresponding forked state as active
    42
        function selectFork(uint256 forkId) external;
    43
    44
        // Returns the identifier of the current fork
    45
        function activeFork() external returns (uint256);
    46
    47
        // Labels the address in traces
    48
        function label(address addr, string calldata label) external;
    49
    50
        /// Sets an address' code.
    51
        function etch(address target, bytes calldata newRuntimeBytecode) external;
    52
    }
    53
    54
    IHevm constant vm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    55
    56
    // slither-disable-end shadowing-local
    100% lib/forge-std/src/StdChains.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    import {VmSafe} from "./Vm.sol";
    5
    6
    /**
    7
     * StdChains provides information about EVM compatible chains that can be used in scripts/tests.
    8
     * For each chain, the chain's name, chain ID, and a default RPC URL are provided. Chains are
    9
     * identified by their alias, which is the same as the alias in the `[rpc_endpoints]` section of
    10
     * the `foundry.toml` file. For best UX, ensure the alias in the `foundry.toml` file match the
    11
     * alias used in this contract, which can be found as the first argument to the
    12
     * `setChainWithDefaultRpcUrl` call in the `initializeStdChains` function.
    13
     *
    14
     * There are two main ways to use this contract:
    15
     *   1. Set a chain with `setChain(string memory chainAlias, ChainData memory chain)` or
    16
     *      `setChain(string memory chainAlias, Chain memory chain)`
    17
     *   2. Get a chain with `getChain(string memory chainAlias)` or `getChain(uint256 chainId)`.
    18
     *
    19
     * The first time either of those are used, chains are initialized with the default set of RPC URLs.
    20
     * This is done in `initializeStdChains`, which uses `setChainWithDefaultRpcUrl`. Defaults are recorded in
    21
     * `defaultRpcUrls`.
    22
     *
    23
     * The `setChain` function is straightforward, and it simply saves off the given chain data.
    24
     *
    25
     * The `getChain` methods use `getChainWithUpdatedRpcUrl` to return a chain. For example, let's say
    26
     * we want to retrieve the RPC URL for `mainnet`:
    27
     *   - If you have specified data with `setChain`, it will return that.
    28
     *   - If you have configured a mainnet RPC URL in `foundry.toml`, it will return the URL, provided it
    29
     *     is valid (e.g. a URL is specified, or an environment variable is given and exists).
    30
     *   - If neither of the above conditions is met, the default data is returned.
    31
     *
    32
     * Summarizing the above, the prioritization hierarchy is `setChain` -> `foundry.toml` -> environment variable -> defaults.
    33
     */
    34
    abstract contract StdChains {
    35
        VmSafe private constant vm = VmSafe(address(uint160(uint256(keccak256("hevm cheat code")))));
    36
    37
        bool private stdChainsInitialized;
    38
    39
        struct ChainData {
    40
            string name;
    41
            uint256 chainId;
    42
            string rpcUrl;
    43
        }
    44
    45
        struct Chain {
    46
            // The chain name.
    47
            string name;
    48
            // The chain's Chain ID.
    49
            uint256 chainId;
    50
            // The chain's alias. (i.e. what gets specified in `foundry.toml`).
    51
            string chainAlias;
    52
            // A default RPC endpoint for this chain.
    53
            // NOTE: This default RPC URL is included for convenience to facilitate quick tests and
    54
            // experimentation. Do not use this RPC URL for production test suites, CI, or other heavy
    55
            // usage as you will be throttled and this is a disservice to others who need this endpoint.
    56
            string rpcUrl;
    57
        }
    58
    59
        // Maps from the chain's alias (matching the alias in the `foundry.toml` file) to chain data.
    60
        mapping(string => Chain) private chains;
    61
        // Maps from the chain's alias to it's default RPC URL.
    62
        mapping(string => string) private defaultRpcUrls;
    63
        // Maps from a chain ID to it's alias.
    64
        mapping(uint256 => string) private idToAlias;
    65
    66 13×
        bool private fallbackToDefaultRpcUrls = true;
    67
    68
        // The RPC URL will be fetched from config or defaultRpcUrls if possible.
    69
        function getChain(string memory chainAlias) internal virtual returns (Chain memory chain) {
    70
            require(bytes(chainAlias).length != 0, "StdChains getChain(string): Chain alias cannot be the empty string.");
    71
    72
            initializeStdChains();
    73
            chain = chains[chainAlias];
    74
            require(
    75
                chain.chainId != 0,
    76
                string(abi.encodePacked("StdChains getChain(string): Chain with alias \"", chainAlias, "\" not found."))
    77
            );
    78
    79
            chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
    80
        }
    81
    82
        function getChain(uint256 chainId) internal virtual returns (Chain memory chain) {
    83
            require(chainId != 0, "StdChains getChain(uint256): Chain ID cannot be 0.");
    84
            initializeStdChains();
    85
            string memory chainAlias = idToAlias[chainId];
    86
    87
            chain = chains[chainAlias];
    88
    89
            require(
    90
                chain.chainId != 0,
    91
                string(abi.encodePacked("StdChains getChain(uint256): Chain with ID ", vm.toString(chainId), " not found."))
    92
            );
    93
    94
            chain = getChainWithUpdatedRpcUrl(chainAlias, chain);
    95
        }
    96
    97
        // set chain info, with priority to argument's rpcUrl field.
    98
        function setChain(string memory chainAlias, ChainData memory chain) internal virtual {
    99
            require(
    100
                bytes(chainAlias).length != 0,
    101
                "StdChains setChain(string,ChainData): Chain alias cannot be the empty string."
    102
            );
    103
    104
            require(chain.chainId != 0, "StdChains setChain(string,ChainData): Chain ID cannot be 0.");
    105
    106
            initializeStdChains();
    107
            string memory foundAlias = idToAlias[chain.chainId];
    108
    109
            require(
    110
                bytes(foundAlias).length == 0 || keccak256(bytes(foundAlias)) == keccak256(bytes(chainAlias)),
    111
                string(
    112
                    abi.encodePacked(
    113
                        "StdChains setChain(string,ChainData): Chain ID ",
    114
                        vm.toString(chain.chainId),
    115
                        " already used by \"",
    116
                        foundAlias,
    117
                        "\"."
    118
                    )
    119
                )
    120
            );
    121
    122
            uint256 oldChainId = chains[chainAlias].chainId;
    123
            delete idToAlias[oldChainId];
    124
    125
            chains[chainAlias] =
    126
                Chain({name: chain.name, chainId: chain.chainId, chainAlias: chainAlias, rpcUrl: chain.rpcUrl});
    127
            idToAlias[chain.chainId] = chainAlias;
    128
        }
    129
    130
        // set chain info, with priority to argument's rpcUrl field.
    131
        function setChain(string memory chainAlias, Chain memory chain) internal virtual {
    132
            setChain(chainAlias, ChainData({name: chain.name, chainId: chain.chainId, rpcUrl: chain.rpcUrl}));
    133
        }
    134
    135
        function _toUpper(string memory str) private pure returns (string memory) {
    136
            bytes memory strb = bytes(str);
    137
            bytes memory copy = new bytes(strb.length);
    138
            for (uint256 i = 0; i < strb.length; i++) {
    139
                bytes1 b = strb[i];
    140
                if (b >= 0x61 && b <= 0x7A) {
    141
                    copy[i] = bytes1(uint8(b) - 32);
    142
                } else {
    143
                    copy[i] = b;
    144
                }
    145
            }
    146
            return string(copy);
    147
        }
    148
    149
        // lookup rpcUrl, in descending order of priority:
    150
        // current -> config (foundry.toml) -> environment variable -> default
    151
        function getChainWithUpdatedRpcUrl(string memory chainAlias, Chain memory chain)
    152
            private
    153
            view
    154
            returns (Chain memory)
    155
        {
    156
            if (bytes(chain.rpcUrl).length == 0) {
    157
                try vm.rpcUrl(chainAlias) returns (string memory configRpcUrl) {
    158
                    chain.rpcUrl = configRpcUrl;
    159
                } catch (bytes memory err) {
    160
                    string memory envName = string(abi.encodePacked(_toUpper(chainAlias), "_RPC_URL"));
    161
                    if (fallbackToDefaultRpcUrls) {
    162
                        chain.rpcUrl = vm.envOr(envName, defaultRpcUrls[chainAlias]);
    163
                    } else {
    164
                        chain.rpcUrl = vm.envString(envName);
    165
                    }
    166
                    // Distinguish 'not found' from 'cannot read'
    167
                    // The upstream error thrown by forge for failing cheats changed so we check both the old and new versions
    168
                    bytes memory oldNotFoundError =
    169
                        abi.encodeWithSignature("CheatCodeError", string(abi.encodePacked("invalid rpc url ", chainAlias)));
    170
                    bytes memory newNotFoundError = abi.encodeWithSignature(
    171
                        "CheatcodeError(string)", string(abi.encodePacked("invalid rpc url: ", chainAlias))
    172
                    );
    173
                    bytes32 errHash = keccak256(err);
    174
                    if (
    175
                        (errHash != keccak256(oldNotFoundError) && errHash != keccak256(newNotFoundError))
    176
                            || bytes(chain.rpcUrl).length == 0
    177
                    ) {
    178
                        /// @solidity memory-safe-assembly
    179
                        assembly {
    180
                            revert(add(32, err), mload(err))
    181
                        }
    182
                    }
    183
                }
    184
            }
    185
            return chain;
    186
        }
    187
    188
        function setFallbackToDefaultRpcUrls(bool useDefault) internal {
    189
            fallbackToDefaultRpcUrls = useDefault;
    190
        }
    191
    192
        function initializeStdChains() private {
    193
            if (stdChainsInitialized) return;
    194
    195
            stdChainsInitialized = true;
    196
    197
            // If adding an RPC here, make sure to test the default RPC URL in `testRpcs`
    198
            setChainWithDefaultRpcUrl("anvil", ChainData("Anvil", 31337, "http://127.0.0.1:8545"));
    199
            setChainWithDefaultRpcUrl(
    200
                "mainnet", ChainData("Mainnet", 1, "https://eth-mainnet.alchemyapi.io/v2/pwc5rmJhrdoaSEfimoKEmsvOjKSmPDrP")
    201
            );
    202
            setChainWithDefaultRpcUrl(
    203
                "sepolia", ChainData("Sepolia", 11155111, "https://sepolia.infura.io/v3/b9794ad1ddf84dfb8c34d6bb5dca2001")
    204
            );
    205
            setChainWithDefaultRpcUrl("holesky", ChainData("Holesky", 17000, "https://rpc.holesky.ethpandaops.io"));
    206
            setChainWithDefaultRpcUrl("optimism", ChainData("Optimism", 10, "https://mainnet.optimism.io"));
    207
            setChainWithDefaultRpcUrl(
    208
                "optimism_sepolia", ChainData("Optimism Sepolia", 11155420, "https://sepolia.optimism.io")
    209
            );
    210
            setChainWithDefaultRpcUrl("arbitrum_one", ChainData("Arbitrum One", 42161, "https://arb1.arbitrum.io/rpc"));
    211
            setChainWithDefaultRpcUrl(
    212
                "arbitrum_one_sepolia", ChainData("Arbitrum One Sepolia", 421614, "https://sepolia-rollup.arbitrum.io/rpc")
    213
            );
    214
            setChainWithDefaultRpcUrl("arbitrum_nova", ChainData("Arbitrum Nova", 42170, "https://nova.arbitrum.io/rpc"));
    215
            setChainWithDefaultRpcUrl("polygon", ChainData("Polygon", 137, "https://polygon-rpc.com"));
    216
            setChainWithDefaultRpcUrl(
    217
                "polygon_amoy", ChainData("Polygon Amoy", 80002, "https://rpc-amoy.polygon.technology")
    218
            );
    219
            setChainWithDefaultRpcUrl("avalanche", ChainData("Avalanche", 43114, "https://api.avax.network/ext/bc/C/rpc"));
    220
            setChainWithDefaultRpcUrl(
    221
                "avalanche_fuji", ChainData("Avalanche Fuji", 43113, "https://api.avax-test.network/ext/bc/C/rpc")
    222
            );
    223
            setChainWithDefaultRpcUrl(
    224
                "bnb_smart_chain", ChainData("BNB Smart Chain", 56, "https://bsc-dataseed1.binance.org")
    225
            );
    226
            setChainWithDefaultRpcUrl(
    227
                "bnb_smart_chain_testnet",
    228
                ChainData("BNB Smart Chain Testnet", 97, "https://rpc.ankr.com/bsc_testnet_chapel")
    229
            );
    230
            setChainWithDefaultRpcUrl("gnosis_chain", ChainData("Gnosis Chain", 100, "https://rpc.gnosischain.com"));
    231
            setChainWithDefaultRpcUrl("moonbeam", ChainData("Moonbeam", 1284, "https://rpc.api.moonbeam.network"));
    232
            setChainWithDefaultRpcUrl(
    233
                "moonriver", ChainData("Moonriver", 1285, "https://rpc.api.moonriver.moonbeam.network")
    234
            );
    235
            setChainWithDefaultRpcUrl("moonbase", ChainData("Moonbase", 1287, "https://rpc.testnet.moonbeam.network"));
    236
            setChainWithDefaultRpcUrl("base_sepolia", ChainData("Base Sepolia", 84532, "https://sepolia.base.org"));
    237
            setChainWithDefaultRpcUrl("base", ChainData("Base", 8453, "https://mainnet.base.org"));
    238
            setChainWithDefaultRpcUrl("fraxtal", ChainData("Fraxtal", 252, "https://rpc.frax.com"));
    239
            setChainWithDefaultRpcUrl("fraxtal_testnet", ChainData("Fraxtal Testnet", 2522, "https://rpc.testnet.frax.com"));
    240
        }
    241
    242
        // set chain info, with priority to chainAlias' rpc url in foundry.toml
    243
        function setChainWithDefaultRpcUrl(string memory chainAlias, ChainData memory chain) private {
    244
            string memory rpcUrl = chain.rpcUrl;
    245
            defaultRpcUrls[chainAlias] = rpcUrl;
    246
            chain.rpcUrl = "";
    247
            setChain(chainAlias, chain);
    248
            chain.rpcUrl = rpcUrl; // restore argument
    249
        }
    250
    }
    20% lib/forge-std/src/StdInvariant.sol
    Lines covered: 4 / 20 (20%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    pragma experimental ABIEncoderV2;
    5
    6
    abstract contract StdInvariant {
    7
        struct FuzzSelector {
    8
            address addr;
    9
            bytes4[] selectors;
    10
        }
    11
    12
        struct FuzzArtifactSelector {
    13
            string artifact;
    14
            bytes4[] selectors;
    15
        }
    16
    17
        struct FuzzInterface {
    18
            address addr;
    19
            string[] artifacts;
    20
        }
    21
    22
        address[] private _excludedContracts;
    23
        address[] private _excludedSenders;
    24
        address[] private _targetedContracts;
    25
        address[] private _targetedSenders;
    26
    27
        string[] private _excludedArtifacts;
    28
        string[] private _targetedArtifacts;
    29
    30
        FuzzArtifactSelector[] private _targetedArtifactSelectors;
    31
    32
        FuzzSelector[] private _targetedSelectors;
    33
    34
        FuzzInterface[] private _targetedInterfaces;
    35
    36
        // Functions for users:
    37
        // These are intended to be called in tests.
    38
    39
        function excludeContract(address newExcludedContract_) internal {
    40
            _excludedContracts.push(newExcludedContract_);
    41
        }
    42
    43
        function excludeSender(address newExcludedSender_) internal {
    44
            _excludedSenders.push(newExcludedSender_);
    45
        }
    46
    47
        function excludeArtifact(string memory newExcludedArtifact_) internal {
    48
            _excludedArtifacts.push(newExcludedArtifact_);
    49
        }
    50
    51
        function targetArtifact(string memory newTargetedArtifact_) internal {
    52
            _targetedArtifacts.push(newTargetedArtifact_);
    53
        }
    54
    55
        function targetArtifactSelector(FuzzArtifactSelector memory newTargetedArtifactSelector_) internal {
    56
            _targetedArtifactSelectors.push(newTargetedArtifactSelector_);
    57
        }
    58
    59
        function targetContract(address newTargetedContract_) internal {
    60 0
            _targetedContracts.push(newTargetedContract_);
    61
        }
    62
    63
        function targetSelector(FuzzSelector memory newTargetedSelector_) internal {
    64
            _targetedSelectors.push(newTargetedSelector_);
    65
        }
    66
    67
        function targetSender(address newTargetedSender_) internal {
    68 0
            _targetedSenders.push(newTargetedSender_);
    69
        }
    70
    71
        function targetInterface(FuzzInterface memory newTargetedInterface_) internal {
    72
            _targetedInterfaces.push(newTargetedInterface_);
    73
        }
    74
    75
        // Functions for forge:
    76
        // These are called by forge to run invariant tests and don't need to be called in tests.
    77
    78 0
        function excludeArtifacts() public view returns (string[] memory excludedArtifacts_) {
    79 0
            excludedArtifacts_ = _excludedArtifacts;
    80
        }
    81
    82 0
        function excludeContracts() public view returns (address[] memory excludedContracts_) {
    83 0
            excludedContracts_ = _excludedContracts;
    84
        }
    85
    86 0
        function excludeSenders() public view returns (address[] memory excludedSenders_) {
    87 0
            excludedSenders_ = _excludedSenders;
    88
        }
    89
    90 0
        function targetArtifacts() public view returns (string[] memory targetedArtifacts_) {
    91 0
            targetedArtifacts_ = _targetedArtifacts;
    92
        }
    93
    94 14×
        function targetArtifactSelectors() public view returns (FuzzArtifactSelector[] memory targetedArtifactSelectors_) {
    95 33×
            targetedArtifactSelectors_ = _targetedArtifactSelectors;
    96
        }
    97
    98 0
        function targetContracts() public view returns (address[] memory targetedContracts_) {
    99 0
            targetedContracts_ = _targetedContracts;
    100
        }
    101
    102 0
        function targetSelectors() public view returns (FuzzSelector[] memory targetedSelectors_) {
    103 0
            targetedSelectors_ = _targetedSelectors;
    104
        }
    105
    106 0
        function targetSenders() public view returns (address[] memory targetedSenders_) {
    107 0
            targetedSenders_ = _targetedSenders;
    108
        }
    109
    110
        function targetInterfaces() public view returns (FuzzInterface[] memory targetedInterfaces_) {
    111
            targetedInterfaces_ = _targetedInterfaces;
    112
        }
    113
    }
    100% lib/forge-std/src/Test.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    pragma experimental ABIEncoderV2;
    5
    6
    // 💬 ABOUT
    7
    // Forge Std's default Test.
    8
    9
    // 🧩 MODULES
    10
    import {console} from "./console.sol";
    11
    import {console2} from "./console2.sol";
    12
    import {safeconsole} from "./safeconsole.sol";
    13
    import {StdAssertions} from "./StdAssertions.sol";
    14
    import {StdChains} from "./StdChains.sol";
    15
    import {StdCheats} from "./StdCheats.sol";
    16
    import {stdError} from "./StdError.sol";
    17
    import {StdInvariant} from "./StdInvariant.sol";
    18
    import {stdJson} from "./StdJson.sol";
    19
    import {stdMath} from "./StdMath.sol";
    20
    import {StdStorage, stdStorage} from "./StdStorage.sol";
    21
    import {StdStyle} from "./StdStyle.sol";
    22
    import {stdToml} from "./StdToml.sol";
    23
    import {StdUtils} from "./StdUtils.sol";
    24
    import {Vm} from "./Vm.sol";
    25
    26
    // 📦 BOILERPLATE
    27
    import {TestBase} from "./Base.sol";
    28
    29
    // ⭐️ TEST
    30
    abstract contract Test is TestBase, StdAssertions, StdChains, StdCheats, StdInvariant, StdUtils {
    31
        // Note: IS_TEST() must return true.
    32 11×
        bool public IS_TEST = true;
    33
    }
    42% lib/forge-std/src/mocks/MockERC20.sol
    Lines covered: 34 / 80 (42%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity >=0.6.2 <0.9.0;
    3
    4
    import {IERC20} from "../interfaces/IERC20.sol";
    5
    6
    /// @notice This is a mock contract of the ERC20 standard for testing purposes only, it SHOULD NOT be used in production.
    7
    /// @dev Forked from: https://github.com/transmissions11/solmate/blob/0384dbaaa4fcb5715738a9254a7c0a4cb62cf458/src/tokens/ERC20.sol
    8 0
    contract MockERC20 is IERC20 {
    9
        /*//////////////////////////////////////////////////////////////
    10
                                METADATA STORAGE
    11
        //////////////////////////////////////////////////////////////*/
    12
    13
        string internal _name;
    14
    15
        string internal _symbol;
    16
    17
        uint8 internal _decimals;
    18
    19 32×
        function name() external view override returns (string memory) {
    20 0
            return _name;
    21
        }
    22
    23 0
        function symbol() external view override returns (string memory) {
    24 0
            return _symbol;
    25
        }
    26
    27 0
        function decimals() external view override returns (uint8) {
    28 0
            return _decimals;
    29
        }
    30
    31
        /*//////////////////////////////////////////////////////////////
    32
                                  ERC20 STORAGE
    33
        //////////////////////////////////////////////////////////////*/
    34
    35
        uint256 internal _totalSupply;
    36
    37
        mapping(address => uint256) internal _balanceOf;
    38
    39
        mapping(address => mapping(address => uint256)) internal _allowance;
    40
    41 12×
        function totalSupply() external view override returns (uint256) {
    42 0
            return _totalSupply;
    43
        }
    44
    45 30×
        function balanceOf(address owner) external view override returns (uint256) {
    46 39×
            return _balanceOf[owner];
    47
        }
    48
    49 0
        function allowance(address owner, address spender) external view override returns (uint256) {
    50 0
            return _allowance[owner][spender];
    51
        }
    52
    53
        /*//////////////////////////////////////////////////////////////
    54
                                EIP-2612 STORAGE
    55
        //////////////////////////////////////////////////////////////*/
    56
    57
        uint256 internal INITIAL_CHAIN_ID;
    58
    59
        bytes32 internal INITIAL_DOMAIN_SEPARATOR;
    60
    61 21×
        mapping(address => uint256) public nonces;
    62
    63
        /*//////////////////////////////////////////////////////////////
    64
                                   INITIALIZE
    65
        //////////////////////////////////////////////////////////////*/
    66
    67
        /// @dev A bool to track whether the contract has been initialized.
    68
        bool private initialized;
    69
    70
        /// @dev To hide constructor warnings across solc versions due to different constructor visibility requirements and
    71
        /// syntaxes, we add an initialization function that can be called only once.
    72 0
        function initialize(string memory name_, string memory symbol_, uint8 decimals_) public {
    73 16×
            require(!initialized, "ALREADY_INITIALIZED");
    74
    75 0
            _name = name_;
    76 0
            _symbol = symbol_;
    77 0
            _decimals = decimals_;
    78
    79 0
            INITIAL_CHAIN_ID = _pureChainId();
    80 0
            INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    81
    82 0
            initialized = true;
    83
        }
    84
    85
        /*//////////////////////////////////////////////////////////////
    86
                                   ERC20 LOGIC
    87
        //////////////////////////////////////////////////////////////*/
    88
    89 54×
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
    90 30×
            _allowance[msg.sender][spender] = amount;
    91
    92 37×
            emit Approval(msg.sender, spender, amount);
    93
    94
            return true;
    95
        }
    96
    97 48×
        function transfer(address to, uint256 amount) public virtual override returns (bool) {
    98 136×
            _balanceOf[msg.sender] = _sub(_balanceOf[msg.sender], amount);
    99 124×
            _balanceOf[to] = _add(_balanceOf[to], amount);
    100
    101 36×
            emit Transfer(msg.sender, to, amount);
    102
    103
            return true;
    104
        }
    105
    106 42×
        function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
    107 72×
            uint256 allowed = _allowance[from][msg.sender]; // Saves gas for limited approvals.
    108
    109 111×
            if (allowed != ~uint256(0)) _allowance[from][msg.sender] = _sub(allowed, amount);
    110
    111 117×
            _balanceOf[from] = _sub(_balanceOf[from], amount);
    112 99×
            _balanceOf[to] = _add(_balanceOf[to], amount);
    113
    114 57×
            emit Transfer(from, to, amount);
    115
    116
            return true;
    117
        }
    118
    119
        /*//////////////////////////////////////////////////////////////
    120
                                 EIP-2612 LOGIC
    121
        //////////////////////////////////////////////////////////////*/
    122
    123 0
        function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)
    124
            public
    125
            virtual
    126 0
        {
    127 0
            require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
    128
    129 0
            address recoveredAddress = ecrecover(
    130 0
                keccak256(
    131 0
                    abi.encodePacked(
    132
                        "\x19\x01",
    133 0
                        DOMAIN_SEPARATOR(),
    134 0
                        keccak256(
    135 0
                            abi.encode(
    136 0
                                keccak256(
    137
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
    138
                                ),
    139 0
                                owner,
    140 0
                                spender,
    141 0
                                value,
    142 0
                                nonces[owner]++,
    143
                                deadline
    144
                            )
    145
                        )
    146
                    )
    147
                ),
    148
                v,
    149
                r,
    150
                s
    151
            );
    152
    153 0
            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
    154
    155 0
            _allowance[recoveredAddress][spender] = value;
    156
    157 0
            emit Approval(owner, spender, value);
    158
        }
    159
    160
        function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
    161 13×
            return _pureChainId() == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    162
        }
    163
    164 0
        function computeDomainSeparator() internal view virtual returns (bytes32) {
    165 0
            return keccak256(
    166 0
                abi.encode(
    167 0
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
    168 0
                    keccak256(bytes(_name)),
    169 0
                    keccak256("1"),
    170 0
                    _pureChainId(),
    171 0
                    address(this)
    172
                )
    173
            );
    174
        }
    175
    176
        /*//////////////////////////////////////////////////////////////
    177
                            INTERNAL MINT/BURN LOGIC
    178
        //////////////////////////////////////////////////////////////*/
    179
    180 0
        function _mint(address to, uint256 amount) internal virtual {
    181 0
            _totalSupply = _add(_totalSupply, amount);
    182 0
            _balanceOf[to] = _add(_balanceOf[to], amount);
    183
    184 0
            emit Transfer(address(0), to, amount);
    185
        }
    186
    187
        function _burn(address from, uint256 amount) internal virtual {
    188
            _balanceOf[from] = _sub(_balanceOf[from], amount);
    189
            _totalSupply = _sub(_totalSupply, amount);
    190
    191
            emit Transfer(from, address(0), amount);
    192
        }
    193
    194
        /*//////////////////////////////////////////////////////////////
    195
                            INTERNAL SAFE MATH LOGIC
    196
        //////////////////////////////////////////////////////////////*/
    197
    198 12×
        function _add(uint256 a, uint256 b) internal pure returns (uint256) {
    199 32×
            uint256 c = a + b;
    200 24×
            require(c >= a, "ERC20: addition overflow");
    201 0
            return c;
    202
        }
    203
    204 16×
        function _sub(uint256 a, uint256 b) internal pure returns (uint256) {
    205 46×
            require(a >= b, "ERC20: subtraction underflow");
    206 28×
            return a - b;
    207
        }
    208
    209
        /*//////////////////////////////////////////////////////////////
    210
                                    HELPERS
    211
        //////////////////////////////////////////////////////////////*/
    212
    213
        // We use this complex approach of `_viewChainId` and `_pureChainId` to ensure there are no
    214
        // compiler warnings when accessing chain ID in any solidity version supported by forge-std. We
    215
        // can't simply access the chain ID in a normal view or pure function because the solc View Pure
    216
        // Checker changed `chainid` from pure to view in 0.8.0.
    217
        function _viewChainId() private view returns (uint256 chainId) {
    218
            // Assembly required since `block.chainid` was introduced in 0.8.0.
    219
            assembly {
    220
                chainId := chainid()
    221
            }
    222
    223
            address(this); // Silence warnings in older Solc versions.
    224
        }
    225
    226
        function _pureChainId() private pure returns (uint256 chainId) {
    227
            function() internal view returns (uint256) fnIn = _viewChainId;
    228
            function() internal pure returns (uint256) pureChainId;
    229
            assembly {
    230
                pureChainId := fnIn
    231
            }
    232
            chainId = pureChainId();
    233
        }
    234
    }
    94% lib/openzeppelin-contracts/contracts/proxy/Clones.sol
    Lines covered: 18 / 19 (94%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.0) (proxy/Clones.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev https://eips.ethereum.org/EIPS/eip-1167[EIP 1167] is a standard for
    8
     * deploying minimal proxy contracts, also known as "clones".
    9
     *
    10
     * > To simply and cheaply clone contract functionality in an immutable way, this standard specifies
    11
     * > a minimal bytecode implementation that delegates all calls to a known, fixed address.
    12
     *
    13
     * The library includes functions to deploy a proxy using either `create` (traditional deployment) or `create2`
    14
     * (salted deterministic deployment). It also includes functions to predict the addresses of clones deployed using the
    15
     * deterministic method.
    16
     */
    17 0
    library Clones {
    18
        /**
    19
         * @dev A clone instance deployment failed.
    20
         */
    21
        error ERC1167FailedCreateClone();
    22
    23
        /**
    24
         * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
    25
         *
    26
         * This function uses the create opcode, which should never revert.
    27
         */
    28
        function clone(address implementation) internal returns (address instance) {
    29
            /// @solidity memory-safe-assembly
    30
            assembly {
    31
                // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
    32
                // of the `implementation` address with the bytecode before the address.
    33
                mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
    34
                // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
    35
                mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
    36
                instance := create(0, 0x09, 0x37)
    37
            }
    38
            if (instance == address(0)) {
    39
                revert ERC1167FailedCreateClone();
    40
            }
    41
        }
    42
    43
        /**
    44
         * @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
    45
         *
    46
         * This function uses the create2 opcode and a `salt` to deterministically deploy
    47
         * the clone. Using the same `implementation` and `salt` multiple time will revert, since
    48
         * the clones cannot be deployed twice at the same address.
    49
         */
    50
        function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
    51
            /// @solidity memory-safe-assembly
    52
            assembly {
    53
                // Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
    54
                // of the `implementation` address with the bytecode before the address.
    55
                mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
    56
                // Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
    57
                mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
    58
                instance := create2(0, 0x09, 0x37, salt)
    59
            }
    60
            if (instance == address(0)) {
    61 14×
                revert ERC1167FailedCreateClone();
    62
            }
    63
        }
    64
    65
        /**
    66
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
    67
         */
    68
        function predictDeterministicAddress(
    69
            address implementation,
    70
            bytes32 salt,
    71
            address deployer
    72
        ) internal pure returns (address predicted) {
    73
            /// @solidity memory-safe-assembly
    74
            assembly {
    75
                let ptr := mload(0x40)
    76
                mstore(add(ptr, 0x38), deployer)
    77
                mstore(add(ptr, 0x24), 0x5af43d82803e903d91602b57fd5bf3ff)
    78
                mstore(add(ptr, 0x14), implementation)
    79
                mstore(ptr, 0x3d602d80600a3d3981f3363d3d373d3d3d363d73)
    80
                mstore(add(ptr, 0x58), salt)
    81
                mstore(add(ptr, 0x78), keccak256(add(ptr, 0x0c), 0x37))
    82
                predicted := keccak256(add(ptr, 0x43), 0x55)
    83
            }
    84
        }
    85
    86
        /**
    87
         * @dev Computes the address of a clone deployed using {Clones-cloneDeterministic}.
    88
         */
    89
        function predictDeterministicAddress(
    90
            address implementation,
    91
            bytes32 salt
    92
        ) internal view returns (address predicted) {
    93
            return predictDeterministicAddress(implementation, salt, address(this));
    94
        }
    95
    }
    77% lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
    Lines covered: 7 / 9 (77%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    import {IERC20} from "../IERC20.sol";
    7
    import {IERC20Permit} from "../extensions/IERC20Permit.sol";
    8
    import {Address} from "../../../utils/Address.sol";
    9
    10
    /**
    11
     * @title SafeERC20
    12
     * @dev Wrappers around ERC20 operations that throw on failure (when the token
    13
     * contract returns false). Tokens that return no value (and instead revert or
    14
     * throw on failure) are also supported, non-reverting calls are assumed to be
    15
     * successful.
    16
     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
    17
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
    18
     */
    19 0
    library SafeERC20 {
    20
        using Address for address;
    21
    22
        /**
    23
         * @dev An operation with an ERC20 token failed.
    24
         */
    25
        error SafeERC20FailedOperation(address token);
    26
    27
        /**
    28
         * @dev Indicates a failed `decreaseAllowance` request.
    29
         */
    30
        error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
    31
    32
        /**
    33
         * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
    34
         * non-reverting calls are assumed to be successful.
    35
         */
    36 10×
        function safeTransfer(IERC20 token, address to, uint256 value) internal {
    37 93×
            _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    38
        }
    39
    40
        /**
    41
         * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
    42
         * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
    43
         */
    44
        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
    45 68×
            _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    46
        }
    47
    48
        /**
    49
         * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
    50
         * non-reverting calls are assumed to be successful.
    51
         */
    52
        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    53
            uint256 oldAllowance = token.allowance(address(this), spender);
    54
            forceApprove(token, spender, oldAllowance + value);
    55
        }
    56
    57
        /**
    58
         * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
    59
         * value, non-reverting calls are assumed to be successful.
    60
         */
    61
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
    62
            unchecked {
    63
                uint256 currentAllowance = token.allowance(address(this), spender);
    64
                if (currentAllowance < requestedDecrease) {
    65
                    revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
    66
                }
    67
                forceApprove(token, spender, currentAllowance - requestedDecrease);
    68
            }
    69
        }
    70
    71
        /**
    72
         * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
    73
         * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
    74
         * to be set to zero before setting it to a non-zero value, such as USDT.
    75
         */
    76
        function forceApprove(IERC20 token, address spender, uint256 value) internal {
    77
            bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
    78
    79
            if (!_callOptionalReturnBool(token, approvalCall)) {
    80
                _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
    81
                _callOptionalReturn(token, approvalCall);
    82
            }
    83
        }
    84
    85
        /**
    86
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    87
         * on the return value: the return value is optional (but if data is returned, it must not be false).
    88
         * @param token The token targeted by the call.
    89
         * @param data The call data (encoded using abi.encode or one of its variants).
    90
         */
    91
        function _callOptionalReturn(IERC20 token, bytes memory data) private {
    92
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
    93
            // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
    94
            // the target address contains contract code and also asserts for success in the low-level call.
    95
    96 29×
            bytes memory returndata = address(token).functionCall(data);
    97 90×
            if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
    98 0
                revert SafeERC20FailedOperation(address(token));
    99
            }
    100
        }
    101
    102
        /**
    103
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    104
         * on the return value: the return value is optional (but if data is returned, it must not be false).
    105
         * @param token The token targeted by the call.
    106
         * @param data The call data (encoded using abi.encode or one of its variants).
    107
         *
    108
         * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
    109
         */
    110
        function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
    111
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
    112
            // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
    113
            // and not revert is the subcall reverts.
    114
    115
            (bool success, bytes memory returndata) = address(token).call(data);
    116
            return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    117
        }
    118
    }
    80% lib/openzeppelin-contracts/contracts/utils/Address.sol
    Lines covered: 16 / 20 (80%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Collection of functions related to the address type
    8
     */
    9 0
    library Address {
    10
        /**
    11
         * @dev The ETH balance of the account is not enough to perform the operation.
    12
         */
    13
        error AddressInsufficientBalance(address account);
    14
    15
        /**
    16
         * @dev There's no code at `target` (it is not a contract).
    17
         */
    18
        error AddressEmptyCode(address target);
    19
    20
        /**
    21
         * @dev A call to an address target failed. The target may have reverted.
    22
         */
    23
        error FailedInnerCall();
    24
    25
        /**
    26
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
    27
         * `recipient`, forwarding all available gas and reverting on errors.
    28
         *
    29
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
    30
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
    31
         * imposed by `transfer`, making them unable to receive funds via
    32
         * `transfer`. {sendValue} removes this limitation.
    33
         *
    34
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
    35
         *
    36
         * IMPORTANT: because control is transferred to `recipient`, care must be
    37
         * taken to not create reentrancy vulnerabilities. Consider using
    38
         * {ReentrancyGuard} or the
    39
         * https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
    40
         */
    41
        function sendValue(address payable recipient, uint256 amount) internal {
    42
            if (address(this).balance < amount) {
    43
                revert AddressInsufficientBalance(address(this));
    44
            }
    45
    46
            (bool success, ) = recipient.call{value: amount}("");
    47
            if (!success) {
    48
                revert FailedInnerCall();
    49
            }
    50
        }
    51
    52
        /**
    53
         * @dev Performs a Solidity function call using a low level `call`. A
    54
         * plain `call` is an unsafe replacement for a function call: use this
    55
         * function instead.
    56
         *
    57
         * If `target` reverts with a revert reason or custom error, it is bubbled
    58
         * up by this function (like regular Solidity function calls). However, if
    59
         * the call reverted with no returned reason, this function reverts with a
    60
         * {FailedInnerCall} error.
    61
         *
    62
         * Returns the raw returned data. To convert to the expected return value,
    63
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
    64
         *
    65
         * Requirements:
    66
         *
    67
         * - `target` must be a contract.
    68
         * - calling `target` with `data` must not revert.
    69
         */
    70
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
    71 18×
            return functionCallWithValue(target, data, 0);
    72
        }
    73
    74
        /**
    75
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    76
         * but also transferring `value` wei to `target`.
    77
         *
    78
         * Requirements:
    79
         *
    80
         * - the calling contract must have an ETH balance of at least `value`.
    81
         * - the called Solidity function must be `payable`.
    82
         */
    83 10×
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
    84 14×
            if (address(this).balance < value) {
    85 0
                revert AddressInsufficientBalance(address(this));
    86
            }
    87 204×
            (bool success, bytes memory returndata) = target.call{value: value}(data);
    88 24×
            return verifyCallResultFromTarget(target, success, returndata);
    89
        }
    90
    91
        /**
    92
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    93
         * but performing a static call.
    94
         */
    95
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
    96
            (bool success, bytes memory returndata) = target.staticcall(data);
    97
            return verifyCallResultFromTarget(target, success, returndata);
    98
        }
    99
    100
        /**
    101
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    102
         * but performing a delegate call.
    103
         */
    104
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
    105
            (bool success, bytes memory returndata) = target.delegatecall(data);
    106
            return verifyCallResultFromTarget(target, success, returndata);
    107
        }
    108
    109
        /**
    110
         * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
    111
         * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
    112
         * unsuccessful call.
    113
         */
    114
        function verifyCallResultFromTarget(
    115
            address target,
    116
            bool success,
    117
            bytes memory returndata
    118
        ) internal view returns (bytes memory) {
    119 12×
            if (!success) {
    120
                _revert(returndata);
    121
            } else {
    122
                // only check if target is a contract if the call was successful and the return data is empty
    123
                // otherwise we already know that it was a contract
    124 36×
                if (returndata.length == 0 && target.code.length == 0) {
    125 0
                    revert AddressEmptyCode(target);
    126
                }
    127
                return returndata;
    128
            }
    129
        }
    130
    131
        /**
    132
         * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
    133
         * revert reason or with a default {FailedInnerCall} error.
    134
         */
    135
        function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
    136
            if (!success) {
    137
                _revert(returndata);
    138
            } else {
    139
                return returndata;
    140
            }
    141
        }
    142
    143
        /**
    144
         * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
    145
         */
    146
        function _revert(bytes memory returndata) private pure {
    147
            // Look for revert reason and bubble it up if present
    148 10×
            if (returndata.length > 0) {
    149
                // The easiest way to bubble the revert reason is using memory via assembly
    150
                /// @solidity memory-safe-assembly
    151
                assembly {
    152
                    let returndata_size := mload(returndata)
    153 10×
                    revert(add(32, returndata), returndata_size)
    154
                }
    155
            } else {
    156 0
                revert FailedInnerCall();
    157
            }
    158
        }
    159
    }
    83% lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol
    Lines covered: 10 / 12 (83%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
    3
    4
    pragma solidity ^0.8.20;
    5
    6
    /**
    7
     * @dev Contract module that helps prevent reentrant calls to a function.
    8
     *
    9
     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
    10
     * available, which can be applied to functions to make sure there are no nested
    11
     * (reentrant) calls to them.
    12
     *
    13
     * Note that because there is a single `nonReentrant` guard, functions marked as
    14
     * `nonReentrant` may not call one another. This can be worked around by making
    15
     * those functions `private`, and then adding `external` `nonReentrant` entry
    16
     * points to them.
    17
     *
    18
     * TIP: If you would like to learn more about reentrancy and alternative ways
    19
     * to protect against it, check out our blog post
    20
     * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
    21
     */
    22
    abstract contract ReentrancyGuard {
    23
        // Booleans are more expensive than uint256 or any type that takes up a full
    24
        // word because each write operation emits an extra SLOAD to first read the
    25
        // slot's contents, replace the bits taken up by the boolean, and then write
    26
        // back. This is the compiler's defense against contract upgrades and
    27
        // pointer aliasing, and it cannot be disabled.
    28
    29
        // The values being non-zero value makes deployment a bit more expensive,
    30
        // but in exchange the refund on every call to nonReentrant will be lower in
    31
        // amount. Since refunds are capped to a percentage of the total
    32
        // transaction's gas, it is best to keep them low in cases like this one, to
    33
        // increase the likelihood of the full refund coming into effect.
    34
        uint256 private constant NOT_ENTERED = 1;
    35
        uint256 private constant ENTERED = 2;
    36
    37
        uint256 private _status;
    38
    39
        /**
    40
         * @dev Unauthorized reentrant call.
    41
         */
    42
        error ReentrancyGuardReentrantCall();
    43
    44
        constructor() {
    45 0
            _status = NOT_ENTERED;
    46
        }
    47
    48
        /**
    49
         * @dev Prevents a contract from calling itself, directly or indirectly.
    50
         * Calling a `nonReentrant` function from another `nonReentrant`
    51
         * function is not supported. It is possible to prevent this from happening
    52
         * by making the `nonReentrant` function external, and making it call a
    53
         * `private` function that does the actual work.
    54
         */
    55
        modifier nonReentrant() {
    56 28×
            _nonReentrantBefore();
    57
            _;
    58 12×
            _nonReentrantAfter();
    59
        }
    60
    61
        function _nonReentrantBefore() private {
    62
            // On the first call to nonReentrant, _status will be NOT_ENTERED
    63
            if (_status == ENTERED) {
    64 0
                revert ReentrancyGuardReentrantCall();
    65
            }
    66
    67
            // Any calls to nonReentrant after this point will fail
    68
            _status = ENTERED;
    69
        }
    70
    71
        function _nonReentrantAfter() private {
    72
            // By storing the original value once again, a refund is triggered (see
    73
            // https://eips.ethereum.org/EIPS/eip-2200)
    74 14×
            _status = NOT_ENTERED;
    75
        }
    76
    77
        /**
    78
         * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
    79
         * `nonReentrant` function in the call stack.
    80
         */
    81
        function _reentrancyGuardEntered() internal view returns (bool) {
    82
            return _status == ENTERED;
    83
        }
    84
    }
    73% src/BribeInitiative.sol
    Lines covered: 76 / 104 (73%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
    5
    import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
    6
    7
    import {IGovernance} from "./interfaces/IGovernance.sol";
    8
    import {IInitiative} from "./interfaces/IInitiative.sol";
    9
    import {IBribeInitiative} from "./interfaces/IBribeInitiative.sol";
    10
    11
    import {DoubleLinkedList} from "./utils/DoubleLinkedList.sol";
    12
    13 234×
    contract BribeInitiative is IInitiative, IBribeInitiative {
    14
        using SafeERC20 for IERC20;
    15
        using DoubleLinkedList for DoubleLinkedList.List;
    16
    17
        /// @inheritdoc IBribeInitiative
    18 0
        IGovernance public immutable governance;
    19
        /// @inheritdoc IBribeInitiative
    20 0
        IERC20 public immutable bold;
    21
        /// @inheritdoc IBribeInitiative
    22 0
        IERC20 public immutable bribeToken;
    23
    24
        /// @inheritdoc IBribeInitiative
    25 0
        mapping(uint16 => Bribe) public bribeByEpoch;
    26
        /// @inheritdoc IBribeInitiative
    27 38×
        mapping(address => mapping(uint16 => bool)) public claimedBribeAtEpoch;
    28
    29
        /// Double linked list of the total LQTY allocated at a given epoch
    30
        DoubleLinkedList.List internal totalLQTYAllocationByEpoch;
    31
        /// Double linked list of LQTY allocated by a user at a given epoch
    32
        mapping(address => DoubleLinkedList.List) internal lqtyAllocationByUserAtEpoch;
    33
    34 27×
        constructor(address _governance, address _bold, address _bribeToken) {
    35
            governance = IGovernance(_governance);
    36
            bold = IERC20(_bold);
    37
            bribeToken = IERC20(_bribeToken);
    38
        }
    39
    40
        modifier onlyGovernance() {
    41 33×
            require(msg.sender == address(governance), "BribeInitiative: invalid-sender");
    42
            _;
    43
        }
    44
    45
        /// @inheritdoc IBribeInitiative
    46 25×
        function totalLQTYAllocatedByEpoch(uint16 _epoch) external view returns (uint88) {
    47
            return totalLQTYAllocationByEpoch.getValue(_epoch);
    48
        }
    49
    50
        /// @inheritdoc IBribeInitiative
    51 16×
        function lqtyAllocatedByUserAtEpoch(address _user, uint16 _epoch) external view returns (uint88) {
    52 19×
            return lqtyAllocationByUserAtEpoch[_user].getValue(_epoch);
    53
        }
    54
    55
        /// @inheritdoc IBribeInitiative
    56 17×
        function depositBribe(uint128 _boldAmount, uint128 _bribeTokenAmount, uint16 _epoch) external {
    57 12×
            bold.safeTransferFrom(msg.sender, address(this), _boldAmount);
    58 12×
            bribeToken.safeTransferFrom(msg.sender, address(this), _bribeTokenAmount);
    59
    60 62×
            uint16 epoch = governance.epoch();
    61 19×
            require(_epoch > epoch, "BribeInitiative: only-future-epochs");
    62
    63 46×
            Bribe memory bribe = bribeByEpoch[_epoch];
    64 13×
            bribe.boldAmount += _boldAmount;
    65 22×
            bribe.bribeTokenAmount += _bribeTokenAmount;
    66 32×
            bribeByEpoch[_epoch] = bribe;
    67
    68 12×
            emit DepositBribe(msg.sender, _boldAmount, _bribeTokenAmount, _epoch);
    69
        }
    70
    71
        function _claimBribe(
    72
            address _user,
    73
            uint16 _epoch,
    74
            uint16 _prevLQTYAllocationEpoch,
    75
            uint16 _prevTotalLQTYAllocationEpoch
    76
        ) internal returns (uint256 boldAmount, uint256 bribeTokenAmount) {
    77 68×
            require(_epoch != governance.epoch(), "BribeInitiative: cannot-claim-for-current-epoch");
    78 33×
            require(!claimedBribeAtEpoch[_user][_epoch], "BribeInitiative: already-claimed");
    79
    80 44×
            Bribe memory bribe = bribeByEpoch[_epoch];
    81 25×
            require(bribe.boldAmount != 0 || bribe.bribeTokenAmount != 0, "BribeInitiative: no-bribe");
    82
    83 0
            DoubleLinkedList.Item memory lqtyAllocation =
    84 0
                lqtyAllocationByUserAtEpoch[_user].getItem(_prevLQTYAllocationEpoch);
    85
    86 0
            require(
    87 0
                lqtyAllocation.value != 0 && _prevLQTYAllocationEpoch <= _epoch
    88 0
                    && (lqtyAllocation.next > _epoch || lqtyAllocation.next == 0),
    89
                "BribeInitiative: invalid-prev-lqty-allocation-epoch"
    90
            );
    91 0
            DoubleLinkedList.Item memory totalLQTYAllocation =
    92 0
                totalLQTYAllocationByEpoch.getItem(_prevTotalLQTYAllocationEpoch);
    93 0
            require(
    94 0
                totalLQTYAllocation.value != 0 && _prevTotalLQTYAllocationEpoch <= _epoch
    95 0
                    && (totalLQTYAllocation.next > _epoch || totalLQTYAllocation.next == 0),
    96
                "BribeInitiative: invalid-prev-total-lqty-allocation-epoch"
    97
            );
    98
    99 0
            boldAmount = uint256(bribe.boldAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value);
    100 0
            bribeTokenAmount =
    101 0
                uint256(bribe.bribeTokenAmount) * uint256(lqtyAllocation.value) / uint256(totalLQTYAllocation.value);
    102
    103 0
            claimedBribeAtEpoch[_user][_epoch] = true;
    104
    105 0
            emit ClaimBribe(_user, _epoch, boldAmount, bribeTokenAmount);
    106
        }
    107
    108
        /// @inheritdoc IBribeInitiative
    109 11×
        function claimBribes(ClaimData[] calldata _claimData)
    110
            external
    111
            returns (uint256 boldAmount, uint256 bribeTokenAmount)
    112
        {
    113
            for (uint256 i = 0; i < _claimData.length; i++) {
    114 29×
                ClaimData memory claimData = _claimData[i];
    115
                (uint256 boldAmount_, uint256 bribeTokenAmount_) = _claimBribe(
    116 13×
                    msg.sender, claimData.epoch, claimData.prevLQTYAllocationEpoch, claimData.prevTotalLQTYAllocationEpoch
    117
                );
    118 0
                boldAmount += boldAmount_;
    119 0
                bribeTokenAmount += bribeTokenAmount_;
    120
            }
    121
    122 0
            if (boldAmount != 0) bold.safeTransfer(msg.sender, boldAmount);
    123 0
            if (bribeTokenAmount != 0) bribeToken.safeTransfer(msg.sender, bribeTokenAmount);
    124
        }
    125
    126
        /// @inheritdoc IInitiative
    127
        function onRegisterInitiative(uint16) external virtual override onlyGovernance {}
    128
    129
        /// @inheritdoc IInitiative
    130 15×
        function onUnregisterInitiative(uint16) external virtual override onlyGovernance {}
    131
    132
        function _setTotalLQTYAllocationByEpoch(uint16 _epoch, uint88 _value, bool _insert) private {
    133
            if (_insert) {
    134
                totalLQTYAllocationByEpoch.insert(_epoch, _value, 0);
    135
            } else {
    136 23×
                totalLQTYAllocationByEpoch.items[_epoch].value = _value;
    137
            }
    138 12×
            emit ModifyTotalLQTYAllocation(_epoch, _value);
    139
        }
    140
    141
        function _setLQTYAllocationByUserAtEpoch(address _user, uint16 _epoch, uint88 _value, bool _insert) private {
    142
            if (_insert) {
    143 22×
                lqtyAllocationByUserAtEpoch[_user].insert(_epoch, _value, 0);
    144
            } else {
    145 36×
                lqtyAllocationByUserAtEpoch[_user].items[_epoch].value = _value;
    146
            }
    147 13×
            emit ModifyLQTYAllocation(_user, _epoch, _value);
    148
        }
    149
    150
        /// @inheritdoc IInitiative
    151 21×
        function onAfterAllocateLQTY(uint16 _currentEpoch, address _user, uint88 _voteLQTY, uint88 _vetoLQTY)
    152
            external
    153
            virtual
    154
            onlyGovernance
    155
        {
    156 18×
            uint16 mostRecentUserEpoch = lqtyAllocationByUserAtEpoch[_user].getHead();
    157
    158
            if (_currentEpoch == 0) return;
    159
    160
            // if this is the first user allocation in the epoch, then insert a new item into the user allocation DLL
    161 16×
            if (mostRecentUserEpoch != _currentEpoch) {
    162 33×
                uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].items[mostRecentUserEpoch].value;
    163 13×
                uint88 newVoteLQTY = (_vetoLQTY == 0) ? _voteLQTY : 0;
    164
                uint16 mostRecentTotalEpoch = totalLQTYAllocationByEpoch.getHead();
    165
                // if this is the first allocation in the epoch, then insert a new item into the total allocation DLL
    166
                if (mostRecentTotalEpoch != _currentEpoch) {
    167 19×
                    uint88 prevTotalLQTYAllocation = totalLQTYAllocationByEpoch.items[mostRecentTotalEpoch].value;
    168 10×
                    if (_vetoLQTY == 0) {
    169
                        // no veto to no veto
    170
                        _setTotalLQTYAllocationByEpoch(
    171 15×
                            _currentEpoch, prevTotalLQTYAllocation + newVoteLQTY - prevVoteLQTY, true
    172
                        );
    173
                    } else {
    174
                        if (prevVoteLQTY != 0) {
    175
                            // if the prev user allocation was counted in, then remove the prev user allocation from the
    176
                            // total allocation (no veto to veto)
    177 0
                            _setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation - prevVoteLQTY, true);
    178
                        } else {
    179
                            // veto to veto
    180
                            _setTotalLQTYAllocationByEpoch(_currentEpoch, prevTotalLQTYAllocation, true);
    181
                        }
    182
                    }
    183
                } else {
    184 0
                    if (_vetoLQTY == 0) {
    185
                        // no veto to no veto
    186
                        _setTotalLQTYAllocationByEpoch(
    187 0
                            _currentEpoch,
    188
                            totalLQTYAllocationByEpoch.items[_currentEpoch].value + newVoteLQTY - prevVoteLQTY,
    189
                            false
    190
                        );
    191 0
                    } else if (prevVoteLQTY != 0) {
    192
                        // no veto to veto
    193
                        _setTotalLQTYAllocationByEpoch(
    194 0
                            _currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false
    195
                        );
    196
                    }
    197
                }
    198
                // insert a new item into the user allocation DLL
    199
                _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, newVoteLQTY, true);
    200
            } else {
    201 19×
                uint88 prevVoteLQTY = lqtyAllocationByUserAtEpoch[_user].getItem(_currentEpoch).value;
    202
                if (_vetoLQTY == 0) {
    203
                    // update the allocation for the current epoch by adding the new allocation and subtracting
    204
                    // the previous one (no veto to no veto)
    205
                    _setTotalLQTYAllocationByEpoch(
    206
                        _currentEpoch,
    207 24×
                        totalLQTYAllocationByEpoch.items[_currentEpoch].value + _voteLQTY - prevVoteLQTY,
    208
                        false
    209
                    );
    210
                    _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, _voteLQTY, false);
    211
                } else {
    212
                    // if the user vetoed the initiative, subtract the allocation from the DLLs (no veto to veto)
    213
                    _setTotalLQTYAllocationByEpoch(
    214 24×
                        _currentEpoch, totalLQTYAllocationByEpoch.items[_currentEpoch].value - prevVoteLQTY, false
    215
                    );
    216
                    _setLQTYAllocationByUserAtEpoch(_user, _currentEpoch, 0, false);
    217
                }
    218
            }
    219
        }
    220
    221
        /// @inheritdoc IInitiative
    222 18×
        function onClaimForInitiative(uint16, uint256) external virtual override onlyGovernance {}
    223
    }
    0% src/CurveV2GaugeRewards.sol
    Lines covered: 0 / 12 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {ILiquidityGauge} from "./../src/interfaces/ILiquidityGauge.sol";
    5
    6
    import {BribeInitiative} from "./BribeInitiative.sol";
    7
    8 0
    contract CurveV2GaugeRewards is BribeInitiative {
    9 0
        ILiquidityGauge public immutable gauge;
    10 0
        uint256 public immutable duration;
    11
    12
        event DepositIntoGauge(uint256 amount);
    13
    14 0
        constructor(address _governance, address _bold, address _bribeToken, address _gauge, uint256 _duration)
    15
            BribeInitiative(_governance, _bold, _bribeToken)
    16
        {
    17 0
            gauge = ILiquidityGauge(_gauge);
    18 0
            duration = _duration;
    19
        }
    20
    21 0
        function depositIntoGauge() external returns (uint256) {
    22 0
            uint256 amount = governance.claimForInitiative(address(this));
    23
    24 0
            bold.approve(address(gauge), amount);
    25 0
            gauge.deposit_reward_token(address(bold), amount, duration);
    26
    27 0
            emit DepositIntoGauge(amount);
    28
    29 0
            return amount;
    30
        }
    31
    }
    0% src/ForwardBribe.sol
    Lines covered: 0 / 10 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
    5
    import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
    6
    7
    import {BribeInitiative} from "./BribeInitiative.sol";
    8
    9 0
    contract ForwardBribe is BribeInitiative {
    10
        using SafeERC20 for IERC20;
    11
    12 0
        address public immutable receiver;
    13
    14 0
        constructor(address _governance, address _bold, address _bribeToken, address _receiver)
    15
            BribeInitiative(_governance, _bold, _bribeToken)
    16
        {
    17 0
            receiver = _receiver;
    18
        }
    19
    20 0
        function forwardBribe() external {
    21 0
            governance.claimForInitiative(address(this));
    22
    23 0
            uint boldAmount = bold.balanceOf(address(this));
    24 0
            uint bribeTokenAmount = bribeToken.balanceOf(address(this));
    25
    26 0
            if (boldAmount != 0) bold.transfer(receiver, boldAmount);
    27 0
            if (bribeTokenAmount != 0) bribeToken.transfer(receiver, bribeTokenAmount);
    28
        }
    29
    }
    84% src/Governance.sol
    Lines covered: 243 / 289 (84%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {console} from "forge-std/console.sol";
    5
    6
    import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
    7
    import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
    8
    import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
    9
    10
    import {IGovernance} from "./interfaces/IGovernance.sol";
    11
    import {IInitiative} from "./interfaces/IInitiative.sol";
    12
    import {ILQTYStaking} from "./interfaces/ILQTYStaking.sol";
    13
    14
    import {UserProxy} from "./UserProxy.sol";
    15
    import {UserProxyFactory} from "./UserProxyFactory.sol";
    16
    17
    import {add, max, abs} from "./utils/Math.sol";
    18
    import {Multicall} from "./utils/Multicall.sol";
    19
    import {WAD, PermitParams} from "./utils/Types.sol";
    20
    21
    import {SafeCastLib} from "lib/solmate/src/utils/SafeCastLib.sol";
    22
    23
    /// @title Governance: Modular Initiative based Governance
    24 224×
    contract Governance is Multicall, UserProxyFactory, ReentrancyGuard, IGovernance {
    25
        using SafeERC20 for IERC20;
    26
    27
        /// @inheritdoc IGovernance
    28 0
        ILQTYStaking public immutable stakingV1;
    29
        /// @inheritdoc IGovernance
    30 0
        IERC20 public immutable lqty;
    31
        /// @inheritdoc IGovernance
    32 0
        IERC20 public immutable bold;
    33
        /// @inheritdoc IGovernance
    34 0
        uint256 public immutable EPOCH_START;
    35
        /// @inheritdoc IGovernance
    36 0
        uint256 public immutable EPOCH_DURATION;
    37
        /// @inheritdoc IGovernance
    38 19×
        uint256 public immutable EPOCH_VOTING_CUTOFF;
    39
        /// @inheritdoc IGovernance
    40 0
        uint256 public immutable MIN_CLAIM;
    41
        /// @inheritdoc IGovernance
    42 0
        uint256 public immutable MIN_ACCRUAL;
    43
        /// @inheritdoc IGovernance
    44 0
        uint256 public immutable REGISTRATION_FEE;
    45
        /// @inheritdoc IGovernance
    46 0
        uint256 public immutable REGISTRATION_THRESHOLD_FACTOR;
    47
        /// @inheritdoc IGovernance
    48 0
        uint256 public immutable UNREGISTRATION_THRESHOLD_FACTOR;
    49
        /// @inheritdoc IGovernance
    50 0
        uint256 public immutable REGISTRATION_WARM_UP_PERIOD;
    51
        /// @inheritdoc IGovernance
    52 0
        uint256 public immutable UNREGISTRATION_AFTER_EPOCHS;
    53
        /// @inheritdoc IGovernance
    54 0
        uint256 public immutable VOTING_THRESHOLD_FACTOR;
    55
    56
        /// @inheritdoc IGovernance
    57 0
        uint256 public boldAccrued;
    58
    59
        /// @inheritdoc IGovernance
    60 0
        VoteSnapshot public votesSnapshot;
    61
        /// @inheritdoc IGovernance
    62 0
        mapping(address => InitiativeVoteSnapshot) public votesForInitiativeSnapshot;
    63
    64
        /// @inheritdoc IGovernance
    65 0
        GlobalState public globalState;
    66
        /// @inheritdoc IGovernance
    67 0
        mapping(address => UserState) public userStates;
    68
        /// @inheritdoc IGovernance
    69 0
        mapping(address => InitiativeState) public initiativeStates;
    70
        /// @inheritdoc IGovernance
    71 0
        mapping(address => mapping(address => Allocation)) public lqtyAllocatedByUserToInitiative;
    72
        /// @inheritdoc IGovernance
    73
        mapping(address => uint16) public override registeredInitiatives;
    74
    75
        uint16 constant UNREGISTERED_INITIATIVE = type(uint16).max;
    76
    77 0
        constructor(
    78
            address _lqty,
    79
            address _lusd,
    80
            address _stakingV1,
    81
            address _bold,
    82
            Configuration memory _config,
    83
            address[] memory _initiatives
    84 0
        ) UserProxyFactory(_lqty, _lusd, _stakingV1) {
    85 0
            stakingV1 = ILQTYStaking(_stakingV1);
    86 0
            lqty = IERC20(_lqty);
    87 0
            bold = IERC20(_bold);
    88 0
            require(_config.minClaim <= _config.minAccrual, "Gov: min-claim-gt-min-accrual");
    89 0
            REGISTRATION_FEE = _config.registrationFee;
    90 0
            REGISTRATION_THRESHOLD_FACTOR = _config.registrationThresholdFactor;
    91 0
            UNREGISTRATION_THRESHOLD_FACTOR = _config.unregistrationThresholdFactor;
    92 0
            REGISTRATION_WARM_UP_PERIOD = _config.registrationWarmUpPeriod;
    93 0
            UNREGISTRATION_AFTER_EPOCHS = _config.unregistrationAfterEpochs;
    94 0
            VOTING_THRESHOLD_FACTOR = _config.votingThresholdFactor;
    95 0
            MIN_CLAIM = _config.minClaim;
    96 0
            MIN_ACCRUAL = _config.minAccrual;
    97 0
            EPOCH_START = _config.epochStart;
    98 0
            require(_config.epochDuration > 0, "Gov: epoch-duration-zero");
    99 0
            EPOCH_DURATION = _config.epochDuration;
    100 0
            require(_config.epochVotingCutoff < _config.epochDuration, "Gov: epoch-voting-cutoff-gt-epoch-duration");
    101 0
            EPOCH_VOTING_CUTOFF = _config.epochVotingCutoff;
    102 0
            for (uint256 i = 0; i < _initiatives.length; i++) {
    103 0
                initiativeStates[_initiatives[i]] = InitiativeState(0, 0, 0, 0, 0);
    104 0
                registeredInitiatives[_initiatives[i]] = 1;
    105
            }
    106
        }
    107
    108
        function _averageAge(uint32 _currentTimestamp, uint32 _averageTimestamp) internal pure returns (uint32) {
    109 23×
            if (_averageTimestamp == 0 || _currentTimestamp < _averageTimestamp) return 0;
    110
            return _currentTimestamp - _averageTimestamp;
    111
        }
    112
    113
        function _calculateAverageTimestamp(
    114
            uint32 _prevOuterAverageTimestamp,
    115
            uint32 _newInnerAverageTimestamp,
    116
            uint88 _prevLQTYBalance,
    117
            uint88 _newLQTYBalance
    118
        ) internal view returns (uint32) {
    119 11×
            if (_newLQTYBalance == 0) return 0;
    120
    121
            uint32 prevOuterAverageAge = _averageAge(uint32(block.timestamp), _prevOuterAverageTimestamp);
    122
            uint32 newInnerAverageAge = _averageAge(uint32(block.timestamp), _newInnerAverageTimestamp);
    123
    124
            uint88 newOuterAverageAge;
    125 17×
            if (_prevLQTYBalance <= _newLQTYBalance) {
    126
                uint88 deltaLQTY = _newLQTYBalance - _prevLQTYBalance;
    127 12×
                uint240 prevVotes = uint240(_prevLQTYBalance) * uint240(prevOuterAverageAge);
    128 13×
                uint240 newVotes = uint240(deltaLQTY) * uint240(newInnerAverageAge);
    129
                uint240 votes = prevVotes + newVotes;
    130 21×
                newOuterAverageAge = (_newLQTYBalance == 0) ? 0 : uint32(votes / uint240(_newLQTYBalance));
    131
            } else {
    132
                uint88 deltaLQTY = _prevLQTYBalance - _newLQTYBalance;
    133 12×
                uint240 prevVotes = uint240(_prevLQTYBalance) * uint240(prevOuterAverageAge);
    134 13×
                uint240 newVotes = uint240(deltaLQTY) * uint240(newInnerAverageAge);
    135 22×
                uint240 votes = (prevVotes >= newVotes) ? prevVotes - newVotes : 0;
    136 21×
                newOuterAverageAge = (_newLQTYBalance == 0) ? 0 : uint32(votes / uint240(_newLQTYBalance));
    137
            }
    138
    139 17×
            if (newOuterAverageAge > block.timestamp) return 0;
    140 13×
            return uint32(block.timestamp - newOuterAverageAge);
    141
        }
    142
    143
        /*//////////////////////////////////////////////////////////////
    144
                                    STAKING
    145
        //////////////////////////////////////////////////////////////*/
    146
    147
        function _deposit(uint88 _lqtyAmount) private returns (UserProxy) {
    148 17×
            require(_lqtyAmount > 0, "Governance: zero-lqty-amount");
    149
    150
            address userProxyAddress = deriveUserProxyAddress(msg.sender);
    151
    152
            if (userProxyAddress.code.length == 0) {
    153 0
                deployUserProxy();
    154
            }
    155
    156
            UserProxy userProxy = UserProxy(payable(userProxyAddress));
    157
    158 62×
            uint88 lqtyStaked = uint88(stakingV1.stakes(userProxyAddress));
    159
    160
            // update the average staked timestamp for LQTY staked by the user
    161 41×
            UserState memory userState = userStates[msg.sender];
    162 14×
            userState.averageStakingTimestamp = _calculateAverageTimestamp(
    163
                userState.averageStakingTimestamp, uint32(block.timestamp), lqtyStaked, lqtyStaked + _lqtyAmount
    164
            );
    165 40×
            userStates[msg.sender] = userState;
    166
    167 11×
            emit DepositLQTY(msg.sender, _lqtyAmount);
    168
    169
            return userProxy;
    170
        }
    171
    172
        /// @inheritdoc IGovernance
    173 18×
        function depositLQTY(uint88 _lqtyAmount) external nonReentrant {
    174
            UserProxy userProxy = _deposit(_lqtyAmount);
    175 52×
            userProxy.stake(_lqtyAmount, msg.sender);
    176
        }
    177
    178
        /// @inheritdoc IGovernance
    179 0
        function depositLQTYViaPermit(uint88 _lqtyAmount, PermitParams calldata _permitParams) external nonReentrant {
    180 0
            UserProxy userProxy = _deposit(_lqtyAmount);
    181 0
            userProxy.stakeViaPermit(_lqtyAmount, msg.sender, _permitParams);
    182
        }
    183
    184
        /// @inheritdoc IGovernance
    185 22×
        function withdrawLQTY(uint88 _lqtyAmount) external nonReentrant {
    186
            UserProxy userProxy = UserProxy(payable(deriveUserProxyAddress(msg.sender)));
    187
            require(address(userProxy).code.length != 0, "Governance: user-proxy-not-deployed");
    188
    189 65×
            uint88 lqtyStaked = uint88(stakingV1.stakes(address(userProxy)));
    190
    191 12×
            UserState storage userState = userStates[msg.sender];
    192
    193
            // check if user has enough unallocated lqty
    194 28×
            require(_lqtyAmount <= lqtyStaked - userState.allocatedLQTY, "Governance: insufficient-unallocated-lqty");
    195
    196 67×
            (uint256 accruedLUSD, uint256 accruedETH) = userProxy.unstake(_lqtyAmount, msg.sender, msg.sender);
    197
    198 13×
            emit WithdrawLQTY(msg.sender, _lqtyAmount, accruedLUSD, accruedETH);
    199
        }
    200
    201
        /// @inheritdoc IGovernance
    202 28×
        function claimFromStakingV1(address _rewardRecipient) external returns (uint256 accruedLUSD, uint256 accruedETH) {
    203
            address payable userProxyAddress = payable(deriveUserProxyAddress(msg.sender));
    204
            require(userProxyAddress.code.length != 0, "Governance: user-proxy-not-deployed");
    205 65×
            return UserProxy(userProxyAddress).unstake(0, _rewardRecipient, _rewardRecipient);
    206
        }
    207
    208
        /*//////////////////////////////////////////////////////////////
    209
                                     VOTING
    210
        //////////////////////////////////////////////////////////////*/
    211
    212
        /// @inheritdoc IGovernance
    213 26×
        function epoch() public view returns (uint16) {
    214 14×
            if (block.timestamp < EPOCH_START) return 0;
    215 36×
            return uint16(((block.timestamp - EPOCH_START) / EPOCH_DURATION) + 1);
    216
        }
    217
    218
        /// @inheritdoc IGovernance
    219
        function epochStart() public view returns (uint32) {
    220
            uint16 currentEpoch = epoch();
    221
            if (currentEpoch == 0) return 0;
    222 24×
            return uint32(EPOCH_START + (currentEpoch - 1) * EPOCH_DURATION);
    223
        }
    224
    225
        /// @inheritdoc IGovernance
    226
        function secondsWithinEpoch() public view returns (uint32) {
    227
            if (block.timestamp < EPOCH_START) return 0;
    228 18×
            return uint32((block.timestamp - EPOCH_START) % EPOCH_DURATION);
    229
        }
    230
    231
        /// @inheritdoc IGovernance
    232
        function lqtyToVotes(uint88 _lqtyAmount, uint256 _currentTimestamp, uint32 _averageTimestamp)
    233
            public
    234
            pure
    235
            returns (uint240)
    236
        {
    237 18×
            return uint240(_lqtyAmount) * _averageAge(uint32(_currentTimestamp), _averageTimestamp);
    238
        }
    239
    240
        /// @inheritdoc IGovernance
    241
        function calculateVotingThreshold() public view returns (uint256) {
    242
            uint256 snapshotVotes = votesSnapshot.votes;
    243 10×
            if (snapshotVotes == 0) return 0;
    244
    245
            uint256 minVotes; // to reach MIN_CLAIM: snapshotVotes * MIN_CLAIM / boldAccrued
    246 17×
            uint256 payoutPerVote = boldAccrued * WAD / snapshotVotes;
    247
            if (payoutPerVote != 0) {
    248 14×
                minVotes = MIN_CLAIM * WAD / payoutPerVote;
    249
            }
    250 22×
            return max(snapshotVotes * VOTING_THRESHOLD_FACTOR / WAD, minVotes);
    251
        }
    252
    253
        // Snapshots votes for the previous epoch and accrues funds for the current epoch
    254
        function _snapshotVotes() internal returns (VoteSnapshot memory snapshot, GlobalState memory state) {
    255
            uint16 currentEpoch = epoch();
    256 30×
            snapshot = votesSnapshot;
    257 25×
            state = globalState;
    258 18×
            if (snapshot.forEpoch < currentEpoch - 1) {
    259 21×
                snapshot.votes = lqtyToVotes(state.countedVoteLQTY, epochStart(), state.countedVoteLQTYAverageTimestamp);
    260 14×
                snapshot.forEpoch = currentEpoch - 1;
    261 13×
                votesSnapshot = snapshot;
    262 61×
                uint256 boldBalance = bold.balanceOf(address(this));
    263 13×
                boldAccrued = (boldBalance < MIN_ACCRUAL) ? 0 : boldBalance;
    264 22×
                emit SnapshotVotes(snapshot.votes, snapshot.forEpoch);
    265
            }
    266
        }
    267
    268
        // Snapshots votes for an initiative for the previous epoch but only count the votes
    269
        // if the received votes meet the voting threshold
    270
        function _snapshotVotesForInitiative(address _initiative)
    271
            internal
    272
            returns (InitiativeVoteSnapshot memory initiativeSnapshot, InitiativeState memory initiativeState)
    273
        {
    274
            uint16 currentEpoch = epoch();
    275 70×
            initiativeSnapshot = votesForInitiativeSnapshot[_initiative];
    276 78×
            initiativeState = initiativeStates[_initiative];
    277 22×
            if (initiativeSnapshot.forEpoch < currentEpoch - 1) {
    278
                uint256 votingThreshold = calculateVotingThreshold();
    279
                uint32 start = epochStart();
    280
                uint240 votes =
    281 15×
                    lqtyToVotes(initiativeState.voteLQTY, start, initiativeState.averageStakingTimestampVoteLQTY);
    282
                uint240 vetos =
    283 15×
                    lqtyToVotes(initiativeState.vetoLQTY, start, initiativeState.averageStakingTimestampVetoLQTY);
    284
                // if the votes didn't meet the voting threshold then no votes qualify
    285
                /// @audit TODO TEST THIS
    286
                /// The change means that all logic for votes and rewards must be done in `getInitiativeState`
    287
                initiativeSnapshot.votes = uint224(votes); /// @audit TODO: We should change this to check the treshold, we should instead use the snapshot to just report all the valid data
    288
    289
                initiativeSnapshot.vetos = uint224(vetos); /// @audit TODO: Overflow + order of operations
    290
    291 12×
                initiativeSnapshot.forEpoch = currentEpoch - 1; 
    292
    293
                /// @audit Conditional
    294
                /// If we meet the threshold then we increase this
    295
                /// TODO: Either simplify, or use this for the state machine as well
    296
                if(
    297 18×
                    initiativeSnapshot.votes > initiativeSnapshot.vetos &&
    298
                    initiativeSnapshot.votes >= votingThreshold
    299
                ) {
    300 12×
                    initiativeSnapshot.lastCountedEpoch = currentEpoch - 1; /// @audit This updating makes it so that we lose track | TODO: Find a better way
    301
                }
    302
    303 88×
                votesForInitiativeSnapshot[_initiative] = initiativeSnapshot;
    304 11×
                emit SnapshotVotesForInitiative(_initiative, initiativeSnapshot.votes, initiativeSnapshot.forEpoch);
    305
            }
    306
        }
    307
    308
        /// @inheritdoc IGovernance
    309 26×
        function snapshotVotesForInitiative(address _initiative)
    310
            external
    311
            nonReentrant
    312
            returns (VoteSnapshot memory voteSnapshot, InitiativeVoteSnapshot memory initiativeVoteSnapshot)
    313
        {
    314
            (voteSnapshot,) = _snapshotVotes();
    315
            (initiativeVoteSnapshot,) = _snapshotVotesForInitiative(_initiative);
    316
        }
    317
    318
    319
        /// @notice Given an initiative, return whether the initiative will be unregisted, whether it can claim and which epoch it last claimed at
    320
        enum InitiativeStatus {
    321
            SKIP, /// This epoch will result in no rewards and no unregistering
    322
            CLAIMABLE, /// This epoch will result in claiming rewards
    323
            CLAIMED, /// The rewards for this epoch have been claimed
    324
            UNREGISTERABLE, /// Can be unregistered
    325
            DISABLED // It was already Unregistered
    326
        }
    327
        /**
    328
            FSM:
    329
                - Can claim (false, true, epoch - 1 - X)
    330
                - Has claimed (false, false, epoch - 1)
    331
                - Cannot claim and should not be kicked (false, false, epoch - 1 - [0, X])
    332
                - Should be kicked (true, false, epoch - 1 - [UNREGISTRATION_AFTER_EPOCHS, UNREGISTRATION_AFTER_EPOCHS + X])
    333
         */
    334
    335 37×
        function getInitiativeState(address _initiative) public returns (InitiativeStatus status, uint16 lastEpochClaim, uint256 claimableAmount) {
    336
            (VoteSnapshot memory votesSnapshot_,) = _snapshotVotes();
    337 10×
            (InitiativeVoteSnapshot memory votesForInitiativeSnapshot_, InitiativeState memory initiativeState) = _snapshotVotesForInitiative(_initiative);
    338
    339 20×
            lastEpochClaim = initiativeStates[_initiative].lastEpochClaim;
    340
    341
            // == Already Claimed Condition == //
    342 20×
            if(lastEpochClaim >= epoch() - 1) {
    343
                // early return, we have already claimed
    344
                return (InitiativeStatus.CLAIMED, lastEpochClaim, claimableAmount);
    345
            }
    346
    347
            // == Disabled Condition == //
    348
            // TODO: If a initiative is disabled, we return false and the last epoch claim
    349 20×
            if(registeredInitiatives[_initiative] == UNREGISTERED_INITIATIVE) {
    350
                return (InitiativeStatus.DISABLED, lastEpochClaim, 0); /// @audit By definition it must have zero rewards
    351
            }
    352
    353
    354
            // == Unregister Condition == //
    355
            /// @audit epoch() - 1 because we can have Now - 1 and that's not a removal case | TODO: Double check | Worst case QA, off by one epoch
    356
            // TODO: IMO we can use the claimed variable here
    357
            /// This shifts the logic by 1 epoch
    358 35×
            if((votesForInitiativeSnapshot_.lastCountedEpoch + UNREGISTRATION_AFTER_EPOCHS < epoch() - 1)
    359 19×
                ||  votesForInitiativeSnapshot_.vetos > votesForInitiativeSnapshot_.votes
    360 24×
                            && votesForInitiativeSnapshot_.vetos > calculateVotingThreshold() * UNREGISTRATION_THRESHOLD_FACTOR / WAD
    361
            ) {
    362
                return (InitiativeStatus.UNREGISTERABLE, lastEpochClaim, 0);
    363
            }
    364
    365
            // How do we know that they have canClaimRewards?
    366
            // They must have votes / totalVotes AND meet the Requirement AND not be vetoed
    367
            /// @audit if we already are above, then why are we re-computing this?
    368
            // Ultimately the checkpoint logic for initiative is fine, so we can skip this
    369
            
    370
            // TODO: Where does this fit exactly?
    371
            // Edge case of 0 votes
    372
            if(votesSnapshot_.votes == 0) {
    373
                return (InitiativeStatus.SKIP, lastEpochClaim, 0);
    374
            }
    375
    376
    377
            // == Vetoed this Epoch Condition == //
    378 16×
            if(votesForInitiativeSnapshot_.vetos >= votesForInitiativeSnapshot_.votes) {
    379
                return (InitiativeStatus.SKIP, lastEpochClaim, 0); /// @audit Technically VETOED
    380
            }
    381
    382
            // == Not meeting threshold Condition == //
    383
    384 13×
            if(calculateVotingThreshold() > votesForInitiativeSnapshot_.votes) {
    385
                return (InitiativeStatus.SKIP, lastEpochClaim, 0);
    386
            }
    387
    388
            // == Rewards Conditions (votes can be zero, logic is the same) == //
    389 25×
            uint256 claim = votesForInitiativeSnapshot_.votes * boldAccrued / votesSnapshot_.votes;
    390
            return (InitiativeStatus.CLAIMABLE, lastEpochClaim, claim);
    391
        }
    392
    393
        /// @inheritdoc IGovernance
    394 21×
        function registerInitiative(address _initiative) external nonReentrant {
    395 10×
            bold.safeTransferFrom(msg.sender, address(this), REGISTRATION_FEE);
    396
    397
            require(_initiative != address(0), "Governance: zero-address");
    398 29×
            require(registeredInitiatives[_initiative] == 0, "Governance: initiative-already-registered");
    399
    400
            address userProxyAddress = deriveUserProxyAddress(msg.sender);
    401
            (VoteSnapshot memory snapshot,) = _snapshotVotes();
    402 39×
            UserState memory userState = userStates[msg.sender];
    403
    404
            // an initiative can be registered if the registrant has more voting power (LQTY * age)
    405
            // than the registration threshold derived from the previous epoch's total global votes
    406
            require(
    407 74×
                lqtyToVotes(uint88(stakingV1.stakes(userProxyAddress)), block.timestamp, userState.averageStakingTimestamp)
    408 19×
                    >= snapshot.votes * REGISTRATION_THRESHOLD_FACTOR / WAD,
    409
                "Governance: insufficient-lqty"
    410
            );
    411
    412
            uint16 currentEpoch = epoch();
    413
    414 32×
            registeredInitiatives[_initiative] = currentEpoch;
    415
    416 12×
            emit RegisterInitiative(_initiative, msg.sender, currentEpoch);
    417
    418 43×
            try IInitiative(_initiative).onRegisterInitiative(currentEpoch) {} catch {}
    419
        }
    420
    421
        /// @inheritdoc IGovernance
    422 24×
        function allocateLQTY(
    423
            address[] calldata _initiatives,
    424
            int88[] calldata _deltaLQTYVotes,
    425
            int88[] calldata _deltaLQTYVetos
    426
        ) external nonReentrant {
    427 12×
            require(
    428 11×
                _initiatives.length == _deltaLQTYVotes.length && _initiatives.length == _deltaLQTYVetos.length,
    429
                "Governance: array-length-mismatch"
    430
            );
    431
    432
            (, GlobalState memory state) = _snapshotVotes();
    433
    434
            uint256 votingThreshold = calculateVotingThreshold();
    435
            uint16 currentEpoch = epoch();
    436
    437 40×
            UserState memory userState = userStates[msg.sender];
    438
    439 16×
            for (uint256 i = 0; i < _initiatives.length; i++) {
    440 27×
                address initiative = _initiatives[i];
    441 27×
                int88 deltaLQTYVotes = _deltaLQTYVotes[i];
    442 27×
                int88 deltaLQTYVetos = _deltaLQTYVetos[i];
    443
    444
                // TODO: Better assertion
    445
                /// Can remove or add
    446
                /// But cannot add or remove both
    447
    448
                // only allow vetoing post the voting cutoff
    449 12×
                require(
    450 31×
                    deltaLQTYVotes <= 0 || deltaLQTYVotes >= 0 && secondsWithinEpoch() <= EPOCH_VOTING_CUTOFF,
    451
                    "Governance: epoch-voting-cutoff"
    452
                );
    453
                
    454
                {
    455 17×
                    uint16 registeredAtEpoch = registeredInitiatives[initiative];
    456 18×
                    if(deltaLQTYVotes > 0 || deltaLQTYVetos > 0) {
    457 29×
                        require(currentEpoch > registeredAtEpoch && registeredAtEpoch != 0, "Governance: initiative-not-active");
    458
                    } /// @audit TODO: We must allow removals for Proposals that are disabled | Should use the flag u16
    459
                    
    460
                    if(registeredAtEpoch == UNREGISTERED_INITIATIVE) {
    461 21×
                        require(deltaLQTYVotes <= 0 && deltaLQTYVetos <= 0, "Must be a withdrawal");
    462
                    }
    463
                }
    464
                // TODO: CHANGE
    465
                // Can add if active
    466
                // Can remove if inactive
    467
                // only allow allocations to initiatives that are active
    468
                // an initiative becomes active in the epoch after it is registered
    469
    470
    471
                (, InitiativeState memory initiativeState) = _snapshotVotesForInitiative(initiative);
    472
    473
                // deep copy of the initiative's state before the allocation
    474 40×
                InitiativeState memory prevInitiativeState = InitiativeState(
    475
                    initiativeState.voteLQTY,
    476
                    initiativeState.vetoLQTY,
    477
                    initiativeState.averageStakingTimestampVoteLQTY,
    478
                    initiativeState.averageStakingTimestampVetoLQTY,
    479
                    initiativeState.lastEpochClaim
    480
                );
    481
    482
                // update the average staking timestamp for the initiative based on the user's average staking timestamp
    483
                initiativeState.averageStakingTimestampVoteLQTY = _calculateAverageTimestamp(
    484
                    initiativeState.averageStakingTimestampVoteLQTY,
    485
                    userState.averageStakingTimestamp,
    486
                    initiativeState.voteLQTY,
    487
                    add(initiativeState.voteLQTY, deltaLQTYVotes)
    488
                );
    489
                initiativeState.averageStakingTimestampVetoLQTY = _calculateAverageTimestamp(
    490
                    initiativeState.averageStakingTimestampVetoLQTY,
    491
                    userState.averageStakingTimestamp,
    492
                    initiativeState.vetoLQTY,
    493
                    add(initiativeState.vetoLQTY, deltaLQTYVetos)
    494
                );
    495
    496
                // allocate the voting and vetoing LQTY to the initiative
    497 12×
                initiativeState.voteLQTY = add(initiativeState.voteLQTY, deltaLQTYVotes);
    498 21×
                initiativeState.vetoLQTY = add(initiativeState.vetoLQTY, deltaLQTYVetos);
    499
    500
                // update the initiative's state
    501 100×
                initiativeStates[initiative] = initiativeState;
    502
    503
                // update the average staking timestamp for all counted voting LQTY
    504 11×
                state.countedVoteLQTYAverageTimestamp = _calculateAverageTimestamp(
    505
                    state.countedVoteLQTYAverageTimestamp,
    506
                    initiativeState.averageStakingTimestampVoteLQTY,
    507
                    state.countedVoteLQTY,
    508
                    state.countedVoteLQTY - prevInitiativeState.voteLQTY
    509
                );
    510 15×
                state.countedVoteLQTY -= prevInitiativeState.voteLQTY;
    511
    512 11×
                state.countedVoteLQTYAverageTimestamp = _calculateAverageTimestamp(
    513
                    state.countedVoteLQTYAverageTimestamp,
    514
                    initiativeState.averageStakingTimestampVoteLQTY,
    515
                    state.countedVoteLQTY,
    516
                    state.countedVoteLQTY + initiativeState.voteLQTY
    517
                );
    518 18×
                state.countedVoteLQTY += initiativeState.voteLQTY;
    519
    520
    521
    522
                // allocate the voting and vetoing LQTY to the initiative
    523 67×
                Allocation memory allocation = lqtyAllocatedByUserToInitiative[msg.sender][initiative];
    524 10×
                allocation.voteLQTY = add(allocation.voteLQTY, deltaLQTYVotes);
    525 18×
                allocation.vetoLQTY = add(allocation.vetoLQTY, deltaLQTYVetos);
    526
                allocation.atEpoch = currentEpoch;
    527 31×
                require(!(allocation.voteLQTY != 0 && allocation.vetoLQTY != 0), "Governance: vote-and-veto");
    528 73×
                lqtyAllocatedByUserToInitiative[msg.sender][initiative] = allocation;
    529
    530 17×
                userState.allocatedLQTY = add(userState.allocatedLQTY, deltaLQTYVotes + deltaLQTYVetos);
    531
    532 14×
                emit AllocateLQTY(msg.sender, initiative, deltaLQTYVotes, deltaLQTYVetos, currentEpoch);
    533
    534 46×
                try IInitiative(initiative).onAfterAllocateLQTY(
    535
                    currentEpoch, msg.sender, allocation.voteLQTY, allocation.vetoLQTY
    536
                ) {} catch {}
    537
            }
    538
    539 12×
            require(
    540 10×
                userState.allocatedLQTY == 0
    541 76×
                    || userState.allocatedLQTY <= uint88(stakingV1.stakes(deriveUserProxyAddress(msg.sender))),
    542
                "Governance: insufficient-or-unallocated-lqty"
    543
            );
    544
    545 32×
            globalState = state;
    546 39×
            userStates[msg.sender] = userState;
    547
        }
    548
    549
        /// @inheritdoc IGovernance
    550 27×
        function unregisterInitiative(address _initiative) external nonReentrant {
    551 17×
            uint16 registrationEpoch = registeredInitiatives[_initiative];
    552 22×
            require(registrationEpoch != 0, "Governance: initiative-not-registered");
    553
            uint16 currentEpoch = epoch();
    554 26×
            require(registrationEpoch + REGISTRATION_WARM_UP_PERIOD < currentEpoch, "Governance: initiative-in-warm-up");
    555
    556
            (, GlobalState memory state) = _snapshotVotes();
    557
            (InitiativeVoteSnapshot memory votesForInitiativeSnapshot_, InitiativeState memory initiativeState) =
    558
                _snapshotVotesForInitiative(_initiative);
    559
    560
            /// Invariant: Must only claim once or unregister
    561 26×
            require(initiativeState.lastEpochClaim < epoch() - 1);
    562
            
    563
            (InitiativeStatus status, , )= getInitiativeState(_initiative);
    564 23×
            require(status == InitiativeStatus.UNREGISTERABLE, "Governance: cannot-unregister-initiative");
    565
    566
            /// @audit TODO: Verify that the FSM here is correct
    567
    568
            // recalculate the average staking timestamp for all counted voting LQTY if the initiative was counted in
    569 13×
            state.countedVoteLQTYAverageTimestamp = _calculateAverageTimestamp(
    570
                state.countedVoteLQTYAverageTimestamp,
    571
                initiativeState.averageStakingTimestampVoteLQTY,
    572
                state.countedVoteLQTY,
    573
                state.countedVoteLQTY - initiativeState.voteLQTY
    574
            );
    575 18×
            state.countedVoteLQTY -= initiativeState.voteLQTY;
    576 32×
            globalState = state;
    577
    578
            /// @audit removal math causes issues
    579
            // delete initiativeStates[_initiative]; 
    580
    581
            /// @audit Should not delete this
    582
            /// weeks * 2^16 > u32 so the contract will stop working before this is an issue
    583 24×
            registeredInitiatives[_initiative] = UNREGISTERED_INITIATIVE; 
    584
    585 11×
            emit UnregisterInitiative(_initiative, currentEpoch);
    586
    587 46×
            try IInitiative(_initiative).onUnregisterInitiative(currentEpoch) {} catch {}
    588
        }
    589
    590
        /// @inheritdoc IGovernance
    591 22×
        function claimForInitiative(address _initiative) external nonReentrant returns (uint256) {
    592
            (VoteSnapshot memory votesSnapshot_,) = _snapshotVotes();
    593 11×
            (InitiativeVoteSnapshot memory votesForInitiativeSnapshot_, InitiativeState memory initiativeState_) = _snapshotVotesForInitiative(_initiative);
    594
    595
            // TODO: Return type from state FSM can be standardized
    596 11×
            (InitiativeStatus status, , uint256 claimableAmount ) = getInitiativeState(_initiative);
    597
    598
            /// @audit Return 0 if we cannot claim
    599
            /// INVARIANT:
    600
            /// We cannot claim only for 2 reasons:
    601
            /// We have already claimed
    602
            /// We do not meet the threshold
    603
            /// TODO: Enforce this with assertions
    604 14×
            if(status != InitiativeStatus.CLAIMABLE) {
    605 10×
                return 0;
    606
            }
    607
            
    608 23×
            assert(votesSnapshot_.forEpoch == epoch() - 1); /// @audit INVARIANT: You can only claim for previous epoch
    609
            /// All unclaimed rewards are always recycled
    610
    611 48×
            initiativeStates[_initiative].lastEpochClaim = epoch() - 1;
    612 76×
            votesForInitiativeSnapshot[_initiative] = votesForInitiativeSnapshot_; // implicitly prevents double claiming
    613
    614
            bold.safeTransfer(_initiative, claimableAmount);
    615
    616 17×
            emit ClaimForInitiative(_initiative, claimableAmount, votesSnapshot_.forEpoch);
    617
    618 50×
            try IInitiative(_initiative).onClaimForInitiative(votesSnapshot_.forEpoch, claimableAmount) {} catch {}
    619
    620
            return claimableAmount;
    621
        }
    622
    }
    0% src/UniV4Donations.sol
    Lines covered: 0 / 67 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
    5
    import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
    6
    7
    import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
    8
    import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
    9
    import {PoolKey} from "v4-core/src/types/PoolKey.sol";
    10
    import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol";
    11
    import {Currency, CurrencyLibrary} from "v4-core/src/types/Currency.sol";
    12
    import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
    13
    14
    import {BaseHook, Hooks} from "./utils/BaseHook.sol";
    15
    import {BribeInitiative} from "./BribeInitiative.sol";
    16
    17 0
    contract UniV4Donations is BribeInitiative, BaseHook {
    18
        using SafeERC20 for IERC20;
    19
        using CurrencyLibrary for Currency;
    20
        using PoolIdLibrary for PoolKey;
    21
    22
        event DonateToPool(uint256 amount);
    23
        event RestartVesting(uint16 epoch, uint240 amount);
    24
    25 0
        uint256 public immutable VESTING_EPOCH_START;
    26 0
        uint256 public immutable VESTING_EPOCH_DURATION;
    27
    28
        address private immutable currency0;
    29
        address private immutable currency1;
    30
        uint24 private immutable fee;
    31
        int24 private immutable tickSpacing;
    32
    33
        struct Vesting {
    34
            uint240 amount;
    35
            uint16 epoch;
    36
            uint256 released;
    37
        }
    38
    39 0
        Vesting public vesting;
    40
    41 0
        constructor(
    42
            address _governance,
    43
            address _bold,
    44
            address _bribeToken,
    45
            uint256 _vestingEpochStart,
    46
            uint256 _vestingEpochDuration,
    47
            address _poolManager,
    48
            address _token,
    49
            uint24 _fee,
    50
            int24 _tickSpacing
    51 0
        ) BribeInitiative(_governance, _bold, _bribeToken) BaseHook(IPoolManager(_poolManager)) {
    52 0
            VESTING_EPOCH_START = _vestingEpochStart;
    53 0
            VESTING_EPOCH_DURATION = _vestingEpochDuration;
    54
    55 0
            if (uint256(uint160(address(_bold))) <= uint256(uint160(address(_token)))) {
    56 0
                currency0 = _bold;
    57 0
                currency1 = _token;
    58
            } else {
    59 0
                currency1 = _token;
    60 0
                currency0 = _bold;
    61
            }
    62 0
            fee = _fee;
    63 0
            tickSpacing = _tickSpacing;
    64
        }
    65
    66 0
        function vestingEpoch() public view returns (uint16) {
    67 0
            return uint16(((block.timestamp - VESTING_EPOCH_START) / VESTING_EPOCH_DURATION)) + 1;
    68
        }
    69
    70 0
        function vestingEpochStart() public view returns (uint256) {
    71 0
            return VESTING_EPOCH_START + ((vestingEpoch() - 1) * VESTING_EPOCH_DURATION);
    72
        }
    73
    74 0
        function _restartVesting(uint240 claimed) internal returns (Vesting memory) {
    75 0
            uint16 epoch = vestingEpoch();
    76 0
            Vesting memory _vesting = vesting;
    77 0
            if (_vesting.epoch < epoch) {
    78 0
                _vesting.amount = claimed + _vesting.amount - uint240(_vesting.released); // roll over unclaimed amount
    79 0
                _vesting.epoch = epoch;
    80 0
                _vesting.released = 0;
    81 0
                vesting = _vesting;
    82 0
                emit RestartVesting(epoch, _vesting.amount);
    83
            }
    84 0
            return _vesting;
    85
        }
    86
    87 0
        function _donateToPool() internal returns (uint256) {
    88 0
            Vesting memory _vesting = _restartVesting(uint240(governance.claimForInitiative(address(this))));
    89 0
            uint256 amount =
    90 0
                (_vesting.amount * (block.timestamp - vestingEpochStart()) / VESTING_EPOCH_DURATION) - _vesting.released;
    91
    92 0
            if (amount != 0) {
    93 0
                PoolKey memory key = poolKey();
    94
    95 0
                manager.donate(key, amount, 0, bytes(""));
    96 0
                manager.sync(key.currency0);
    97 0
                IERC20(Currency.unwrap(key.currency0)).safeTransfer(address(manager), amount);
    98 0
                manager.settle(key.currency0);
    99
    100 0
                vesting.released += amount;
    101
    102 0
                emit DonateToPool(amount);
    103
            }
    104
    105 0
            return amount;
    106
        }
    107
    108 0
        function donateToPool() public returns (uint256) {
    109 0
            return abi.decode(manager.unlock(abi.encode(address(this), poolKey())), (uint256));
    110
        }
    111
    112 0
        function poolKey() public view returns (PoolKey memory key) {
    113 0
            key = PoolKey({
    114 0
                currency0: Currency.wrap(currency0),
    115 0
                currency1: Currency.wrap(currency1),
    116 0
                fee: fee,
    117 0
                tickSpacing: tickSpacing,
    118 0
                hooks: IHooks(address(this))
    119
            });
    120
        }
    121
    122 0
        function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
    123 0
            return Hooks.Permissions({
    124
                beforeInitialize: false,
    125
                afterInitialize: true,
    126
                beforeAddLiquidity: false,
    127
                beforeRemoveLiquidity: false,
    128
                afterAddLiquidity: true,
    129
                afterRemoveLiquidity: false,
    130
                beforeSwap: false,
    131
                afterSwap: false,
    132
                beforeDonate: false,
    133
                afterDonate: false,
    134
                beforeSwapReturnDelta: false,
    135
                afterSwapReturnDelta: false,
    136
                afterAddLiquidityReturnDelta: false,
    137
                afterRemoveLiquidityReturnDelta: false
    138
            });
    139
        }
    140
    141 0
        function afterInitialize(address, PoolKey calldata key, uint160, int24, bytes calldata)
    142
            external
    143
            view
    144
            override
    145
            onlyByManager
    146 0
            returns (bytes4)
    147
        {
    148 0
            require(PoolId.unwrap(poolKey().toId()) == PoolId.unwrap(key.toId()), "UniV4Donations: invalid-pool-id");
    149 0
            return this.afterInitialize.selector;
    150
        }
    151
    152 0
        function afterAddLiquidity(
    153
            address,
    154
            PoolKey calldata key,
    155
            IPoolManager.ModifyLiquidityParams calldata,
    156
            BalanceDelta delta,
    157
            bytes calldata
    158 0
        ) external override onlyByManager returns (bytes4, BalanceDelta) {
    159 0
            require(PoolId.unwrap(poolKey().toId()) == PoolId.unwrap(key.toId()), "UniV4Donations: invalid-pool-id");
    160 0
            _donateToPool();
    161 0
            return (this.afterAddLiquidity.selector, delta);
    162
        }
    163
    164 0
        function _unlockCallback(bytes calldata data) internal override returns (bytes memory) {
    165 0
            (address sender, PoolKey memory key) = abi.decode(data, (address, PoolKey));
    166 0
            require(sender == address(this), "UniV4Donations: invalid-sender");
    167 0
            require(PoolId.unwrap(poolKey().toId()) == PoolId.unwrap(key.toId()), "UniV4Donations: invalid-pool-id");
    168 0
            return abi.encode(_donateToPool());
    169
        }
    170
    }
    65% src/UserProxy.sol
    Lines covered: 27 / 41 (65%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
    5
    import {IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
    6
    import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
    7
    8
    import {IUserProxy} from "./interfaces/IUserProxy.sol";
    9
    import {ILQTYStaking} from "./interfaces/ILQTYStaking.sol";
    10
    import {PermitParams} from "./utils/Types.sol";
    11
    12 167×
    contract UserProxy is IUserProxy {
    13
        using SafeERC20 for IERC20;
    14
    15
        /// @inheritdoc IUserProxy
    16 0
        IERC20 public immutable lqty;
    17
        /// @inheritdoc IUserProxy
    18 0
        IERC20 public immutable lusd;
    19
    20
        /// @inheritdoc IUserProxy
    21 0
        ILQTYStaking public immutable stakingV1;
    22
        /// @inheritdoc IUserProxy
    23 0
        address public immutable stakingV2;
    24
    25 27×
        constructor(address _lqty, address _lusd, address _stakingV1) {
    26
            lqty = IERC20(_lqty);
    27
            lusd = IERC20(_lusd);
    28
            stakingV1 = ILQTYStaking(_stakingV1);
    29
            stakingV2 = msg.sender;
    30
        }
    31
    32
        modifier onlyStakingV2() {
    33 16×
            require(msg.sender == stakingV2, "UserProxy: caller-not-stakingV2");
    34
            _;
    35
        }
    36
    37
        /// @inheritdoc IUserProxy
    38 20×
        function stake(uint256 _amount, address _lqtyFrom) public onlyStakingV2 {
    39 67×
            lqty.transferFrom(_lqtyFrom, address(this), _amount);
    40 60×
            lqty.approve(address(stakingV1), _amount);
    41 40×
            stakingV1.stake(_amount);
    42 12×
            emit Stake(_amount, _lqtyFrom);
    43
        }
    44
    45
        /// @inheritdoc IUserProxy
    46
        function stakeViaPermit(uint256 _amount, address _lqtyFrom, PermitParams calldata _permitParams)
    47
            public
    48
            onlyStakingV2
    49
        {
    50 0
            require(_lqtyFrom == _permitParams.owner, "UserProxy: owner-not-sender");
    51 0
            try IERC20Permit(address(lqty)).permit(
    52 0
                _permitParams.owner,
    53 0
                _permitParams.spender,
    54 0
                _permitParams.value,
    55 0
                _permitParams.deadline,
    56 0
                _permitParams.v,
    57 0
                _permitParams.r,
    58 0
                _permitParams.s
    59
            ) {} catch {}
    60
            stake(_amount, _lqtyFrom);
    61
        }
    62
    63
        /// @inheritdoc IUserProxy
    64 29×
        function unstake(uint256 _amount, address _lqtyRecipient, address _lusdEthRecipient)
    65
            public
    66
            onlyStakingV2
    67
            returns (uint256 lusdAmount, uint256 ethAmount)
    68
        {
    69 40×
            stakingV1.unstake(_amount);
    70
    71 62×
            uint256 lqtyAmount = lqty.balanceOf(address(this));
    72 13×
            if (lqtyAmount > 0) lqty.safeTransfer(_lqtyRecipient, lqtyAmount);
    73 60×
            lusdAmount = lusd.balanceOf(address(this));
    74
            if (lusdAmount > 0) lusd.safeTransfer(_lusdEthRecipient, lusdAmount);
    75
            ethAmount = address(this).balance;
    76
            if (ethAmount > 0) {
    77 0
                (bool success,) = payable(_lusdEthRecipient).call{value: ethAmount}("");
    78
                success;
    79
            }
    80
    81 13×
            emit Unstake(_amount, _lqtyRecipient, _lusdEthRecipient, lusdAmount, ethAmount);
    82
        }
    83
    84
        /// @inheritdoc IUserProxy
    85 35×
        function staked() external view returns (uint88) {
    86 61×
            return uint88(stakingV1.stakes(address(this)));
    87
        }
    88
    89
        receive() external payable {}
    90
    }
    40% src/UserProxyFactory.sol
    Lines covered: 4 / 10 (40%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {Clones} from "openzeppelin-contracts/contracts/proxy/Clones.sol";
    5
    6
    import {IUserProxyFactory} from "./interfaces/IUserProxyFactory.sol";
    7
    import {UserProxy} from "./UserProxy.sol";
    8
    9 0
    contract UserProxyFactory is IUserProxyFactory {
    10
        /// @inheritdoc IUserProxyFactory
    11 0
        address public immutable userProxyImplementation;
    12
    13 0
        constructor(address _lqty, address _lusd, address _stakingV1) {
    14 0
            userProxyImplementation = address(new UserProxy(_lqty, _lusd, _stakingV1));
    15
        }
    16
    17
        /// @inheritdoc IUserProxyFactory
    18 22×
        function deriveUserProxyAddress(address _user) public view returns (address) {
    19 11×
            return Clones.predictDeterministicAddress(userProxyImplementation, bytes32(uint256(uint160(_user))));
    20
        }
    21
    22
        /// @inheritdoc IUserProxyFactory
    23 18×
        function deployUserProxy() public returns (address) {
    24
            // reverts if the user already has a proxy
    25
            address userProxy = Clones.cloneDeterministic(userProxyImplementation, bytes32(uint256(uint160(msg.sender))));
    26
    27 0
            emit DeployUserProxy(msg.sender, userProxy);
    28
    29 0
            return userProxy;
    30
        }
    31
    }
    0% src/utils/BaseHook.sol
    Lines covered: 0 / 22 (0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    import {Hooks} from "v4-core/src/libraries/Hooks.sol";
    5
    import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol";
    6
    import {IHooks} from "v4-core/src/interfaces/IHooks.sol";
    7
    import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol";
    8
    import {PoolKey} from "v4-core/src/types/PoolKey.sol";
    9
    import {BeforeSwapDelta} from "v4-core/src/types/BeforeSwapDelta.sol";
    10
    import {IUnlockCallback} from "v4-core/src/interfaces/callback/IUnlockCallback.sol";
    11
    12 0
    contract ImmutableState {
    13 0
        IPoolManager public immutable manager;
    14
    15 0
        constructor(IPoolManager _manager) {
    16 0
            manager = _manager;
    17
        }
    18
    }
    19
    20
    abstract contract SafeCallback is ImmutableState, IUnlockCallback {
    21
        error NotManager();
    22
    23
        modifier onlyByManager() {
    24 0
            if (msg.sender != address(manager)) revert NotManager();
    25
            _;
    26
        }
    27
    28
        /// @dev We force the onlyByManager modifier by exposing a virtual function after the onlyByManager check.
    29 0
        function unlockCallback(bytes calldata data) external onlyByManager returns (bytes memory) {
    30 0
            return _unlockCallback(data);
    31
        }
    32
    33
        function _unlockCallback(bytes calldata data) internal virtual returns (bytes memory);
    34
    }
    35
    36
    abstract contract BaseHook is IHooks, SafeCallback {
    37
        error NotSelf();
    38
        error InvalidPool();
    39
        error LockFailure();
    40
        error HookNotImplemented();
    41
    42
        constructor(IPoolManager _manager) ImmutableState(_manager) {
    43 0
            validateHookAddress(this);
    44
        }
    45
    46
        /// @dev Only this address may call this function
    47
        modifier selfOnly() {
    48
            if (msg.sender != address(this)) revert NotSelf();
    49
            _;
    50
        }
    51
    52
        /// @dev Only pools with hooks set to this contract may call this function
    53
        modifier onlyValidPools(IHooks hooks) {
    54
            if (hooks != this) revert InvalidPool();
    55
            _;
    56
        }
    57
    58
        function getHookPermissions() public pure virtual returns (Hooks.Permissions memory);
    59
    60
        // this function is virtual so that we can override it during testing,
    61
        // which allows us to deploy an implementation to any address
    62
        // and then etch the bytecode into the correct address
    63 0
        function validateHookAddress(BaseHook _this) internal pure virtual {
    64 0
            Hooks.validateHookPermissions(_this, getHookPermissions());
    65
        }
    66
    67
        function _unlockCallback(bytes calldata data) internal virtual override returns (bytes memory) {
    68
            (bool success, bytes memory returnData) = address(this).call(data);
    69
            if (success) return returnData;
    70
            if (returnData.length == 0) revert LockFailure();
    71
            // if the call failed, bubble up the reason
    72
            /// @solidity memory-safe-assembly
    73
            assembly {
    74
                revert(add(returnData, 32), mload(returnData))
    75
            }
    76
        }
    77
    78 0
        function beforeInitialize(address, PoolKey calldata, uint160, bytes calldata) external virtual returns (bytes4) {
    79
            revert HookNotImplemented();
    80
        }
    81
    82
        function afterInitialize(address, PoolKey calldata, uint160, int24, bytes calldata)
    83
            external
    84
            virtual
    85
            returns (bytes4)
    86
        {
    87
            revert HookNotImplemented();
    88
        }
    89
    90
        function beforeAddLiquidity(address, PoolKey calldata, IPoolManager.ModifyLiquidityParams calldata, bytes calldata)
    91
            external
    92
            virtual
    93
            returns (bytes4)
    94
        {
    95
            revert HookNotImplemented();
    96
        }
    97
    98 0
        function beforeRemoveLiquidity(
    99
            address,
    100
            PoolKey calldata,
    101
            IPoolManager.ModifyLiquidityParams calldata,
    102
            bytes calldata
    103 0
        ) external virtual returns (bytes4) {
    104 0
            revert HookNotImplemented();
    105
        }
    106
    107
        function afterAddLiquidity(
    108
            address,
    109
            PoolKey calldata,
    110
            IPoolManager.ModifyLiquidityParams calldata,
    111
            BalanceDelta,
    112
            bytes calldata
    113
        ) external virtual returns (bytes4, BalanceDelta) {
    114
            revert HookNotImplemented();
    115
        }
    116
    117 0
        function afterRemoveLiquidity(
    118
            address,
    119
            PoolKey calldata,
    120
            IPoolManager.ModifyLiquidityParams calldata,
    121
            BalanceDelta,
    122
            bytes calldata
    123 0
        ) external virtual returns (bytes4, BalanceDelta) {
    124 0
            revert HookNotImplemented();
    125
        }
    126
    127 0
        function beforeSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, bytes calldata)
    128
            external
    129
            virtual
    130 0
            returns (bytes4, BeforeSwapDelta, uint24)
    131
        {
    132 0
            revert HookNotImplemented();
    133
        }
    134
    135 0
        function afterSwap(address, PoolKey calldata, IPoolManager.SwapParams calldata, BalanceDelta, bytes calldata)
    136
            external
    137
            virtual
    138
            returns (bytes4, int128)
    139
        {
    140
            revert HookNotImplemented();
    141
        }
    142
    143 0
        function beforeDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
    144
            external
    145
            virtual
    146
            returns (bytes4)
    147
        {
    148
            revert HookNotImplemented();
    149
        }
    150
    151
        function afterDonate(address, PoolKey calldata, uint256, uint256, bytes calldata)
    152
            external
    153
            virtual
    154
            returns (bytes4)
    155
        {
    156
            revert HookNotImplemented();
    157
        }
    158
    }
    68% src/utils/DoubleLinkedList.sol
    Lines covered: 15 / 22 (68%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    /// @title DoubleLinkedList
    5
    /// @notice Implements a double linked list where the head is defined as the null item's prev pointer
    6
    /// and the tail is defined as the null item's next pointer ([tail][prev][item][next][head])
    7 0
    library DoubleLinkedList {
    8
        struct Item {
    9
            uint88 value;
    10
            uint16 prev;
    11
            uint16 next;
    12
        }
    13
    14
        struct List {
    15
            mapping(uint16 => Item) items;
    16
        }
    17
    18
        error IdIsZero();
    19
        error ItemNotInList();
    20
        error ItemInList();
    21
    22
        /// @notice Returns the head item id of the list
    23
        /// @param list Linked list which contains the item
    24
        /// @return _ Id of the head item
    25
        function getHead(List storage list) internal view returns (uint16) {
    26 28×
            return list.items[0].prev;
    27
        }
    28
    29
        /// @notice Returns the tail item id of the list
    30
        /// @param list Linked list which contains the item
    31
        /// @return _ Id of the tail item
    32
        function getTail(List storage list) internal view returns (uint16) {
    33 0
            return list.items[0].next;
    34
        }
    35
    36
        /// @notice Returns the item id which follows item `id`. Returns the head item id of the list if the `id` is 0.
    37
        /// @param list Linked list which contains the items
    38
        /// @param id Id of the current item
    39
        /// @return _ Id of the current item's next item
    40 0
        function getNext(List storage list, uint16 id) internal view returns (uint16) {
    41 0
            return list.items[id].next;
    42
        }
    43
    44
        /// @notice Returns the item id which precedes item `id`. Returns the tail item id of the list if the `id` is 0.
    45
        /// @param list Linked list which contains the items
    46
        /// @param id Id of the current item
    47
        /// @return _ Id of the current item's previous item
    48
        function getPrev(List storage list, uint16 id) internal view returns (uint16) {
    49 0
            return list.items[id].prev;
    50
        }
    51
    52
        /// @notice Returns the value of item `id`
    53
        /// @param list Linked list which contains the item
    54
        /// @param id Id of the item
    55
        /// @return _ Value of the item
    56 0
        function getValue(List storage list, uint16 id) internal view returns (uint88) {
    57 27×
            return list.items[id].value;
    58
        }
    59
    60
        /// @notice Returns the item `id`
    61
        /// @param list Linked list which contains the item
    62
        /// @param id Id of the item
    63
        /// @return _ Item
    64 0
        function getItem(List storage list, uint16 id) internal view returns (Item memory) {
    65 46×
            return list.items[id];
    66
        }
    67
    68
        /// @notice Returns whether the list contains item `id`
    69
        /// @param list Linked list which should contain the item
    70
        /// @param id Id of the item to check
    71
        /// @return _ True if the list contains the item, false otherwise
    72
        function contains(List storage list, uint16 id) internal view returns (bool) {
    73
            if (id == 0) revert IdIsZero();
    74 76×
            return (list.items[id].prev != 0 || list.items[id].next != 0 || list.items[0].next == id);
    75
        }
    76
    77
        /// @notice Inserts an item with `id` in the list before item `next`
    78
        /// - if `next` is 0, the item is inserted at the start (head) of the list
    79
        /// @dev This function should not be called with an `id` that is already in the list.
    80
        /// @param list Linked list which contains the next item and into which the new item will be inserted
    81
        /// @param id Id of the item to insert
    82
        /// @param value Value of the item to insert
    83
        /// @param next Id of the item which should follow item `id`
    84
        function insert(List storage list, uint16 id, uint88 value, uint16 next) internal {
    85 10×
            if (contains(list, id)) revert ItemInList();
    86 14×
            if (next != 0 && !contains(list, next)) revert ItemNotInList();
    87 26×
            uint16 prev = list.items[next].prev;
    88 27×
            list.items[prev].next = id;
    89 11×
            list.items[next].prev = id;
    90 13×
            list.items[id].prev = prev;
    91 15×
            list.items[id].next = next;
    92 10×
            list.items[id].value = value;
    93
        }
    94
    }
    100% src/utils/Math.sol
    Lines covered: 8 / 8 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4 10×
    function add(uint88 a, int88 b) pure returns (uint88) {
    5 18×
        if (b < 0) {
    6 15×
            return a - abs(b);
    7
        }
    8 23×
        return a + abs(b);
    9
    }
    10
    11
    function max(uint256 a, uint256 b) pure returns (uint256) {
    12 11×
        return a > b ? a : b;
    13
    }
    14
    15
    function abs(int88 a) pure returns (uint88) {
    16 28×
        return a < 0 ? uint88(uint256(-int256(a))) : uint88(a);
    17
    }
    22% src/utils/Multicall.sol
    Lines covered: 2 / 9 (22%)
    1
    // SPDX-License-Identifier: GPL-2.0-or-later
    2
    pragma solidity ^0.8.24;
    3
    4
    import {IMulticall} from "../interfaces/IMulticall.sol";
    5
    6
    /// Copied from: https://github.com/Uniswap/v3-periphery/blob/main/contracts/base/Multicall.sol
    7
    /// @title Multicall
    8
    /// @notice Enables calling multiple methods in a single call to the contract
    9
    abstract contract Multicall is IMulticall {
    10
        /// @inheritdoc IMulticall
    11
        function multicall(bytes[] calldata data) public payable override returns (bytes[] memory results) {
    12 0
            results = new bytes[](data.length);
    13
            for (uint256 i = 0; i < data.length; i++) {
    14 0
                (bool success, bytes memory result) = address(this).delegatecall(data[i]);
    15
    16 0
                if (!success) {
    17
                    // Next 5 lines from https://ethereum.stackexchange.com/a/83577
    18 0
                    if (result.length < 68) revert();
    19
                    assembly {
    20 0
                        result := add(result, 0x04)
    21
                    }
    22 0
                    revert(abi.decode(result, (string)));
    23
                }
    24
    25 0
                results[i] = result;
    26
            }
    27
        }
    28
    }
    100% src/utils/Types.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.24;
    3
    4
    struct PermitParams {
    5
        address owner;
    6
        address spender;
    7
        uint256 value;
    8
        uint256 deadline;
    9
        uint8 v;
    10
        bytes32 r;
    11
        bytes32 s;
    12
    }
    13
    14
    uint256 constant WAD = 1e18;
    12% test/mocks/MockERC20Tester.sol
    Lines covered: 1 / 8 (12%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {MockERC20} from "forge-std/mocks/MockERC20.sol";
    5
    6 279×
    contract MockERC20Tester is MockERC20 {
    7
        address owner;
    8
    9
        modifier onlyOwner() {
    10 0
            require(msg.sender == owner);
    11
            _;
    12
        }
    13
    14 0
        constructor(address recipient, uint256 mintAmount, string memory name, string memory symbol, uint8 decimals) {
    15 0
            super.initialize(name, symbol, decimals);
    16 0
            _mint(recipient, mintAmount);
    17
    18 0
            owner = msg.sender;
    19
        }
    20
    21 0
        function mint(address to, uint256 amount) public onlyOwner {
    22 0
            _mint(to, amount);
    23
        }
    24
    }
    90% test/mocks/MockStakingV1.sol
    Lines covered: 10 / 11 (90%)
    1
    // SPDX-License-Identifier: UNLICENSED
    2
    pragma solidity ^0.8.24;
    3
    4
    import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
    5
    6 115×
    contract MockStakingV1 {
    7 0
        IERC20 public immutable lqty;
    8
    9 66×
        mapping(address => uint256) public stakes;
    10
    11 27×
        constructor(address _lqty) {
    12
            lqty = IERC20(_lqty);
    13
        }
    14
    15 11×
        function stake(uint256 _LQTYamount) external {
    16 27×
            stakes[msg.sender] += _LQTYamount;
    17 17×
            lqty.transferFrom(msg.sender, address(this), _LQTYamount);
    18
        }
    19
    20 15×
        function unstake(uint256 _LQTYamount) external {
    21 27×
            stakes[msg.sender] -= _LQTYamount;
    22 62×
            lqty.transfer(msg.sender, _LQTYamount);
    23
        }
    24
    }
    100% test/recon/BeforeAfter.sol
    Lines covered: 22 / 22 (100%)
    1
    2
    // SPDX-License-Identifier: GPL-2.0
    3
    pragma solidity ^0.8.0;
    4
    5
    import {Asserts} from "@chimera/Asserts.sol";
    6
    import {Setup} from "./Setup.sol";
    7
    import {IGovernance} from "../../src/interfaces/IGovernance.sol";
    8
    import {IBribeInitiative} from "../../src/interfaces/IBribeInitiative.sol";
    9
    import {Governance} from "../../src/Governance.sol";
    10
    11
    12
    abstract contract BeforeAfter is Setup, Asserts {
    13
        struct Vars {
    14
            uint16 epoch;
    15
            mapping(address => Governance.InitiativeStatus) initiativeStatus;
    16
            // initiative => user => epoch => claimed
    17
            mapping(address => mapping(address => mapping(uint16 => bool))) claimedBribeForInitiativeAtEpoch;
    18
            uint128 lqtyBalance;
    19
            uint128 lusdBalance;
    20
        }
    21
    22
        Vars internal _before;
    23
        Vars internal _after;
    24
    25
        modifier withChecks {
    26 60×
            __before();
    27
            _;
    28 28×
            __after();
    29
        }
    30
    31
        function __before() internal {
    32 70×
            uint16 currentEpoch = governance.epoch();
    33 12×
            _before.epoch = currentEpoch;
    34 22×
            for(uint8 i; i < deployedInitiatives.length; i++) {
    35 27×
                address initiative = deployedInitiatives[i];
    36 69×
                (Governance.InitiativeStatus status,,) = governance.getInitiativeState(initiative);
    37 29×
                _before.initiativeStatus[initiative] = status;
    38 119×
                _before.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch] = IBribeInitiative(initiative).claimedBribeAtEpoch(user, currentEpoch);
    39
            }
    40
    41 81×
            _before.lqtyBalance = uint128(lqty.balanceOf(user));
    42 82×
            _before.lusdBalance = uint128(lusd.balanceOf(user));
    43
        }
    44
    45
        function __after() internal {
    46 70×
            uint16 currentEpoch = governance.epoch();
    47 12×
            _after.epoch = currentEpoch;
    48 22×
            for(uint8 i; i < deployedInitiatives.length; i++) {
    49 27×
                address initiative = deployedInitiatives[i];
    50 69×
                (Governance.InitiativeStatus status,,) = governance.getInitiativeState(initiative);
    51 29×
                _after.initiativeStatus[initiative] = status;
    52 119×
                _after.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch] = IBribeInitiative(initiative).claimedBribeAtEpoch(user, currentEpoch);
    53
            }
    54
    55 81×
            _after.lqtyBalance = uint128(lqty.balanceOf(user));
    56 82×
            _after.lusdBalance = uint128(lusd.balanceOf(user));
    57
        }
    58
    }
    100% test/recon/CryticTester.sol
    Lines covered: 2 / 2 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {TargetFunctions} from "./TargetFunctions.sol";
    5
    import {CryticAsserts} from "@chimera/CryticAsserts.sol";
    6
    7
    // echidna . --contract CryticTester --config echidna.yaml
    8
    // medusa fuzz
    9 372×
    contract CryticTester is TargetFunctions, CryticAsserts {
    10
        constructor() payable {
    11
            setup();
    12
        }
    13
    }
    63% test/recon/Properties.sol
    Lines covered: 14 / 22 (63%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {GovernanceProperties} from "./properties/GovernanceProperties.sol";
    5
    import {BribeInitiativeProperties} from "./properties/BribeInitiativeProperties.sol";
    6
    7
    abstract contract Properties is GovernanceProperties, BribeInitiativeProperties {
    8
        string internal constant ASSERTION_CANARY = "!!! canary assertion";
    9
        string internal constant ASSERTION_GV01 =
    10
            "!!! GV-01: Initiative state should only return one state per epoch";
    11
        string internal constant ASSERTION_BI01_LQTY =
    12
            "!!! BI-01: User should receive percentage of bribes corresponding to their allocation";
    13
        string internal constant ASSERTION_BI01_BOLD =
    14
            "!!! BI-01: User should receive percentage of BOLD bribes corresponding to their allocation";
    15
        string internal constant ASSERTION_BI02 =
    16
            "!!! BI-02: User can only claim bribes once in an epoch";
    17
        string internal constant ASSERTION_BI03 =
    18
            "!!! BI-03: Accounting for user allocation amount is always correct";
    19
        string internal constant ASSERTION_BI04 =
    20
            "!!! BI-04: Accounting for total allocation amount is always correct";
    21
        string internal constant ASSERTION_BI05_BRIBE_DUST =
    22
            "!!! BI-05: Bribe token dust amount remaining after claiming should be less than 100 million wei";
    23
        string internal constant ASSERTION_BI05_BOLD_DUST =
    24
            "!!! BI-05: Bold token dust amount remaining after claiming should be less than 100 million wei";
    25
        string internal constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE =
    26
            "Canary invariant";
    27
    28
        function _assertionGV01() internal pure virtual override returns (string memory) {
    29 19×
            return ASSERTION_GV01;
    30
        }
    31
    32 0
        function _assertionBI01Lqty() internal pure virtual override returns (string memory) {
    33 0
            return ASSERTION_BI01_LQTY;
    34
        }
    35
    36 0
        function _assertionBI01Bold() internal pure virtual override returns (string memory) {
    37 0
            return ASSERTION_BI01_BOLD;
    38
        }
    39
    40
        function _assertionBI02() internal pure virtual override returns (string memory) {
    41 19×
            return ASSERTION_BI02;
    42
        }
    43
    44
        function _assertionBI03() internal pure virtual override returns (string memory) {
    45 19×
            return ASSERTION_BI03;
    46
        }
    47
    48
        function _assertionBI04() internal pure virtual override returns (string memory) {
    49 19×
            return ASSERTION_BI04;
    50
        }
    51
    52 0
        function _assertionBI05BribeDust() internal pure virtual override returns (string memory) {
    53 0
            return ASSERTION_BI05_BRIBE_DUST;
    54
        }
    55
    56 0
        function _assertionBI05BoldDust() internal pure virtual override returns (string memory) {
    57 0
            return ASSERTION_BI05_BOLD_DUST;
    58
        }
    59
    60
        /// @dev Canary assertion helper. A failing input is expected to be discovered during fuzzing.
    61 11×
        function assert_canary_ASSERTION_CANARY(uint256 entropy) public {
    62 23×
            t(entropy > 0, ASSERTION_CANARY);
    63
        }
    64
    65
        /// @dev Canary global invariant expected to fail immediately.
    66 17×
        function invariant_canary()
    67
            public
    68
            virtual
    69
            returns (bool)
    70
        {
    71 22×
            t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE);
    72
            return true;
    73
        }
    74
    }
    100% test/recon/Setup.sol
    Lines covered: 39 / 39 (100%)
    1
    2
    // SPDX-License-Identifier: GPL-2.0
    3
    pragma solidity ^0.8.0;
    4
    5
    import {BaseSetup} from "@chimera/BaseSetup.sol";
    6
    import {console2} from "forge-std/Test.sol";
    7
    import {vm} from "@chimera/Hevm.sol";
    8
    import {IERC20} from "forge-std/interfaces/IERC20.sol";
    9
    10
    import {MockERC20Tester} from "../mocks/MockERC20Tester.sol";
    11
    import {MockStakingV1} from "../mocks/MockStakingV1.sol";
    12
    import {MaliciousInitiative} from "../mocks/MaliciousInitiative.sol";
    13
    import {Governance} from "src/Governance.sol";
    14
    import {BribeInitiative} from "../../src/BribeInitiative.sol";
    15
    import {IBribeInitiative} from "../../src/interfaces/IBribeInitiative.sol";
    16
    import {IGovernance} from "src/interfaces/IGovernance.sol";
    17
    import {IInitiative} from "src/interfaces/IInitiative.sol";
    18
    19
    abstract contract Setup is BaseSetup {
    20
        Governance governance;
    21
        MockERC20Tester internal lqty;
    22
        MockERC20Tester internal lusd;
    23
        IBribeInitiative internal initiative1;
    24
    25 11×
        address internal user = address(this);
    26 10×
        address internal user2 = address(0x537C8f3d3E18dF5517a58B3fB9D9143697996802); // derived using makeAddrAndKey
    27
        address internal stakingV1;
    28
        address internal userProxy;
    29 26×
        address[] internal users = new address[](2);
    30
        address[] internal deployedInitiatives;
    31
        uint256 internal user2Pk = 23868421370328131711506074113045611601786642648093516849953535378706721142721; // derived using makeAddrAndKey
    32
        bool internal claimedTwice;
    33
    34
        mapping(uint16 => uint88) internal ghostTotalAllocationAtEpoch;
    35
        mapping(address => uint88) internal ghostLqtyAllocationByUserAtEpoch;
    36
    37
        uint128 internal constant REGISTRATION_FEE = 1e18;
    38
        uint128 internal constant REGISTRATION_THRESHOLD_FACTOR = 0.01e18;
    39
        uint128 internal constant UNREGISTRATION_THRESHOLD_FACTOR = 4e18;
    40
        uint16 internal constant REGISTRATION_WARM_UP_PERIOD = 4;
    41
        uint16 internal constant UNREGISTRATION_AFTER_EPOCHS = 4;
    42
        uint128 internal constant VOTING_THRESHOLD_FACTOR = 0.04e18;
    43
        uint88 internal constant MIN_CLAIM = 500e18;
    44
        uint88 internal constant MIN_ACCRUAL = 1000e18;
    45
        uint32 internal constant EPOCH_DURATION = 604800;
    46
        uint32 internal constant EPOCH_VOTING_CUTOFF = 518400;
    47
    48
    49
      function setup() internal virtual override {
    50 32×
          users.push(user);
    51 26×
          users.push(user2);
    52
    53
          uint256 initialMintAmount = type(uint88).max;
    54 41×
          lqty = new MockERC20Tester(user, initialMintAmount, "Liquity", "LQTY", 18);
    55 41×
          lusd = new MockERC20Tester(user, initialMintAmount, "Liquity USD", "LUSD", 18);
    56 45×
          lqty.mint(user2, initialMintAmount);
    57
    58 43×
          stakingV1 = address(new MockStakingV1(address(lqty)));
    59 51×
          governance = new Governance(
    60
              address(lqty),
    61
              address(lusd),
    62
              stakingV1,
    63
              address(lusd), // bold
    64 57×
              IGovernance.Configuration({
    65
                  registrationFee: REGISTRATION_FEE,
    66
                  registrationThresholdFactor: REGISTRATION_THRESHOLD_FACTOR,
    67
                  unregistrationThresholdFactor: UNREGISTRATION_THRESHOLD_FACTOR,
    68
                  registrationWarmUpPeriod: REGISTRATION_WARM_UP_PERIOD,
    69
                  unregistrationAfterEpochs: UNREGISTRATION_AFTER_EPOCHS,
    70
                  votingThresholdFactor: VOTING_THRESHOLD_FACTOR,
    71
                  minClaim: MIN_CLAIM,
    72
                  minAccrual: MIN_ACCRUAL,
    73
                  epochStart: uint32(block.timestamp),
    74
                  epochDuration: EPOCH_DURATION,
    75
                  epochVotingCutoff: EPOCH_VOTING_CUTOFF
    76
              }),
    77
              deployedInitiatives // no initial initiatives passed in because don't have cheatcodes for calculating address where gov will be deployed
    78
          );
    79
    80
          // deploy proxy so user can approve it
    81 81×
          userProxy = governance.deployUserProxy();
    82 60×
          lqty.approve(address(userProxy), initialMintAmount);
    83 64×
          lusd.approve(address(userProxy), initialMintAmount);
    84
    85
          // approve governance for user's tokens
    86 67×
          lqty.approve(address(governance), initialMintAmount);
    87 67×
          lusd.approve(address(governance), initialMintAmount);
    88
    89
          // register one of the initiatives, leave the other for registering/unregistering via TargetFunction
    90 57×
          initiative1 = IBribeInitiative(address(new BribeInitiative(address(governance), address(lusd), address(lqty))));
    91 21×
          deployedInitiatives.push(address(initiative1));
    92
    93 53×
          governance.registerInitiative(address(initiative1));
    94
      }
    95
    96
      function _getDeployedInitiative(uint8 index) internal returns (address initiative) {
    97 32×
        return deployedInitiatives[index % deployedInitiatives.length];
    98
      }
    99
    100
      function _getClampedTokenBalance(address token, address holder) internal returns (uint256 balance) {
    101
        return IERC20(token).balanceOf(holder);
    102
      }
    103
    104
      function _getRandomUser(uint8 index) internal returns (address randomUser) {
    105 11×
        return users[index % users.length];
    106
      }
    107
    108
    }
    100% test/recon/TargetFunctions.sol
    Lines covered: 6 / 6 (100%)
    1
    2
    // SPDX-License-Identifier: GPL-2.0
    3
    pragma solidity ^0.8.0;
    4
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
    8
    import {console2} from "forge-std/Test.sol";
    9
    10
    import {Properties} from "./Properties.sol";
    11
    import {GovernanceTargets} from "./targets/GovernanceTargets.sol";
    12
    import {BribeInitiativeTargets} from "./targets/BribeInitiativeTargets.sol";
    13
    import {MaliciousInitiative} from "../mocks/MaliciousInitiative.sol";
    14
    import {BribeInitiative} from "../../src/BribeInitiative.sol";
    15
    import {ILQTYStaking} from "../../src/interfaces/ILQTYStaking.sol";
    16
    import {IInitiative} from "../../src/interfaces/IInitiative.sol";
    17
    import {IUserProxy} from "../../src/interfaces/IUserProxy.sol";
    18
    import {PermitParams} from "../../src/utils/Types.sol";
    19
    20
    21
    abstract contract TargetFunctions is GovernanceTargets, BribeInitiativeTargets {
    22
    23
        // helper to deploy initiatives for registering that results in more bold transferred to the Governance contract
    24
        function helper_deployInitiative() withChecks public {
    25 43×
            address initiative = address(new BribeInitiative(address(governance), address(lusd), address(lqty)));
    26 29×
            deployedInitiatives.push(initiative);
    27
        }
    28
    29
        // helper to simulate bold accrual in Governance contract
    30 11×
        function helper_accrueBold(uint88 boldAmount) withChecks public {
    31 73×
            boldAmount = uint88(boldAmount % lusd.balanceOf(user));
    32
            // target contract is the user so it can transfer directly
    33 69×
            lusd.transfer(address(governance), boldAmount);
    34
        }
    35
    }
    50% test/recon/properties/BribeInitiativeProperties.sol
    Lines covered: 33 / 65 (50%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BeforeAfter} from "../BeforeAfter.sol";
    5
    import {IBribeInitiative} from "../../../src/interfaces/IBribeInitiative.sol";
    6
    7
    abstract contract BribeInitiativeProperties is BeforeAfter {
    8
        function _assertionBI01Lqty() internal pure virtual returns (string memory);
    9
        function _assertionBI01Bold() internal pure virtual returns (string memory);
    10
        function _assertionBI02() internal pure virtual returns (string memory);
    11
        function _assertionBI03() internal pure virtual returns (string memory);
    12
        function _assertionBI04() internal pure virtual returns (string memory);
    13
        function _assertionBI05BribeDust() internal pure virtual returns (string memory);
    14
        function _assertionBI05BoldDust() internal pure virtual returns (string memory);
    15
    16
        function invariant_BI01() public returns (bool) {
    17 72×
            uint16 currentEpoch = governance.epoch();
    18 22×
            for (uint8 i; i < deployedInitiatives.length; i++) {
    19 30×
                address initiative = deployedInitiatives[i];
    20
                // if the bool switches, the user has claimed their bribe for the epoch
    21
                if (
    22 37×
                    _before.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch]
    23 40×
                        != _after.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch]
    24 0
                ) {
    25
                    // calculate user balance delta of the bribe tokens
    26 0
                    uint128 lqtyBalanceDelta = _after.lqtyBalance - _before.lqtyBalance;
    27 0
                    uint128 lusdBalanceDelta = _after.lusdBalance - _before.lusdBalance;
    28
    29
                    // calculate balance delta as a percentage of the total bribe for this epoch
    30 0
                    (uint128 bribeBoldAmount, uint128 bribeBribeTokenAmount) =
    31 0
                        IBribeInitiative(initiative).bribeByEpoch(currentEpoch);
    32 0
                    uint128 lqtyPercentageOfBribe = (lqtyBalanceDelta / bribeBribeTokenAmount) * 10_000;
    33 0
                    uint128 lusdPercentageOfBribe = (lusdBalanceDelta / bribeBoldAmount) * 10_000;
    34
    35
                    // Shift right by 40 bits (128 - 88) to get the 88 most significant bits
    36 0
                    uint88 lqtyPercentageOfBribe88 = uint88(lqtyPercentageOfBribe >> 40);
    37 0
                    uint88 lusdPercentageOfBribe88 = uint88(lusdPercentageOfBribe >> 40);
    38
    39
                    // calculate user allocation percentage of total for this epoch
    40 0
                    uint88 lqtyAllocatedByUserAtEpoch =
    41 0
                        IBribeInitiative(initiative).lqtyAllocatedByUserAtEpoch(user, currentEpoch);
    42 0
                    uint88 totalLQTYAllocatedAtEpoch = IBribeInitiative(initiative).totalLQTYAllocatedByEpoch(currentEpoch);
    43 0
                    uint88 allocationPercentageOfTotal = (lqtyAllocatedByUserAtEpoch / totalLQTYAllocatedAtEpoch) * 10_000;
    44
    45
                    // check that allocation percentage and received bribe percentage match
    46 0
                    eq(
    47 0
                        lqtyPercentageOfBribe88,
    48 0
                        allocationPercentageOfTotal,
    49 0
                        _assertionBI01Lqty()
    50
                    );
    51 0
                    eq(
    52 0
                        lusdPercentageOfBribe88,
    53 0
                        allocationPercentageOfTotal,
    54 0
                        _assertionBI01Bold()
    55
                    );
    56
                }
    57
            }
    58
            return true;
    59
        }
    60
    61
        function invariant_BI02() public returns (bool) {
    62 13×
            t(!claimedTwice, _assertionBI02());
    63
            return true;
    64
        }
    65
    66
        function invariant_BI03() public returns (bool) {
    67 72×
            uint16 currentEpoch = governance.epoch();
    68 25×
            for (uint8 i; i < deployedInitiatives.length; i++) {
    69 27×
                IBribeInitiative initiative = IBribeInitiative(deployedInitiatives[i]);
    70 61×
                uint88 lqtyAllocatedByUserAtEpoch = initiative.lqtyAllocatedByUserAtEpoch(user, currentEpoch);
    71
                eq(
    72 20×
                    ghostLqtyAllocationByUserAtEpoch[user],
    73
                    lqtyAllocatedByUserAtEpoch,
    74
                    _assertionBI03()
    75
                );
    76
            }
    77
            return true;
    78
        }
    79
    80
        function invariant_BI04() public returns (bool) {
    81 72×
            uint16 currentEpoch = governance.epoch();
    82 27×
            for (uint8 i; i < deployedInitiatives.length; i++) {
    83 28×
                IBribeInitiative initiative = IBribeInitiative(deployedInitiatives[i]);
    84 56×
                uint88 totalLQTYAllocatedAtEpoch = initiative.totalLQTYAllocatedByEpoch(currentEpoch);
    85
                eq(
    86 19×
                    ghostTotalAllocationAtEpoch[currentEpoch],
    87
                    totalLQTYAllocatedAtEpoch,
    88
                    _assertionBI04()
    89
                );
    90
            }
    91
            return true;
    92
        }
    93
    94
        // TODO: double check that this implementation is correct
    95
        function invariant_BI05() public returns (bool) {
    96 72×
            uint16 currentEpoch = governance.epoch();
    97 22×
            for (uint8 i; i < deployedInitiatives.length; i++) {
    98 30×
                address initiative = deployedInitiatives[i];
    99
                // if the bool switches, the user has claimed their bribe for the epoch
    100
                if (
    101 37×
                    _before.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch]
    102 40×
                        != _after.claimedBribeForInitiativeAtEpoch[initiative][user][currentEpoch]
    103 0
                ) {
    104
                    // check that the remaining bribe amount left over is less than 100 million wei
    105 0
                    uint256 bribeTokenBalanceInitiative = lqty.balanceOf(initiative);
    106 0
                    uint256 boldTokenBalanceInitiative = lusd.balanceOf(initiative);
    107
    108 0
                    lte(
    109 0
                        bribeTokenBalanceInitiative,
    110 0
                        1e8,
    111 0
                        _assertionBI05BribeDust()
    112
                    );
    113 0
                    lte(
    114 0
                        boldTokenBalanceInitiative,
    115 0
                        1e8,
    116 0
                        _assertionBI05BoldDust()
    117
                    );
    118
                }
    119
            }
    120
            return true;
    121
        }
    122
    }
    100% test/recon/properties/GovernanceProperties.sol
    Lines covered: 9 / 9 (100%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {BeforeAfter} from "../BeforeAfter.sol";
    5
    6
    abstract contract GovernanceProperties is BeforeAfter {
    7
        function _assertionGV01() internal pure virtual returns (string memory);
    8
    9
        function invariant_GV01() public returns (bool) {
    10
            // first check that epoch hasn't changed after the operation
    11 13×
            if(_before.epoch == _after.epoch) {
    12
                // loop through the initiatives and check that their status hasn't changed
    13 24×
                for(uint8 i; i < deployedInitiatives.length; i++) {
    14 28×
                    address initiative = deployedInitiatives[i];
    15
                    eq(
    16 21×
                        uint256(_before.initiativeStatus[initiative]),
    17 23×
                        uint256(_after.initiativeStatus[initiative]),
    18
                        _assertionGV01()
    19
                    );
    20
                }
    21
            }
    22
            return true;
    23
        }
    24
    25
    }
    89% test/recon/targets/BribeInitiativeTargets.sol
    Lines covered: 17 / 19 (89%)
    1
    // SPDX-License-Identifier: GPL-2.0
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Test} from "forge-std/Test.sol";
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    8
    import {IInitiative} from "../../../src/interfaces/IInitiative.sol";
    9
    import {IBribeInitiative} from "../../../src/interfaces/IBribeInitiative.sol";
    10
    import {DoubleLinkedList} from "../../../src/utils/DoubleLinkedList.sol";
    11
    import {Properties} from "../Properties.sol";
    12
    13
    14
    abstract contract BribeInitiativeTargets is Test, BaseTargetFunctions, Properties {
    15
        using DoubleLinkedList for DoubleLinkedList.List;
    16
    17
        // NOTE: initiatives that get called here are deployed but not necessarily registered
    18
    19 17×
        function initiative_depositBribe(uint128 boldAmount, uint128 bribeTokenAmount, uint16 epoch, uint8 initiativeIndex) withChecks public {
    20
            IBribeInitiative initiative = IBribeInitiative(_getDeployedInitiative(initiativeIndex));
    21
    22
            // clamp token amounts using user balance
    23 74×
            boldAmount = uint128(boldAmount % lusd.balanceOf(user));
    24 74×
            bribeTokenAmount = uint128(bribeTokenAmount % lqty.balanceOf(user));
    25
    26 51×
            initiative.depositBribe(boldAmount, bribeTokenAmount, epoch);
    27
        }
    28
    29 11×
        function initiative_claimBribes(uint16 epoch, uint16 prevAllocationEpoch, uint16 prevTotalAllocationEpoch, uint8 initiativeIndex) withChecks public {
    30
            IBribeInitiative initiative = IBribeInitiative(_getDeployedInitiative(initiativeIndex));
    31
    32
            // clamp epochs by using the current governance epoch
    33 77×
            epoch = epoch % governance.epoch();
    34 77×
            prevAllocationEpoch = prevAllocationEpoch % governance.epoch();
    35 77×
            prevTotalAllocationEpoch = prevTotalAllocationEpoch % governance.epoch();
    36
    37 36×
            IBribeInitiative.ClaimData[] memory claimData = new IBribeInitiative.ClaimData[](1);
    38 44×
            claimData[0] =  IBribeInitiative.ClaimData({
    39
                epoch: epoch,
    40
                prevLQTYAllocationEpoch: prevAllocationEpoch,
    41
                prevTotalLQTYAllocationEpoch: prevTotalAllocationEpoch
    42
            });
    43
    44 67×
            bool alreadyClaimed = initiative.claimedBribeAtEpoch(user, epoch);
    45
    46 43×
            initiative.claimBribes(claimData);
    47
    48
            // check if the bribe was already claimed at the given epoch
    49 0
            if(alreadyClaimed) {
    50
                // toggle canary that breaks the BI-02 property
    51 0
                claimedTwice = true;
    52
            }
    53
        }
    54
    }
    84% test/recon/targets/GovernanceTargets.sol
    Lines covered: 48 / 57 (84%)
    1
    2
    // SPDX-License-Identifier: GPL-2.0
    3
    pragma solidity ^0.8.0;
    4
    5
    import {BaseTargetFunctions} from "@chimera/BaseTargetFunctions.sol";
    6
    import {vm} from "@chimera/Hevm.sol";
    7
    import {IERC20Permit} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol";
    8
    import {console2} from "forge-std/Test.sol";
    9
    10
    import {Properties} from "../Properties.sol";
    11
    import {MaliciousInitiative} from "../../mocks/MaliciousInitiative.sol";
    12
    import {BribeInitiative} from "../../../src/BribeInitiative.sol";
    13
    import {ILQTYStaking} from "../../../src/interfaces/ILQTYStaking.sol";
    14
    import {IInitiative} from "../../../src/interfaces/IInitiative.sol";
    15
    import {IUserProxy} from "../../../src/interfaces/IUserProxy.sol";
    16
    import {PermitParams} from "../../../src/utils/Types.sol";
    17
    import {add} from "../../../src/utils/Math.sol";
    18
    19
    20
    21
    abstract contract GovernanceTargets is BaseTargetFunctions, Properties {
    22
    23
        // clamps to a single initiative to ensure coverage in case both haven't been registered yet
    24 22×
        function governance_allocateLQTY_clamped_single_initiative(uint8 initiativesIndex, uint96 deltaLQTYVotes, uint96 deltaLQTYVetos) withChecks public {
    25 72×
            uint16 currentEpoch = governance.epoch();
    26 134×
            uint96 stakedAmount = IUserProxy(governance.deriveUserProxyAddress(user)).staked(); // clamp using the user's staked balance
    27
    28 30×
            address[] memory initiatives = new address[](1);
    29 31×
            initiatives[0] = _getDeployedInitiative(initiativesIndex);
    30 28×
            int88[] memory deltaLQTYVotesArray = new int88[](1);
    31 32×
            deltaLQTYVotesArray[0] = int88(uint88(deltaLQTYVotes % stakedAmount));
    32 28×
            int88[] memory deltaLQTYVetosArray = new int88[](1);
    33 32×
            deltaLQTYVetosArray[0] = int88(uint88(deltaLQTYVetos % stakedAmount));
    34
    35 66×
            governance.allocateLQTY(initiatives, deltaLQTYVotesArray, deltaLQTYVetosArray);
    36
    37
            // if call was successful update the ghost tracking variables
    38
            // allocation only allows voting OR vetoing at a time so need to check which was executed
    39 26×
            if(deltaLQTYVotesArray[0] > 0) {
    40 66×
                ghostLqtyAllocationByUserAtEpoch[user] = add(ghostLqtyAllocationByUserAtEpoch[user], deltaLQTYVotesArray[0]);
    41 54×
                ghostTotalAllocationAtEpoch[currentEpoch] = add(ghostTotalAllocationAtEpoch[currentEpoch], deltaLQTYVotesArray[0]);
    42
            } else {
    43 57×
                ghostLqtyAllocationByUserAtEpoch[user] = add(ghostLqtyAllocationByUserAtEpoch[user], deltaLQTYVetosArray[0]);
    44 54×
                ghostTotalAllocationAtEpoch[currentEpoch] = add(ghostTotalAllocationAtEpoch[currentEpoch], deltaLQTYVetosArray[0]);
    45
            }
    46
        }
    47
    48 11×
        function governance_allocateLQTY(int88[] calldata _deltaLQTYVotes, int88[] calldata _deltaLQTYVetos) withChecks public {
    49 65×
            governance.allocateLQTY(deployedInitiatives, _deltaLQTYVotes, _deltaLQTYVetos);
    50
        }
    51
    52 11×
        function governance_claimForInitiative(uint8 initiativeIndex) withChecks public {
    53
            address initiative = _getDeployedInitiative(initiativeIndex);
    54 72×
            governance.claimForInitiative(initiative);
    55
        }
    56
    57 12×
        function governance_claimFromStakingV1(uint8 recipientIndex) withChecks public {
    58
            address rewardRecipient = _getRandomUser(recipientIndex);
    59 68×
            governance.claimFromStakingV1(rewardRecipient);
    60
        }
    61
    62
        function governance_deployUserProxy() withChecks public {
    63 48×
            governance.deployUserProxy();
    64
        }
    65
    66 11×
        function governance_depositLQTY(uint88 lqtyAmount) withChecks public {
    67 75×
            lqtyAmount = uint88(lqtyAmount % lqty.balanceOf(user));
    68 19×
            governance.depositLQTY(lqtyAmount);
    69
        }
    70
    71 13×
        function governance_depositLQTYViaPermit(uint88 _lqtyAmount) withChecks public {
    72
             // Get the current block timestamp for the deadline
    73
            uint256 deadline = block.timestamp + 1 hours;
    74
    75
            // Create the permit message
    76
            bytes32 permitTypeHash = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
    77 68×
            bytes32 domainSeparator = IERC20Permit(address(lqty)).DOMAIN_SEPARATOR();
    78
    79
    80 67×
            uint256 nonce = IERC20Permit(address(lqty)).nonces(user);
    81
    82 27×
            bytes32 structHash = keccak256(abi.encode(
    83
                permitTypeHash,
    84
                user,
    85
                address(governance),
    86
                _lqtyAmount,
    87
                nonce,
    88
                deadline
    89
            ));
    90
    91 25×
            bytes32 digest = keccak256(abi.encodePacked(
    92
                "\x19\x01",
    93
                domainSeparator,
    94
                structHash
    95
            ));
    96
    97 60×
            (uint8 v, bytes32 r, bytes32 s) = vm.sign(user2Pk, digest);
    98
    99 0
            PermitParams memory permitParams = PermitParams({
    100 0
                owner: user2,
    101 0
                spender: user,
    102 0
                value: _lqtyAmount,
    103 0
                deadline: deadline,
    104 0
                v: v,
    105 0
                r: r,
    106 0
                s: s
    107
            });
    108
    109 0
            governance.depositLQTYViaPermit(_lqtyAmount, permitParams);
    110
        }
    111
    112 12×
        function governance_registerInitiative(uint8 initiativeIndex) withChecks public {
    113
            address initiative = _getDeployedInitiative(initiativeIndex);
    114 59×
            governance.registerInitiative(initiative);
    115
        }
    116
    117 11×
        function governance_snapshotVotesForInitiative(address _initiative) withChecks public {
    118 68×
            governance.snapshotVotesForInitiative(_initiative);
    119
        }
    120
    121 11×
        function governance_unregisterInitiative(uint8 initiativeIndex) withChecks public {
    122
            address initiative = _getDeployedInitiative(initiativeIndex);
    123 22×
            governance.unregisterInitiative(initiative);
    124
        }
    125
    126 11×
        function governance_withdrawLQTY(uint88 _lqtyAmount) withChecks public {
    127 57×
            governance.withdrawLQTY(_lqtyAmount);
    128
        }
    129
    }