Recon Coverage Report 79% coverage
Files 17 files
    Files
    17
    Lines
    2800
    Coverage
    79%
    573 / 720
    Actions
    80% contracts/contracts/echidna/Debugger.sol
    Lines covered: 4 / 5 (80%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4 0
    library Debugger {
    5
        event Debug(string debugString);
    6
        event Debug(string description, string data);
    7
        event Debug(string prefix, string description, string data);
    8
        event Debug(string description, bytes32 data);
    9
        event Debug(string prefix, string description, bytes32 data);
    10
        event Debug(string description, uint256 data);
    11
        event Debug(string prefix, string description, uint256 data);
    12
        event Debug(string description, int256 data);
    13
        event Debug(string prefix, string description, int256 data);
    14
        event Debug(string description, address data);
    15
        event Debug(string prefix, string description, address data);
    16
        event Debug(string description, bool data);
    17
        event Debug(string prefix, string description, bool data);
    18
    19
        function log(string memory debugString) internal {
    20
            emit Debug(debugString);
    21
        }
    22
    23
        function log(string memory description, string memory data) internal {
    24
            emit Debug(description, data);
    25
        }
    26
    27
        function log(
    28
            string memory prefix,
    29
            string memory description,
    30
            string memory data
    31
        ) internal {
    32
            emit Debug(prefix, description, data);
    33
        }
    34
    35
        function log(string memory description, bytes32 data) internal {
    36
            emit Debug(description, data);
    37
        }
    38
    39
        function log(
    40
            string memory prefix,
    41
            string memory description,
    42
            bytes32 data
    43
        ) internal {
    44
            emit Debug(prefix, description, data);
    45
        }
    46
    47
        function log(string memory description, uint256 data) internal {
    48 38×
            emit Debug(description, data);
    49
        }
    50
    51
        function log(
    52
            string memory prefix,
    53
            string memory description,
    54
            uint256 data
    55
        ) internal {
    56
            emit Debug(prefix, description, data);
    57
        }
    58
    59
        function log(string memory description, int256 data) internal {
    60 11×
            emit Debug(description, data);
    61
        }
    62
    63
        function log(
    64
            string memory prefix,
    65
            string memory description,
    66
            int256 data
    67
        ) internal {
    68
            emit Debug(prefix, description, data);
    69
        }
    70
    71
        function log(string memory description, address data) internal {
    72
            emit Debug(description, data);
    73
        }
    74
    75
        function log(
    76
            string memory prefix,
    77
            string memory description,
    78
            address data
    79
        ) internal {
    80
            emit Debug(prefix, description, data);
    81
        }
    82
    83
        function log(string memory description, bool data) internal {
    84
            emit Debug(description, data);
    85
        }
    86
    87
        function log(
    88
            string memory prefix,
    89
            string memory description,
    90
            bool data
    91
        ) internal {
    92
            emit Debug(prefix, description, data);
    93
        }
    94
    }
    95% contracts/contracts/echidna/EchidnaConfig.sol
    Lines covered: 19 / 20 (95%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    /**
    5
     * @title Top-level mixin for configuring the desired fuzzing setup
    6
     * @author Rappie
    7
     */
    8 0
    contract EchidnaConfig {
    9 10×
        address internal constant ADDRESS_VAULT = address(0x10000);
    10
        address internal constant ADDRESS_OUTSIDER_USER = address(0x20000);
    11
    12
        address internal constant ADDRESS_USER0 = address(0x30000);
    13
        address internal constant ADDRESS_USER1 = address(0x40000);
    14
    15
        // Will be set in EchidnaSetup constructor
    16
        address internal ADDRESS_OUTSIDER_CONTRACT;
    17
        address internal ADDRESS_CONTRACT0;
    18
        address internal ADDRESS_CONTRACT1;
    19
    20
        // Toggle known issues
    21
        //
    22
        // This can be used to skip tests that are known to fail. This is useful
    23
        // when debugging a specific issue, but should be disabled when running
    24
        // the full test suite.
    25
        //
    26
        //   True => skip tests that are known to fail
    27
        //   False => run all tests
    28
        //
    29
        bool internal constant TOGGLE_KNOWN_ISSUES = false;
    30
    31
        // Toggle known issues within limits
    32
        //
    33
        // Same as TOGGLE_KNOWN_ISSUES, but also skip tests that are known to fail
    34
        // within limits set by the variables below.
    35
        //
    36
        bool internal constant TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS = false;
    37
    38
        // Starting balance
    39
        //
    40
        // Gives OUSD a non-zero starting supply, which can be useful to ignore
    41
        // certain edge cases.
    42
        //
    43
        // The starting balance is given to outsider accounts that are not used as
    44
        // accounts while fuzzing.
    45
        //
    46
        bool internal constant TOGGLE_STARTING_BALANCE = true;
    47
        uint256 internal constant STARTING_BALANCE = 1_000_000e18;
    48
    49
        // Change supply
    50
        //
    51
        // Set a limit to the amount of change per rebase, which can be useful to
    52
        // ignore certain edge cases.
    53
        //
    54
        //  True => limit the amount of change to a percentage of total supply
    55
        //  False => no limit
    56
        //
    57
        bool internal constant TOGGLE_CHANGESUPPLY_LIMIT = true;
    58
        uint256 internal constant CHANGESUPPLY_DIVISOR = 10; // 10% of total supply
    59
    60
        // Mint limit
    61
        //
    62
        // Set a limit the amount minted per mint, which can be useful to
    63
        // ignore certain edge cases.
    64
        //
    65
        //  True => limit the amount of minted tokens
    66
        //  False => no limit
    67
        //
    68
        bool internal constant TOGGLE_MINT_LIMIT = true;
    69
        uint256 internal constant MINT_MODULO = 1_000_000_000_000e18;
    70
    71
        // Known rounding errors
    72
        uint256 internal constant TRANSFER_ROUNDING_ERROR = 1e18 - 1;
    73
        uint256 internal constant OPT_IN_ROUNDING_ERROR = 1e18 - 1;
    74
        uint256 internal constant MINT_ROUNDING_ERROR = 1e18 - 1;
    75
    76
        /**
    77
         * @notice Modifier to skip tests that are known to fail
    78
         * @dev see TOGGLE_KNOWN_ISSUES for more information
    79
         */
    80
        modifier hasKnownIssue() {
    81
            if (TOGGLE_KNOWN_ISSUES) return;
    82
            _;
    83
        }
    84
    85
        /**
    86
         * @notice Modifier to skip tests that are known to fail within limits
    87
         * @dev see TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS for more information
    88
         */
    89
        modifier hasKnownIssueWithinLimits() {
    90
            if (TOGGLE_KNOWN_ISSUES_WITHIN_LIMITS) return;
    91
            _;
    92
        }
    93
    94
        /**
    95
         * @notice Translate an account ID to an address
    96
         * @param accountId The ID of the account
    97
         * @return account The address of the account
    98
         */
    99 22×
        function getAccount(uint8 accountId)
    100
            internal
    101
            view
    102
            returns (address account)
    103
        {
    104 16×
            accountId = accountId / 64;
    105 16×
            if (accountId == 0) return account = ADDRESS_USER0;
    106 16×
            if (accountId == 1) return account = ADDRESS_USER1;
    107 24×
            if (accountId == 2) return account = ADDRESS_CONTRACT0;
    108 22×
            if (accountId == 3) return account = ADDRESS_CONTRACT1;
    109
            require(false, "Unknown account ID");
    110
        }
    111
    }
    95% contracts/contracts/echidna/EchidnaHelper.sol
    Lines covered: 70 / 73 (95%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./EchidnaSetup.sol";
    5
    import "./Debugger.sol";
    6
    7
    /**
    8
     * @title Mixin containing helper functions
    9
     * @author Rappie
    10
     */
    11 0
    contract EchidnaHelper is EchidnaSetup {
    12
        /**
    13
         * @notice Mint tokens to an account
    14
         * @param toAcc Account to mint to
    15
         * @param amount Amount to mint
    16
         * @return Amount minted (in case of capped mint with modulo)
    17
         */
    18 24×
        function mint(uint8 toAcc, uint256 amount) public returns (uint256) {
    19
            address to = getAccount(toAcc);
    20
    21
            if (TOGGLE_MINT_LIMIT) {
    22
                amount = amount % MINT_MODULO;
    23
            }
    24
    25 39×
            hevm.prank(ADDRESS_VAULT);
    26 52×
            ousd.mint(to, amount);
    27
    28
            return amount;
    29
        }
    30
    31
        /**
    32
         * @notice Burn tokens from an account
    33
         * @param fromAcc Account to burn from
    34
         * @param amount Amount to burn
    35
         */
    36 11×
        function burn(uint8 fromAcc, uint256 amount) public {
    37
            address from = getAccount(fromAcc);
    38 39×
            hevm.prank(ADDRESS_VAULT);
    39 47×
            ousd.burn(from, amount);
    40
        }
    41
    42
        /**
    43
         * @notice Change the total supply of OUSD (rebase)
    44
         * @param amount New total supply
    45
         */
    46 11×
        function changeSupply(uint256 amount) public {
    47
            if (TOGGLE_CHANGESUPPLY_LIMIT) {
    48
                amount =
    49 71×
                    ousd.totalSupply() +
    50 79×
                    (amount % (ousd.totalSupply() / CHANGESUPPLY_DIVISOR));
    51
            }
    52
    53 39×
            hevm.prank(ADDRESS_VAULT);
    54 46×
            ousd.changeSupply(amount);
    55
        }
    56
    57
        /**
    58
         * @notice Transfer tokens between accounts
    59
         * @param fromAcc Account to transfer from
    60
         * @param toAcc Account to transfer to
    61
         * @param amount Amount to transfer
    62
         */
    63 11×
        function transfer(
    64
            uint8 fromAcc,
    65
            uint8 toAcc,
    66
            uint256 amount
    67 0
        ) public {
    68
            address from = getAccount(fromAcc);
    69
            address to = getAccount(toAcc);
    70 40×
            hevm.prank(from);
    71
            // slither-disable-next-line unchecked-transfer
    72 67×
            ousd.transfer(to, amount);
    73
        }
    74
    75
        /**
    76
         * @notice Transfer approved tokens between accounts
    77
         * @param authorizedAcc Account that is authorized to transfer
    78
         * @param fromAcc Account to transfer from
    79
         * @param toAcc Account to transfer to
    80
         * @param amount Amount to transfer
    81
         */
    82 16×
        function transferFrom(
    83
            uint8 authorizedAcc,
    84
            uint8 fromAcc,
    85
            uint8 toAcc,
    86
            uint256 amount
    87
        ) public {
    88
            address authorized = getAccount(authorizedAcc);
    89
            address from = getAccount(fromAcc);
    90
            address to = getAccount(toAcc);
    91 40×
            hevm.prank(authorized);
    92
            // slither-disable-next-line unchecked-transfer
    93 68×
            ousd.transferFrom(from, to, amount);
    94
        }
    95
    96
        /**
    97
         * @notice Opt in to rebasing
    98
         * @param targetAcc Account to opt in
    99
         */
    100 11×
        function optIn(uint8 targetAcc) public {
    101
            address target = getAccount(targetAcc);
    102 40×
            hevm.prank(target);
    103 37×
            ousd.rebaseOptIn();
    104
        }
    105
    106
        /**
    107
         * @notice Opt out of rebasing
    108
         * @param targetAcc Account to opt out
    109
         */
    110 11×
        function optOut(uint8 targetAcc) public {
    111
            address target = getAccount(targetAcc);
    112 40×
            hevm.prank(target);
    113 53×
            ousd.rebaseOptOut();
    114
        }
    115
    116
        /**
    117
         * @notice Approve an account to spend OUSD
    118
         * @param ownerAcc Account that owns the OUSD
    119
         * @param spenderAcc Account that is approved to spend the OUSD
    120
         * @param amount Amount to approve
    121
         */
    122 15×
        function approve(
    123
            uint8 ownerAcc,
    124
            uint8 spenderAcc,
    125
            uint256 amount
    126
        ) public {
    127 16×
            address owner = getAccount(ownerAcc);
    128 16×
            address spender = getAccount(spenderAcc);
    129 74×
            hevm.prank(owner);
    130
            // slither-disable-next-line unused-return
    131 16×
            ousd.approve(spender, amount);
    132
        }
    133
    134
        /**
    135
         * @notice Get the sum of all OUSD balances
    136
         * @return total Total balance
    137
         */
    138 27×
        function getTotalBalance() public view returns (uint256 total) {
    139 68×
            total += ousd.balanceOf(ADDRESS_VAULT);
    140 68×
            total += ousd.balanceOf(ADDRESS_OUTSIDER_USER);
    141 74×
            total += ousd.balanceOf(ADDRESS_OUTSIDER_CONTRACT);
    142 68×
            total += ousd.balanceOf(ADDRESS_USER0);
    143 68×
            total += ousd.balanceOf(ADDRESS_USER1);
    144 74×
            total += ousd.balanceOf(ADDRESS_CONTRACT0);
    145 75×
            total += ousd.balanceOf(ADDRESS_CONTRACT1);
    146
        }
    147
    148
        /**
    149
         * @notice Get the sum of all non-rebasing OUSD balances
    150
         * @return total Total balance
    151
         */
    152
        function getTotalNonRebasingBalance() public returns (uint256 total) {
    153 73×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_VAULT)
    154
                ? ousd.balanceOf(ADDRESS_VAULT)
    155
                : 0;
    156 73×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_OUTSIDER_USER)
    157
                ? ousd.balanceOf(ADDRESS_OUTSIDER_USER)
    158
                : 0;
    159 78×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_OUTSIDER_CONTRACT)
    160 63×
                ? ousd.balanceOf(ADDRESS_OUTSIDER_CONTRACT)
    161 0
                : 0;
    162 74×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_USER0)
    163 58×
                ? ousd.balanceOf(ADDRESS_USER0)
    164
                : 0;
    165 74×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_USER1)
    166 58×
                ? ousd.balanceOf(ADDRESS_USER1)
    167
                : 0;
    168 80×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_CONTRACT0)
    169 63×
                ? ousd.balanceOf(ADDRESS_CONTRACT0)
    170
                : 0;
    171 72×
            total += ousd._isNonRebasingAccountEchidna(ADDRESS_CONTRACT1)
    172 20×
                ? ousd.balanceOf(ADDRESS_CONTRACT1)
    173
                : 0;
    174
        }
    175
    }
    93% contracts/contracts/echidna/EchidnaSetup.sol
    Lines covered: 15 / 16 (93%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./IHevm.sol";
    5
    import "./EchidnaConfig.sol";
    6
    import "./OUSDEchidna.sol";
    7
    8 17×
    contract Dummy {}
    9
    10
    /**
    11
     * @title Mixin for setup and deployment
    12
     * @author Rappie
    13
     */
    14 0
    contract EchidnaSetup is EchidnaConfig {
    15
        IHevm hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    16 34×
        OUSDEchidna ousd = new OUSDEchidna();
    17
    18
        /**
    19
         * @notice Deploy the OUSD contract and set up initial state
    20
         */
    21
        constructor() {
    22 45×
            ousd.initialize(ADDRESS_VAULT, 1e18);
    23
    24
            // Deploy dummny contracts as users
    25 23×
            Dummy outsider = new Dummy();
    26 12×
            ADDRESS_OUTSIDER_CONTRACT = address(outsider);
    27 22×
            Dummy dummy0 = new Dummy();
    28 11×
            ADDRESS_CONTRACT0 = address(dummy0);
    29 22×
            Dummy dummy1 = new Dummy();
    30 16×
            ADDRESS_CONTRACT1 = address(dummy1);
    31
    32
            // Start out with a reasonable amount of OUSD
    33
            if (TOGGLE_STARTING_BALANCE) {
    34
                // Rebasing tokens
    35 39×
                hevm.prank(ADDRESS_VAULT);
    36 49×
                ousd.mint(ADDRESS_OUTSIDER_USER, STARTING_BALANCE / 2);
    37
    38
                // Non-rebasing tokens
    39 41×
                hevm.prank(ADDRESS_VAULT);
    40 58×
                ousd.mint(ADDRESS_OUTSIDER_CONTRACT, STARTING_BALANCE / 2);
    41
            }
    42
        }
    43
    }
    92% contracts/contracts/echidna/EchidnaTestAccounting.sol
    Lines covered: 37 / 40 (92%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./EchidnaDebug.sol";
    5
    import "./EchidnaTestSupply.sol";
    6
    7
    /**
    8
     * @title Mixin for testing accounting functions
    9
     * @author Rappie
    10
     */
    11 0
    contract EchidnaTestAccounting is EchidnaTestSupply {
    12
        /**
    13
         * @notice After opting in, balance should not increase. (Ok to lose rounding funds doing this)
    14
         * @param targetAcc Account to opt in
    15
         */
    16 11×
        function testOptInBalance(uint8 targetAcc) public {
    17
            address target = getAccount(targetAcc);
    18
    19 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    20
            optIn(targetAcc);
    21 64×
            uint256 balanceAfter = ousd.balanceOf(target);
    22
    23
            assert(balanceAfter <= balanceBefore);
    24
        }
    25
    26
        /**
    27
         * @notice After opting out, balance should remain the same
    28
         * @param targetAcc Account to opt out
    29
         */
    30 11×
        function testOptOutBalance(uint8 targetAcc) public {
    31
            address target = getAccount(targetAcc);
    32
    33 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    34
            optOut(targetAcc);
    35 19×
            uint256 balanceAfter = ousd.balanceOf(target);
    36
    37
            assert(balanceAfter == balanceBefore);
    38
        }
    39
    40
        /**
    41
         * @notice Account balance should remain the same after opting in minus rounding error
    42
         * @param targetAcc Account to opt in
    43
         */
    44 11×
        function testOptInBalanceRounding(uint8 targetAcc) public {
    45
            address target = getAccount(targetAcc);
    46
    47 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    48
            optIn(targetAcc);
    49 64×
            uint256 balanceAfter = ousd.balanceOf(target);
    50
    51
            int256 delta = int256(balanceAfter) - int256(balanceBefore);
    52 21×
            Debugger.log("delta", delta);
    53
    54
            // slither-disable-next-line tautology
    55 14×
            assert(-1 * delta >= 0);
    56
            assert(-1 * delta <= int256(OPT_IN_ROUNDING_ERROR));
    57
        }
    58
    59
        /**
    60
         * @notice After opting in, total supply should remain the same
    61
         * @param targetAcc Account to opt in
    62
         */
    63 11×
        function testOptInTotalSupply(uint8 targetAcc) public {
    64 70×
            uint256 totalSupplyBefore = ousd.totalSupply();
    65
            optIn(targetAcc);
    66
            uint256 totalSupplyAfter = ousd.totalSupply();
    67
    68
            assert(totalSupplyAfter == totalSupplyBefore);
    69
        }
    70
    71
        /**
    72
         * @notice After opting out, total supply should remain the same
    73
         * @param targetAcc Account to opt out
    74
         */
    75 11×
        function testOptOutTotalSupply(uint8 targetAcc) public {
    76 70×
            uint256 totalSupplyBefore = ousd.totalSupply();
    77
            optOut(targetAcc);
    78 70×
            uint256 totalSupplyAfter = ousd.totalSupply();
    79
    80
            assert(totalSupplyAfter == totalSupplyBefore);
    81
        }
    82
    83
        /**
    84
         * @notice Account balance should remain the same when a smart contract auto converts
    85
         * @param targetAcc Account to auto convert
    86
         */
    87 21×
        function testAutoConvertBalance(uint8 targetAcc) public {
    88
            address target = getAccount(targetAcc);
    89
    90 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    91
            // slither-disable-next-line unused-return
    92 60×
            ousd._isNonRebasingAccountEchidna(target);
    93 130×
            uint256 balanceAfter = ousd.balanceOf(target);
    94
    95 12×
            assert(balanceAfter == balanceBefore);
    96
        }
    97
    98
        /**
    99
         * @notice The `balanceOf` function should never revert
    100
         * @param targetAcc Account to check balance of
    101
         */
    102 11×
        function testBalanceOfShouldNotRevert(uint8 targetAcc) public {
    103
            address target = getAccount(targetAcc);
    104
    105
            // slither-disable-next-line unused-return
    106 58×
            try ousd.balanceOf(target) {
    107 0
                assert(true);
    108
            } catch {
    109 0
                assert(false);
    110
            }
    111
        }
    112
    }
    96% contracts/contracts/echidna/EchidnaTestApproval.sol
    Lines covered: 30 / 31 (96%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./EchidnaTestMintBurn.sol";
    5
    import "./Debugger.sol";
    6
    7
    /**
    8
     * @title Mixin for testing approval related functions
    9
     * @author Rappie
    10
     */
    11 0
    contract EchidnaTestApproval is EchidnaTestMintBurn {
    12
        /**
    13
         * @notice Performing `transferFrom` with an amount inside the allowance should not revert
    14
         * @param authorizedAcc The account that is authorized to transfer
    15
         * @param fromAcc The account that is transferring
    16
         * @param toAcc The account that is receiving
    17
         * @param amount The amount to transfer
    18
         */
    19 11×
        function testTransferFromShouldNotRevert(
    20
            uint8 authorizedAcc,
    21
            uint8 fromAcc,
    22
            uint8 toAcc,
    23
            uint256 amount
    24
        ) public {
    25
            address authorized = getAccount(authorizedAcc);
    26
            address from = getAccount(fromAcc);
    27
            address to = getAccount(toAcc);
    28
    29 67×
            require(amount <= ousd.balanceOf(from));
    30 68×
            require(amount <= ousd.allowance(from, authorized));
    31
    32 41×
            hevm.prank(authorized);
    33
            // slither-disable-next-line unchecked-transfer
    34 60×
            try ousd.transferFrom(from, to, amount) {
    35
                // pass
    36
            } catch {
    37
                assert(false);
    38
            }
    39
        }
    40
    41
        /**
    42
         * @notice Performing `transferFrom` with an amount outside the allowance should revert
    43
         * @param authorizedAcc The account that is authorized to transfer
    44
         * @param fromAcc The account that is transferring
    45
         * @param toAcc The account that is receiving
    46
         * @param amount The amount to transfer
    47
         */
    48 16×
        function testTransferFromShouldRevert(
    49
            uint8 authorizedAcc,
    50
            uint8 fromAcc,
    51
            uint8 toAcc,
    52
            uint256 amount
    53
        ) public {
    54
            address authorized = getAccount(authorizedAcc);
    55
            address from = getAccount(fromAcc);
    56
            address to = getAccount(toAcc);
    57
    58
            require(amount > 0);
    59
            require(
    60 68×
                !(amount <= ousd.balanceOf(from) &&
    61 62×
                    amount <= ousd.allowance(from, authorized))
    62
            );
    63
    64 41×
            hevm.prank(authorized);
    65
            // slither-disable-next-line unchecked-transfer
    66 38×
            try ousd.transferFrom(from, to, amount) {
    67
                assert(false);
    68
            } catch {
    69
                // pass
    70
            }
    71
        }
    72
    73
        /**
    74
         * @notice Approving an amount should update the allowance and overwrite any previous allowance
    75
         * @param ownerAcc The account that is approving
    76
         * @param spenderAcc The account that is being approved
    77
         * @param amount The amount to approve
    78
         */
    79 11×
        function testApprove(
    80
            uint8 ownerAcc,
    81
            uint8 spenderAcc,
    82
            uint256 amount
    83
        ) public {
    84
            address owner = getAccount(ownerAcc);
    85
            address spender = getAccount(spenderAcc);
    86
    87
            approve(ownerAcc, spenderAcc, amount);
    88 64×
            uint256 allowanceAfter1 = ousd.allowance(owner, spender);
    89
    90
            assert(allowanceAfter1 == amount);
    91
    92
            approve(ownerAcc, spenderAcc, amount / 2);
    93 63×
            uint256 allowanceAfter2 = ousd.allowance(owner, spender);
    94
    95 10×
            assert(allowanceAfter2 == amount / 2);
    96
        }
    97
    }
    95% contracts/contracts/echidna/EchidnaTestMintBurn.sol
    Lines covered: 43 / 45 (95%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./EchidnaDebug.sol";
    5
    import "./EchidnaTestAccounting.sol";
    6
    7
    /**
    8
     * @title Mixin for testing Mint and Burn functions
    9
     * @author Rappie
    10
     */
    11 0
    contract EchidnaTestMintBurn is EchidnaTestAccounting {
    12
        /**
    13
         * @notice Minting 0 tokens should not affect account balance
    14
         * @param targetAcc Account to mint to
    15
         */
    16 11×
        function testMintZeroBalance(uint8 targetAcc) public {
    17
            address target = getAccount(targetAcc);
    18
    19 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    20
            mint(targetAcc, 0);
    21
            uint256 balanceAfter = ousd.balanceOf(target);
    22
    23
            assert(balanceAfter == balanceBefore);
    24
        }
    25
    26
        /**
    27
         * @notice Burning 0 tokens should not affect account balance
    28
         * @param targetAcc Account to burn from
    29
         */
    30 11×
        function testBurnZeroBalance(uint8 targetAcc) public {
    31
            address target = getAccount(targetAcc);
    32
    33 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    34
            burn(targetAcc, 0);
    35
            uint256 balanceAfter = ousd.balanceOf(target);
    36
    37
            assert(balanceAfter == balanceBefore);
    38
        }
    39
    40
        /**
    41
         * @notice Minting tokens must increase the account balance by at least amount
    42
         * @param targetAcc Account to mint to
    43
         * @param amount Amount to mint
    44
         * @custom:error testMintBalance(uint8,uint256): failed!💥
    45
         *   Call sequence:
    46
         *       changeSupply(1)
    47
         *       testMintBalance(0,1)
    48
         *   Event sequence:
    49
         *       Debug(«balanceBefore», 0)
    50
         *       Debug(«balanceAfter», 0)
    51
         */
    52 17×
        function testMintBalance(uint8 targetAcc, uint256 amount)
    53
            public
    54
            hasKnownIssue
    55
            hasKnownIssueWithinLimits
    56
        {
    57
            address target = getAccount(targetAcc);
    58
    59 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    60
            uint256 amountMinted = mint(targetAcc, amount);
    61 63×
            uint256 balanceAfter = ousd.balanceOf(target);
    62
    63 21×
            Debugger.log("amountMinted", amountMinted);
    64 21×
            Debugger.log("balanceBefore", balanceBefore);
    65 21×
            Debugger.log("balanceAfter", balanceAfter);
    66
    67 11×
            assert(balanceAfter >= balanceBefore + amountMinted);
    68
        }
    69
    70
        /**
    71
         * @notice Burning tokens must decrease the account balance by at least amount
    72
         * @param targetAcc Account to burn from
    73
         * @param amount Amount to burn
    74
         * @custom:error testBurnBalance(uint8,uint256): failed!💥
    75
         *   Call sequence:
    76
         *       changeSupply(1)
    77
         *       mint(0,3)
    78
         *       testBurnBalance(0,1)
    79
         *   Event sequence:
    80
         *       Debug(«balanceBefore», 2)
    81
         *       Debug(«balanceAfter», 2)
    82
         */
    83 11×
        function testBurnBalance(uint8 targetAcc, uint256 amount)
    84
            public
    85
            hasKnownIssue
    86
            hasKnownIssueWithinLimits
    87
        {
    88
            address target = getAccount(targetAcc);
    89
    90 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    91
            burn(targetAcc, amount);
    92 64×
            uint256 balanceAfter = ousd.balanceOf(target);
    93
    94 21×
            Debugger.log("balanceBefore", balanceBefore);
    95 21×
            Debugger.log("balanceAfter", balanceAfter);
    96
    97 14×
            assert(balanceAfter <= balanceBefore - amount);
    98
        }
    99
    100
        /**
    101
         * @notice Minting tokens should not increase the account balance by less than rounding error above amount
    102
         * @param targetAcc Account to mint to
    103
         * @param amount Amount to mint
    104
         */
    105 11×
        function testMintBalanceRounding(uint8 targetAcc, uint256 amount) public {
    106
            address target = getAccount(targetAcc);
    107
    108 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    109
            uint256 amountMinted = mint(targetAcc, amount);
    110 63×
            uint256 balanceAfter = ousd.balanceOf(target);
    111
    112
            int256 delta = int256(balanceAfter) - int256(balanceBefore);
    113
    114
            // delta == amount, if no error
    115
            // delta < amount,  if too little is minted
    116
            // delta > amount,  if too much is minted
    117
            int256 error = int256(amountMinted) - delta;
    118
    119 10×
            assert(error >= 0);
    120
            assert(error <= int256(MINT_ROUNDING_ERROR));
    121
        }
    122
    123
        /**
    124
         * @notice A burn of an account balance must result in a zero balance
    125
         * @param targetAcc Account to burn from
    126
         */
    127 11×
        function testBurnAllBalanceToZero(uint8 targetAcc) public hasKnownIssue {
    128
            address target = getAccount(targetAcc);
    129
    130 64×
            burn(targetAcc, ousd.balanceOf(target));
    131 62×
            assert(ousd.balanceOf(target) == 0);
    132
        }
    133
    134
        /**
    135
         * @notice You should always be able to burn an account's balance
    136
         * @param targetAcc Account to burn from
    137
         */
    138 11×
        function testBurnAllBalanceShouldNotRevert(uint8 targetAcc)
    139
            public
    140
            hasKnownIssue
    141
        {
    142
            address target = getAccount(targetAcc);
    143 63×
            uint256 balance = ousd.balanceOf(target);
    144
    145 39×
            hevm.prank(ADDRESS_VAULT);
    146 48×
            try ousd.burn(target, balance) {
    147
                assert(true);
    148
            } catch {
    149 0
                assert(false);
    150
            }
    151
        }
    152
    }
    95% contracts/contracts/echidna/EchidnaTestSupply.sol
    Lines covered: 39 / 41 (95%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./EchidnaDebug.sol";
    5
    import "./EchidnaTestTransfer.sol";
    6
    7
    import { StableMath } from "../utils/StableMath.sol";
    8
    9
    /**
    10
     * @title Mixin for testing supply related functions
    11
     * @author Rappie
    12
     */
    13 0
    contract EchidnaTestSupply is EchidnaTestTransfer {
    14
        using StableMath for uint256;
    15
    16
        uint256 prevRebasingCreditsPerToken = type(uint256).max;
    17
    18
        /**
    19
         * @notice After a `changeSupply`, the total supply should exactly
    20
         * match the target total supply. (This is needed to ensure successive
    21
         * rebases are correct).
    22
         * @param supply New total supply
    23
         * @custom:error testChangeSupply(uint256): failed!💥
    24
         *   Call sequence:
    25
         *       testChangeSupply(1044505275072865171609)
    26
         *   Event sequence:
    27
         *       TotalSupplyUpdatedHighres(1044505275072865171610, 1000000000000000000000000, 957391048054055578595)
    28
         */
    29 15×
        function testChangeSupply(uint256 supply)
    30
            public
    31
            hasKnownIssue
    32
            hasKnownIssueWithinLimits
    33
        {
    34 41×
            hevm.prank(ADDRESS_VAULT);
    35 51×
            ousd.changeSupply(supply);
    36
    37 76×
            assert(ousd.totalSupply() == supply);
    38
        }
    39
    40
        /**
    41
         * @notice The total supply must not be less than the sum of account balances.
    42
         * (The difference will go into future rebases)
    43
         * @custom:error testTotalSupplyLessThanTotalBalance(): failed!💥
    44
         *   Call sequence:
    45
         *     mint(0,1)
    46
         *     changeSupply(1)
    47
         *     optOut(64)
    48
         *     transfer(0,64,1)
    49
         *     testTotalSupplyLessThanTotalBalance()
    50
         *   Event sequence:
    51
         *     Debug(«totalSupply», 1000000000000000001000001)
    52
         *     Debug(«totalBalance», 1000000000000000001000002)
    53
         */
    54
        function testTotalSupplyLessThanTotalBalance()
    55
            public
    56
            hasKnownIssue
    57
            hasKnownIssueWithinLimits
    58
        {
    59 70×
            uint256 totalSupply = ousd.totalSupply();
    60
            uint256 totalBalance = getTotalBalance();
    61
    62 21×
            Debugger.log("totalSupply", totalSupply);
    63 20×
            Debugger.log("totalBalance", totalBalance);
    64
    65
            assert(totalSupply >= totalBalance);
    66
        }
    67
    68
        /**
    69
         * @notice Non-rebasing supply should not be larger than total supply
    70
         * @custom:error testNonRebasingSupplyVsTotalSupply(): failed!💥
    71
         *   Call sequence:
    72
         *     mint(0,2)
    73
         *     changeSupply(3)
    74
         *     burn(0,1)
    75
         *     optOut(0)
    76
         *     testNonRebasingSupplyVsTotalSupply()
    77
         */
    78
        function testNonRebasingSupplyVsTotalSupply() public hasKnownIssue {
    79 70×
            uint256 nonRebasingSupply = ousd.nonRebasingSupply();
    80 70×
            uint256 totalSupply = ousd.totalSupply();
    81
    82
            assert(nonRebasingSupply <= totalSupply);
    83
        }
    84
    85
        /**
    86
         * @notice Global `rebasingCreditsPerToken` should never increase
    87
         * @custom:error testRebasingCreditsPerTokenNotIncreased(): failed!💥
    88
         *   Call sequence:
    89
         *     testRebasingCreditsPerTokenNotIncreased()
    90
         *     changeSupply(1)
    91
         *     testRebasingCreditsPerTokenNotIncreased()
    92
         */
    93
        function testRebasingCreditsPerTokenNotIncreased() public hasKnownIssue {
    94 70×
            uint256 curRebasingCreditsPerToken = ousd
    95
                .rebasingCreditsPerTokenHighres();
    96
    97 21×
            Debugger.log(
    98
                "prevRebasingCreditsPerToken",
    99
                prevRebasingCreditsPerToken
    100
            );
    101 22×
            Debugger.log("curRebasingCreditsPerToken", curRebasingCreditsPerToken);
    102
    103 11×
            assert(curRebasingCreditsPerToken <= prevRebasingCreditsPerToken);
    104
    105
            prevRebasingCreditsPerToken = curRebasingCreditsPerToken;
    106
        }
    107
    108
        /**
    109
         * @notice The rebasing credits per token ratio must greater than zero
    110
         */
    111
        function testRebasingCreditsPerTokenAboveZero() public {
    112 71×
            assert(ousd.rebasingCreditsPerTokenHighres() > 0);
    113
        }
    114
    115
        /**
    116
         * @notice The sum of all non-rebasing balances should not be larger than
    117
         * non-rebasing supply
    118
         * @custom:error testTotalNonRebasingSupplyLessThanTotalBalance(): failed!💥
    119
         *   Call sequence
    120
         *     mint(0,2)
    121
         *     changeSupply(1)
    122
         *     optOut(0)
    123
         *     burn(0,1)
    124
         *     testTotalNonRebasingSupplyLessThanTotalBalance()
    125
         *   Event sequence:
    126
         *     Debug(«totalNonRebasingSupply», 500000000000000000000001)
    127
         *     Debug(«totalNonRebasingBalance», 500000000000000000000002)
    128
         */
    129
        function testTotalNonRebasingSupplyLessThanTotalBalance()
    130
            public
    131
            hasKnownIssue
    132
            hasKnownIssueWithinLimits
    133 0
        {
    134 70×
            uint256 totalNonRebasingSupply = ousd.nonRebasingSupply();
    135
            uint256 totalNonRebasingBalance = getTotalNonRebasingBalance();
    136
    137 21×
            Debugger.log("totalNonRebasingSupply", totalNonRebasingSupply);
    138 22×
            Debugger.log("totalNonRebasingBalance", totalNonRebasingBalance);
    139
    140
            assert(totalNonRebasingSupply >= totalNonRebasingBalance);
    141
        }
    142
    143
        /**
    144
         * @notice An accounts credits / credits per token should not be larger it's balance
    145
         * @param targetAcc The account to check
    146
         */
    147 11×
        function testCreditsPerTokenVsBalance(uint8 targetAcc) public {
    148
            address target = getAccount(targetAcc);
    149
    150 69×
            (uint256 credits, uint256 creditsPerToken, ) = ousd
    151
                .creditsBalanceOfHighres(target);
    152
            uint256 expectedBalance = credits.divPrecisely(creditsPerToken);
    153
    154 63×
            uint256 balance = ousd.balanceOf(target);
    155
    156 21×
            Debugger.log("credits", credits);
    157 21×
            Debugger.log("creditsPerToken", creditsPerToken);
    158 21×
            Debugger.log("expectedBalance", expectedBalance);
    159 21×
            Debugger.log("balance", balance);
    160
    161
            assert(expectedBalance == balance);
    162
        }
    163
    }
    96% contracts/contracts/echidna/EchidnaTestTransfer.sol
    Lines covered: 103 / 107 (96%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "./EchidnaDebug.sol";
    5
    import "./Debugger.sol";
    6
    7
    /**
    8
     * @title Mixin for testing transfer related functions
    9
     * @author Rappie
    10
     */
    11 0
    contract EchidnaTestTransfer is EchidnaDebug {
    12
        /**
    13
         * @notice The receiving account's balance after a transfer must not increase by
    14
         * less than the amount transferred
    15
         * @param fromAcc Account to transfer from
    16
         * @param toAcc Account to transfer to
    17
         * @param amount Amount to transfer
    18
         * @custom:error testTransferBalanceReceivedLess(uint8,uint8,uint256): failed!💥
    19
         *   Call sequence:
    20
         *     changeSupply(1)
    21
         *     mint(64,2)
    22
         *     testTransferBalanceReceivedLess(64,0,1)
    23
         *   Event sequence:
    24
         *     Debug(«totalSupply», 1000000000000000000500002)
    25
         *     Debug(«toBalBefore», 0)
    26
         *     Debug(«toBalAfter», 0)
    27
         */
    28 11×
        function testTransferBalanceReceivedLess(
    29
            uint8 fromAcc,
    30
            uint8 toAcc,
    31
            uint256 amount
    32
        ) public hasKnownIssue hasKnownIssueWithinLimits {
    33
            address from = getAccount(fromAcc);
    34
            address to = getAccount(toAcc);
    35
    36 11×
            require(from != to);
    37
    38 64×
            uint256 toBalBefore = ousd.balanceOf(to);
    39
            transfer(fromAcc, toAcc, amount);
    40 64×
            uint256 toBalAfter = ousd.balanceOf(to);
    41
    42 56×
            Debugger.log("totalSupply", ousd.totalSupply());
    43 21×
            Debugger.log("toBalBefore", toBalBefore);
    44 21×
            Debugger.log("toBalAfter", toBalAfter);
    45
    46
            assert(toBalAfter >= toBalBefore + amount);
    47
        }
    48
    49
        /**
    50
         * @notice The receiving account's balance after a transfer must not
    51
         * increase by more than the amount transferred
    52
         * @param fromAcc Account to transfer from
    53
         * @param toAcc Account to transfer to
    54
         * @param amount Amount to transfer
    55
         */
    56 11×
        function testTransferBalanceReceivedMore(
    57
            uint8 fromAcc,
    58
            uint8 toAcc,
    59
            uint256 amount
    60
        ) public {
    61
            address from = getAccount(fromAcc);
    62
            address to = getAccount(toAcc);
    63
    64 11×
            require(from != to);
    65
    66 64×
            uint256 toBalBefore = ousd.balanceOf(to);
    67
            transfer(fromAcc, toAcc, amount);
    68 64×
            uint256 toBalAfter = ousd.balanceOf(to);
    69
    70 56×
            Debugger.log("totalSupply", ousd.totalSupply());
    71 21×
            Debugger.log("toBalBefore", toBalBefore);
    72 21×
            Debugger.log("toBalAfter", toBalAfter);
    73
    74
            assert(toBalAfter <= toBalBefore + amount);
    75
        }
    76
    77
        /**
    78
         * @notice The sending account's balance after a transfer must not
    79
         * decrease by less than the amount transferred
    80
         * @param fromAcc Account to transfer from
    81
         * @param toAcc Account to transfer to
    82
         * @param amount Amount to transfer
    83
         * @custom:error testTransferBalanceSentLess(uint8,uint8,uint256): failed!💥
    84
         *   Call sequence:
    85
         *     mint(0,1)
    86
         *     changeSupply(1)
    87
         *     testTransferBalanceSentLess(0,64,1)
    88
         *   Event sequence:
    89
         *     Debug(«totalSupply», 1000000000000000000500001)
    90
         *     Debug(«fromBalBefore», 1)
    91
         *     Debug(«fromBalAfter», 1)
    92
         */
    93 19×
        function testTransferBalanceSentLess(
    94
            uint8 fromAcc,
    95
            uint8 toAcc,
    96
            uint256 amount
    97 0
        ) public hasKnownIssue hasKnownIssueWithinLimits {
    98
            address from = getAccount(fromAcc);
    99
            address to = getAccount(toAcc);
    100
    101 11×
            require(from != to);
    102
    103 64×
            uint256 fromBalBefore = ousd.balanceOf(from);
    104
            transfer(fromAcc, toAcc, amount);
    105 64×
            uint256 fromBalAfter = ousd.balanceOf(from);
    106
    107 56×
            Debugger.log("totalSupply", ousd.totalSupply());
    108 21×
            Debugger.log("fromBalBefore", fromBalBefore);
    109 21×
            Debugger.log("fromBalAfter", fromBalAfter);
    110
    111 14×
            assert(fromBalAfter <= fromBalBefore - amount);
    112
        }
    113
    114
        /**
    115
         * @notice The sending account's balance after a transfer must not
    116
         * decrease by more than the amount transferred
    117
         * @param fromAcc Account to transfer from
    118
         * @param toAcc Account to transfer to
    119
         * @param amount Amount to transfer
    120
         */
    121 11×
        function testTransferBalanceSentMore(
    122
            uint8 fromAcc,
    123
            uint8 toAcc,
    124
            uint256 amount
    125
        ) public {
    126
            address from = getAccount(fromAcc);
    127
            address to = getAccount(toAcc);
    128
    129 11×
            require(from != to);
    130
    131 64×
            uint256 fromBalBefore = ousd.balanceOf(from);
    132
            transfer(fromAcc, toAcc, amount);
    133 64×
            uint256 fromBalAfter = ousd.balanceOf(from);
    134
    135 56×
            Debugger.log("totalSupply", ousd.totalSupply());
    136 21×
            Debugger.log("fromBalBefore", fromBalBefore);
    137 21×
            Debugger.log("fromBalAfter", fromBalAfter);
    138
    139 11×
            assert(fromBalAfter >= fromBalBefore - amount);
    140
        }
    141
    142
        /**
    143
         * @notice The receiving account's balance after a transfer must not
    144
         * increase by less than the amount transferred (minus rounding error)
    145
         * @param fromAcc Account to transfer from
    146
         * @param toAcc Account to transfer to
    147
         * @param amount Amount to transfer
    148
         */
    149 15×
        function testTransferBalanceReceivedLessRounding(
    150
            uint8 fromAcc,
    151
            uint8 toAcc,
    152
            uint256 amount
    153
        ) public {
    154
            address from = getAccount(fromAcc);
    155
            address to = getAccount(toAcc);
    156
    157 11×
            require(from != to);
    158
    159 64×
            uint256 toBalBefore = ousd.balanceOf(to);
    160
            transfer(fromAcc, toAcc, amount);
    161 64×
            uint256 toBalAfter = ousd.balanceOf(to);
    162
    163
            int256 toDelta = int256(toBalAfter) - int256(toBalBefore);
    164
    165
            // delta == amount, if no error
    166
            // delta < amount,  if too little is sent
    167
            // delta > amount,  if too much is sent
    168
            int256 error = int256(amount) - toDelta;
    169
    170 56×
            Debugger.log("totalSupply", ousd.totalSupply());
    171 21×
            Debugger.log("toBalBefore", toBalBefore);
    172 21×
            Debugger.log("toBalAfter", toBalAfter);
    173 21×
            Debugger.log("toDelta", toDelta);
    174 21×
            Debugger.log("error", error);
    175
    176 10×
            assert(error >= 0);
    177
            assert(error <= int256(TRANSFER_ROUNDING_ERROR));
    178
        }
    179
    180
        /**
    181
         * @notice The sending account's balance after a transfer must
    182
         * not decrease by less than the amount transferred (minus rounding error)
    183
         * @param fromAcc Account to transfer from
    184
         * @param toAcc Account to transfer to
    185
         * @param amount Amount to transfer
    186
         */
    187 11×
        function testTransferBalanceSentLessRounding(
    188
            uint8 fromAcc,
    189
            uint8 toAcc,
    190
            uint256 amount
    191
        ) public {
    192
            address from = getAccount(fromAcc);
    193
            address to = getAccount(toAcc);
    194
    195 11×
            require(from != to);
    196
    197 64×
            uint256 fromBalBefore = ousd.balanceOf(from);
    198
            transfer(fromAcc, toAcc, amount);
    199 64×
            uint256 fromBalAfter = ousd.balanceOf(from);
    200
    201
            int256 fromDelta = int256(fromBalAfter) - int256(fromBalBefore);
    202
    203
            // delta == -amount, if no error
    204
            // delta < -amount,  if too much is sent
    205
            // delta > -amount,  if too little is sent
    206
            int256 error = int256(amount) + fromDelta;
    207
    208 56×
            Debugger.log("totalSupply", ousd.totalSupply());
    209 21×
            Debugger.log("fromBalBefore", fromBalBefore);
    210 21×
            Debugger.log("fromBalAfter", fromBalAfter);
    211 20×
            Debugger.log("fromDelta", fromDelta);
    212
            Debugger.log("error", error);
    213
    214
            assert(error >= 0);
    215
            assert(error <= int256(TRANSFER_ROUNDING_ERROR));
    216
        }
    217
    218
        /**
    219
         * @notice An account should always be able to successfully transfer
    220
         * an amount within its balance.
    221
         * @param fromAcc Account to transfer from
    222
         * @param toAcc Account to transfer to
    223
         * @param amount Amount to transfer
    224
         * @custom:error testTransferWithinBalanceDoesNotRevert(uint8,uint8,uint8): failed!💥
    225
         *   Call sequence:
    226
         *       mint(0,1)
    227
         *       changeSupply(3)
    228
         *       optOut(0)
    229
         *       testTransferWithinBalanceDoesNotRevert(0,128,2)
    230
         *       optIn(0)
    231
         *       testTransferWithinBalanceDoesNotRevert(128,0,1)
    232
         *   Event sequence:
    233
         *       error Revert Panic(17): SafeMath over-/under-flows
    234
         */
    235 23×
        function testTransferWithinBalanceDoesNotRevert(
    236
            uint8 fromAcc,
    237
            uint8 toAcc,
    238
            uint256 amount
    239
        ) public hasKnownIssue {
    240
            address from = getAccount(fromAcc);
    241
            address to = getAccount(toAcc);
    242
    243
            require(amount > 0);
    244 67×
            amount = amount % ousd.balanceOf(from);
    245
    246 118×
            Debugger.log("Total supply", ousd.totalSupply());
    247
    248 41×
            hevm.prank(from);
    249
            // slither-disable-next-line unchecked-transfer
    250 64×
            try ousd.transfer(to, amount) {
    251
                assert(true);
    252
            } catch {
    253
                assert(false);
    254
            }
    255
        }
    256
    257
        /**
    258
         * @notice An account should never be able to successfully transfer
    259
         * an amount greater than their balance.
    260
         * @param fromAcc Account to transfer from
    261
         * @param toAcc Account to transfer to
    262
         * @param amount Amount to transfer
    263
         */
    264 11×
        function testTransferExceedingBalanceReverts(
    265
            uint8 fromAcc,
    266
            uint8 toAcc,
    267
            uint256 amount
    268
        ) public {
    269
            address from = getAccount(fromAcc);
    270
            address to = getAccount(toAcc);
    271
    272 75×
            amount = ousd.balanceOf(from) + 1 + amount;
    273
    274 40×
            hevm.prank(from);
    275
            // slither-disable-next-line unchecked-transfer
    276 37×
            try ousd.transfer(to, amount) {
    277 0
                assert(false);
    278
            } catch {
    279
                assert(true);
    280
            }
    281
        }
    282
    283
        /**
    284
         * @notice A transfer to the same account should not change that account's balance
    285
         * @param targetAcc Account to transfer to
    286
         * @param amount Amount to transfer
    287
         */
    288 11×
        function testTransferSelf(uint8 targetAcc, uint256 amount) public {
    289
            address target = getAccount(targetAcc);
    290
    291 63×
            uint256 balanceBefore = ousd.balanceOf(target);
    292
            transfer(targetAcc, targetAcc, amount);
    293 64×
            uint256 balanceAfter = ousd.balanceOf(target);
    294
    295
            assert(balanceBefore == balanceAfter);
    296
        }
    297
    298
        /**
    299
         * @notice Transfers to the zero account revert
    300
         * @param fromAcc Account to transfer from
    301
         * @param amount Amount to transfer
    302
         */
    303 11×
        function testTransferToZeroAddress(uint8 fromAcc, uint256 amount) public {
    304
            address from = getAccount(fromAcc);
    305
    306 40×
            hevm.prank(from);
    307
            // slither-disable-next-line unchecked-transfer
    308 37×
            try ousd.transfer(address(0), amount) {
    309 0
                assert(false);
    310
            } catch {
    311
                assert(true);
    312
            }
    313
        }
    314
    }
    100% contracts/contracts/echidna/OUSDEchidna.sol
    Lines covered: 7 / 7 (100%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import "../token/OUSD.sol";
    5
    6 277×
    contract OUSDEchidna is OUSD {
    7
        constructor() OUSD() {
    8
            // The Yield Delegation rewrite (#2298) left Governable without a
    9
            // constructor, so the governor slot is zero and the harness could
    10
            // never call initialize() (onlyGovernor). Make the deploying harness
    11
            // the governor so the suite is runnable again.
    12
            _setGovernor(msg.sender);
    13
        }
    14
    15 12×
        function _isNonRebasingAccountEchidna(address _account)
    16
            public
    17
            returns (bool)
    18
        {
    19
            _autoMigrate(_account);
    20 15×
            return alternativeCreditsPerToken[_account] > 0;
    21
        }
    22
    }
    29% contracts/contracts/governance/Governable.sol
    Lines covered: 7 / 24 (29%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    /**
    5
     * @title Base for contracts that are managed by the Origin Protocol's Governor.
    6
     * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change
    7
     *      from owner to governor and renounce methods removed. Does not use
    8
     *      Context.sol like Ownable.sol does for simplification.
    9
     * @author Origin Protocol Inc
    10
     */
    11
    abstract contract Governable {
    12
        // Storage position of the owner and pendingOwner of the contract
    13
        // keccak256("OUSD.governor");
    14
        bytes32 private constant governorPosition =
    15
            0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;
    16
    17
        // keccak256("OUSD.pending.governor");
    18
        bytes32 private constant pendingGovernorPosition =
    19 0
            0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;
    20
    21
        // keccak256("OUSD.reentry.status");
    22
        bytes32 private constant reentryStatusPosition =
    23
            0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;
    24
    25
        // See OpenZeppelin ReentrancyGuard implementation
    26
        uint256 constant _NOT_ENTERED = 1;
    27
        uint256 constant _ENTERED = 2;
    28
    29
        event PendingGovernorshipTransfer(
    30
            address indexed previousGovernor,
    31
            address indexed newGovernor
    32
        );
    33
    34
        event GovernorshipTransferred(
    35
            address indexed previousGovernor,
    36
            address indexed newGovernor
    37
        );
    38
    39
        /**
    40
         * @notice Returns the address of the current Governor.
    41
         */
    42 0
        function governor() public view returns (address) {
    43 0
            return _governor();
    44
        }
    45
    46
        /**
    47
         * @dev Returns the address of the current Governor.
    48
         */
    49
        function _governor() internal view returns (address governorOut) {
    50
            bytes32 position = governorPosition;
    51
            // solhint-disable-next-line no-inline-assembly
    52
            assembly {
    53
                governorOut := sload(position)
    54
            }
    55
        }
    56
    57
        /**
    58
         * @dev Returns the address of the pending Governor.
    59
         */
    60
        function _pendingGovernor()
    61
            internal
    62
            view
    63
            returns (address pendingGovernor)
    64
        {
    65
            bytes32 position = pendingGovernorPosition;
    66
            // solhint-disable-next-line no-inline-assembly
    67
            assembly {
    68 0
                pendingGovernor := sload(position)
    69
            }
    70
        }
    71
    72
        /**
    73
         * @dev Throws if called by any account other than the Governor.
    74
         */
    75
        modifier onlyGovernor() {
    76 0
            require(isGovernor(), "Caller is not the Governor");
    77
            _;
    78
        }
    79
    80
        /**
    81
         * @notice Returns true if the caller is the current Governor.
    82
         */
    83 0
        function isGovernor() public view returns (bool) {
    84 0
            return msg.sender == _governor();
    85
        }
    86
    87
        function _setGovernor(address newGovernor) internal {
    88 15×
            emit GovernorshipTransferred(_governor(), newGovernor);
    89
    90
            bytes32 position = governorPosition;
    91
            // solhint-disable-next-line no-inline-assembly
    92
            assembly {
    93
                sstore(position, newGovernor)
    94
            }
    95
        }
    96
    97
        /**
    98
         * @dev Prevents a contract from calling itself, directly or indirectly.
    99
         * Calling a `nonReentrant` function from another `nonReentrant`
    100
         * function is not supported. It is possible to prevent this from happening
    101
         * by making the `nonReentrant` function external, and make it call a
    102
         * `private` function that does the actual work.
    103
         */
    104
        modifier nonReentrant() {
    105
            bytes32 position = reentryStatusPosition;
    106
            uint256 _reentry_status;
    107
            // solhint-disable-next-line no-inline-assembly
    108
            assembly {
    109
                _reentry_status := sload(position)
    110
            }
    111
    112
            // On the first call to nonReentrant, _notEntered will be true
    113
            require(_reentry_status != _ENTERED, "Reentrant call");
    114
    115
            // Any calls to nonReentrant after this point will fail
    116
            // solhint-disable-next-line no-inline-assembly
    117
            assembly {
    118
                sstore(position, _ENTERED)
    119
            }
    120
    121
            _;
    122
    123
            // By storing the original value once again, a refund is triggered (see
    124
            // https://eips.ethereum.org/EIPS/eip-2200)
    125
            // solhint-disable-next-line no-inline-assembly
    126
            assembly {
    127
                sstore(position, _NOT_ENTERED)
    128
            }
    129
        }
    130
    131 0
        function _setPendingGovernor(address newGovernor) internal {
    132
            bytes32 position = pendingGovernorPosition;
    133
            // solhint-disable-next-line no-inline-assembly
    134
            assembly {
    135 0
                sstore(position, newGovernor)
    136
            }
    137
        }
    138
    139
        /**
    140
         * @notice Transfers Governance of the contract to a new account (`newGovernor`).
    141
         * Can only be called by the current Governor. Must be claimed for this to complete
    142
         * @param _newGovernor Address of the new Governor
    143
         */
    144 0
        function transferGovernance(address _newGovernor) external onlyGovernor {
    145 0
            _setPendingGovernor(_newGovernor);
    146 0
            emit PendingGovernorshipTransfer(_governor(), _newGovernor);
    147
        }
    148
    149
        /**
    150
         * @notice Claim Governance of the contract to a new account (`newGovernor`).
    151
         * Can only be called by the new Governor.
    152
         */
    153
        function claimGovernance() external {
    154 0
            require(
    155 0
                msg.sender == _pendingGovernor(),
    156
                "Only the pending Governor can complete the claim"
    157
            );
    158
            _changeGovernor(msg.sender);
    159
        }
    160
    161
        /**
    162
         * @dev Change Governance of the contract to a new account (`newGovernor`).
    163
         * @param _newGovernor Address of the new Governor
    164
         */
    165 0
        function _changeGovernor(address _newGovernor) internal {
    166 0
            require(_newGovernor != address(0), "New Governor is address(0)");
    167 0
            _setGovernor(_newGovernor);
    168
        }
    169
    }
    61% contracts/contracts/token/OUSD.sol
    Lines covered: 176 / 284 (61%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    /**
    5
     * @title OUSD Token Contract
    6
     * @dev ERC20 compatible contract for OUSD
    7
     * @dev Implements an elastic supply
    8
     * @author Origin Protocol Inc
    9
     */
    10
    import { IVault } from "../interfaces/IVault.sol";
    11
    import { Governable } from "../governance/Governable.sol";
    12
    import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
    13
    14 0
    contract OUSD is Governable {
    15
        using SafeCast for int256;
    16
        using SafeCast for uint256;
    17
    18
        /// @dev Event triggered when the supply changes
    19
        /// @param totalSupply Updated token total supply
    20
        /// @param rebasingCredits Updated token rebasing credits
    21
        /// @param rebasingCreditsPerToken Updated token rebasing credits per token
    22
        event TotalSupplyUpdatedHighres(
    23
            uint256 totalSupply,
    24
            uint256 rebasingCredits,
    25
            uint256 rebasingCreditsPerToken
    26
        );
    27
        /// @dev Event triggered when an account opts in for rebasing
    28
        /// @param account Address of the account
    29
        event AccountRebasingEnabled(address account);
    30
        /// @dev Event triggered when an account opts out of rebasing
    31
        /// @param account Address of the account
    32
        event AccountRebasingDisabled(address account);
    33
        /// @dev Emitted when `value` tokens are moved from one account `from` to
    34
        ///      another `to`.
    35
        /// @param from Address of the account tokens are moved from
    36
        /// @param to Address of the account tokens are moved to
    37
        /// @param value Amount of tokens transferred
    38
        event Transfer(address indexed from, address indexed to, uint256 value);
    39
        /// @dev Emitted when the allowance of a `spender` for an `owner` is set by
    40
        ///      a call to {approve}. `value` is the new allowance.
    41
        /// @param owner Address of the owner approving allowance
    42
        /// @param spender Address of the spender allowance is granted to
    43
        /// @param value Amount of tokens spender can transfer
    44
        event Approval(
    45
            address indexed owner,
    46
            address indexed spender,
    47
            uint256 value
    48
        );
    49
        /// @dev Yield resulting from {changeSupply} that a `source` account would
    50
        ///      receive is directed to `target` account.
    51
        /// @param source Address of the source forwarding the yield
    52
        /// @param target Address of the target receiving the yield
    53
        event YieldDelegated(address source, address target);
    54
        /// @dev Yield delegation from `source` account to the `target` account is
    55
        ///      suspended.
    56
        /// @param source Address of the source suspending yield forwarding
    57
        /// @param target Address of the target no longer receiving yield from `source`
    58
        ///        account
    59
        event YieldUndelegated(address source, address target);
    60
    61
        enum RebaseOptions {
    62
            NotSet,
    63
            StdNonRebasing,
    64
            StdRebasing,
    65
            YieldDelegationSource,
    66
            YieldDelegationTarget
    67
        }
    68
    69
        uint256[154] private _gap; // Slots to align with deployed contract
    70
        uint256 private constant MAX_SUPPLY = type(uint128).max;
    71
        /// @dev The amount of tokens in existence
    72 12×
        uint256 public totalSupply;
    73
        mapping(address => mapping(address => uint256)) private allowances;
    74
        /// @dev The vault with privileges to execute {mint}, {burn}
    75
        ///     and {changeSupply}
    76 0
        address public vaultAddress;
    77
        mapping(address => uint256) internal creditBalances;
    78
        // the 2 storage variables below need trailing underscores to not name collide with public functions
    79
        uint256 private rebasingCredits_; // Sum of all rebasing credits (creditBalances for rebasing accounts)
    80
        uint256 private rebasingCreditsPerToken_;
    81
        /// @dev The amount of tokens that are not rebasing - receiving yield
    82
        uint256 public nonRebasingSupply;
    83
        mapping(address => uint256) internal alternativeCreditsPerToken;
    84
        /// @dev A map of all addresses and their respective RebaseOptions
    85 0
        mapping(address => RebaseOptions) public rebaseState;
    86
        mapping(address => uint256) private __deprecated_isUpgraded;
    87
        /// @dev A map of addresses that have yields forwarded to. This is an
    88
        ///      inverse mapping of {yieldFrom}
    89
        /// Key Account forwarding yield
    90
        /// Value Account receiving yield
    91 0
        mapping(address => address) public yieldTo;
    92
        /// @dev A map of addresses that are receiving the yield. This is an
    93
        ///      inverse mapping of {yieldTo}
    94
        /// Key Account receiving yield
    95
        /// Value Account forwarding yield
    96 0
        mapping(address => address) public yieldFrom;
    97
    98 0
        uint256 private constant RESOLUTION_INCREASE = 1e9;
    99
        uint256[34] private __gap; // including below gap totals up to 200
    100
    101
        /// @dev Verifies that the caller is the Governor or Strategist.
    102
        modifier onlyGovernorOrStrategist() {
    103
            require(
    104 0
                isGovernor() || msg.sender == IVault(vaultAddress).strategistAddr(),
    105
                "Caller is not the Strategist or Governor"
    106
            );
    107
            _;
    108
        }
    109
    110
        /// @dev Initializes the contract and sets necessary variables.
    111
        /// @param _vaultAddress Address of the vault contract
    112
        /// @param _initialCreditsPerToken The starting rebasing credits per token.
    113 0
        function initialize(address _vaultAddress, uint256 _initialCreditsPerToken)
    114
            external
    115
            onlyGovernor
    116
        {
    117 0
            require(_vaultAddress != address(0), "Zero vault address");
    118 0
            require(vaultAddress == address(0), "Already initialized");
    119
    120 0
            rebasingCreditsPerToken_ = _initialCreditsPerToken;
    121 0
            vaultAddress = _vaultAddress;
    122
        }
    123
    124
        /// @dev Returns the symbol of the token, a shorter version
    125
        ///      of the name.
    126 0
        function symbol() external pure virtual returns (string memory) {
    127 0
            return "OUSD";
    128
        }
    129
    130
        /// @dev Returns the name of the token.
    131 16×
        function name() external pure virtual returns (string memory) {
    132 0
            return "Origin Dollar";
    133
        }
    134
    135
        /// @dev Returns the number of decimals used to get its user representation.
    136 0
        function decimals() external pure virtual returns (uint8) {
    137 0
            return 18;
    138
        }
    139
    140
        /**
    141
         * @dev Verifies that the caller is the Vault contract
    142
         */
    143
        modifier onlyVault() {
    144 24×
            require(vaultAddress == msg.sender, "Caller is not the Vault");
    145
            _;
    146
        }
    147
    148
        /**
    149
         * @return High resolution rebasingCreditsPerToken
    150
         */
    151
        function rebasingCreditsPerTokenHighres() external view returns (uint256) {
    152
            return rebasingCreditsPerToken_;
    153
        }
    154
    155
        /**
    156
         * @return Low resolution rebasingCreditsPerToken
    157
         */
    158 0
        function rebasingCreditsPerToken() external view returns (uint256) {
    159 0
            return rebasingCreditsPerToken_ / RESOLUTION_INCREASE;
    160
        }
    161
    162
        /**
    163
         * @return High resolution total number of rebasing credits
    164
         */
    165 0
        function rebasingCreditsHighres() external view returns (uint256) {
    166 0
            return rebasingCredits_;
    167
        }
    168
    169
        /**
    170
         * @return Low resolution total number of rebasing credits
    171
         */
    172
        function rebasingCredits() external view returns (uint256) {
    173 0
            return rebasingCredits_ / RESOLUTION_INCREASE;
    174
        }
    175
    176
        /**
    177
         * @notice Gets the balance of the specified address.
    178
         * @param _account Address to query the balance of.
    179
         * @return A uint256 representing the amount of base units owned by the
    180
         *         specified address.
    181
         */
    182 28×
        function balanceOf(address _account) public view returns (uint256) {
    183 28×
            RebaseOptions state = rebaseState[_account];
    184 26×
            if (state == RebaseOptions.YieldDelegationSource) {
    185
                // Saves a slot read when transferring to or from a yield delegating source
    186
                // since we know creditBalances equals the balance.
    187 0
                return creditBalances[_account];
    188
            }
    189 54×
            uint256 baseBalance = (creditBalances[_account] * 1e18) /
    190 10×
                _creditsPerToken(_account);
    191 26×
            if (state == RebaseOptions.YieldDelegationTarget) {
    192
                // creditBalances of yieldFrom accounts equals token balances
    193 0
                return baseBalance - creditBalances[yieldFrom[_account]];
    194
            }
    195
            return baseBalance;
    196
        }
    197
    198
        /**
    199
         * @notice Gets the credits balance of the specified address.
    200
         * @dev Backwards compatible with old low res credits per token.
    201
         * @param _account The address to query the balance of.
    202
         * @return (uint256, uint256) Credit balance and credits per token of the
    203
         *         address
    204
         */
    205 0
        function creditsBalanceOf(address _account)
    206
            external
    207
            view
    208 0
            returns (uint256, uint256)
    209
        {
    210 0
            uint256 cpt = _creditsPerToken(_account);
    211 0
            if (cpt == 1e27) {
    212
                // For a period before the resolution upgrade, we created all new
    213
                // contract accounts at high resolution. Since they are not changing
    214
                // as a result of this upgrade, we will return their true values
    215 0
                return (creditBalances[_account], cpt);
    216
            } else {
    217 0
                return (
    218 0
                    creditBalances[_account] / RESOLUTION_INCREASE,
    219 0
                    cpt / RESOLUTION_INCREASE
    220
                );
    221
            }
    222
        }
    223
    224
        /**
    225
         * @notice Gets the credits balance of the specified address.
    226
         * @param _account The address to query the balance of.
    227
         * @return (uint256, uint256, bool) Credit balance, credits per token of the
    228
         *         address, and isUpgraded
    229
         */
    230 18×
        function creditsBalanceOfHighres(address _account)
    231
            external
    232
            view
    233
            returns (
    234
                uint256,
    235
                uint256,
    236
                bool
    237
            )
    238
        {
    239
            return (
    240 13×
                creditBalances[_account],
    241
                _creditsPerToken(_account),
    242
                true // all accounts have their resolution "upgraded"
    243
            );
    244
        }
    245
    246
        // Backwards compatible view
    247 0
        function nonRebasingCreditsPerToken(address _account)
    248
            external
    249
            view
    250 0
            returns (uint256)
    251
        {
    252 0
            return alternativeCreditsPerToken[_account];
    253
        }
    254
    255
        /**
    256
         * @notice Transfer tokens to a specified address.
    257
         * @param _to the address to transfer to.
    258
         * @param _value the amount to be transferred.
    259
         * @return true on success.
    260
         */
    261 12×
        function transfer(address _to, uint256 _value) external returns (bool) {
    262 13×
            require(_to != address(0), "Transfer to zero address");
    263
    264
            _executeTransfer(msg.sender, _to, _value);
    265
    266
            emit Transfer(msg.sender, _to, _value);
    267
            return true;
    268
        }
    269
    270
        /**
    271
         * @notice Transfer tokens from one address to another.
    272
         * @param _from The address you want to send tokens from.
    273
         * @param _to The address you want to transfer to.
    274
         * @param _value The amount of tokens to be transferred.
    275
         * @return true on success.
    276
         */
    277 13×
        function transferFrom(
    278
            address _from,
    279
            address _to,
    280
            uint256 _value
    281
        ) external returns (bool) {
    282
            require(_to != address(0), "Transfer to zero address");
    283 24×
            uint256 userAllowance = allowances[_from][msg.sender];
    284 15×
            require(_value <= userAllowance, "Allowance exceeded");
    285
    286
            unchecked {
    287 30×
                allowances[_from][msg.sender] = userAllowance - _value;
    288
            }
    289
    290
            _executeTransfer(_from, _to, _value);
    291
    292 17×
            emit Transfer(_from, _to, _value);
    293
            return true;
    294
        }
    295
    296
        function _executeTransfer(
    297
            address _from,
    298
            address _to,
    299
            uint256 _value
    300
        ) internal {
    301
            (
    302
                int256 fromRebasingCreditsDiff,
    303
                int256 fromNonRebasingSupplyDiff
    304
            ) = _adjustAccount(_from, -_value.toInt256());
    305
            (
    306
                int256 toRebasingCreditsDiff,
    307
                int256 toNonRebasingSupplyDiff
    308
            ) = _adjustAccount(_to, _value.toInt256());
    309
    310
            _adjustGlobals(
    311
                fromRebasingCreditsDiff + toRebasingCreditsDiff,
    312
                fromNonRebasingSupplyDiff + toNonRebasingSupplyDiff
    313
            );
    314
        }
    315
    316
        function _adjustAccount(address _account, int256 _balanceChange)
    317
            internal
    318
            returns (int256 rebasingCreditsDiff, int256 nonRebasingSupplyDiff)
    319
        {
    320 15×
            RebaseOptions state = rebaseState[_account];
    321 10×
            int256 currentBalance = balanceOf(_account).toInt256();
    322 11×
            if (currentBalance + _balanceChange < 0) {
    323
                revert("Transfer amount exceeds balance");
    324
            }
    325
            uint256 newBalance = (currentBalance + _balanceChange).toUint256();
    326
    327 13×
            if (state == RebaseOptions.YieldDelegationSource) {
    328 0
                address target = yieldTo[_account];
    329 0
                uint256 targetOldBalance = balanceOf(target);
    330 0
                uint256 targetNewCredits = _balanceToRebasingCredits(
    331 0
                    targetOldBalance + newBalance
    332
                );
    333 0
                rebasingCreditsDiff =
    334 0
                    targetNewCredits.toInt256() -
    335 0
                    creditBalances[target].toInt256();
    336
    337 0
                creditBalances[_account] = newBalance;
    338 0
                creditBalances[target] = targetNewCredits;
    339 14×
            } else if (state == RebaseOptions.YieldDelegationTarget) {
    340 0
                uint256 newCredits = _balanceToRebasingCredits(
    341 0
                    newBalance + creditBalances[yieldFrom[_account]]
    342
                );
    343 0
                rebasingCreditsDiff =
    344 0
                    newCredits.toInt256() -
    345 0
                    creditBalances[_account].toInt256();
    346 0
                creditBalances[_account] = newCredits;
    347
            } else {
    348
                _autoMigrate(_account);
    349 13×
                uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[
    350
                    _account
    351
                ];
    352
                if (alternativeCreditsPerTokenMem > 0) {
    353
                    nonRebasingSupplyDiff = _balanceChange;
    354
                    if (alternativeCreditsPerTokenMem != 1e18) {
    355 0
                        alternativeCreditsPerToken[_account] = 1e18;
    356
                    }
    357 15×
                    creditBalances[_account] = newBalance;
    358
                } else {
    359
                    uint256 newCredits = _balanceToRebasingCredits(newBalance);
    360
                    rebasingCreditsDiff =
    361 11×
                        newCredits.toInt256() -
    362 18×
                        creditBalances[_account].toInt256();
    363 16×
                    creditBalances[_account] = newCredits;
    364
                }
    365
            }
    366
        }
    367
    368
        function _adjustGlobals(
    369
            int256 _rebasingCreditsDiff,
    370
            int256 _nonRebasingSupplyDiff
    371
        ) internal {
    372 10×
            if (_rebasingCreditsDiff != 0) {
    373 18×
                rebasingCredits_ = (rebasingCredits_.toInt256() +
    374
                    _rebasingCreditsDiff).toUint256();
    375
            }
    376
            if (_nonRebasingSupplyDiff != 0) {
    377
                nonRebasingSupply = (nonRebasingSupply.toInt256() +
    378
                    _nonRebasingSupplyDiff).toUint256();
    379
            }
    380
        }
    381
    382
        /**
    383
         * @notice Function to check the amount of tokens that _owner has allowed
    384
         *      to `_spender`.
    385
         * @param _owner The address which owns the funds.
    386
         * @param _spender The address which will spend the funds.
    387
         * @return The number of tokens still available for the _spender.
    388
         */
    389 18×
        function allowance(address _owner, address _spender)
    390
            external
    391
            view
    392
            returns (uint256)
    393
        {
    394 56×
            return allowances[_owner][_spender];
    395
        }
    396
    397
        /**
    398
         * @notice Approve the passed address to spend the specified amount of
    399
         *      tokens on behalf of msg.sender.
    400
         * @param _spender The address which will spend the funds.
    401
         * @param _value The amount of tokens to be spent.
    402
         * @return true on success.
    403
         */
    404 34×
        function approve(address _spender, uint256 _value) external returns (bool) {
    405 29×
            allowances[msg.sender][_spender] = _value;
    406 13×
            emit Approval(msg.sender, _spender, _value);
    407
            return true;
    408
        }
    409
    410
        /**
    411
         * @notice Creates `_amount` tokens and assigns them to `_account`,
    412
         *     increasing the total supply.
    413
         */
    414 16×
        function mint(address _account, uint256 _amount) external onlyVault {
    415
            require(_account != address(0), "Mint to the zero address");
    416
    417
            // Account
    418
            (
    419
                int256 toRebasingCreditsDiff,
    420
                int256 toNonRebasingSupplyDiff
    421 10×
            ) = _adjustAccount(_account, _amount.toInt256());
    422
            // Globals
    423
            _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);
    424 13×
            totalSupply = totalSupply + _amount;
    425
    426 11×
            require(totalSupply < MAX_SUPPLY, "Max supply");
    427 16×
            emit Transfer(address(0), _account, _amount);
    428
        }
    429
    430
        /**
    431
         * @notice Destroys `_amount` tokens from `_account`,
    432
         *     reducing the total supply.
    433
         */
    434 17×
        function burn(address _account, uint256 _amount) external onlyVault {
    435
            require(_account != address(0), "Burn from the zero address");
    436
            if (_amount == 0) {
    437
                return;
    438
            }
    439
    440
            // Account
    441
            (
    442
                int256 toRebasingCreditsDiff,
    443
                int256 toNonRebasingSupplyDiff
    444 12×
            ) = _adjustAccount(_account, -_amount.toInt256());
    445
            // Globals
    446
            _adjustGlobals(toRebasingCreditsDiff, toNonRebasingSupplyDiff);
    447 11×
            totalSupply = totalSupply - _amount;
    448
    449
            emit Transfer(_account, address(0), _amount);
    450
        }
    451
    452
        /**
    453
         * @dev Get the credits per token for an account. Returns a fixed amount
    454
         *      if the account is non-rebasing.
    455
         * @param _account Address of the account.
    456
         */
    457 10×
        function _creditsPerToken(address _account)
    458
            internal
    459
            view
    460
            returns (uint256)
    461
        {
    462 24×
            uint256 alternativeCreditsPerTokenMem = alternativeCreditsPerToken[
    463
                _account
    464
            ];
    465 10×
            if (alternativeCreditsPerTokenMem != 0) {
    466
                return alternativeCreditsPerTokenMem;
    467
            } else {
    468
                return rebasingCreditsPerToken_;
    469
            }
    470
        }
    471
    472
        /**
    473
         * @dev Auto migrate contracts to be non rebasing,
    474
         *     unless they have opted into yield.
    475
         * @param _account Address of the account.
    476
         */
    477 10×
        function _autoMigrate(address _account) internal {
    478
            uint256 codeLen = _account.code.length;
    479 16×
            bool isEOA = (codeLen == 0) ||
    480 16×
                (codeLen == 23 && bytes3(_account.code) == 0xef0100);
    481
            // In previous code versions, contracts would not have had their
    482
            // rebaseState[_account] set to RebaseOptions.NonRebasing when migrated
    483
            // therefore we check the actual accounting used on the account as well.
    484
            if (
    485 24×
                (!isEOA) &&
    486 46×
                rebaseState[_account] == RebaseOptions.NotSet &&
    487 28×
                alternativeCreditsPerToken[_account] == 0
    488
            ) {
    489 10×
                _rebaseOptOut(_account);
    490
            }
    491
        }
    492
    493
        /**
    494
         * @dev Calculates credits from contract's global rebasingCreditsPerToken_, and
    495
         *      also balance that corresponds to those credits. The latter is important
    496
         *      when adjusting the contract's global nonRebasingSupply to circumvent any
    497
         *      possible rounding errors.
    498
         *
    499
         * @param _balance Balance of the account.
    500
         */
    501
        function _balanceToRebasingCredits(uint256 _balance)
    502
            internal
    503
            view
    504
            returns (uint256 rebasingCredits)
    505
        {
    506
            // Rounds up, because we need to ensure that accounts always have
    507
            // at least the balance that they should have.
    508
            // Note this should always be used on an absolute account value,
    509
            // not on a possibly negative diff, because then the rounding would be wrong.
    510 28×
            return ((_balance) * rebasingCreditsPerToken_ + 1e18 - 1) / 1e18;
    511
        }
    512
    513
        /**
    514
         * @notice The calling account will start receiving yield after a successful call.
    515
         * @param _account Address of the account.
    516
         */
    517 0
        function governanceRebaseOptIn(address _account) external onlyGovernor {
    518 0
            require(_account != address(0), "Zero address not allowed");
    519 0
            _rebaseOptIn(_account);
    520
        }
    521
    522
        /**
    523
         * @notice The calling account will start receiving yield after a successful call.
    524
         */
    525
        function rebaseOptIn() external {
    526
            _rebaseOptIn(msg.sender);
    527
        }
    528
    529
        function _rebaseOptIn(address _account) internal {
    530
            uint256 balance = balanceOf(_account);
    531
    532
            // prettier-ignore
    533 11×
            require(
    534 19×
                alternativeCreditsPerToken[_account] > 0 ||
    535
                    // Accounts may explicitly `rebaseOptIn` regardless of
    536
                    // accounting if they have a 0 balance.
    537 14×
                    creditBalances[_account] == 0
    538
                ,
    539
                "Account must be non-rebasing"
    540
            );
    541 16×
            RebaseOptions state = rebaseState[_account];
    542
            // prettier-ignore
    543 11×
            require(
    544 13×
                state == RebaseOptions.StdNonRebasing ||
    545 10×
                    state == RebaseOptions.NotSet,
    546
                "Only standard non-rebasing accounts can opt in"
    547
            );
    548
    549
            uint256 newCredits = _balanceToRebasingCredits(balance);
    550
    551
            // Account
    552 22×
            rebaseState[_account] = RebaseOptions.StdRebasing;
    553
            alternativeCreditsPerToken[_account] = 0;
    554
            creditBalances[_account] = newCredits;
    555
            // Globals
    556 10×
            _adjustGlobals(newCredits.toInt256(), -balance.toInt256());
    557
    558
            emit AccountRebasingEnabled(_account);
    559
        }
    560
    561
        /**
    562
         * @notice The calling account will no longer receive yield
    563
         */
    564
        function rebaseOptOut() external {
    565
            _rebaseOptOut(msg.sender);
    566
        }
    567
    568 12×
        function _rebaseOptOut(address _account) internal {
    569 14×
            require(
    570 28×
                alternativeCreditsPerToken[_account] == 0,
    571
                "Account must be rebasing"
    572
            );
    573 30×
            RebaseOptions state = rebaseState[_account];
    574
            require(
    575 48×
                state == RebaseOptions.StdRebasing || state == RebaseOptions.NotSet,
    576
                "Only standard rebasing accounts can opt out"
    577
            );
    578
    579 30×
            uint256 oldCredits = creditBalances[_account];
    580 10×
            uint256 balance = balanceOf(_account);
    581
    582
            // Account
    583 44×
            rebaseState[_account] = RebaseOptions.StdNonRebasing;
    584 18×
            alternativeCreditsPerToken[_account] = 1e18;
    585 18×
            creditBalances[_account] = balance;
    586
            // Globals
    587 32×
            _adjustGlobals(-oldCredits.toInt256(), balance.toInt256());
    588
    589 24×
            emit AccountRebasingDisabled(_account);
    590
        }
    591
    592
        /**
    593
         * @notice Distribute yield to users. This changes the exchange rate
    594
         *  between "credits" and OUSD tokens to change rebasing user's balances.
    595
         * @param _newTotalSupply New total supply of OUSD.
    596
         */
    597 18×
        function changeSupply(uint256 _newTotalSupply) external onlyVault {
    598
            require(totalSupply > 0, "Cannot increase 0 supply");
    599
    600
            if (totalSupply == _newTotalSupply) {
    601 12×
                emit TotalSupplyUpdatedHighres(
    602
                    totalSupply,
    603
                    rebasingCredits_,
    604
                    rebasingCreditsPerToken_
    605
                );
    606
                return;
    607
            }
    608
    609 12×
            totalSupply = _newTotalSupply > MAX_SUPPLY
    610
                ? MAX_SUPPLY
    611
                : _newTotalSupply;
    612
    613 12×
            uint256 rebasingSupply = totalSupply - nonRebasingSupply;
    614
            // round up in the favour of the protocol
    615
            rebasingCreditsPerToken_ =
    616 29×
                (rebasingCredits_ * 1e18 + rebasingSupply - 1) /
    617
                rebasingSupply;
    618
    619
            require(rebasingCreditsPerToken_ > 0, "Invalid change in supply");
    620
    621 12×
            emit TotalSupplyUpdatedHighres(
    622
                totalSupply,
    623
                rebasingCredits_,
    624
                rebasingCreditsPerToken_
    625
            );
    626
        }
    627
    628
        /*
    629
         * @notice Send the yield from one account to another account.
    630
         *         Each account keeps its own balances.
    631
         */
    632 0
        function delegateYield(address _from, address _to)
    633
            external
    634
            onlyGovernorOrStrategist
    635 0
        {
    636 0
            require(_from != address(0), "Zero from address not allowed");
    637 0
            require(_to != address(0), "Zero to address not allowed");
    638
    639 0
            require(_from != _to, "Cannot delegate to self");
    640 0
            require(
    641 0
                yieldFrom[_to] == address(0) &&
    642 0
                    yieldTo[_to] == address(0) &&
    643 0
                    yieldFrom[_from] == address(0) &&
    644 0
                    yieldTo[_from] == address(0),
    645
                "Blocked by existing yield delegation"
    646
            );
    647 0
            RebaseOptions stateFrom = rebaseState[_from];
    648 0
            RebaseOptions stateTo = rebaseState[_to];
    649
    650 0
            require(
    651 0
                stateFrom == RebaseOptions.NotSet ||
    652 0
                    stateFrom == RebaseOptions.StdNonRebasing ||
    653 0
                    stateFrom == RebaseOptions.StdRebasing,
    654
                "Invalid rebaseState from"
    655
            );
    656
    657 0
            require(
    658 0
                stateTo == RebaseOptions.NotSet ||
    659 0
                    stateTo == RebaseOptions.StdNonRebasing ||
    660 0
                    stateTo == RebaseOptions.StdRebasing,
    661
                "Invalid rebaseState to"
    662
            );
    663
    664 0
            if (alternativeCreditsPerToken[_from] == 0) {
    665 0
                _rebaseOptOut(_from);
    666
            }
    667 0
            if (alternativeCreditsPerToken[_to] > 0) {
    668 0
                _rebaseOptIn(_to);
    669
            }
    670
    671 0
            uint256 fromBalance = balanceOf(_from);
    672 0
            uint256 toBalance = balanceOf(_to);
    673 0
            uint256 oldToCredits = creditBalances[_to];
    674 0
            uint256 newToCredits = _balanceToRebasingCredits(
    675 0
                fromBalance + toBalance
    676
            );
    677
    678
            // Set up the bidirectional links
    679 0
            yieldTo[_from] = _to;
    680 0
            yieldFrom[_to] = _from;
    681
    682
            // Local
    683 0
            rebaseState[_from] = RebaseOptions.YieldDelegationSource;
    684 0
            alternativeCreditsPerToken[_from] = 1e18;
    685 0
            creditBalances[_from] = fromBalance;
    686 0
            rebaseState[_to] = RebaseOptions.YieldDelegationTarget;
    687 0
            creditBalances[_to] = newToCredits;
    688
    689
            // Global
    690 0
            int256 creditsChange = newToCredits.toInt256() -
    691 0
                oldToCredits.toInt256();
    692
            _adjustGlobals(creditsChange, -(fromBalance).toInt256());
    693 0
            emit YieldDelegated(_from, _to);
    694
        }
    695
    696
        /*
    697
         * @notice Stop sending the yield from one account to another account.
    698
         */
    699
        function undelegateYield(address _from) external onlyGovernorOrStrategist {
    700
            // Require a delegation, which will also ensure a valid delegation
    701 0
            require(yieldTo[_from] != address(0), "Zero address not allowed");
    702
    703 0
            address to = yieldTo[_from];
    704 0
            uint256 fromBalance = balanceOf(_from);
    705 0
            uint256 toBalance = balanceOf(to);
    706 0
            uint256 oldToCredits = creditBalances[to];
    707 0
            uint256 newToCredits = _balanceToRebasingCredits(toBalance);
    708
    709
            // Remove the bidirectional links
    710 0
            yieldFrom[to] = address(0);
    711 0
            yieldTo[_from] = address(0);
    712
    713
            // Local
    714 0
            rebaseState[_from] = RebaseOptions.StdNonRebasing;
    715
            // alternativeCreditsPerToken[from] already 1e18 from `delegateYield()`
    716 0
            creditBalances[_from] = fromBalance;
    717 0
            rebaseState[to] = RebaseOptions.StdRebasing;
    718
            // alternativeCreditsPerToken[to] already 0 from `delegateYield()`
    719 0
            creditBalances[to] = newToCredits;
    720
    721
            // Global
    722 0
            int256 creditsChange = newToCredits.toInt256() -
    723 0
                oldToCredits.toInt256();
    724
            _adjustGlobals(creditsChange, fromBalance.toInt256());
    725 0
            emit YieldUndelegated(_from, to);
    726
        }
    727
    }
    83% contracts/contracts/utils/StableMath.sol
    Lines covered: 5 / 6 (83%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol";
    5
    6
    // Based on StableMath from Stability Labs Pty. Ltd.
    7
    // https://github.com/mstable/mStable-contracts/blob/master/contracts/shared/StableMath.sol
    8
    9 0
    library StableMath {
    10
        using SafeMath for uint256;
    11
    12
        /**
    13
         * @dev Scaling unit for use in specific calculations,
    14
         * where 1 * 10**18, or 1e18 represents a unit '1'
    15
         */
    16
        uint256 private constant FULL_SCALE = 1e18;
    17
    18
        /***************************************
    19
                        Helpers
    20
        ****************************************/
    21
    22
        /**
    23
         * @dev Adjust the scale of an integer
    24
         * @param to Decimals to scale to
    25
         * @param from Decimals to scale from
    26
         */
    27
        function scaleBy(
    28
            uint256 x,
    29
            uint256 to,
    30
            uint256 from
    31
        ) internal pure returns (uint256) {
    32
            if (to > from) {
    33
                x = x.mul(10**(to - from));
    34
            } else if (to < from) {
    35
                // slither-disable-next-line divide-before-multiply
    36
                x = x.div(10**(from - to));
    37
            }
    38
            return x;
    39
        }
    40
    41
        /***************************************
    42
                   Precise Arithmetic
    43
        ****************************************/
    44
    45
        /**
    46
         * @dev Multiplies two precise units, and then truncates by the full scale
    47
         * @param x Left hand input to multiplication
    48
         * @param y Right hand input to multiplication
    49
         * @return Result after multiplying the two inputs and then dividing by the shared
    50
         *         scale unit
    51
         */
    52
        function mulTruncate(uint256 x, uint256 y) internal pure returns (uint256) {
    53
            return mulTruncateScale(x, y, FULL_SCALE);
    54
        }
    55
    56
        /**
    57
         * @dev Multiplies two precise units, and then truncates by the given scale. For example,
    58
         * when calculating 90% of 10e18, (10e18 * 9e17) / 1e18 = (9e36) / 1e18 = 9e18
    59
         * @param x Left hand input to multiplication
    60
         * @param y Right hand input to multiplication
    61
         * @param scale Scale unit
    62
         * @return Result after multiplying the two inputs and then dividing by the shared
    63
         *         scale unit
    64
         */
    65
        function mulTruncateScale(
    66
            uint256 x,
    67
            uint256 y,
    68
            uint256 scale
    69
        ) internal pure returns (uint256) {
    70
            // e.g. assume scale = fullScale
    71
            // z = 10e18 * 9e17 = 9e36
    72
            uint256 z = x.mul(y);
    73
            // return 9e36 / 1e18 = 9e18
    74
            return z.div(scale);
    75
        }
    76
    77
        /**
    78
         * @dev Multiplies two precise units, and then truncates by the full scale, rounding up the result
    79
         * @param x Left hand input to multiplication
    80
         * @param y Right hand input to multiplication
    81
         * @return Result after multiplying the two inputs and then dividing by the shared
    82
         *          scale unit, rounded up to the closest base unit.
    83
         */
    84
        function mulTruncateCeil(uint256 x, uint256 y)
    85
            internal
    86
            pure
    87
            returns (uint256)
    88
        {
    89
            // e.g. 8e17 * 17268172638 = 138145381104e17
    90
            uint256 scaled = x.mul(y);
    91
            // e.g. 138145381104e17 + 9.99...e17 = 138145381113.99...e17
    92
            uint256 ceil = scaled.add(FULL_SCALE.sub(1));
    93
            // e.g. 13814538111.399...e18 / 1e18 = 13814538111
    94
            return ceil.div(FULL_SCALE);
    95
        }
    96
    97
        /**
    98
         * @dev Precisely divides two units, by first scaling the left hand operand. Useful
    99
         *      for finding percentage weightings, i.e. 8e18/10e18 = 80% (or 8e17)
    100
         * @param x Left hand input to division
    101
         * @param y Right hand input to division
    102
         * @return Result after multiplying the left operand by the scale, and
    103
         *         executing the division on the right hand input.
    104
         */
    105
        function divPrecisely(uint256 x, uint256 y)
    106
            internal
    107
            pure
    108
            returns (uint256)
    109
        {
    110
            // e.g. 8e18 * 1e18 = 8e36
    111
            uint256 z = x.mul(FULL_SCALE);
    112
            // e.g. 8e36 / 10e18 = 8e17
    113
            return z.div(y);
    114
        }
    115
    }
    83% lib/openzeppelin-contracts/contracts/utils/math/SafeCast.sol
    Lines covered: 5 / 6 (83%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts v4.4.1 (utils/math/SafeCast.sol)
    3
    4
    pragma solidity ^0.8.0;
    5
    6
    /**
    7
     * @dev Wrappers over Solidity's uintXX/intXX casting operators with added overflow
    8
     * checks.
    9
     *
    10
     * Downcasting from uint256/int256 in Solidity does not revert on overflow. This can
    11
     * easily result in undesired exploitation or bugs, since developers usually
    12
     * assume that overflows raise errors. `SafeCast` restores this intuition by
    13
     * reverting the transaction when such an operation overflows.
    14
     *
    15
     * Using this library instead of the unchecked operations eliminates an entire
    16
     * class of bugs, so it's recommended to use it always.
    17
     *
    18
     * Can be combined with {SafeMath} and {SignedSafeMath} to extend it to smaller types, by performing
    19
     * all math on `uint256` and `int256` and then downcasting.
    20
     */
    21 0
    library SafeCast {
    22
        /**
    23
         * @dev Returns the downcasted uint224 from uint256, reverting on
    24
         * overflow (when the input is greater than largest uint224).
    25
         *
    26
         * Counterpart to Solidity's `uint224` operator.
    27
         *
    28
         * Requirements:
    29
         *
    30
         * - input must fit into 224 bits
    31
         */
    32
        function toUint224(uint256 value) internal pure returns (uint224) {
    33
            require(value <= type(uint224).max, "SafeCast: value doesn't fit in 224 bits");
    34
            return uint224(value);
    35
        }
    36
    37
        /**
    38
         * @dev Returns the downcasted uint128 from uint256, reverting on
    39
         * overflow (when the input is greater than largest uint128).
    40
         *
    41
         * Counterpart to Solidity's `uint128` operator.
    42
         *
    43
         * Requirements:
    44
         *
    45
         * - input must fit into 128 bits
    46
         */
    47
        function toUint128(uint256 value) internal pure returns (uint128) {
    48
            require(value <= type(uint128).max, "SafeCast: value doesn't fit in 128 bits");
    49
            return uint128(value);
    50
        }
    51
    52
        /**
    53
         * @dev Returns the downcasted uint96 from uint256, reverting on
    54
         * overflow (when the input is greater than largest uint96).
    55
         *
    56
         * Counterpart to Solidity's `uint96` operator.
    57
         *
    58
         * Requirements:
    59
         *
    60
         * - input must fit into 96 bits
    61
         */
    62
        function toUint96(uint256 value) internal pure returns (uint96) {
    63
            require(value <= type(uint96).max, "SafeCast: value doesn't fit in 96 bits");
    64
            return uint96(value);
    65
        }
    66
    67
        /**
    68
         * @dev Returns the downcasted uint64 from uint256, reverting on
    69
         * overflow (when the input is greater than largest uint64).
    70
         *
    71
         * Counterpart to Solidity's `uint64` operator.
    72
         *
    73
         * Requirements:
    74
         *
    75
         * - input must fit into 64 bits
    76
         */
    77
        function toUint64(uint256 value) internal pure returns (uint64) {
    78
            require(value <= type(uint64).max, "SafeCast: value doesn't fit in 64 bits");
    79
            return uint64(value);
    80
        }
    81
    82
        /**
    83
         * @dev Returns the downcasted uint32 from uint256, reverting on
    84
         * overflow (when the input is greater than largest uint32).
    85
         *
    86
         * Counterpart to Solidity's `uint32` operator.
    87
         *
    88
         * Requirements:
    89
         *
    90
         * - input must fit into 32 bits
    91
         */
    92
        function toUint32(uint256 value) internal pure returns (uint32) {
    93
            require(value <= type(uint32).max, "SafeCast: value doesn't fit in 32 bits");
    94
            return uint32(value);
    95
        }
    96
    97
        /**
    98
         * @dev Returns the downcasted uint16 from uint256, reverting on
    99
         * overflow (when the input is greater than largest uint16).
    100
         *
    101
         * Counterpart to Solidity's `uint16` operator.
    102
         *
    103
         * Requirements:
    104
         *
    105
         * - input must fit into 16 bits
    106
         */
    107
        function toUint16(uint256 value) internal pure returns (uint16) {
    108
            require(value <= type(uint16).max, "SafeCast: value doesn't fit in 16 bits");
    109
            return uint16(value);
    110
        }
    111
    112
        /**
    113
         * @dev Returns the downcasted uint8 from uint256, reverting on
    114
         * overflow (when the input is greater than largest uint8).
    115
         *
    116
         * Counterpart to Solidity's `uint8` operator.
    117
         *
    118
         * Requirements:
    119
         *
    120
         * - input must fit into 8 bits.
    121
         */
    122
        function toUint8(uint256 value) internal pure returns (uint8) {
    123
            require(value <= type(uint8).max, "SafeCast: value doesn't fit in 8 bits");
    124
            return uint8(value);
    125
        }
    126
    127
        /**
    128
         * @dev Converts a signed int256 into an unsigned uint256.
    129
         *
    130
         * Requirements:
    131
         *
    132
         * - input must be greater than or equal to 0.
    133
         */
    134
        function toUint256(int256 value) internal pure returns (uint256) {
    135
            require(value >= 0, "SafeCast: value must be positive");
    136
            return uint256(value);
    137
        }
    138
    139
        /**
    140
         * @dev Returns the downcasted int128 from int256, reverting on
    141
         * overflow (when the input is less than smallest int128 or
    142
         * greater than largest int128).
    143
         *
    144
         * Counterpart to Solidity's `int128` operator.
    145
         *
    146
         * Requirements:
    147
         *
    148
         * - input must fit into 128 bits
    149
         *
    150
         * _Available since v3.1._
    151
         */
    152
        function toInt128(int256 value) internal pure returns (int128) {
    153
            require(value >= type(int128).min && value <= type(int128).max, "SafeCast: value doesn't fit in 128 bits");
    154
            return int128(value);
    155
        }
    156
    157
        /**
    158
         * @dev Returns the downcasted int64 from int256, reverting on
    159
         * overflow (when the input is less than smallest int64 or
    160
         * greater than largest int64).
    161
         *
    162
         * Counterpart to Solidity's `int64` operator.
    163
         *
    164
         * Requirements:
    165
         *
    166
         * - input must fit into 64 bits
    167
         *
    168
         * _Available since v3.1._
    169
         */
    170
        function toInt64(int256 value) internal pure returns (int64) {
    171
            require(value >= type(int64).min && value <= type(int64).max, "SafeCast: value doesn't fit in 64 bits");
    172
            return int64(value);
    173
        }
    174
    175
        /**
    176
         * @dev Returns the downcasted int32 from int256, reverting on
    177
         * overflow (when the input is less than smallest int32 or
    178
         * greater than largest int32).
    179
         *
    180
         * Counterpart to Solidity's `int32` operator.
    181
         *
    182
         * Requirements:
    183
         *
    184
         * - input must fit into 32 bits
    185
         *
    186
         * _Available since v3.1._
    187
         */
    188
        function toInt32(int256 value) internal pure returns (int32) {
    189
            require(value >= type(int32).min && value <= type(int32).max, "SafeCast: value doesn't fit in 32 bits");
    190
            return int32(value);
    191
        }
    192
    193
        /**
    194
         * @dev Returns the downcasted int16 from int256, reverting on
    195
         * overflow (when the input is less than smallest int16 or
    196
         * greater than largest int16).
    197
         *
    198
         * Counterpart to Solidity's `int16` operator.
    199
         *
    200
         * Requirements:
    201
         *
    202
         * - input must fit into 16 bits
    203
         *
    204
         * _Available since v3.1._
    205
         */
    206
        function toInt16(int256 value) internal pure returns (int16) {
    207
            require(value >= type(int16).min && value <= type(int16).max, "SafeCast: value doesn't fit in 16 bits");
    208
            return int16(value);
    209
        }
    210
    211
        /**
    212
         * @dev Returns the downcasted int8 from int256, reverting on
    213
         * overflow (when the input is less than smallest int8 or
    214
         * greater than largest int8).
    215
         *
    216
         * Counterpart to Solidity's `int8` operator.
    217
         *
    218
         * Requirements:
    219
         *
    220
         * - input must fit into 8 bits.
    221
         *
    222
         * _Available since v3.1._
    223
         */
    224
        function toInt8(int256 value) internal pure returns (int8) {
    225
            require(value >= type(int8).min && value <= type(int8).max, "SafeCast: value doesn't fit in 8 bits");
    226
            return int8(value);
    227
        }
    228
    229
        /**
    230
         * @dev Converts an unsigned uint256 into a signed int256.
    231
         *
    232
         * Requirements:
    233
         *
    234
         * - input must be less than or equal to maxInt256.
    235
         */
    236
        function toInt256(uint256 value) internal pure returns (int256) {
    237
            // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive
    238 20×
            require(value <= uint256(type(int256).max), "SafeCast: value doesn't fit in an int256");
    239
            return int256(value);
    240
        }
    241
    }
    80% lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol
    Lines covered: 4 / 5 (80%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts v4.4.1 (utils/math/SafeMath.sol)
    3
    4
    pragma solidity ^0.8.0;
    5
    6
    // CAUTION
    7
    // This version of SafeMath should only be used with Solidity 0.8 or later,
    8
    // because it relies on the compiler's built in overflow checks.
    9
    10
    /**
    11
     * @dev Wrappers over Solidity's arithmetic operations.
    12
     *
    13
     * NOTE: `SafeMath` is generally not needed starting with Solidity 0.8, since the compiler
    14
     * now has built in overflow checking.
    15
     */
    16 0
    library SafeMath {
    17
        /**
    18
         * @dev Returns the addition of two unsigned integers, with an overflow flag.
    19
         *
    20
         * _Available since v3.4._
    21
         */
    22
        function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
    23
            unchecked {
    24
                uint256 c = a + b;
    25
                if (c < a) return (false, 0);
    26
                return (true, c);
    27
            }
    28
        }
    29
    30
        /**
    31
         * @dev Returns the substraction of two unsigned integers, with an overflow flag.
    32
         *
    33
         * _Available since v3.4._
    34
         */
    35
        function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
    36
            unchecked {
    37
                if (b > a) return (false, 0);
    38
                return (true, a - b);
    39
            }
    40
        }
    41
    42
        /**
    43
         * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
    44
         *
    45
         * _Available since v3.4._
    46
         */
    47
        function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
    48
            unchecked {
    49
                // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    50
                // benefit is lost if 'b' is also tested.
    51
                // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
    52
                if (a == 0) return (true, 0);
    53
                uint256 c = a * b;
    54
                if (c / a != b) return (false, 0);
    55
                return (true, c);
    56
            }
    57
        }
    58
    59
        /**
    60
         * @dev Returns the division of two unsigned integers, with a division by zero flag.
    61
         *
    62
         * _Available since v3.4._
    63
         */
    64
        function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
    65
            unchecked {
    66
                if (b == 0) return (false, 0);
    67
                return (true, a / b);
    68
            }
    69
        }
    70
    71
        /**
    72
         * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
    73
         *
    74
         * _Available since v3.4._
    75
         */
    76
        function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
    77
            unchecked {
    78
                if (b == 0) return (false, 0);
    79
                return (true, a % b);
    80
            }
    81
        }
    82
    83
        /**
    84
         * @dev Returns the addition of two unsigned integers, reverting on
    85
         * overflow.
    86
         *
    87
         * Counterpart to Solidity's `+` operator.
    88
         *
    89
         * Requirements:
    90
         *
    91
         * - Addition cannot overflow.
    92
         */
    93
        function add(uint256 a, uint256 b) internal pure returns (uint256) {
    94
            return a + b;
    95
        }
    96
    97
        /**
    98
         * @dev Returns the subtraction of two unsigned integers, reverting on
    99
         * overflow (when the result is negative).
    100
         *
    101
         * Counterpart to Solidity's `-` operator.
    102
         *
    103
         * Requirements:
    104
         *
    105
         * - Subtraction cannot overflow.
    106
         */
    107
        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    108
            return a - b;
    109
        }
    110
    111
        /**
    112
         * @dev Returns the multiplication of two unsigned integers, reverting on
    113
         * overflow.
    114
         *
    115
         * Counterpart to Solidity's `*` operator.
    116
         *
    117
         * Requirements:
    118
         *
    119
         * - Multiplication cannot overflow.
    120
         */
    121
        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    122
            return a * b;
    123
        }
    124
    125
        /**
    126
         * @dev Returns the integer division of two unsigned integers, reverting on
    127
         * division by zero. The result is rounded towards zero.
    128
         *
    129
         * Counterpart to Solidity's `/` operator.
    130
         *
    131
         * Requirements:
    132
         *
    133
         * - The divisor cannot be zero.
    134
         */
    135
        function div(uint256 a, uint256 b) internal pure returns (uint256) {
    136
            return a / b;
    137
        }
    138
    139
        /**
    140
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
    141
         * reverting when dividing by zero.
    142
         *
    143
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
    144
         * opcode (which leaves remaining gas untouched) while Solidity uses an
    145
         * invalid opcode to revert (consuming all remaining gas).
    146
         *
    147
         * Requirements:
    148
         *
    149
         * - The divisor cannot be zero.
    150
         */
    151
        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    152
            return a % b;
    153
        }
    154
    155
        /**
    156
         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
    157
         * overflow (when the result is negative).
    158
         *
    159
         * CAUTION: This function is deprecated because it requires allocating memory for the error
    160
         * message unnecessarily. For custom revert reasons use {trySub}.
    161
         *
    162
         * Counterpart to Solidity's `-` operator.
    163
         *
    164
         * Requirements:
    165
         *
    166
         * - Subtraction cannot overflow.
    167
         */
    168
        function sub(
    169
            uint256 a,
    170
            uint256 b,
    171
            string memory errorMessage
    172
        ) internal pure returns (uint256) {
    173
            unchecked {
    174
                require(b <= a, errorMessage);
    175
                return a - b;
    176
            }
    177
        }
    178
    179
        /**
    180
         * @dev Returns the integer division of two unsigned integers, reverting with custom message on
    181
         * division by zero. The result is rounded towards zero.
    182
         *
    183
         * Counterpart to Solidity's `/` operator. Note: this function uses a
    184
         * `revert` opcode (which leaves remaining gas untouched) while Solidity
    185
         * uses an invalid opcode to revert (consuming all remaining gas).
    186
         *
    187
         * Requirements:
    188
         *
    189
         * - The divisor cannot be zero.
    190
         */
    191
        function div(
    192
            uint256 a,
    193
            uint256 b,
    194
            string memory errorMessage
    195
        ) internal pure returns (uint256) {
    196
            unchecked {
    197
                require(b > 0, errorMessage);
    198
                return a / b;
    199
            }
    200
        }
    201
    202
        /**
    203
         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
    204
         * reverting with custom message when dividing by zero.
    205
         *
    206
         * CAUTION: This function is deprecated because it requires allocating memory for the error
    207
         * message unnecessarily. For custom revert reasons use {tryMod}.
    208
         *
    209
         * Counterpart to Solidity's `%` operator. This function uses a `revert`
    210
         * opcode (which leaves remaining gas untouched) while Solidity uses an
    211
         * invalid opcode to revert (consuming all remaining gas).
    212
         *
    213
         * Requirements:
    214
         *
    215
         * - The divisor cannot be zero.
    216
         */
    217
        function mod(
    218
            uint256 a,
    219
            uint256 b,
    220
            string memory errorMessage
    221
        ) internal pure returns (uint256) {
    222
            unchecked {
    223
                require(b > 0, errorMessage);
    224
                return a % b;
    225
            }
    226
        }
    227
    }
    100% test/recon/CryticTester.sol
    Lines covered: 1 / 1 (100%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Properties} from "./Properties.sol";
    5
    6
    // echidna test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml
    7 332×
    contract CryticTester is Properties {}
    88% test/recon/Properties.sol
    Lines covered: 8 / 9 (88%)
    1
    // SPDX-License-Identifier: BUSL-1.1
    2
    pragma solidity ^0.8.0;
    3
    4
    import {Echidna} from "../../contracts/contracts/echidna/Echidna.sol";
    5
    6
    /// @notice scfuzzbench adaptation layer: canary checks on top of the
    7
    /// upstream OUSD Echidna suite. Assertion failures are surfaced the same
    8
    /// way for Echidna, Medusa, Foundry, and Recon (AssertionFailed event +
    9
    /// assert), and dedup to the emitting function name across fuzzers.
    10
    abstract contract Properties is Echidna {
    11
        string internal constant ASSERTION_CANARY = "!!! canary assertion";
    12
        string internal constant INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE = "Canary invariant";
    13
    14
        event AssertionFailed(string reason);
    15
    16
        function t(bool b, string memory reason) internal {
    17
            if (!b) {
    18 17×
                emit AssertionFailed(reason);
    19
                assert(false);
    20
            }
    21
        }
    22
    23
        function invariant_canary() public returns (bool) {
    24 20×
            t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE);
    25 0
            return true;
    26
        }
    27
    28 11×
        function assert_canary_ASSERTION_CANARY(uint256 entropy) public {
    29 22×
            t(entropy > 0, ASSERTION_CANARY);
    30
        }
    31
    }