Medusa Coverage Report GitHub

Files

    Files
    47
    Total Lines
    7965
    Coverage
    87.7%
    1552 / 1769 lines
    Actions
    0.0% lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC.
    8
     *
    9
     * _Available since v4.8.3._
    10
     */
    11
    interface IERC1967 {
    12
        /**
    13
         * @dev Emitted when the implementation is upgraded.
    14
         */
    15
        event Upgraded(address indexed implementation);
    16
    
                                                    
                                                
    17
        /**
    18
         * @dev Emitted when the admin account has changed.
    19
         */
    20
        event AdminChanged(address previousAdmin, address newAdmin);
    21
    
                                                    
                                                
    22
        /**
    23
         * @dev Emitted when the beacon is changed.
    24
         */
    25
        event BeaconUpgraded(address indexed beacon);
    26
    }
    27
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/interfaces/draft-IERC1822.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
    8
     * proxy whose upgrades are fully controlled by the current implementation.
    9
     */
    10
    interface IERC1822Proxiable {
    11
        /**
    12
         * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
    13
         * address.
    14
         *
    15
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
    16
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
    17
         * function revert if invoked through a proxy.
    18
         */
    19
        function proxiableUUID() external view returns (bytes32);
    20
    }
    21
    
                                                    
                                                
    85.7% lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol
    Lines covered: 6 / 7 (85.7%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.9;
    5
    
                                                    
                                                
    6
    import "../utils/Context.sol";
    7
    
                                                    
                                                
    8
    /**
    9
     * @dev Context variant with ERC2771 support.
    10
     */
    11
    abstract contract ERC2771Context is Context {
    12
        /// @custom:oz-upgrades-unsafe-allow state-variable-immutable
    13
        address private immutable _trustedForwarder;
    14
    
                                                    
                                                
    15
        /// @custom:oz-upgrades-unsafe-allow constructor
    16
        constructor(address trustedForwarder) {
    17
    ✓ 1
            _trustedForwarder = trustedForwarder;
    18
        }
    19
    
                                                    
                                                
    20
    ✓ 71.2M
        function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
    21
    ✓ 71.2M
            return forwarder == _trustedForwarder;
    22
        }
    23
    
                                                    
                                                
    24
    ✓ 71.2M
        function _msgSender() internal view virtual override returns (address sender) {
    25
    ✓ 71.2M
            if (isTrustedForwarder(msg.sender)) {
    26
                // The assembly code is more direct than the Solidity version using `abi.decode`.
    27
                /// @solidity memory-safe-assembly
    28
                assembly {
    29
                    sender := shr(96, calldataload(sub(calldatasize(), 20)))
    30
                }
    31
            } else {
    32
    ✓ 71.2M
                return super._msgSender();
    33
            }
    34
        }
    35
    
                                                    
                                                
    36
        function _msgData() internal view virtual override returns (bytes calldata) {
    37
            if (isTrustedForwarder(msg.sender)) {
    38
                return msg.data[:msg.data.length - 20];
    39
            } else {
    40
                return super._msgData();
    41
            }
    42
        }
    43
    }
    44
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol
    Lines covered: 0 / 3 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    import "../Proxy.sol";
    7
    import "./ERC1967Upgrade.sol";
    8
    
                                                    
                                                
    9
    /**
    10
     * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
    11
     * implementation address that can be changed. This address is stored in storage in the location specified by
    12
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
    13
     * implementation behind the proxy.
    14
     */
    15
    contract ERC1967Proxy is Proxy, ERC1967Upgrade {
    16
        /**
    17
         * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
    18
         *
    19
         * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
    20
         * function call, and allows initializing the storage of the proxy like a Solidity constructor.
    21
         */
    22
        constructor(address _logic, bytes memory _data) payable {
    23
            _upgradeToAndCall(_logic, _data, false);
    24
        }
    25
    
                                                    
                                                
    26
        /**
    27
         * @dev Returns the current implementation address.
    28
         */
    29
        function _implementation() internal view virtual override returns (address impl) {
    30
            return ERC1967Upgrade._getImplementation();
    31
        }
    32
    }
    33
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Upgrade.sol
    Lines covered: 0 / 24 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.2;
    5
    
                                                    
                                                
    6
    import "../beacon/IBeacon.sol";
    7
    import "../../interfaces/IERC1967.sol";
    8
    import "../../interfaces/draft-IERC1822.sol";
    9
    import "../../utils/Address.sol";
    10
    import "../../utils/StorageSlot.sol";
    11
    
                                                    
                                                
    12
    /**
    13
     * @dev This abstract contract provides getters and event emitting update functions for
    14
     * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
    15
     *
    16
     * _Available since v4.1._
    17
     */
    18
    abstract contract ERC1967Upgrade is IERC1967 {
    19
        // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
    20
        bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
    21
    
                                                    
                                                
    22
        /**
    23
         * @dev Storage slot with the address of the current implementation.
    24
         * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
    25
         * validated in the constructor.
    26
         */
    27
        bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    28
    
                                                    
                                                
    29
        /**
    30
         * @dev Returns the current implementation address.
    31
         */
    32
        function _getImplementation() internal view returns (address) {
    33
            return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    34
        }
    35
    
                                                    
                                                
    36
        /**
    37
         * @dev Stores a new address in the EIP1967 implementation slot.
    38
         */
    39
        function _setImplementation(address newImplementation) private {
    40
            require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
    41
            StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    42
        }
    43
    
                                                    
                                                
    44
        /**
    45
         * @dev Perform implementation upgrade
    46
         *
    47
         * Emits an {Upgraded} event.
    48
         */
    49
        function _upgradeTo(address newImplementation) internal {
    50
            _setImplementation(newImplementation);
    51
            emit Upgraded(newImplementation);
    52
        }
    53
    
                                                    
                                                
    54
        /**
    55
         * @dev Perform implementation upgrade with additional setup call.
    56
         *
    57
         * Emits an {Upgraded} event.
    58
         */
    59
        function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal {
    60
            _upgradeTo(newImplementation);
    61
            if (data.length > 0 || forceCall) {
    62
                Address.functionDelegateCall(newImplementation, data);
    63
            }
    64
        }
    65
    
                                                    
                                                
    66
        /**
    67
         * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
    68
         *
    69
         * Emits an {Upgraded} event.
    70
         */
    71
        function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal {
    72
            // Upgrades from old implementations will perform a rollback test. This test requires the new
    73
            // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
    74
            // this special case will break upgrade paths from old UUPS implementation to new ones.
    75
            if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
    76
                _setImplementation(newImplementation);
    77
            } else {
    78
                try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
    79
                    require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
    80
                } catch {
    81
                    revert("ERC1967Upgrade: new implementation is not UUPS");
    82
                }
    83
                _upgradeToAndCall(newImplementation, data, forceCall);
    84
            }
    85
        }
    86
    
                                                    
                                                
    87
        /**
    88
         * @dev Storage slot with the admin of the contract.
    89
         * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
    90
         * validated in the constructor.
    91
         */
    92
        bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    93
    
                                                    
                                                
    94
        /**
    95
         * @dev Returns the current admin.
    96
         */
    97
        function _getAdmin() internal view returns (address) {
    98
            return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
    99
        }
    100
    
                                                    
                                                
    101
        /**
    102
         * @dev Stores a new address in the EIP1967 admin slot.
    103
         */
    104
        function _setAdmin(address newAdmin) private {
    105
            require(newAdmin != address(0), "ERC1967: new admin is the zero address");
    106
            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    107
        }
    108
    
                                                    
                                                
    109
        /**
    110
         * @dev Changes the admin of the proxy.
    111
         *
    112
         * Emits an {AdminChanged} event.
    113
         */
    114
        function _changeAdmin(address newAdmin) internal {
    115
            emit AdminChanged(_getAdmin(), newAdmin);
    116
            _setAdmin(newAdmin);
    117
        }
    118
    
                                                    
                                                
    119
        /**
    120
         * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
    121
         * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
    122
         */
    123
        bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
    124
    
                                                    
                                                
    125
        /**
    126
         * @dev Returns the current beacon.
    127
         */
    128
        function _getBeacon() internal view returns (address) {
    129
            return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
    130
        }
    131
    
                                                    
                                                
    132
        /**
    133
         * @dev Stores a new beacon in the EIP1967 beacon slot.
    134
         */
    135
        function _setBeacon(address newBeacon) private {
    136
            require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
    137
            require(
    138
                Address.isContract(IBeacon(newBeacon).implementation()),
    139
                "ERC1967: beacon implementation is not a contract"
    140
            );
    141
            StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
    142
        }
    143
    
                                                    
                                                
    144
        /**
    145
         * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
    146
         * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
    147
         *
    148
         * Emits a {BeaconUpgraded} event.
    149
         */
    150
        function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal {
    151
            _setBeacon(newBeacon);
    152
            emit BeaconUpgraded(newBeacon);
    153
            if (data.length > 0 || forceCall) {
    154
                Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
    155
            }
    156
        }
    157
    }
    158
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/proxy/Proxy.sol
    Lines covered: 0 / 12 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
    8
     * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
    9
     * be specified by overriding the virtual {_implementation} function.
    10
     *
    11
     * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
    12
     * different contract through the {_delegate} function.
    13
     *
    14
     * The success and return data of the delegated call will be returned back to the caller of the proxy.
    15
     */
    16
    abstract contract Proxy {
    17
        /**
    18
         * @dev Delegates the current call to `implementation`.
    19
         *
    20
         * This function does not return to its internal call site, it will return directly to the external caller.
    21
         */
    22
        function _delegate(address implementation) internal virtual {
    23
            assembly {
    24
                // Copy msg.data. We take full control of memory in this inline assembly
    25
                // block because it will not return to Solidity code. We overwrite the
    26
                // Solidity scratch pad at memory position 0.
    27
                calldatacopy(0, 0, calldatasize())
    28
    
                                                    
                                                
    29
                // Call the implementation.
    30
                // out and outsize are 0 because we don't know the size yet.
    31
                let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
    32
    
                                                    
                                                
    33
                // Copy the returned data.
    34
                returndatacopy(0, 0, returndatasize())
    35
    
                                                    
                                                
    36
                switch result
    37
                // delegatecall returns 0 on error.
    38
                case 0 {
    39
                    revert(0, returndatasize())
    40
                }
    41
                default {
    42
                    return(0, returndatasize())
    43
                }
    44
            }
    45
        }
    46
    
                                                    
                                                
    47
        /**
    48
         * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function
    49
         * and {_fallback} should delegate.
    50
         */
    51
        function _implementation() internal view virtual returns (address);
    52
    
                                                    
                                                
    53
        /**
    54
         * @dev Delegates the current call to the address returned by `_implementation()`.
    55
         *
    56
         * This function does not return to its internal call site, it will return directly to the external caller.
    57
         */
    58
        function _fallback() internal virtual {
    59
            _beforeFallback();
    60
            _delegate(_implementation());
    61
        }
    62
    
                                                    
                                                
    63
        /**
    64
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
    65
         * function in the contract matches the call data.
    66
         */
    67
        fallback() external payable virtual {
    68
            _fallback();
    69
        }
    70
    
                                                    
                                                
    71
        /**
    72
         * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
    73
         * is empty.
    74
         */
    75
        receive() external payable virtual {
    76
            _fallback();
    77
        }
    78
    
                                                    
                                                
    79
        /**
    80
         * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
    81
         * call, or as part of the Solidity `fallback` or `receive` functions.
    82
         *
    83
         * If overridden should call `super._beforeFallback()`.
    84
         */
    85
        function _beforeFallback() internal virtual {}
    86
    }
    87
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/proxy/beacon/IBeacon.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev This is the interface that {BeaconProxy} expects of its beacon.
    8
     */
    9
    interface IBeacon {
    10
        /**
    11
         * @dev Must return an address that can be used as a delegate call target.
    12
         *
    13
         * {BeaconProxy} will check that this address is a contract.
    14
         */
    15
        function implementation() external view returns (address);
    16
    }
    17
    
                                                    
                                                
    10.0% lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol
    Lines covered: 1 / 10 (10.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    import "../../interfaces/draft-IERC1822.sol";
    7
    import "../ERC1967/ERC1967Upgrade.sol";
    8
    
                                                    
                                                
    9
    /**
    10
     * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
    11
     * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
    12
     *
    13
     * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
    14
     * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
    15
     * `UUPSUpgradeable` with a custom implementation of upgrades.
    16
     *
    17
     * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
    18
     *
    19
     * _Available since v4.1._
    20
     */
    21
    abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade {
    22
        /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
    23
    ✓ 1
        address private immutable __self = address(this);
    24
    
                                                    
                                                
    25
        /**
    26
         * @dev Check that the execution is being performed through a delegatecall call and that the execution context is
    27
         * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
    28
         * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
    29
         * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
    30
         * fail.
    31
         */
    32
        modifier onlyProxy() {
    33
            require(address(this) != __self, "Function must be called through delegatecall");
    34
            require(_getImplementation() == __self, "Function must be called through active proxy");
    35
            _;
    36
        }
    37
    
                                                    
                                                
    38
        /**
    39
         * @dev Check that the execution is not being performed through a delegate call. This allows a function to be
    40
         * callable on the implementing contract but not through proxies.
    41
         */
    42
        modifier notDelegated() {
    43
            require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall");
    44
            _;
    45
        }
    46
    
                                                    
                                                
    47
        /**
    48
         * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
    49
         * implementation. It is used to validate the implementation's compatibility when performing an upgrade.
    50
         *
    51
         * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
    52
         * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
    53
         * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
    54
         */
    55
        function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
    56
            return _IMPLEMENTATION_SLOT;
    57
        }
    58
    
                                                    
                                                
    59
        /**
    60
         * @dev Upgrade the implementation of the proxy to `newImplementation`.
    61
         *
    62
         * Calls {_authorizeUpgrade}.
    63
         *
    64
         * Emits an {Upgraded} event.
    65
         *
    66
         * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
    67
         */
    68
        function upgradeTo(address newImplementation) public virtual onlyProxy {
    69
            _authorizeUpgrade(newImplementation);
    70
            _upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
    71
        }
    72
    
                                                    
                                                
    73
        /**
    74
         * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
    75
         * encoded in `data`.
    76
         *
    77
         * Calls {_authorizeUpgrade}.
    78
         *
    79
         * Emits an {Upgraded} event.
    80
         *
    81
         * @custom:oz-upgrades-unsafe-allow-reachable delegatecall
    82
         */
    83
        function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy {
    84
            _authorizeUpgrade(newImplementation);
    85
            _upgradeToAndCallUUPS(newImplementation, data, true);
    86
        }
    87
    
                                                    
                                                
    88
        /**
    89
         * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
    90
         * {upgradeTo} and {upgradeToAndCall}.
    91
         *
    92
         * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
    93
         *
    94
         * ```solidity
    95
         * function _authorizeUpgrade(address) internal override onlyOwner {}
    96
         * ```
    97
         */
    98
        function _authorizeUpgrade(address newImplementation) internal virtual;
    99
    }
    100
    
                                                    
                                                
    60.0% lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol
    Lines covered: 42 / 70 (60.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    import "./IERC20.sol";
    7
    import "./extensions/IERC20Metadata.sol";
    8
    import "../../utils/Context.sol";
    9
    
                                                    
                                                
    10
    /**
    11
     * @dev Implementation of the {IERC20} interface.
    12
     *
    13
     * This implementation is agnostic to the way tokens are created. This means
    14
     * that a supply mechanism has to be added in a derived contract using {_mint}.
    15
     * For a generic mechanism see {ERC20PresetMinterPauser}.
    16
     *
    17
     * TIP: For a detailed writeup see our guide
    18
     * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
    19
     * to implement supply mechanisms].
    20
     *
    21
     * The default value of {decimals} is 18. To change this, you should override
    22
     * this function so it returns a different value.
    23
     *
    24
     * We have followed general OpenZeppelin Contracts guidelines: functions revert
    25
     * instead returning `false` on failure. This behavior is nonetheless
    26
     * conventional and does not conflict with the expectations of ERC20
    27
     * applications.
    28
     *
    29
     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
    30
     * This allows applications to reconstruct the allowance for all accounts just
    31
     * by listening to said events. Other implementations of the EIP may not emit
    32
     * these events, as it isn't required by the specification.
    33
     *
    34
     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
    35
     * functions have been added to mitigate the well-known issues around setting
    36
     * allowances. See {IERC20-approve}.
    37
     */
    38
    contract ERC20 is Context, IERC20, IERC20Metadata {
    39
        mapping(address => uint256) private _balances;
    40
    
                                                    
                                                
    41
        mapping(address => mapping(address => uint256)) private _allowances;
    42
    
                                                    
                                                
    43
        uint256 private _totalSupply;
    44
    
                                                    
                                                
    45
        string private _name;
    46
        string private _symbol;
    47
    
                                                    
                                                
    48
        /**
    49
         * @dev Sets the values for {name} and {symbol}.
    50
         *
    51
         * All two of these values are immutable: they can only be set once during
    52
         * construction.
    53
         */
    54
        constructor(string memory name_, string memory symbol_) {
    55
    ✓ 1
            _name = name_;
    56
    ✓ 1
            _symbol = symbol_;
    57
        }
    58
    
                                                    
                                                
    59
        /**
    60
         * @dev Returns the name of the token.
    61
         */
    62
        function name() public view virtual override returns (string memory) {
    63
            return _name;
    64
        }
    65
    
                                                    
                                                
    66
        /**
    67
         * @dev Returns the symbol of the token, usually a shorter version of the
    68
         * name.
    69
         */
    70
        function symbol() public view virtual override returns (string memory) {
    71
            return _symbol;
    72
        }
    73
    
                                                    
                                                
    74
        /**
    75
         * @dev Returns the number of decimals used to get its user representation.
    76
         * For example, if `decimals` equals `2`, a balance of `505` tokens should
    77
         * be displayed to a user as `5.05` (`505 / 10 ** 2`).
    78
         *
    79
         * Tokens usually opt for a value of 18, imitating the relationship between
    80
         * Ether and Wei. This is the default value returned by this function, unless
    81
         * it's overridden.
    82
         *
    83
         * NOTE: This information is only used for _display_ purposes: it in
    84
         * no way affects any of the arithmetic of the contract, including
    85
         * {IERC20-balanceOf} and {IERC20-transfer}.
    86
         */
    87
        function decimals() public view virtual override returns (uint8) {
    88
            return 18;
    89
        }
    90
    
                                                    
                                                
    91
        /**
    92
         * @dev See {IERC20-totalSupply}.
    93
         */
    94
        function totalSupply() public view virtual override returns (uint256) {
    95
            return _totalSupply;
    96
        }
    97
    
                                                    
                                                
    98
        /**
    99
         * @dev See {IERC20-balanceOf}.
    100
         */
    101
    ✓ 41.7M
        function balanceOf(address account) public view virtual override returns (uint256) {
    102
    ✓ 41.7M
            return _balances[account];
    103
        }
    104
    
                                                    
                                                
    105
        /**
    106
         * @dev See {IERC20-transfer}.
    107
         *
    108
         * Requirements:
    109
         *
    110
         * - `to` cannot be the zero address.
    111
         * - the caller must have a balance of at least `amount`.
    112
         */
    113
    ✓ 7.3M
        function transfer(address to, uint256 amount) public virtual override returns (bool) {
    114
    ✓ 7.3M
            address owner = _msgSender();
    115
    ✓ 7.3M
            _transfer(owner, to, amount);
    116
    ✓ 7.3M
            return true;
    117
        }
    118
    
                                                    
                                                
    119
        /**
    120
         * @dev See {IERC20-allowance}.
    121
         */
    122
    ✓ 10.9M
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
    123
    ✓ 10.9M
            return _allowances[owner][spender];
    124
        }
    125
    
                                                    
                                                
    126
        /**
    127
         * @dev See {IERC20-approve}.
    128
         *
    129
         * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
    130
         * `transferFrom`. This is semantically equivalent to an infinite approval.
    131
         *
    132
         * Requirements:
    133
         *
    134
         * - `spender` cannot be the zero address.
    135
         */
    136
    ✓ 4
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
    137
    ✓ 4
            address owner = _msgSender();
    138
    ✓ 4
            _approve(owner, spender, amount);
    139
    ✓ 4
            return true;
    140
        }
    141
    
                                                    
                                                
    142
        /**
    143
         * @dev See {IERC20-transferFrom}.
    144
         *
    145
         * Emits an {Approval} event indicating the updated allowance. This is not
    146
         * required by the EIP. See the note at the beginning of {ERC20}.
    147
         *
    148
         * NOTE: Does not update the allowance if the current allowance
    149
         * is the maximum `uint256`.
    150
         *
    151
         * Requirements:
    152
         *
    153
         * - `from` and `to` cannot be the zero address.
    154
         * - `from` must have a balance of at least `amount`.
    155
         * - the caller must have allowance for ``from``'s tokens of at least
    156
         * `amount`.
    157
         */
    158
    ✓ 10.9M
        function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
    159
    ✓ 10.9M
            address spender = _msgSender();
    160
    ✓ 10.9M
            _spendAllowance(from, spender, amount);
    161
    ✓ 10.9M
            _transfer(from, to, amount);
    162
    ✓ 8.6M
            return true;
    163
        }
    164
    
                                                    
                                                
    165
        /**
    166
         * @dev Atomically increases the allowance granted to `spender` by the caller.
    167
         *
    168
         * This is an alternative to {approve} that can be used as a mitigation for
    169
         * problems described in {IERC20-approve}.
    170
         *
    171
         * Emits an {Approval} event indicating the updated allowance.
    172
         *
    173
         * Requirements:
    174
         *
    175
         * - `spender` cannot be the zero address.
    176
         */
    177
        function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
    178
            address owner = _msgSender();
    179
            _approve(owner, spender, allowance(owner, spender) + addedValue);
    180
            return true;
    181
        }
    182
    
                                                    
                                                
    183
        /**
    184
         * @dev Atomically decreases the allowance granted to `spender` by the caller.
    185
         *
    186
         * This is an alternative to {approve} that can be used as a mitigation for
    187
         * problems described in {IERC20-approve}.
    188
         *
    189
         * Emits an {Approval} event indicating the updated allowance.
    190
         *
    191
         * Requirements:
    192
         *
    193
         * - `spender` cannot be the zero address.
    194
         * - `spender` must have allowance for the caller of at least
    195
         * `subtractedValue`.
    196
         */
    197
        function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
    198
            address owner = _msgSender();
    199
            uint256 currentAllowance = allowance(owner, spender);
    200
            require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
    201
            unchecked {
    202
                _approve(owner, spender, currentAllowance - subtractedValue);
    203
            }
    204
    
                                                    
                                                
    205
            return true;
    206
        }
    207
    
                                                    
                                                
    208
        /**
    209
         * @dev Moves `amount` of tokens from `from` to `to`.
    210
         *
    211
         * This internal function is equivalent to {transfer}, and can be used to
    212
         * e.g. implement automatic token fees, slashing mechanisms, etc.
    213
         *
    214
         * Emits a {Transfer} event.
    215
         *
    216
         * Requirements:
    217
         *
    218
         * - `from` cannot be the zero address.
    219
         * - `to` cannot be the zero address.
    220
         * - `from` must have a balance of at least `amount`.
    221
         */
    222
        function _transfer(address from, address to, uint256 amount) internal virtual {
    223
    ✓ 18.2M
            require(from != address(0), "ERC20: transfer from the zero address");
    224
    ✓ 18.2M
            require(to != address(0), "ERC20: transfer to the zero address");
    225
    
                                                    
                                                
    226
    ✓ 18.2M
            _beforeTokenTransfer(from, to, amount);
    227
    
                                                    
                                                
    228
    ✓ 18.2M
            uint256 fromBalance = _balances[from];
    229
    ✓ 18.2M
            require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
    230
            unchecked {
    231
    ✓ 15.9M
                _balances[from] = fromBalance - amount;
    232
                // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
    233
                // decrementing then incrementing.
    234
    ✓ 15.9M
                _balances[to] += amount;
    235
            }
    236
    
                                                    
                                                
    237
    ✓ 15.9M
            emit Transfer(from, to, amount);
    238
    
                                                    
                                                
    239
    ✓ 15.9M
            _afterTokenTransfer(from, to, amount);
    240
        }
    241
    
                                                    
                                                
    242
        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
    243
         * the total supply.
    244
         *
    245
         * Emits a {Transfer} event with `from` set to the zero address.
    246
         *
    247
         * Requirements:
    248
         *
    249
         * - `account` cannot be the zero address.
    250
         */
    251
        function _mint(address account, uint256 amount) internal virtual {
    252
    ✓ 1
            require(account != address(0), "ERC20: mint to the zero address");
    253
    
                                                    
                                                
    254
    ✓ 1
            _beforeTokenTransfer(address(0), account, amount);
    255
    
                                                    
                                                
    256
    ✓ 1
            _totalSupply += amount;
    257
            unchecked {
    258
                // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
    259
    ✓ 1
                _balances[account] += amount;
    260
            }
    261
    ✓ 1
            emit Transfer(address(0), account, amount);
    262
    
                                                    
                                                
    263
    ✓ 1
            _afterTokenTransfer(address(0), account, amount);
    264
        }
    265
    
                                                    
                                                
    266
        /**
    267
         * @dev Destroys `amount` tokens from `account`, reducing the
    268
         * total supply.
    269
         *
    270
         * Emits a {Transfer} event with `to` set to the zero address.
    271
         *
    272
         * Requirements:
    273
         *
    274
         * - `account` cannot be the zero address.
    275
         * - `account` must have at least `amount` tokens.
    276
         */
    277
        function _burn(address account, uint256 amount) internal virtual {
    278
            require(account != address(0), "ERC20: burn from the zero address");
    279
    
                                                    
                                                
    280
            _beforeTokenTransfer(account, address(0), amount);
    281
    
                                                    
                                                
    282
            uint256 accountBalance = _balances[account];
    283
            require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
    284
            unchecked {
    285
                _balances[account] = accountBalance - amount;
    286
                // Overflow not possible: amount <= accountBalance <= totalSupply.
    287
                _totalSupply -= amount;
    288
            }
    289
    
                                                    
                                                
    290
            emit Transfer(account, address(0), amount);
    291
    
                                                    
                                                
    292
            _afterTokenTransfer(account, address(0), amount);
    293
        }
    294
    
                                                    
                                                
    295
        /**
    296
         * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
    297
         *
    298
         * This internal function is equivalent to `approve`, and can be used to
    299
         * e.g. set automatic allowances for certain subsystems, etc.
    300
         *
    301
         * Emits an {Approval} event.
    302
         *
    303
         * Requirements:
    304
         *
    305
         * - `owner` cannot be the zero address.
    306
         * - `spender` cannot be the zero address.
    307
         */
    308
        function _approve(address owner, address spender, uint256 amount) internal virtual {
    309
    ✓ 4
            require(owner != address(0), "ERC20: approve from the zero address");
    310
    ✓ 4
            require(spender != address(0), "ERC20: approve to the zero address");
    311
    
                                                    
                                                
    312
    ✓ 4
            _allowances[owner][spender] = amount;
    313
    ✓ 4
            emit Approval(owner, spender, amount);
    314
        }
    315
    
                                                    
                                                
    316
        /**
    317
         * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
    318
         *
    319
         * Does not update the allowance amount in case of infinite allowance.
    320
         * Revert if not enough allowance is available.
    321
         *
    322
         * Might emit an {Approval} event.
    323
         */
    324
        function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
    325
    ✓ 10.9M
            uint256 currentAllowance = allowance(owner, spender);
    326
    ✓ 10.9M
            if (currentAllowance != type(uint256).max) {
    327
                require(currentAllowance >= amount, "ERC20: insufficient allowance");
    328
                unchecked {
    329
                    _approve(owner, spender, currentAllowance - amount);
    330
                }
    331
            }
    332
        }
    333
    
                                                    
                                                
    334
        /**
    335
         * @dev Hook that is called before any transfer of tokens. This includes
    336
         * minting and burning.
    337
         *
    338
         * Calling conditions:
    339
         *
    340
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
    341
         * will be transferred to `to`.
    342
         * - when `from` is zero, `amount` tokens will be minted for `to`.
    343
         * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
    344
         * - `from` and `to` are never both zero.
    345
         *
    346
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
    347
         */
    348
    ✓ 18.2M
        function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
    349
    
                                                    
                                                
    350
        /**
    351
         * @dev Hook that is called after any transfer of tokens. This includes
    352
         * minting and burning.
    353
         *
    354
         * Calling conditions:
    355
         *
    356
         * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
    357
         * has been transferred to `to`.
    358
         * - when `from` is zero, `amount` tokens have been minted for `to`.
    359
         * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
    360
         * - `from` and `to` are never both zero.
    361
         *
    362
         * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
    363
         */
    364
    ✓ 15.9M
        function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
    365
    }
    366
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev Interface of the ERC20 standard as defined in the EIP.
    8
     */
    9
    interface IERC20 {
    10
        /**
    11
         * @dev Emitted when `value` tokens are moved from one account (`from`) to
    12
         * another (`to`).
    13
         *
    14
         * Note that `value` may be zero.
    15
         */
    16
        event Transfer(address indexed from, address indexed to, uint256 value);
    17
    
                                                    
                                                
    18
        /**
    19
         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
    20
         * a call to {approve}. `value` is the new allowance.
    21
         */
    22
        event Approval(address indexed owner, address indexed spender, uint256 value);
    23
    
                                                    
                                                
    24
        /**
    25
         * @dev Returns the amount of tokens in existence.
    26
         */
    27
        function totalSupply() external view returns (uint256);
    28
    
                                                    
                                                
    29
        /**
    30
         * @dev Returns the amount of tokens owned by `account`.
    31
         */
    32
        function balanceOf(address account) external view returns (uint256);
    33
    
                                                    
                                                
    34
        /**
    35
         * @dev Moves `amount` tokens from the caller's account to `to`.
    36
         *
    37
         * Returns a boolean value indicating whether the operation succeeded.
    38
         *
    39
         * Emits a {Transfer} event.
    40
         */
    41
        function transfer(address to, uint256 amount) external returns (bool);
    42
    
                                                    
                                                
    43
        /**
    44
         * @dev Returns the remaining number of tokens that `spender` will be
    45
         * allowed to spend on behalf of `owner` through {transferFrom}. This is
    46
         * zero by default.
    47
         *
    48
         * This value changes when {approve} or {transferFrom} are called.
    49
         */
    50
        function allowance(address owner, address spender) external view returns (uint256);
    51
    
                                                    
                                                
    52
        /**
    53
         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
    54
         *
    55
         * Returns a boolean value indicating whether the operation succeeded.
    56
         *
    57
         * IMPORTANT: Beware that changing an allowance with this method brings the risk
    58
         * that someone may use both the old and the new allowance by unfortunate
    59
         * transaction ordering. One possible solution to mitigate this race
    60
         * condition is to first reduce the spender's allowance to 0 and set the
    61
         * desired value afterwards:
    62
         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    63
         *
    64
         * Emits an {Approval} event.
    65
         */
    66
        function approve(address spender, uint256 amount) external returns (bool);
    67
    
                                                    
                                                
    68
        /**
    69
         * @dev Moves `amount` tokens from `from` to `to` using the
    70
         * allowance mechanism. `amount` is then deducted from the caller's
    71
         * allowance.
    72
         *
    73
         * Returns a boolean value indicating whether the operation succeeded.
    74
         *
    75
         * Emits a {Transfer} event.
    76
         */
    77
        function transferFrom(address from, address to, uint256 amount) external returns (bool);
    78
    }
    79
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol
    Lines covered: 0 / 3 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    import "../ERC20.sol";
    7
    import "../../../utils/Context.sol";
    8
    
                                                    
                                                
    9
    /**
    10
     * @dev Extension of {ERC20} that allows token holders to destroy both their own
    11
     * tokens and those that they have an allowance for, in a way that can be
    12
     * recognized off-chain (via event analysis).
    13
     */
    14
    abstract contract ERC20Burnable is Context, ERC20 {
    15
        /**
    16
         * @dev Destroys `amount` tokens from the caller.
    17
         *
    18
         * See {ERC20-_burn}.
    19
         */
    20
        function burn(uint256 amount) public virtual {
    21
            _burn(_msgSender(), amount);
    22
        }
    23
    
                                                    
                                                
    24
        /**
    25
         * @dev Destroys `amount` tokens from `account`, deducting from the caller's
    26
         * allowance.
    27
         *
    28
         * See {ERC20-_burn} and {ERC20-allowance}.
    29
         *
    30
         * Requirements:
    31
         *
    32
         * - the caller must have allowance for ``accounts``'s tokens of at least
    33
         * `amount`.
    34
         */
    35
        function burnFrom(address account, uint256 amount) public virtual {
    36
            _spendAllowance(account, _msgSender(), amount);
    37
            _burn(account, amount);
    38
        }
    39
    }
    40
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    import "../IERC20.sol";
    7
    
                                                    
                                                
    8
    /**
    9
     * @dev Interface for the optional metadata functions from the ERC20 standard.
    10
     *
    11
     * _Available since v4.1._
    12
     */
    13
    interface IERC20Metadata is IERC20 {
    14
        /**
    15
         * @dev Returns the name of the token.
    16
         */
    17
        function name() external view returns (string memory);
    18
    
                                                    
                                                
    19
        /**
    20
         * @dev Returns the symbol of the token.
    21
         */
    22
        function symbol() external view returns (string memory);
    23
    
                                                    
                                                
    24
        /**
    25
         * @dev Returns the decimals places of the token.
    26
         */
    27
        function decimals() external view returns (uint8);
    28
    }
    29
    
                                                    
                                                
    0.0% lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Permit.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
    8
     * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
    9
     *
    10
     * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
    11
     * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
    12
     * need to send a transaction, and thus is not required to hold Ether at all.
    13
     */
    14
    interface IERC20Permit {
    15
        /**
    16
         * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
    17
         * given ``owner``'s signed approval.
    18
         *
    19
         * IMPORTANT: The same issues {IERC20-approve} has related to transaction
    20
         * ordering also apply here.
    21
         *
    22
         * Emits an {Approval} event.
    23
         *
    24
         * Requirements:
    25
         *
    26
         * - `spender` cannot be the zero address.
    27
         * - `deadline` must be a timestamp in the future.
    28
         * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
    29
         * over the EIP712-formatted function arguments.
    30
         * - the signature must use ``owner``'s current nonce (see {nonces}).
    31
         *
    32
         * For more information on the signature format, see the
    33
         * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
    34
         * section].
    35
         */
    36
        function permit(
    37
            address owner,
    38
            address spender,
    39
            uint256 value,
    40
            uint256 deadline,
    41
            uint8 v,
    42
            bytes32 r,
    43
            bytes32 s
    44
        ) external;
    45
    
                                                    
                                                
    46
        /**
    47
         * @dev Returns the current nonce for `owner`. This value must be
    48
         * included whenever a signature is generated for {permit}.
    49
         *
    50
         * Every successful call to {permit} increases ``owner``'s nonce by one. This
    51
         * prevents a signature from being used multiple times.
    52
         */
    53
        function nonces(address owner) external view returns (uint256);
    54
    
                                                    
                                                
    55
        /**
    56
         * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
    57
         */
    58
        // solhint-disable-next-line func-name-mixedcase
    59
        function DOMAIN_SEPARATOR() external view returns (bytes32);
    60
    }
    61
    
                                                    
                                                
    100.0% lib/openzeppelin-contracts/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol
    Lines covered: 3 / 3 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/presets/ERC20PresetFixedSupply.sol)
    3
    pragma solidity ^0.8.0;
    4
    
                                                    
                                                
    5
    import "../extensions/ERC20Burnable.sol";
    6
    
                                                    
                                                
    7
    /**
    8
     * @dev {ERC20} token, including:
    9
     *
    10
     *  - Preminted initial supply
    11
     *  - Ability for holders to burn (destroy) their tokens
    12
     *  - No access control mechanism (for minting/pausing) and hence no governance
    13
     *
    14
     * This contract uses {ERC20Burnable} to include burn capabilities - head to
    15
     * its documentation for details.
    16
     *
    17
     * _Available since v3.4._
    18
     *
    19
     * _Deprecated in favor of https://wizard.openzeppelin.com/[Contracts Wizard]._
    20
     */
    21
    ✓ 59.9M
    contract ERC20PresetFixedSupply is ERC20Burnable {
    22
        /**
    23
         * @dev Mints `initialSupply` amount of token and transfers them to `owner`.
    24
         *
    25
         * See {ERC20-constructor}.
    26
         */
    27
    ✓ 1
        constructor(string memory name, string memory symbol, uint256 initialSupply, address owner) ERC20(name, symbol) {
    28
    ✓ 1
            _mint(owner, initialSupply);
    29
        }
    30
    }
    31
    
                                                    
                                                
    100.0% lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
    Lines covered: 5 / 5 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    import "../IERC20.sol";
    7
    import "../extensions/IERC20Permit.sol";
    8
    import "../../../utils/Address.sol";
    9
    
                                                    
                                                
    10
    /**
    11
     * @title SafeERC20
    12
     * @dev Wrappers around ERC20 operations that throw on failure (when the token
    13
     * contract returns false). Tokens that return no value (and instead revert or
    14
     * throw on failure) are also supported, non-reverting calls are assumed to be
    15
     * successful.
    16
     * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
    17
     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
    18
     */
    19
    ✓ 6
    library SafeERC20 {
    20
        using Address for address;
    21
    
                                                    
                                                
    22
        /**
    23
         * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
    24
         * non-reverting calls are assumed to be successful.
    25
         */
    26
        function safeTransfer(IERC20 token, address to, uint256 value) internal {
    27
    ✓ 7.3M
            _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    28
        }
    29
    
                                                    
                                                
    30
        /**
    31
         * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
    32
         * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
    33
         */
    34
        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
    35
    ✓ 10.9M
            _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    36
        }
    37
    
                                                    
                                                
    38
        /**
    39
         * @dev Deprecated. This function has issues similar to the ones found in
    40
         * {IERC20-approve}, and its usage is discouraged.
    41
         *
    42
         * Whenever possible, use {safeIncreaseAllowance} and
    43
         * {safeDecreaseAllowance} instead.
    44
         */
    45
        function safeApprove(IERC20 token, address spender, uint256 value) internal {
    46
            // safeApprove should only be called when setting an initial allowance,
    47
            // or when resetting it to zero. To increase and decrease it, use
    48
            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
    49
            require(
    50
                (value == 0) || (token.allowance(address(this), spender) == 0),
    51
                "SafeERC20: approve from non-zero to non-zero allowance"
    52
            );
    53
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    54
        }
    55
    
                                                    
                                                
    56
        /**
    57
         * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
    58
         * non-reverting calls are assumed to be successful.
    59
         */
    60
        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    61
            uint256 oldAllowance = token.allowance(address(this), spender);
    62
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    63
        }
    64
    
                                                    
                                                
    65
        /**
    66
         * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
    67
         * non-reverting calls are assumed to be successful.
    68
         */
    69
        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
    70
            unchecked {
    71
                uint256 oldAllowance = token.allowance(address(this), spender);
    72
                require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
    73
                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
    74
            }
    75
        }
    76
    
                                                    
                                                
    77
        /**
    78
         * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
    79
         * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
    80
         * 0 before setting it to a non-zero value.
    81
         */
    82
        function forceApprove(IERC20 token, address spender, uint256 value) internal {
    83
            bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
    84
    
                                                    
                                                
    85
            if (!_callOptionalReturnBool(token, approvalCall)) {
    86
                _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
    87
                _callOptionalReturn(token, approvalCall);
    88
            }
    89
        }
    90
    
                                                    
                                                
    91
        /**
    92
         * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
    93
         * Revert on invalid signature.
    94
         */
    95
        function safePermit(
    96
            IERC20Permit token,
    97
            address owner,
    98
            address spender,
    99
            uint256 value,
    100
            uint256 deadline,
    101
            uint8 v,
    102
            bytes32 r,
    103
            bytes32 s
    104
        ) internal {
    105
            uint256 nonceBefore = token.nonces(owner);
    106
            token.permit(owner, spender, value, deadline, v, r, s);
    107
            uint256 nonceAfter = token.nonces(owner);
    108
            require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    109
        }
    110
    
                                                    
                                                
    111
        /**
    112
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    113
         * on the return value: the return value is optional (but if data is returned, it must not be false).
    114
         * @param token The token targeted by the call.
    115
         * @param data The call data (encoded using abi.encode or one of its variants).
    116
         */
    117
        function _callOptionalReturn(IERC20 token, bytes memory data) private {
    118
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
    119
            // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
    120
            // the target address contains contract code and also asserts for success in the low-level call.
    121
    
                                                    
                                                
    122
    ✓ 10.9M
            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
    123
    ✓ 8.6M
            require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    124
        }
    125
    
                                                    
                                                
    126
        /**
    127
         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
    128
         * on the return value: the return value is optional (but if data is returned, it must not be false).
    129
         * @param token The token targeted by the call.
    130
         * @param data The call data (encoded using abi.encode or one of its variants).
    131
         *
    132
         * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
    133
         */
    134
        function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
    135
            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
    136
            // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
    137
            // and not revert is the subcall reverts.
    138
    
                                                    
                                                
    139
            (bool success, bytes memory returndata) = address(token).call(data);
    140
            return
    141
                success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    142
        }
    143
    }
    144
    
                                                    
                                                
    62.5% lib/openzeppelin-contracts/contracts/utils/Address.sol
    Lines covered: 15 / 24 (62.5%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.1;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev Collection of functions related to the address type
    8
     */
    9
    ✓ 6
    library Address {
    10
        /**
    11
         * @dev Returns true if `account` is a contract.
    12
         *
    13
         * [IMPORTANT]
    14
         * ====
    15
         * It is unsafe to assume that an address for which this function returns
    16
         * false is an externally-owned account (EOA) and not a contract.
    17
         *
    18
         * Among others, `isContract` will return false for the following
    19
         * types of addresses:
    20
         *
    21
         *  - an externally-owned account
    22
         *  - a contract in construction
    23
         *  - an address where a contract will be created
    24
         *  - an address where a contract lived, but was destroyed
    25
         *
    26
         * Furthermore, `isContract` will also return true if the target contract within
    27
         * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
    28
         * which only has an effect at the end of a transaction.
    29
         * ====
    30
         *
    31
         * [IMPORTANT]
    32
         * ====
    33
         * You shouldn't rely on `isContract` to protect against flash loan attacks!
    34
         *
    35
         * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
    36
         * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
    37
         * constructor.
    38
         * ====
    39
         */
    40
        function isContract(address account) internal view returns (bool) {
    41
            // This method relies on extcodesize/address.code.length, which returns 0
    42
            // for contracts in construction, since the code is only stored at the end
    43
            // of the constructor execution.
    44
    
                                                    
                                                
    45
            return account.code.length > 0;
    46
        }
    47
    
                                                    
                                                
    48
        /**
    49
         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
    50
         * `recipient`, forwarding all available gas and reverting on errors.
    51
         *
    52
         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
    53
         * of certain opcodes, possibly making contracts go over the 2300 gas limit
    54
         * imposed by `transfer`, making them unable to receive funds via
    55
         * `transfer`. {sendValue} removes this limitation.
    56
         *
    57
         * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
    58
         *
    59
         * IMPORTANT: because control is transferred to `recipient`, care must be
    60
         * taken to not create reentrancy vulnerabilities. Consider using
    61
         * {ReentrancyGuard} or the
    62
         * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
    63
         */
    64
        function sendValue(address payable recipient, uint256 amount) internal {
    65
            require(address(this).balance >= amount, "Address: insufficient balance");
    66
    
                                                    
                                                
    67
            (bool success, ) = recipient.call{value: amount}("");
    68
            require(success, "Address: unable to send value, recipient may have reverted");
    69
        }
    70
    
                                                    
                                                
    71
        /**
    72
         * @dev Performs a Solidity function call using a low level `call`. A
    73
         * plain `call` is an unsafe replacement for a function call: use this
    74
         * function instead.
    75
         *
    76
         * If `target` reverts with a revert reason, it is bubbled up by this
    77
         * function (like regular Solidity function calls).
    78
         *
    79
         * Returns the raw returned data. To convert to the expected return value,
    80
         * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
    81
         *
    82
         * Requirements:
    83
         *
    84
         * - `target` must be a contract.
    85
         * - calling `target` with `data` must not revert.
    86
         *
    87
         * _Available since v3.1._
    88
         */
    89
        function functionCall(address target, bytes memory data) internal returns (bytes memory) {
    90
            return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    91
        }
    92
    
                                                    
                                                
    93
        /**
    94
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
    95
         * `errorMessage` as a fallback revert reason when `target` reverts.
    96
         *
    97
         * _Available since v3.1._
    98
         */
    99
        function functionCall(
    100
            address target,
    101
            bytes memory data,
    102
            string memory errorMessage
    103
    ✓ 10.9M
        ) internal returns (bytes memory) {
    104
    ✓ 10.9M
            return functionCallWithValue(target, data, 0, errorMessage);
    105
        }
    106
    
                                                    
                                                
    107
        /**
    108
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    109
         * but also transferring `value` wei to `target`.
    110
         *
    111
         * Requirements:
    112
         *
    113
         * - the calling contract must have an ETH balance of at least `value`.
    114
         * - the called Solidity function must be `payable`.
    115
         *
    116
         * _Available since v3.1._
    117
         */
    118
        function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
    119
            return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    120
        }
    121
    
                                                    
                                                
    122
        /**
    123
         * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
    124
         * with `errorMessage` as a fallback revert reason when `target` reverts.
    125
         *
    126
         * _Available since v3.1._
    127
         */
    128
        function functionCallWithValue(
    129
            address target,
    130
            bytes memory data,
    131
            uint256 value,
    132
            string memory errorMessage
    133
    ✓ 10.9M
        ) internal returns (bytes memory) {
    134
    ✓ 10.9M
            require(address(this).balance >= value, "Address: insufficient balance for call");
    135
    ✓ 10.9M
            (bool success, bytes memory returndata) = target.call{value: value}(data);
    136
    ✓ 10.9M
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    137
        }
    138
    
                                                    
                                                
    139
        /**
    140
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    141
         * but performing a static call.
    142
         *
    143
         * _Available since v3.3._
    144
         */
    145
        function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
    146
            return functionStaticCall(target, data, "Address: low-level static call failed");
    147
        }
    148
    
                                                    
                                                
    149
        /**
    150
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
    151
         * but performing a static call.
    152
         *
    153
         * _Available since v3.3._
    154
         */
    155
        function functionStaticCall(
    156
            address target,
    157
            bytes memory data,
    158
            string memory errorMessage
    159
        ) internal view returns (bytes memory) {
    160
            (bool success, bytes memory returndata) = target.staticcall(data);
    161
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    162
        }
    163
    
                                                    
                                                
    164
        /**
    165
         * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
    166
         * but performing a delegate call.
    167
         *
    168
         * _Available since v3.4._
    169
         */
    170
        function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
    171
            return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    172
        }
    173
    
                                                    
                                                
    174
        /**
    175
         * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
    176
         * but performing a delegate call.
    177
         *
    178
         * _Available since v3.4._
    179
         */
    180
        function functionDelegateCall(
    181
            address target,
    182
            bytes memory data,
    183
            string memory errorMessage
    184
        ) internal returns (bytes memory) {
    185
            (bool success, bytes memory returndata) = target.delegatecall(data);
    186
            return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    187
        }
    188
    
                                                    
                                                
    189
        /**
    190
         * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
    191
         * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
    192
         *
    193
         * _Available since v4.8._
    194
         */
    195
        function verifyCallResultFromTarget(
    196
            address target,
    197
            bool success,
    198
            bytes memory returndata,
    199
            string memory errorMessage
    200
    ✓ 10.9M
        ) internal view returns (bytes memory) {
    201
    ✓ 10.9M
            if (success) {
    202
    ✓ 8.6M
                if (returndata.length == 0) {
    203
                    // only check isContract if the call was successful and the return data is empty
    204
                    // otherwise we already know that it was a contract
    205
                    require(isContract(target), "Address: call to non-contract");
    206
                }
    207
    ✓ 8.6M
                return returndata;
    208
            } else {
    209
    ✓ 2.3M
                _revert(returndata, errorMessage);
    210
            }
    211
        }
    212
    
                                                    
                                                
    213
        /**
    214
         * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
    215
         * revert reason or using the provided one.
    216
         *
    217
         * _Available since v4.3._
    218
         */
    219
        function verifyCallResult(
    220
            bool success,
    221
            bytes memory returndata,
    222
            string memory errorMessage
    223
        ) internal pure returns (bytes memory) {
    224
            if (success) {
    225
                return returndata;
    226
            } else {
    227
                _revert(returndata, errorMessage);
    228
            }
    229
        }
    230
    
                                                    
                                                
    231
        function _revert(bytes memory returndata, string memory errorMessage) private pure {
    232
            // Look for revert reason and bubble it up if present
    233
    ✓ 2.3M
            if (returndata.length > 0) {
    234
                // The easiest way to bubble the revert reason is using memory via assembly
    235
                /// @solidity memory-safe-assembly
    236
                assembly {
    237
    ✓ 2.3M
                    let returndata_size := mload(returndata)
    238
    ✓ 2.3M
                    revert(add(32, returndata), returndata_size)
    239
                }
    240
            } else {
    241
                revert(errorMessage);
    242
            }
    243
        }
    244
    }
    245
    
                                                    
                                                
    100.0% lib/openzeppelin-contracts/contracts/utils/Context.sol
    Lines covered: 2 / 2 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
    3
    
                                                    
                                                
    4
    pragma solidity ^0.8.0;
    5
    
                                                    
                                                
    6
    /**
    7
     * @dev Provides information about the current execution context, including the
    8
     * sender of the transaction and its data. While these are generally available
    9
     * via msg.sender and msg.data, they should not be accessed in such a direct
    10
     * manner, since when dealing with meta-transactions the account sending and
    11
     * paying for execution may not be the actual sender (as far as an application
    12
     * is concerned).
    13
     *
    14
     * This contract is only required for intermediate, library-like contracts.
    15
     */
    16
    abstract contract Context {
    17
    ✓ 71.2M
        function _msgSender() internal view virtual returns (address) {
    18
    ✓ 71.2M
            return msg.sender;
    19
        }
    20
    
                                                    
                                                
    21
        function _msgData() internal view virtual returns (bytes calldata) {
    22
            return msg.data;
    23
        }
    24
    }
    25
    
                                                    
                                                
    20.0% lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
    Lines covered: 1 / 5 (20.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/StorageSlot.js.
    4
    
                                                    
                                                
    5
    pragma solidity ^0.8.0;
    6
    
                                                    
                                                
    7
    /**
    8
     * @dev Library for reading and writing primitive types to specific storage slots.
    9
     *
    10
     * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
    11
     * This library helps with reading and writing to such slots without the need for inline assembly.
    12
     *
    13
     * The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
    14
     *
    15
     * Example usage to set ERC1967 implementation slot:
    16
     * ```solidity
    17
     * contract ERC1967 {
    18
     *     bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    19
     *
    20
     *     function _getImplementation() internal view returns (address) {
    21
     *         return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
    22
     *     }
    23
     *
    24
     *     function _setImplementation(address newImplementation) internal {
    25
     *         require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
    26
     *         StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
    27
     *     }
    28
     * }
    29
     * ```
    30
     *
    31
     * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._
    32
     * _Available since v4.9 for `string`, `bytes`._
    33
     */
    34
    ✓ 6
    library StorageSlot {
    35
        struct AddressSlot {
    36
            address value;
    37
        }
    38
    
                                                    
                                                
    39
        struct BooleanSlot {
    40
            bool value;
    41
        }
    42
    
                                                    
                                                
    43
        struct Bytes32Slot {
    44
            bytes32 value;
    45
        }
    46
    
                                                    
                                                
    47
        struct Uint256Slot {
    48
            uint256 value;
    49
        }
    50
    
                                                    
                                                
    51
        struct StringSlot {
    52
            string value;
    53
        }
    54
    
                                                    
                                                
    55
        struct BytesSlot {
    56
            bytes value;
    57
        }
    58
    
                                                    
                                                
    59
        /**
    60
         * @dev Returns an `AddressSlot` with member `value` located at `slot`.
    61
         */
    62
        function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
    63
            /// @solidity memory-safe-assembly
    64
            assembly {
    65
                r.slot := slot
    66
            }
    67
        }
    68
    
                                                    
                                                
    69
        /**
    70
         * @dev Returns an `BooleanSlot` with member `value` located at `slot`.
    71
         */
    72
        function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
    73
            /// @solidity memory-safe-assembly
    74
            assembly {
    75
                r.slot := slot
    76
            }
    77
        }
    78
    
                                                    
                                                
    79
        /**
    80
         * @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
    81
         */
    82
        function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
    83
            /// @solidity memory-safe-assembly
    84
            assembly {
    85
                r.slot := slot
    86
            }
    87
        }
    88
    
                                                    
                                                
    89
        /**
    90
         * @dev Returns an `Uint256Slot` with member `value` located at `slot`.
    91
         */
    92
        function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
    93
            /// @solidity memory-safe-assembly
    94
            assembly {
    95
                r.slot := slot
    96
            }
    97
        }
    98
    
                                                    
                                                
    99
        /**
    100
         * @dev Returns an `StringSlot` with member `value` located at `slot`.
    101
         */
    102
        function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) {
    103
            /// @solidity memory-safe-assembly
    104
            assembly {
    105
                r.slot := slot
    106
            }
    107
        }
    108
    
                                                    
                                                
    109
        /**
    110
         * @dev Returns an `StringSlot` representation of the string storage pointer `store`.
    111
         */
    112
        function getStringSlot(string storage store) internal pure returns (StringSlot storage r) {
    113
            /// @solidity memory-safe-assembly
    114
            assembly {
    115
                r.slot := store.slot
    116
            }
    117
        }
    118
    
                                                    
                                                
    119
        /**
    120
         * @dev Returns an `BytesSlot` with member `value` located at `slot`.
    121
         */
    122
        function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) {
    123
            /// @solidity memory-safe-assembly
    124
            assembly {
    125
                r.slot := slot
    126
            }
    127
        }
    128
    
                                                    
                                                
    129
        /**
    130
         * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`.
    131
         */
    132
        function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) {
    133
            /// @solidity memory-safe-assembly
    134
            assembly {
    135
                r.slot := store.slot
    136
            }
    137
        }
    138
    }
    139
    
                                                    
                                                
    2.9% lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
    Lines covered: 1 / 35 (2.9%)
    1
    // SPDX-License-Identifier: MIT
    2
    // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
    3
    // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
    4
    
                                                    
                                                
    5
    pragma solidity ^0.8.0;
    6
    
                                                    
                                                
    7
    /**
    8
     * @dev Library for managing
    9
     * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
    10
     * types.
    11
     *
    12
     * Sets have the following properties:
    13
     *
    14
     * - Elements are added, removed, and checked for existence in constant time
    15
     * (O(1)).
    16
     * - Elements are enumerated in O(n). No guarantees are made on the ordering.
    17
     *
    18
     * ```solidity
    19
     * contract Example {
    20
     *     // Add the library methods
    21
     *     using EnumerableSet for EnumerableSet.AddressSet;
    22
     *
    23
     *     // Declare a set state variable
    24
     *     EnumerableSet.AddressSet private mySet;
    25
     * }
    26
     * ```
    27
     *
    28
     * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
    29
     * and `uint256` (`UintSet`) are supported.
    30
     *
    31
     * [WARNING]
    32
     * ====
    33
     * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
    34
     * unusable.
    35
     * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
    36
     *
    37
     * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
    38
     * array of EnumerableSet.
    39
     * ====
    40
     */
    41
    ✓ 6
    library EnumerableSet {
    42
        // To implement this library for multiple types with as little code
    43
        // repetition as possible, we write it in terms of a generic Set type with
    44
        // bytes32 values.
    45
        // The Set implementation uses private functions, and user-facing
    46
        // implementations (such as AddressSet) are just wrappers around the
    47
        // underlying Set.
    48
        // This means that we can only create new EnumerableSets for types that fit
    49
        // in bytes32.
    50
    
                                                    
                                                
    51
        struct Set {
    52
            // Storage of set values
    53
            bytes32[] _values;
    54
            // Position of the value in the `values` array, plus 1 because index 0
    55
            // means a value is not in the set.
    56
            mapping(bytes32 => uint256) _indexes;
    57
        }
    58
    
                                                    
                                                
    59
        /**
    60
         * @dev Add a value to a set. O(1).
    61
         *
    62
         * Returns true if the value was added to the set, that is if it was not
    63
         * already present.
    64
         */
    65
        function _add(Set storage set, bytes32 value) private returns (bool) {
    66
            if (!_contains(set, value)) {
    67
                set._values.push(value);
    68
                // The value is stored at length-1, but we add 1 to all indexes
    69
                // and use 0 as a sentinel value
    70
                set._indexes[value] = set._values.length;
    71
                return true;
    72
            } else {
    73
                return false;
    74
            }
    75
        }
    76
    
                                                    
                                                
    77
        /**
    78
         * @dev Removes a value from a set. O(1).
    79
         *
    80
         * Returns true if the value was removed from the set, that is if it was
    81
         * present.
    82
         */
    83
        function _remove(Set storage set, bytes32 value) private returns (bool) {
    84
            // We read and store the value's index to prevent multiple reads from the same storage slot
    85
            uint256 valueIndex = set._indexes[value];
    86
    
                                                    
                                                
    87
            if (valueIndex != 0) {
    88
                // Equivalent to contains(set, value)
    89
                // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
    90
                // the array, and then remove the last element (sometimes called as 'swap and pop').
    91
                // This modifies the order of the array, as noted in {at}.
    92
    
                                                    
                                                
    93
                uint256 toDeleteIndex = valueIndex - 1;
    94
                uint256 lastIndex = set._values.length - 1;
    95
    
                                                    
                                                
    96
                if (lastIndex != toDeleteIndex) {
    97
                    bytes32 lastValue = set._values[lastIndex];
    98
    
                                                    
                                                
    99
                    // Move the last value to the index where the value to delete is
    100
                    set._values[toDeleteIndex] = lastValue;
    101
                    // Update the index for the moved value
    102
                    set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
    103
                }
    104
    
                                                    
                                                
    105
                // Delete the slot where the moved value was stored
    106
                set._values.pop();
    107
    
                                                    
                                                
    108
                // Delete the index for the deleted slot
    109
                delete set._indexes[value];
    110
    
                                                    
                                                
    111
                return true;
    112
            } else {
    113
                return false;
    114
            }
    115
        }
    116
    
                                                    
                                                
    117
        /**
    118
         * @dev Returns true if the value is in the set. O(1).
    119
         */
    120
        function _contains(Set storage set, bytes32 value) private view returns (bool) {
    121
            return set._indexes[value] != 0;
    122
        }
    123
    
                                                    
                                                
    124
        /**
    125
         * @dev Returns the number of values on the set. O(1).
    126
         */
    127
        function _length(Set storage set) private view returns (uint256) {
    128
            return set._values.length;
    129
        }
    130
    
                                                    
                                                
    131
        /**
    132
         * @dev Returns the value stored at position `index` in the set. O(1).
    133
         *
    134
         * Note that there are no guarantees on the ordering of values inside the
    135
         * array, and it may change when more values are added or removed.
    136
         *
    137
         * Requirements:
    138
         *
    139
         * - `index` must be strictly less than {length}.
    140
         */
    141
        function _at(Set storage set, uint256 index) private view returns (bytes32) {
    142
            return set._values[index];
    143
        }
    144
    
                                                    
                                                
    145
        /**
    146
         * @dev Return the entire set in an array
    147
         *
    148
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    149
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    150
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    151
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    152
         */
    153
        function _values(Set storage set) private view returns (bytes32[] memory) {
    154
            return set._values;
    155
        }
    156
    
                                                    
                                                
    157
        // Bytes32Set
    158
    
                                                    
                                                
    159
        struct Bytes32Set {
    160
            Set _inner;
    161
        }
    162
    
                                                    
                                                
    163
        /**
    164
         * @dev Add a value to a set. O(1).
    165
         *
    166
         * Returns true if the value was added to the set, that is if it was not
    167
         * already present.
    168
         */
    169
        function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    170
            return _add(set._inner, value);
    171
        }
    172
    
                                                    
                                                
    173
        /**
    174
         * @dev Removes a value from a set. O(1).
    175
         *
    176
         * Returns true if the value was removed from the set, that is if it was
    177
         * present.
    178
         */
    179
        function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
    180
            return _remove(set._inner, value);
    181
        }
    182
    
                                                    
                                                
    183
        /**
    184
         * @dev Returns true if the value is in the set. O(1).
    185
         */
    186
        function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
    187
            return _contains(set._inner, value);
    188
        }
    189
    
                                                    
                                                
    190
        /**
    191
         * @dev Returns the number of values in the set. O(1).
    192
         */
    193
        function length(Bytes32Set storage set) internal view returns (uint256) {
    194
            return _length(set._inner);
    195
        }
    196
    
                                                    
                                                
    197
        /**
    198
         * @dev Returns the value stored at position `index` in the set. O(1).
    199
         *
    200
         * Note that there are no guarantees on the ordering of values inside the
    201
         * array, and it may change when more values are added or removed.
    202
         *
    203
         * Requirements:
    204
         *
    205
         * - `index` must be strictly less than {length}.
    206
         */
    207
        function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
    208
            return _at(set._inner, index);
    209
        }
    210
    
                                                    
                                                
    211
        /**
    212
         * @dev Return the entire set in an array
    213
         *
    214
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    215
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    216
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    217
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    218
         */
    219
        function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
    220
            bytes32[] memory store = _values(set._inner);
    221
            bytes32[] memory result;
    222
    
                                                    
                                                
    223
            /// @solidity memory-safe-assembly
    224
            assembly {
    225
                result := store
    226
            }
    227
    
                                                    
                                                
    228
            return result;
    229
        }
    230
    
                                                    
                                                
    231
        // AddressSet
    232
    
                                                    
                                                
    233
        struct AddressSet {
    234
            Set _inner;
    235
        }
    236
    
                                                    
                                                
    237
        /**
    238
         * @dev Add a value to a set. O(1).
    239
         *
    240
         * Returns true if the value was added to the set, that is if it was not
    241
         * already present.
    242
         */
    243
        function add(AddressSet storage set, address value) internal returns (bool) {
    244
            return _add(set._inner, bytes32(uint256(uint160(value))));
    245
        }
    246
    
                                                    
                                                
    247
        /**
    248
         * @dev Removes a value from a set. O(1).
    249
         *
    250
         * Returns true if the value was removed from the set, that is if it was
    251
         * present.
    252
         */
    253
        function remove(AddressSet storage set, address value) internal returns (bool) {
    254
            return _remove(set._inner, bytes32(uint256(uint160(value))));
    255
        }
    256
    
                                                    
                                                
    257
        /**
    258
         * @dev Returns true if the value is in the set. O(1).
    259
         */
    260
        function contains(AddressSet storage set, address value) internal view returns (bool) {
    261
            return _contains(set._inner, bytes32(uint256(uint160(value))));
    262
        }
    263
    
                                                    
                                                
    264
        /**
    265
         * @dev Returns the number of values in the set. O(1).
    266
         */
    267
        function length(AddressSet storage set) internal view returns (uint256) {
    268
            return _length(set._inner);
    269
        }
    270
    
                                                    
                                                
    271
        /**
    272
         * @dev Returns the value stored at position `index` in the set. O(1).
    273
         *
    274
         * Note that there are no guarantees on the ordering of values inside the
    275
         * array, and it may change when more values are added or removed.
    276
         *
    277
         * Requirements:
    278
         *
    279
         * - `index` must be strictly less than {length}.
    280
         */
    281
        function at(AddressSet storage set, uint256 index) internal view returns (address) {
    282
            return address(uint160(uint256(_at(set._inner, index))));
    283
        }
    284
    
                                                    
                                                
    285
        /**
    286
         * @dev Return the entire set in an array
    287
         *
    288
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    289
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    290
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    291
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    292
         */
    293
        function values(AddressSet storage set) internal view returns (address[] memory) {
    294
            bytes32[] memory store = _values(set._inner);
    295
            address[] memory result;
    296
    
                                                    
                                                
    297
            /// @solidity memory-safe-assembly
    298
            assembly {
    299
                result := store
    300
            }
    301
    
                                                    
                                                
    302
            return result;
    303
        }
    304
    
                                                    
                                                
    305
        // UintSet
    306
    
                                                    
                                                
    307
        struct UintSet {
    308
            Set _inner;
    309
        }
    310
    
                                                    
                                                
    311
        /**
    312
         * @dev Add a value to a set. O(1).
    313
         *
    314
         * Returns true if the value was added to the set, that is if it was not
    315
         * already present.
    316
         */
    317
        function add(UintSet storage set, uint256 value) internal returns (bool) {
    318
            return _add(set._inner, bytes32(value));
    319
        }
    320
    
                                                    
                                                
    321
        /**
    322
         * @dev Removes a value from a set. O(1).
    323
         *
    324
         * Returns true if the value was removed from the set, that is if it was
    325
         * present.
    326
         */
    327
        function remove(UintSet storage set, uint256 value) internal returns (bool) {
    328
            return _remove(set._inner, bytes32(value));
    329
        }
    330
    
                                                    
                                                
    331
        /**
    332
         * @dev Returns true if the value is in the set. O(1).
    333
         */
    334
        function contains(UintSet storage set, uint256 value) internal view returns (bool) {
    335
            return _contains(set._inner, bytes32(value));
    336
        }
    337
    
                                                    
                                                
    338
        /**
    339
         * @dev Returns the number of values in the set. O(1).
    340
         */
    341
        function length(UintSet storage set) internal view returns (uint256) {
    342
            return _length(set._inner);
    343
        }
    344
    
                                                    
                                                
    345
        /**
    346
         * @dev Returns the value stored at position `index` in the set. O(1).
    347
         *
    348
         * Note that there are no guarantees on the ordering of values inside the
    349
         * array, and it may change when more values are added or removed.
    350
         *
    351
         * Requirements:
    352
         *
    353
         * - `index` must be strictly less than {length}.
    354
         */
    355
        function at(UintSet storage set, uint256 index) internal view returns (uint256) {
    356
            return uint256(_at(set._inner, index));
    357
        }
    358
    
                                                    
                                                
    359
        /**
    360
         * @dev Return the entire set in an array
    361
         *
    362
         * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
    363
         * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
    364
         * this function has an unbounded cost, and using it as part of a state-changing function may render the function
    365
         * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
    366
         */
    367
        function values(UintSet storage set) internal view returns (uint256[] memory) {
    368
            bytes32[] memory store = _values(set._inner);
    369
            uint256[] memory result;
    370
    
                                                    
                                                
    371
            /// @solidity memory-safe-assembly
    372
            assembly {
    373
                result := store
    374
            }
    375
    
                                                    
                                                
    376
            return result;
    377
        }
    378
    }
    379
    
                                                    
                                                
    85.2% src/AddressDriver.sol
    Lines covered: 23 / 27 (85.2%)
    1
    // SPDX-License-Identifier: GPL-3.0-only
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {AccountMetadata, Drips, StreamReceiver, IERC20, SplitsReceiver} from "./Drips.sol";
    5
    import {Managed} from "./Managed.sol";
    6
    import {DriverTransferUtils} from "./DriverTransferUtils.sol";
    7
    
                                                    
                                                
    8
    /// @notice A Drips driver implementing address-based account identification.
    9
    /// Each address can use `AddressDriver` to control a single account ID derived from that address.
    10
    /// No registration is required, an `AddressDriver`-based account ID
    11
    /// for each address is available upfront.
    12
    contract AddressDriver is DriverTransferUtils, Managed {
    13
        /// @notice The Drips address used by this driver.
    14
        Drips public immutable drips;
    15
        /// @notice The driver ID which this driver uses when calling Drips.
    16
        uint32 public immutable driverId;
    17
    
                                                    
                                                
    18
        /// @param drips_ The Drips contract to use.
    19
        /// @param forwarder The ERC-2771 forwarder to trust. May be the zero address.
    20
        /// @param driverId_ The driver ID to use when calling Drips.
    21
    ✓ 1
        constructor(Drips drips_, address forwarder, uint32 driverId_) DriverTransferUtils(forwarder) {
    22
    ✓ 1
            drips = drips_;
    23
    ✓ 1
            driverId = driverId_;
    24
        }
    25
    
                                                    
                                                
    26
        /// @notice Calculates the account ID for an address.
    27
        /// Every account ID is a 256-bit integer constructed by concatenating:
    28
        /// `driverId (32 bits) | zeros (64 bits) | addr (160 bits)`.
    29
        /// @param addr The address
    30
        /// @return accountId The account ID
    31
    ✓ 64.9M
        function calcAccountId(address addr) public view returns (uint256 accountId) {
    32
            // By assignment we get `accountId` value:
    33
            // `zeros (224 bits) | driverId (32 bits)`
    34
    ✓ 64.9M
            accountId = driverId;
    35
            // By bit shifting we get `accountId` value:
    36
            // `driverId (32 bits) | zeros (224 bits)`
    37
            // By bit masking we get `accountId` value:
    38
            // `driverId (32 bits) | zeros (64 bits) | addr (160 bits)`
    39
    ✓ 64.9M
            accountId = (accountId << 224) | uint160(addr);
    40
        }
    41
    
                                                    
                                                
    42
        /// @notice Calculates the account ID for the message sender
    43
        /// @return accountId The account ID
    44
    ✓ 60.3M
        function _callerAccountId() internal view returns (uint256 accountId) {
    45
    ✓ 60.3M
            return calcAccountId(_msgSender());
    46
        }
    47
    
                                                    
                                                
    48
        /// @notice Collects the account's received already split funds
    49
        /// and transfers them out of the Drips contract.
    50
        /// @param erc20 The used ERC-20 token.
    51
        /// It must preserve amounts, so if some amount of tokens is transferred to
    52
        /// an address, then later the same amount must be transferable from that address.
    53
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    54
        /// or impose any restrictions on holding or transferring tokens are not supported.
    55
        /// If you use such tokens in the protocol, they can get stuck or lost.
    56
        /// @param transferTo The address to send collected funds to
    57
        /// @return amt The collected amount
    58
    ✓ 14.6M
        function collect(IERC20 erc20, address transferTo) public whenNotPaused returns (uint128 amt) {
    59
    ✓ 14.6M
            return _collectAndTransfer(drips, _callerAccountId(), erc20, transferTo);
    60
        }
    61
    
                                                    
                                                
    62
        /// @notice Gives funds from the message sender to the receiver.
    63
        /// The receiver can split and collect them immediately.
    64
        /// Transfers the funds to be given from the message sender's wallet to the Drips contract.
    65
        /// @param receiver The receiver account ID.
    66
        /// @param erc20 The used ERC-20 token.
    67
        /// It must preserve amounts, so if some amount of tokens is transferred to
    68
        /// an address, then later the same amount must be transferable from that address.
    69
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    70
        /// or impose any restrictions on holding or transferring tokens are not supported.
    71
        /// If you use such tokens in the protocol, they can get stuck or lost.
    72
        /// @param amt The given amount
    73
        function give(uint256 receiver, IERC20 erc20, uint128 amt) public whenNotPaused {
    74
    ✓ 2.3M
            _giveAndTransfer(drips, _callerAccountId(), receiver, erc20, amt);
    75
        }
    76
    
                                                    
                                                
    77
        /// @notice Sets the message sender's streams configuration.
    78
        /// Transfers funds between the message sender's wallet and the Drips contract
    79
        /// to fulfil the change of the streams balance.
    80
        /// @param erc20 The used ERC-20 token.
    81
        /// It must preserve amounts, so if some amount of tokens is transferred to
    82
        /// an address, then later the same amount must be transferable from that address.
    83
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    84
        /// or impose any restrictions on holding or transferring tokens are not supported.
    85
        /// If you use such tokens in the protocol, they can get stuck or lost.
    86
        /// @param currReceivers The current streams receivers list.
    87
        /// It must be exactly the same as the last list set for the sender with `setStreams`.
    88
        /// If this is the first update, pass an empty array.
    89
        /// @param balanceDelta The streams balance change to be applied.
    90
        /// If it's positive, the balance is increased by `balanceDelta`.
    91
        /// If it's zero, the balance doesn't change.
    92
        /// If it's negative, the balance is decreased by `balanceDelta`,
    93
        /// but the change is capped at the current balance amount, so it doesn't go below 0.
    94
        /// Passing `type(int128).min` always decreases the current balance to 0.
    95
        /// @param newReceivers The list of the streams receivers of the sender to be set.
    96
        /// Must be sorted by the account IDs and then by the stream configurations,
    97
        /// without identical elements and without 0 amtPerSecs.
    98
        /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it.
    99
        /// The first hint for finding the maximum end time when all streams stop due to funds
    100
        /// running out after the balance is updated and the new receivers list is applied.
    101
        /// Hints have no effect on the results of calling this function, except potentially saving gas.
    102
        /// Hints are Unix timestamps used as the starting points for binary search for the time
    103
        /// when funds run out in the range of timestamps from the current block's to `2^32`.
    104
        /// Hints lower than the current timestamp are ignored.
    105
        /// You can provide zero, one or two hints. The order of hints doesn't matter.
    106
        /// Hints are the most effective when one of them is lower than or equal to
    107
        /// the last timestamp when funds are still streamed, and the other one is strictly larger
    108
        /// than that timestamp,the smaller the difference between such hints, the higher gas savings.
    109
        /// The savings are the highest possible when one of the hints is equal to
    110
        /// the last timestamp when funds are still streamed, and the other one is larger by 1.
    111
        /// It's worth noting that the exact timestamp of the block in which this function is executed
    112
        /// may affect correctness of the hints, especially if they're precise.
    113
        /// Hints don't provide any benefits when balance is not enough to cover
    114
        /// a single second of streaming or is enough to cover all streams until timestamp `2^32`.
    115
        /// Even inaccurate hints can be useful, and providing a single hint
    116
        /// or two hints that don't enclose the time when funds run out can still save some gas.
    117
        /// Providing poor hints that don't reduce the number of binary search steps
    118
        /// may cause slightly higher gas usage than not providing any hints.
    119
        /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it.
    120
        /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details.
    121
        /// @param transferTo The address to send funds to in case of decreasing balance
    122
        /// @return realBalanceDelta The actually applied streams balance change.
    123
        /// It's equal to the passed `balanceDelta`, unless it's negative
    124
        /// and it gets capped at the current balance amount.
    125
        function setStreams(
    126
            IERC20 erc20,
    127
            StreamReceiver[] calldata currReceivers,
    128
            int128 balanceDelta,
    129
            StreamReceiver[] calldata newReceivers,
    130
            // slither-disable-next-line similar-names
    131
            uint32 maxEndHint1,
    132
            uint32 maxEndHint2,
    133
            address transferTo
    134
    ✓ 30.3M
        ) public whenNotPaused returns (int128 realBalanceDelta) {
    135
    ✓ 30.3M
            return _setStreamsAndTransfer(
    136
    ✓ 30.3M
                drips,
    137
    ✓ 30.3M
                _callerAccountId(),
    138
    ✓ 30.3M
                erc20,
    139
    ✓ 30.3M
                currReceivers,
    140
    ✓ 30.3M
                balanceDelta,
    141
    ✓ 30.3M
                newReceivers,
    142
    ✓ 30.3M
                maxEndHint1,
    143
    ✓ 30.3M
                maxEndHint2,
    144
    ✓ 30.3M
                transferTo
    145
            );
    146
        }
    147
    
                                                    
                                                
    148
        /// @notice Sets the account splits configuration.
    149
        /// The configuration is common for all ERC-20 tokens.
    150
        /// Nothing happens to the currently splittable funds, but when they are split
    151
        /// after this function finishes, the new splits configuration will be used.
    152
        /// Because anybody can call `split` on `Drips`, calling this function may be frontrun
    153
        /// and all the currently splittable funds will be split using the old splits configuration.
    154
        /// @param receivers The list of the account's splits receivers to be set.
    155
        /// Must be sorted by the account IDs, without duplicate account IDs and without 0 weights.
    156
        /// Each splits receiver will be getting `weight / TOTAL_SPLITS_WEIGHT`
    157
        /// share of the funds collected by the account.
    158
        /// If the sum of weights of all receivers is less than `_TOTAL_SPLITS_WEIGHT`,
    159
        /// some funds won't be split, but they will be left for the account to collect.
    160
        /// Fractions of tokens are always rounder either up or down depending on the amount
    161
        /// being split, the receiver's position on the list and the other receivers' weights.
    162
        /// It's valid to include the account's own `accountId` in the list of receivers,
    163
        /// but funds split to themselves return to their splittable balance and are not collectable.
    164
        /// This is usually unwanted, because if splitting is repeated,
    165
        /// funds split to themselves will be again split using the current configuration.
    166
        /// Splitting 100% to self effectively blocks splitting unless the configuration is updated.
    167
        function setSplits(SplitsReceiver[] calldata receivers) public whenNotPaused {
    168
    ✓ 13.1M
            drips.setSplits(_callerAccountId(), receivers);
    169
        }
    170
    
                                                    
                                                
    171
        /// @notice Emits the account metadata for the message sender.
    172
        /// The keys and the values are not standardized by the protocol, it's up to the users
    173
        /// to establish and follow conventions to ensure compatibility with the consumers.
    174
        /// @param accountMetadata The list of account metadata.
    175
        function emitAccountMetadata(AccountMetadata[] calldata accountMetadata) public whenNotPaused {
    176
            if (accountMetadata.length != 0) {
    177
                drips.emitAccountMetadata(_callerAccountId(), accountMetadata);
    178
            }
    179
        }
    180
    }
    181
    
                                                    
                                                
    86.0% src/Drips.sol
    Lines covered: 98 / 114 (86.0%)
    1
    // SPDX-License-Identifier: GPL-3.0-only
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {
    5
        Streams, StreamConfig, StreamsHistory, StreamConfigImpl, StreamReceiver
    6
    } from "./Streams.sol";
    7
    import {Managed} from "./Managed.sol";
    8
    import {Splits, SplitsReceiver} from "./Splits.sol";
    9
    import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
    10
    
                                                    
                                                
    11
    using SafeERC20 for IERC20;
    12
    
                                                    
                                                
    13
    /// @notice The account metadata.
    14
    /// The key and the value are not standardized by the protocol, it's up to the users
    15
    /// to establish and follow conventions to ensure compatibility with the consumers.
    16
    struct AccountMetadata {
    17
        /// @param key The metadata key
    18
        bytes32 key;
    19
        /// @param value The metadata value
    20
        bytes value;
    21
    }
    22
    
                                                    
                                                
    23
    /// @notice Drips protocol contract. Automatically streams and splits funds between accounts.
    24
    ///
    25
    /// The account can transfer some funds to their streams balance in the contract
    26
    /// and configure a list of receivers, to whom they want to stream these funds.
    27
    /// As soon as the streams balance is enough to cover at least 1 second of streaming
    28
    /// to the configured receivers, the funds start streaming automatically.
    29
    /// Every second funds are deducted from the streams balance and moved to their receivers.
    30
    /// The process stops automatically when the streams balance is not enough to cover another second.
    31
    ///
    32
    /// Every account has a receiver balance, in which they have funds received from other accounts.
    33
    /// The streamed funds are added to the receiver balances in global cycles.
    34
    /// Every `cycleSecs` seconds the protocol adds streamed funds to the receivers' balances,
    35
    /// so recently streamed funds may not be receivable immediately.
    36
    /// `cycleSecs` is a constant configured when the Drips contract is deployed.
    37
    /// The receiver balance is independent from the streams balance,
    38
    /// to stream received funds they need to be first collected and then added to the streams balance.
    39
    ///
    40
    /// The account can share collected funds with other accounts by using splits.
    41
    /// When collecting, the account gives each of their splits receivers
    42
    /// a fraction of the received funds.
    43
    /// Funds received from splits are available for collection immediately regardless of the cycle.
    44
    /// They aren't exempt from being split, so they too can be split when collected.
    45
    /// Accounts can build chains and networks of splits between each other.
    46
    /// Anybody can request collection of funds for any account,
    47
    /// which can be used to enforce the flow of funds in the network of splits.
    48
    ///
    49
    /// The concept of something happening periodically, e.g. every second or every `cycleSecs` are
    50
    /// only high-level abstractions for the account, Ethereum isn't really capable of scheduling work.
    51
    /// The actual implementation emulates that behavior by calculating the results of the scheduled
    52
    /// events based on how many seconds have passed and only when the account needs their outcomes.
    53
    ///
    54
    /// The contract can store at most `type(int128).max` which is `2 ^ 127 - 1` units of each token.
    55
    contract Drips is Managed, Streams, Splits {
    56
        /// @notice Maximum number of streams receivers of a single account.
    57
        /// Limits cost of changes in streams configuration.
    58
        uint256 public constant MAX_STREAMS_RECEIVERS = _MAX_STREAMS_RECEIVERS;
    59
        /// @notice The additional decimals for all amtPerSec values.
    60
        uint8 public constant AMT_PER_SEC_EXTRA_DECIMALS = _AMT_PER_SEC_EXTRA_DECIMALS;
    61
        /// @notice The multiplier for all amtPerSec values.
    62
        uint160 public constant AMT_PER_SEC_MULTIPLIER = _AMT_PER_SEC_MULTIPLIER;
    63
        /// @notice Maximum number of splits receivers of a single account.
    64
        /// Limits the cost of splitting.
    65
        uint256 public constant MAX_SPLITS_RECEIVERS = _MAX_SPLITS_RECEIVERS;
    66
        /// @notice The total splits weight of an account
    67
    ✓ 8.3M
        uint32 public constant TOTAL_SPLITS_WEIGHT = _TOTAL_SPLITS_WEIGHT;
    68
        /// @notice The offset of the controlling driver ID in the account ID.
    69
        /// In other words the controlling driver ID is the highest 32 bits of the account ID.
    70
        /// Every account ID is a 256-bit integer constructed by concatenating:
    71
        /// `driverId (32 bits) | driverCustomData (224 bits)`.
    72
    ✓ 28.8M
        uint8 public constant DRIVER_ID_OFFSET = 224;
    73
        /// @notice The total amount the protocol can store of each token.
    74
        /// It's the minimum of _MAX_STREAMS_BALANCE and _MAX_SPLITS_BALANCE.
    75
        uint128 public constant MAX_TOTAL_BALANCE = _MAX_STREAMS_BALANCE;
    76
        /// @notice On every timestamp `T`, which is a multiple of `cycleSecs`, the receivers
    77
        /// gain access to steams received during `T - cycleSecs` to `T - 1`.
    78
        /// Always higher than 1.
    79
        uint32 public immutable cycleSecs;
    80
        /// @notice The minimum amtPerSec of a stream. It's 1 token per cycle.
    81
    ✓ 16.0M
        uint160 public immutable minAmtPerSec;
    82
        /// @notice The ERC-1967 storage slot holding a single `DripsStorage` structure.
    83
    ✓ 1
        bytes32 private immutable _dripsStorageSlot = _erc1967Slot("eip1967.drips.storage");
    84
    
                                                    
                                                
    85
        /// @notice Emitted when a driver is registered
    86
        /// @param driverId The driver ID
    87
        /// @param driverAddr The driver address
    88
        event DriverRegistered(uint32 indexed driverId, address indexed driverAddr);
    89
    
                                                    
                                                
    90
        /// @notice Emitted when a driver address is updated
    91
        /// @param driverId The driver ID
    92
        /// @param oldDriverAddr The old driver address
    93
        /// @param newDriverAddr The new driver address
    94
        event DriverAddressUpdated(
    95
            uint32 indexed driverId, address indexed oldDriverAddr, address indexed newDriverAddr
    96
        );
    97
    
                                                    
                                                
    98
        /// @notice Emitted when funds are withdrawn.
    99
        /// @param erc20 The used ERC-20 token.
    100
        /// @param receiver The address that the funds are sent to.
    101
        /// @param amt The withdrawn amount.
    102
        event Withdrawn(IERC20 indexed erc20, address indexed receiver, uint256 amt);
    103
    
                                                    
                                                
    104
        /// @notice Emitted by the account to broadcast metadata.
    105
        /// The key and the value are not standardized by the protocol, it's up to the users
    106
        /// to establish and follow conventions to ensure compatibility with the consumers.
    107
        /// @param accountId The ID of the account emitting metadata
    108
        /// @param key The metadata key
    109
        /// @param value The metadata value
    110
        event AccountMetadataEmitted(uint256 indexed accountId, bytes32 indexed key, bytes value);
    111
    
                                                    
                                                
    112
        struct DripsStorage {
    113
            /// @notice The next driver ID that will be used when registering.
    114
            uint32 nextDriverId;
    115
            /// @notice Driver addresses.
    116
            mapping(uint32 driverId => address) driverAddresses;
    117
            /// @notice The balance of each token currently stored in the protocol.
    118
            mapping(IERC20 erc20 => Balance) balances;
    119
        }
    120
    
                                                    
                                                
    121
        /// @notice The balance currently stored in the protocol.
    122
        struct Balance {
    123
            /// @notice The balance currently stored in the protocol in streaming.
    124
            /// It's the sum of all the funds of all the users
    125
            /// that are in the streams balances, are squeezable or are receivable.
    126
            uint128 streams;
    127
            /// @notice The balance currently stored in the protocol in splitting.
    128
            /// It's the sum of all the funds of all the users that are splittable or are collectable.
    129
            uint128 splits;
    130
        }
    131
    
                                                    
                                                
    132
        /// @param cycleSecs_ The length of cycleSecs to be used in the contract instance.
    133
        /// Low value makes funds more available by shortening the average time
    134
        /// of funds being frozen between being taken from the accounts'
    135
        /// streams balance and being receivable by their receivers.
    136
        /// High value makes receiving cheaper by making it process less cycles for a given time range.
    137
        /// Must be higher than 1.
    138
        constructor(uint32 cycleSecs_)
    139
    ✓ 1
            Streams(cycleSecs_, _erc1967Slot("eip1967.streams.storage"))
    140
    ✓ 1
            Splits(_erc1967Slot("eip1967.splits.storage"))
    141
        {
    142
    ✓ 1
            cycleSecs = Streams._cycleSecs;
    143
    ✓ 1
            minAmtPerSec = Streams._minAmtPerSec;
    144
        }
    145
    
                                                    
                                                
    146
        /// @notice A modifier making functions callable only by the driver controlling the account.
    147
        /// @param accountId The account ID.
    148
        modifier onlyDriver(uint256 accountId) {
    149
            // `accountId` has value:
    150
            // `driverId (32 bits) | driverCustomData (224 bits)`
    151
            // By bit shifting we get value:
    152
            // `zeros (224 bits) | driverId (32 bits)`
    153
            // By casting down we get value:
    154
            // `driverId (32 bits)`
    155
    ✓ 28.8M
            uint32 driverId = uint32(accountId >> DRIVER_ID_OFFSET);
    156
    ✓ 28.8M
            _assertCallerIsDriver(driverId);
    157
            _;
    158
        }
    159
    
                                                    
                                                
    160
        /// @notice Verifies that the caller controls the given driver ID and reverts otherwise.
    161
        /// @param driverId The driver ID.
    162
        function _assertCallerIsDriver(uint32 driverId) internal view {
    163
    ✓ 58.0M
            require(driverAddress(driverId) == msg.sender, "Callable only by the driver");
    164
        }
    165
    
                                                    
                                                
    166
        /// @notice Registers a driver.
    167
        /// The driver is assigned a unique ID and a range of account IDs it can control.
    168
        /// That range consists of all 2^224 account IDs with highest 32 bits equal to the driver ID.
    169
        /// Every account ID is a 256-bit integer constructed by concatenating:
    170
        /// `driverId (32 bits) | driverCustomData (224 bits)`.
    171
        /// Every driver ID is assigned only to a single address,
    172
        /// but a single address can have multiple driver IDs assigned to it.
    173
        /// @param driverAddr The address of the driver. Must not be zero address.
    174
        /// It should be a smart contract capable of dealing with the Drips API.
    175
        /// It shouldn't be an EOA because the API requires making multiple calls per transaction.
    176
        /// @return driverId The registered driver ID.
    177
    ✓ 1
        function registerDriver(address driverAddr) public whenNotPaused returns (uint32 driverId) {
    178
    ✓ 1
            require(driverAddr != address(0), "Driver registered for 0 address");
    179
    ✓ 1
            DripsStorage storage dripsStorage = _dripsStorage();
    180
    ✓ 1
            driverId = dripsStorage.nextDriverId++;
    181
    ✓ 1
            dripsStorage.driverAddresses[driverId] = driverAddr;
    182
    ✓ 1
            emit DriverRegistered(driverId, driverAddr);
    183
        }
    184
    
                                                    
                                                
    185
        /// @notice Returns the driver address.
    186
        /// @param driverId The driver ID to look up.
    187
        /// @return driverAddr The address of the driver.
    188
        /// If the driver hasn't been registered yet, returns address 0.
    189
    ✓ 58.0M
        function driverAddress(uint32 driverId) public view returns (address driverAddr) {
    190
    ✓ 58.0M
            return _dripsStorage().driverAddresses[driverId];
    191
        }
    192
    
                                                    
                                                
    193
        /// @notice Updates the driver address. Must be called from the current driver address.
    194
        /// @param driverId The driver ID.
    195
        /// @param newDriverAddr The new address of the driver.
    196
        /// It should be a smart contract capable of dealing with the Drips API.
    197
        /// It shouldn't be an EOA because the API requires making multiple calls per transaction.
    198
        function updateDriverAddress(uint32 driverId, address newDriverAddr) public whenNotPaused {
    199
    ✓ 1
            _assertCallerIsDriver(driverId);
    200
    ✓ 1
            _dripsStorage().driverAddresses[driverId] = newDriverAddr;
    201
    ✓ 1
            emit DriverAddressUpdated(driverId, msg.sender, newDriverAddr);
    202
        }
    203
    
                                                    
                                                
    204
        /// @notice Returns the driver ID which will be assigned for the next registered driver.
    205
        /// @return driverId The next driver ID.
    206
        function nextDriverId() public view returns (uint32 driverId) {
    207
            return _dripsStorage().nextDriverId;
    208
        }
    209
    
                                                    
                                                
    210
        /// @notice Returns the amount currently stored in the protocol of the given token.
    211
        /// The sum of streaming and splitting balances can never exceed `MAX_TOTAL_BALANCE`.
    212
        /// The amount of tokens held by the Drips contract exceeding the sum of
    213
        /// streaming and splitting balances can be `withdraw`n.
    214
        /// @param erc20 The used ERC-20 token.
    215
        /// It must preserve amounts, so if some amount of tokens is transferred to
    216
        /// an address, then later the same amount must be transferable from that address.
    217
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    218
        /// or impose any restrictions on holding or transferring tokens are not supported.
    219
        /// If you use such tokens in the protocol, they can get stuck or lost.
    220
        /// @return streamsBalance The balance currently stored in the protocol in streaming.
    221
        /// It's the sum of all the funds of all the users
    222
        /// that are in the streams balances, are squeezable or are receivable.
    223
        /// @return splitsBalance The balance currently stored in the protocol in splitting.
    224
        /// It's the sum of all the funds of all the users that are splittable or are collectable.
    225
        function balances(IERC20 erc20)
    226
            public
    227
            view
    228
    ✓ 17.2M
            returns (uint128 streamsBalance, uint128 splitsBalance)
    229
        {
    230
    ✓ 17.2M
            Balance storage balance = _dripsStorage().balances[erc20];
    231
    ✓ 17.2M
            return (balance.streams, balance.splits);
    232
        }
    233
    
                                                    
                                                
    234
        /// @notice Increases the balance of the given token currently stored in streams.
    235
        /// No funds are transferred, all the tokens are expected to be already held by Drips.
    236
        /// The new total balance is verified to have coverage in the held tokens
    237
        /// and to be within the limit of `MAX_TOTAL_BALANCE`.
    238
        /// @param erc20 The used ERC-20 token.
    239
        /// @param amt The amount to increase the streams balance by.
    240
        function _increaseStreamsBalance(IERC20 erc20, uint128 amt) internal {
    241
    ✓ 7.3M
            _verifyBalanceIncrease(erc20, amt);
    242
    ✓ 7.3M
            _dripsStorage().balances[erc20].streams += amt;
    243
        }
    244
    
                                                    
                                                
    245
        /// @notice Decreases the balance of the given token currently stored in streams.
    246
        /// No funds are transferred, but the tokens held by Drips
    247
        /// above the total balance become withdrawable.
    248
        /// @param erc20 The used ERC-20 token.
    249
        /// @param amt The amount to decrease the streams balance by.
    250
        function _decreaseStreamsBalance(IERC20 erc20, uint128 amt) internal {
    251
    ✓ 4.2M
            _dripsStorage().balances[erc20].streams -= amt;
    252
        }
    253
    
                                                    
                                                
    254
        /// @notice Increases the balance of the given token currently stored in splits.
    255
        /// No funds are transferred, all the tokens are expected to be already held by Drips.
    256
        /// The new total balance is verified to have coverage in the held tokens
    257
        /// and to be within the limit of `MAX_TOTAL_BALANCE`.
    258
        /// @param erc20 The used ERC-20 token.
    259
        /// @param amt The amount to increase the splits balance by.
    260
        function _increaseSplitsBalance(IERC20 erc20, uint128 amt) internal {
    261
    ✓ 1.3M
            _verifyBalanceIncrease(erc20, amt);
    262
    ✓ 1.3M
            _dripsStorage().balances[erc20].splits += amt;
    263
        }
    264
    
                                                    
                                                
    265
        /// @notice Decreases the balance of the given token currently stored in splits.
    266
        /// No funds are transferred, but the tokens held by Drips
    267
        /// above the total balance become withdrawable.
    268
        /// @param erc20 The used ERC-20 token.
    269
        /// @param amt The amount to decrease the splits balance by.
    270
        function _decreaseSplitsBalance(IERC20 erc20, uint128 amt) internal {
    271
    ✓ 3.1M
            _dripsStorage().balances[erc20].splits -= amt;
    272
        }
    273
    
                                                    
                                                
    274
        /// @notice Moves the balance of the given token currently stored in streams to splits.
    275
        /// No funds are transferred, all the tokens are already held by Drips.
    276
        /// @param erc20 The used ERC-20 token.
    277
        /// @param amt The amount to decrease the splits balance by.
    278
        function _moveBalanceFromStreamsToSplits(IERC20 erc20, uint128 amt) internal {
    279
    ✓ 2.3M
            Balance storage balance = _dripsStorage().balances[erc20];
    280
    ✓ 2.3M
            balance.streams -= amt;
    281
    ✓ 2.3M
            balance.splits += amt;
    282
        }
    283
    
                                                    
                                                
    284
        /// @notice Verifies that the balance of streams or splits can be increased by the given amount.
    285
        /// The sum of streaming and splitting balances is checked to not exceed
    286
        /// `MAX_TOTAL_BALANCE` or the amount of tokens held by the Drips.
    287
        /// @param erc20 The used ERC-20 token.
    288
        /// @param amt The amount to increase the streams or splits balance by.
    289
        function _verifyBalanceIncrease(IERC20 erc20, uint128 amt) internal view {
    290
    ✓ 8.6M
            (uint256 streamsBalance, uint128 splitsBalance) = balances(erc20);
    291
    ✓ 8.6M
            uint256 newTotalBalance = streamsBalance + splitsBalance + amt;
    292
    ✓ 8.6M
            require(newTotalBalance <= MAX_TOTAL_BALANCE, "Total balance too high");
    293
    ✓ 8.6M
            require(newTotalBalance <= _tokenBalance(erc20), "Token balance too low");
    294
        }
    295
    
                                                    
                                                
    296
        /// @notice Transfers withdrawable funds to an address.
    297
        /// The withdrawable funds are held by the Drips contract,
    298
        /// but not used in the protocol, so they are free to be transferred out.
    299
        /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn
    300
        /// or used in the protocol before any 3rd parties have a chance to do that.
    301
        /// @param erc20 The used ERC-20 token.
    302
        /// It must preserve amounts, so if some amount of tokens is transferred to
    303
        /// an address, then later the same amount must be transferable from that address.
    304
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    305
        /// or impose any restrictions on holding or transferring tokens are not supported.
    306
        /// If you use such tokens in the protocol, they can get stuck or lost.
    307
        /// @param receiver The address to send withdrawn funds to.
    308
        /// @param amt The withdrawn amount.
    309
        /// It must be at most the difference between the balance of the token held by the Drips
    310
        /// contract address and the sum of balances managed by the protocol as indicated by `balances`.
    311
        function withdraw(IERC20 erc20, address receiver, uint256 amt) public {
    312
    ✓ 8.6M
            (uint128 streamsBalance, uint128 splitsBalance) = balances(erc20);
    313
    ✓ 8.6M
            uint256 withdrawable = _tokenBalance(erc20) - streamsBalance - splitsBalance;
    314
    ✓ 8.6M
            require(amt <= withdrawable, "Withdrawal amount too high");
    315
    ✓ 7.3M
            emit Withdrawn(erc20, receiver, amt);
    316
    ✓ 7.3M
            erc20.safeTransfer(receiver, amt);
    317
        }
    318
    
                                                    
                                                
    319
    ✓ 17.2M
        function _tokenBalance(IERC20 erc20) internal view returns (uint256) {
    320
    ✓ 17.2M
            return erc20.balanceOf(address(this));
    321
        }
    322
    
                                                    
                                                
    323
        /// @notice Counts cycles from which streams can be collected.
    324
        /// This function can be used to detect that there are
    325
        /// too many cycles to analyze in a single transaction.
    326
        /// @param accountId The account ID.
    327
        /// @param erc20 The used ERC-20 token.
    328
        /// It must preserve amounts, so if some amount of tokens is transferred to
    329
        /// an address, then later the same amount must be transferable from that address.
    330
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    331
        /// or impose any restrictions on holding or transferring tokens are not supported.
    332
        /// If you use such tokens in the protocol, they can get stuck or lost.
    333
        /// @return cycles The number of cycles which can be flushed
    334
        function receivableStreamsCycles(uint256 accountId, IERC20 erc20)
    335
            public
    336
            view
    337
    ✓ 670.7K
            returns (uint32 cycles)
    338
        {
    339
    ✓ 670.7K
            return Streams._receivableStreamsCycles(accountId, erc20);
    340
        }
    341
    
                                                    
                                                
    342
        /// @notice Calculate effects of calling `receiveStreams` with the given parameters.
    343
        /// @param accountId The account ID.
    344
        /// @param erc20 The used ERC-20 token.
    345
        /// It must preserve amounts, so if some amount of tokens is transferred to
    346
        /// an address, then later the same amount must be transferable from that address.
    347
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    348
        /// or impose any restrictions on holding or transferring tokens are not supported.
    349
        /// If you use such tokens in the protocol, they can get stuck or lost.
    350
        /// @param maxCycles The maximum number of received streams cycles.
    351
        /// If too low, receiving will be cheap, but may not cover many cycles.
    352
        /// If too high, receiving may become too expensive to fit in a single transaction.
    353
        /// @return receivableAmt The amount which would be received
    354
        function receiveStreamsResult(uint256 accountId, IERC20 erc20, uint32 maxCycles)
    355
            public
    356
            view
    357
    ✓ 24.5M
            returns (uint128 receivableAmt)
    358
        {
    359
    ✓ 24.5M
            (receivableAmt,,,,) = Streams._receiveStreamsResult(accountId, erc20, maxCycles);
    360
        }
    361
    
                                                    
                                                
    362
        /// @notice Receive streams for the account.
    363
        /// Received streams cycles won't need to be analyzed ever again.
    364
        /// Calling this function does not collect but makes the funds ready to be split and collected.
    365
        /// @param accountId The account ID.
    366
        /// @param erc20 The used ERC-20 token.
    367
        /// It must preserve amounts, so if some amount of tokens is transferred to
    368
        /// an address, then later the same amount must be transferable from that address.
    369
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    370
        /// or impose any restrictions on holding or transferring tokens are not supported.
    371
        /// If you use such tokens in the protocol, they can get stuck or lost.
    372
        /// @param maxCycles The maximum number of received streams cycles.
    373
        /// If too low, receiving will be cheap, but may not cover many cycles.
    374
        /// If too high, receiving may become too expensive to fit in a single transaction.
    375
        /// @return receivedAmt The received amount
    376
        function receiveStreams(uint256 accountId, IERC20 erc20, uint32 maxCycles)
    377
            public
    378
            whenNotPaused
    379
    ✓ 13.5M
            returns (uint128 receivedAmt)
    380
        {
    381
    ✓ 13.5M
            receivedAmt = Streams._receiveStreams(accountId, erc20, maxCycles);
    382
    ✓ 13.5M
            if (receivedAmt != 0) {
    383
    ✓ 2.2M
                _moveBalanceFromStreamsToSplits(erc20, receivedAmt);
    384
    ✓ 2.2M
                Splits._addSplittable(accountId, erc20, receivedAmt);
    385
            }
    386
        }
    387
    
                                                    
                                                
    388
        /// @notice Receive streams from the currently running cycle from a single sender.
    389
        /// It doesn't receive streams from the finished cycles, to do that use `receiveStreams`.
    390
        /// Squeezed funds won't be received in the next calls to `squeezeStreams` or `receiveStreams`.
    391
        /// Only funds streamed before `block.timestamp` can be squeezed.
    392
        /// @param accountId The ID of the account receiving streams to squeeze funds for.
    393
        /// @param erc20 The used ERC-20 token.
    394
        /// It must preserve amounts, so if some amount of tokens is transferred to
    395
        /// an address, then later the same amount must be transferable from that address.
    396
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    397
        /// or impose any restrictions on holding or transferring tokens are not supported.
    398
        /// If you use such tokens in the protocol, they can get stuck or lost.
    399
        /// @param senderId The ID of the streaming account to squeeze funds from.
    400
        /// @param historyHash The sender's history hash that was valid right before
    401
        /// they set up the sequence of configurations described by `streamsHistory`.
    402
        /// @param streamsHistory The sequence of the sender's streams configurations.
    403
        /// It can start at an arbitrary past configuration, but must describe all the configurations
    404
        /// which have been used since then including the current one, in the chronological order.
    405
        /// Only streams described by `streamsHistory` will be squeezed.
    406
        /// If `streamsHistory` entries have no receivers, they won't be squeezed.
    407
        /// @return amt The squeezed amount.
    408
        function squeezeStreams(
    409
            uint256 accountId,
    410
            IERC20 erc20,
    411
            uint256 senderId,
    412
            bytes32 historyHash,
    413
            StreamsHistory[] memory streamsHistory
    414
    ✓ 43.2M
        ) public whenNotPaused returns (uint128 amt) {
    415
    ✓ 43.2M
            amt = Streams._squeezeStreams(accountId, erc20, senderId, historyHash, streamsHistory);
    416
    ✓ 43.2M
            if (amt != 0) {
    417
    ✓ 72.6K
                _moveBalanceFromStreamsToSplits(erc20, amt);
    418
    ✓ 72.6K
                Splits._addSplittable(accountId, erc20, amt);
    419
            }
    420
        }
    421
    
                                                    
                                                
    422
        /// @notice Calculate effects of calling `squeezeStreams` with the given parameters.
    423
        /// See its documentation for more details.
    424
        /// @param accountId The ID of the account receiving streams to squeeze funds for.
    425
        /// @param erc20 The used ERC-20 token.
    426
        /// It must preserve amounts, so if some amount of tokens is transferred to
    427
        /// an address, then later the same amount must be transferable from that address.
    428
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    429
        /// or impose any restrictions on holding or transferring tokens are not supported.
    430
        /// If you use such tokens in the protocol, they can get stuck or lost.
    431
        /// @param senderId The ID of the streaming account to squeeze funds from.
    432
        /// @param historyHash The sender's history hash that was valid right before `streamsHistory`.
    433
        /// @param streamsHistory The sequence of the sender's streams configurations.
    434
        /// @return amt The squeezed amount.
    435
        function squeezeStreamsResult(
    436
            uint256 accountId,
    437
            IERC20 erc20,
    438
            uint256 senderId,
    439
            bytes32 historyHash,
    440
            StreamsHistory[] memory streamsHistory
    441
    ✓ 28.6M
        ) public view returns (uint128 amt) {
    442
            (amt,,,,) =
    443
    ✓ 28.6M
                Streams._squeezeStreamsResult(accountId, erc20, senderId, historyHash, streamsHistory);
    444
        }
    445
    
                                                    
                                                
    446
        /// @notice Returns account's received but not split yet funds.
    447
        /// @param accountId The account ID.
    448
        /// @param erc20 The used ERC-20 token.
    449
        /// It must preserve amounts, so if some amount of tokens is transferred to
    450
        /// an address, then later the same amount must be transferable from that address.
    451
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    452
        /// or impose any restrictions on holding or transferring tokens are not supported.
    453
        /// If you use such tokens in the protocol, they can get stuck or lost.
    454
        /// @return amt The amount received but not split yet.
    455
    ✓ 16.4M
        function splittable(uint256 accountId, IERC20 erc20) public view returns (uint128 amt) {
    456
    ✓ 16.4M
            return Splits._splittable(accountId, erc20);
    457
        }
    458
    
                                                    
                                                
    459
        /// @notice Calculate the result of splitting an amount using the current splits configuration.
    460
        /// Fractions of tokens are always rounder either up or down depending on the amount
    461
        /// being split, the receiver's position on the list and the other receivers' weights.
    462
        /// @param accountId The account ID.
    463
        /// @param currReceivers The list of the account's current splits receivers.
    464
        /// It must be exactly the same as the last list set for the account with `setSplits`.
    465
        /// If the splits have never been set, pass an empty array.
    466
        /// @param amount The amount being split.
    467
        /// @return collectableAmt The amount made collectable for the account
    468
        /// on top of what was collectable before.
    469
        /// @return splitAmt The amount split to the account's splits receivers
    470
        function splitResult(uint256 accountId, SplitsReceiver[] memory currReceivers, uint128 amount)
    471
            public
    472
            view
    473
    ✓ 1.2M
            returns (uint128 collectableAmt, uint128 splitAmt)
    474
        {
    475
    ✓ 1.2M
            return Splits._splitResult(accountId, currReceivers, amount);
    476
        }
    477
    
                                                    
                                                
    478
        /// @notice Splits the account's splittable funds among receivers.
    479
        /// The entire splittable balance of the given ERC-20 token is split.
    480
        /// Fractions of tokens are always rounder either up or down depending on the amount
    481
        /// being split, the receiver's position on the list and the other receivers' weights.
    482
        /// All split funds are split using the current splits configuration.
    483
        /// Because the account can update their splits configuration at any time,
    484
        /// it is possible that calling this function will be frontrun,
    485
        /// and all the splittable funds will become splittable only using the new configuration.
    486
        /// The account must be trusted with how funds sent to them will be splits,
    487
        /// in the end they can do with their funds whatever they want by changing the configuration.
    488
        /// @param accountId The account ID.
    489
        /// @param erc20 The used ERC-20 token.
    490
        /// It must preserve amounts, so if some amount of tokens is transferred to
    491
        /// an address, then later the same amount must be transferable from that address.
    492
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    493
        /// or impose any restrictions on holding or transferring tokens are not supported.
    494
        /// If you use such tokens in the protocol, they can get stuck or lost.
    495
        /// @param currReceivers The list of the account's current splits receivers.
    496
        /// It must be exactly the same as the last list set for the account with `setSplits`.
    497
        /// If the splits have never been set, pass an empty array.
    498
        /// @return collectableAmt The amount made collectable for the account
    499
        /// on top of what was collectable before.
    500
        /// @return splitAmt The amount split to the account's splits receivers
    501
        function split(uint256 accountId, IERC20 erc20, SplitsReceiver[] memory currReceivers)
    502
            public
    503
            whenNotPaused
    504
    ✓ 17.2M
            returns (uint128 collectableAmt, uint128 splitAmt)
    505
        {
    506
    ✓ 17.2M
            return Splits._split(accountId, erc20, currReceivers);
    507
        }
    508
    
                                                    
                                                
    509
        /// @notice Returns account's received funds already split and ready to be collected.
    510
        /// @param accountId The account ID.
    511
        /// @param erc20 The used ERC-20 token.
    512
        /// It must preserve amounts, so if some amount of tokens is transferred to
    513
        /// an address, then later the same amount must be transferable from that address.
    514
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    515
        /// or impose any restrictions on holding or transferring tokens are not supported.
    516
        /// If you use such tokens in the protocol, they can get stuck or lost.
    517
        /// @return amt The collectable amount.
    518
    ✓ 7.7M
        function collectable(uint256 accountId, IERC20 erc20) public view returns (uint128 amt) {
    519
    ✓ 7.7M
            return Splits._collectable(accountId, erc20);
    520
        }
    521
    
                                                    
                                                
    522
        /// @notice Collects account's received already split funds and makes them withdrawable.
    523
        /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn
    524
        /// or used in the protocol before any 3rd parties have a chance to do that.
    525
        /// @param accountId The account ID.
    526
        /// @param erc20 The used ERC-20 token.
    527
        /// It must preserve amounts, so if some amount of tokens is transferred to
    528
        /// an address, then later the same amount must be transferable from that address.
    529
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    530
        /// or impose any restrictions on holding or transferring tokens are not supported.
    531
        /// If you use such tokens in the protocol, they can get stuck or lost.
    532
        /// @return amt The collected amount
    533
        function collect(uint256 accountId, IERC20 erc20)
    534
            public
    535
            whenNotPaused
    536
    ✓ 14.6M
            onlyDriver(accountId)
    537
    ✓ 14.6M
            returns (uint128 amt)
    538
        {
    539
    ✓ 14.6M
            amt = Splits._collect(accountId, erc20);
    540
    ✓ 14.6M
            if (amt != 0) _decreaseSplitsBalance(erc20, amt);
    541
        }
    542
    
                                                    
                                                
    543
        /// @notice Gives funds from the account to the receiver.
    544
        /// The receiver can split and collect them immediately.
    545
        /// Requires that the tokens used to give are already sent to Drips and are withdrawable.
    546
        /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn
    547
        /// or used in the protocol before any 3rd parties have a chance to do that.
    548
        /// @param accountId The account ID.
    549
        /// @param receiver The receiver account ID.
    550
        /// @param erc20 The used ERC-20 token.
    551
        /// It must preserve amounts, so if some amount of tokens is transferred to
    552
        /// an address, then later the same amount must be transferable from that address.
    553
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    554
        /// or impose any restrictions on holding or transferring tokens are not supported.
    555
        /// If you use such tokens in the protocol, they can get stuck or lost.
    556
        /// @param amt The given amount
    557
        function give(uint256 accountId, uint256 receiver, IERC20 erc20, uint128 amt)
    558
            public
    559
            whenNotPaused
    560
    ✓ 1.6M
            onlyDriver(accountId)
    561
        {
    562
    ✓ 1.6M
            if (amt != 0) _increaseSplitsBalance(erc20, amt);
    563
    ✓ 1.6M
            Splits._give(accountId, receiver, erc20, amt);
    564
        }
    565
    
                                                    
                                                
    566
        /// @notice Current account streams state.
    567
        /// @param accountId The account ID.
    568
        /// @param erc20 The used ERC-20 token.
    569
        /// It must preserve amounts, so if some amount of tokens is transferred to
    570
        /// an address, then later the same amount must be transferable from that address.
    571
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    572
        /// or impose any restrictions on holding or transferring tokens are not supported.
    573
        /// If you use such tokens in the protocol, they can get stuck or lost.
    574
        /// @return streamsHash The current streams receivers list hash, see `hashStreams`
    575
        /// @return streamsHistoryHash The current streams history hash, see `hashStreamsHistory`.
    576
        /// @return updateTime The time when streams have been configured for the last time.
    577
        /// @return balance The balance when streams have been configured for the last time.
    578
        /// @return maxEnd The current maximum end time of streaming.
    579
        function streamsState(uint256 accountId, IERC20 erc20)
    580
            public
    581
            view
    582
            returns (
    583
    ✓ 33.2M
                bytes32 streamsHash,
    584
    ✓ 33.2M
                bytes32 streamsHistoryHash,
    585
    ✓ 33.2M
                uint32 updateTime,
    586
    ✓ 33.2M
                uint128 balance,
    587
    ✓ 33.2M
                uint32 maxEnd
    588
            )
    589
        {
    590
    ✓ 33.2M
            return Streams._streamsState(accountId, erc20);
    591
        }
    592
    
                                                    
                                                
    593
        /// @notice The account's streams balance at the given timestamp.
    594
        /// @param accountId The account ID.
    595
        /// @param erc20 The used ERC-20 token.
    596
        /// It must preserve amounts, so if some amount of tokens is transferred to
    597
        /// an address, then later the same amount must be transferable from that address.
    598
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    599
        /// or impose any restrictions on holding or transferring tokens are not supported.
    600
        /// If you use such tokens in the protocol, they can get stuck or lost.
    601
        /// @param currReceivers The current streams receivers list.
    602
        /// It must be exactly the same as the last list set for the account with `setStreams`.
    603
        /// @param timestamp The timestamp for which balance should be calculated.
    604
        /// It can't be lower than the timestamp of the last call to `setStreams`.
    605
        /// If it's bigger than `block.timestamp`, then it's a prediction assuming
    606
        /// that `setStreams` won't be called before `timestamp`.
    607
        /// @return balance The account balance on `timestamp`
    608
        function balanceAt(
    609
            uint256 accountId,
    610
            IERC20 erc20,
    611
            StreamReceiver[] memory currReceivers,
    612
            uint32 timestamp
    613
    ✓ 9.0M
        ) public view returns (uint128 balance) {
    614
    ✓ 9.0M
            return Streams._balanceAt(accountId, erc20, currReceivers, timestamp);
    615
        }
    616
    
                                                    
                                                
    617
        /// @notice Sets the account's streams configuration.
    618
        /// Requires that the tokens used to increase the streams balance
    619
        /// are already sent to Drips and are withdrawable.
    620
        /// If the streams balance is decreased, the released tokens become withdrawable.
    621
        /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn
    622
        /// or used in the protocol before any 3rd parties have a chance to do that.
    623
        /// @param accountId The account ID.
    624
        /// @param erc20 The used ERC-20 token.
    625
        /// It must preserve amounts, so if some amount of tokens is transferred to
    626
        /// an address, then later the same amount must be transferable from that address.
    627
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    628
        /// or impose any restrictions on holding or transferring tokens are not supported.
    629
        /// If you use such tokens in the protocol, they can get stuck or lost.
    630
        /// @param currReceivers The current streams receivers list.
    631
        /// It must be exactly the same as the last list set for the account with `setStreams`.
    632
        /// If this is the first update, pass an empty array.
    633
        /// @param balanceDelta The streams balance change to be applied.
    634
        /// If it's positive, the balance is increased by `balanceDelta`.
    635
        /// If it's zero, the balance doesn't change.
    636
        /// If it's negative, the balance is decreased by `balanceDelta`,
    637
        /// but the change is capped at the current balance amount, so it doesn't go below 0.
    638
        /// Passing `type(int128).min` always decreases the current balance to 0.
    639
        /// @param newReceivers The list of the streams receivers of the account to be set.
    640
        /// Must be sorted by the account IDs and then by the stream configurations,
    641
        /// without identical elements and without 0 amtPerSecs.
    642
        /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it.
    643
        /// The first hint for finding the maximum end time when all streams stop due to funds
    644
        /// running out after the balance is updated and the new receivers list is applied.
    645
        /// Hints have no effect on the results of calling this function, except potentially saving gas.
    646
        /// Hints are Unix timestamps used as the starting points for binary search for the time
    647
        /// when funds run out in the range of timestamps from the current block's to `2^32`.
    648
        /// Hints lower than the current timestamp are ignored.
    649
        /// You can provide zero, one or two hints. The order of hints doesn't matter.
    650
        /// Hints are the most effective when one of them is lower than or equal to
    651
        /// the last timestamp when funds are still streamed, and the other one is strictly larger
    652
        /// than that timestamp,the smaller the difference between such hints, the higher gas savings.
    653
        /// The savings are the highest possible when one of the hints is equal to
    654
        /// the last timestamp when funds are still streamed, and the other one is larger by 1.
    655
        /// It's worth noting that the exact timestamp of the block in which this function is executed
    656
        /// may affect correctness of the hints, especially if they're precise.
    657
        /// Hints don't provide any benefits when balance is not enough to cover
    658
        /// a single second of streaming or is enough to cover all streams until timestamp `2^32`.
    659
        /// Even inaccurate hints can be useful, and providing a single hint
    660
        /// or two hints that don't enclose the time when funds run out can still save some gas.
    661
        /// Providing poor hints that don't reduce the number of binary search steps
    662
        /// may cause slightly higher gas usage than not providing any hints.
    663
        /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it.
    664
        /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details.
    665
        /// @return realBalanceDelta The actually applied streams balance change.
    666
        /// It's equal to the passed `balanceDelta`, unless it's negative
    667
        /// and it gets capped at the current balance amount.
    668
        /// If it's lower than zero, it's the negative of the amount that became withdrawable.
    669
        function setStreams(
    670
            uint256 accountId,
    671
            IERC20 erc20,
    672
            StreamReceiver[] memory currReceivers,
    673
            int128 balanceDelta,
    674
            StreamReceiver[] memory newReceivers,
    675
            // slither-disable-next-line similar-names
    676
            uint32 maxEndHint1,
    677
            uint32 maxEndHint2
    678
    ✓ 28.8M
        ) public whenNotPaused onlyDriver(accountId) returns (int128 realBalanceDelta) {
    679
    ✓ 28.8M
            if (balanceDelta > 0) _increaseStreamsBalance(erc20, uint128(balanceDelta));
    680
    ✓ 28.8M
            realBalanceDelta = Streams._setStreams(
    681
    ✓ 28.8M
                accountId, erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2
    682
            );
    683
    ✓ 28.4M
            if (realBalanceDelta < 0) _decreaseStreamsBalance(erc20, uint128(-realBalanceDelta));
    684
        }
    685
    
                                                    
                                                
    686
        /// @notice Calculates the hash of the streams configuration.
    687
        /// It's used to verify if streams configuration is the previously set one.
    688
        /// @param receivers The list of the streams receivers.
    689
        /// Must be sorted by the account IDs and then by the stream configurations,
    690
        /// without identical elements and without 0 amtPerSecs.
    691
        /// If the streams have never been set, pass an empty array.
    692
        /// @return streamsHash The hash of the streams configuration
    693
        function hashStreams(StreamReceiver[] memory receivers)
    694
            public
    695
            pure
    696
    ✓ 38.5M
            returns (bytes32 streamsHash)
    697
        {
    698
    ✓ 38.5M
            return Streams._hashStreams(receivers);
    699
        }
    700
    
                                                    
                                                
    701
        /// @notice Calculates the hash of the streams history
    702
        /// after the streams configuration is updated.
    703
        /// @param oldStreamsHistoryHash The history hash
    704
        /// that was valid before the streams were updated.
    705
        /// The `streamsHistoryHash` of the account before they set streams for the first time is `0`.
    706
        /// @param streamsHash The hash of the streams receivers being set.
    707
        /// @param updateTime The timestamp when the streams were updated.
    708
        /// @param maxEnd The maximum end of the streams being set.
    709
        /// @return streamsHistoryHash The hash of the updated streams history.
    710
        function hashStreamsHistory(
    711
            bytes32 oldStreamsHistoryHash,
    712
            bytes32 streamsHash,
    713
            uint32 updateTime,
    714
            uint32 maxEnd
    715
    ✓ 28.4M
        ) public pure returns (bytes32 streamsHistoryHash) {
    716
    ✓ 28.4M
            return Streams._hashStreamsHistory(oldStreamsHistoryHash, streamsHash, updateTime, maxEnd);
    717
        }
    718
    
                                                    
                                                
    719
        /// @notice Sets the account splits configuration.
    720
        /// The configuration is common for all ERC-20 tokens.
    721
        /// Nothing happens to the currently splittable funds, but when they are split
    722
        /// after this function finishes, the new splits configuration will be used.
    723
        /// Because anybody can call `split`, calling this function may be frontrun
    724
        /// and all the currently splittable funds will be split using the old splits configuration.
    725
        /// @param accountId The account ID.
    726
        /// @param receivers The list of the account's splits receivers to be set.
    727
        /// Must be sorted by the account IDs, without duplicate account IDs and without 0 weights.
    728
        /// Each splits receiver will be getting `weight / TOTAL_SPLITS_WEIGHT`
    729
        /// share of the funds collected by the account.
    730
        /// If the sum of weights of all receivers is less than `_TOTAL_SPLITS_WEIGHT`,
    731
        /// some funds won't be split, but they will be left for the account to collect.
    732
        /// Fractions of tokens are always rounder either up or down depending on the amount
    733
        /// being split, the receiver's position on the list and the other receivers' weights.
    734
        /// It's valid to include the account's own `accountId` in the list of receivers,
    735
        /// but funds split to themselves return to their splittable balance and are not collectable.
    736
        /// This is usually unwanted, because if splitting is repeated,
    737
        /// funds split to themselves will be again split using the current configuration.
    738
        /// Splitting 100% to self effectively blocks splitting unless the configuration is updated.
    739
        function setSplits(uint256 accountId, SplitsReceiver[] memory receivers)
    740
            public
    741
            whenNotPaused
    742
    ✓ 13.1M
            onlyDriver(accountId)
    743
        {
    744
    ✓ 13.1M
            Splits._setSplits(accountId, receivers);
    745
        }
    746
    
                                                    
                                                
    747
        /// @notice Current account's splits hash, see `hashSplits`.
    748
        /// @param accountId The account ID.
    749
        /// @return currSplitsHash The current account's splits hash
    750
        function splitsHash(uint256 accountId) public view returns (bytes32 currSplitsHash) {
    751
            return Splits._splitsHash(accountId);
    752
        }
    753
    
                                                    
                                                
    754
        /// @notice Calculates the hash of the list of splits receivers.
    755
        /// @param receivers The list of the splits receivers.
    756
        /// Must be sorted by the account IDs, without duplicate account IDs and without 0 weights.
    757
        /// @return receiversHash The hash of the list of splits receivers.
    758
        function hashSplits(SplitsReceiver[] memory receivers)
    759
            public
    760
            pure
    761
            returns (bytes32 receiversHash)
    762
        {
    763
            return Splits._hashSplits(receivers);
    764
        }
    765
    
                                                    
                                                
    766
        /// @notice Emits account metadata.
    767
        /// The keys and the values are not standardized by the protocol, it's up to the users
    768
        /// to establish and follow conventions to ensure compatibility with the consumers.
    769
        /// @param accountId The account ID.
    770
        /// @param accountMetadata The list of account metadata.
    771
        function emitAccountMetadata(uint256 accountId, AccountMetadata[] calldata accountMetadata)
    772
            public
    773
            whenNotPaused
    774
            onlyDriver(accountId)
    775
        {
    776
            unchecked {
    777
                for (uint256 i = 0; i < accountMetadata.length; i++) {
    778
                    AccountMetadata calldata metadata = accountMetadata[i];
    779
                    emit AccountMetadataEmitted(accountId, metadata.key, metadata.value);
    780
                }
    781
            }
    782
        }
    783
    
                                                    
                                                
    784
        /// @notice Returns the Drips storage.
    785
        /// @return storageRef The storage.
    786
    ✓ 93.4M
        function _dripsStorage() internal view returns (DripsStorage storage storageRef) {
    787
    ✓ 93.4M
            bytes32 slot = _dripsStorageSlot;
    788
            // slither-disable-next-line assembly
    789
            assembly {
    790
    ✓ 93.4M
                storageRef.slot := slot
    791
            }
    792
        }
    793
    }
    794
    
                                                    
                                                
    100.0% src/DriverTransferUtils.sol
    Lines covered: 12 / 12 (100.0%)
    1
    // SPDX-License-Identifier: GPL-3.0-only
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {Drips, StreamReceiver, IERC20, SafeERC20} from "./Drips.sol";
    5
    import {ERC2771Context} from "openzeppelin-contracts/metatx/ERC2771Context.sol";
    6
    
                                                    
                                                
    7
    /// @notice ERC-20 token transfer utilities for drivers.
    8
    /// Encapsulates the logic for token transfers made by drivers implementing user identities.
    9
    /// All funds going into Drips are transferred ad-hoc from the caller (`msg.sender`),
    10
    /// and all funds going out of Drips are transferred in full to the provided address.
    11
    /// Compatible with `Caller` by supporting ERC-2771.
    12
    abstract contract DriverTransferUtils is ERC2771Context {
    13
        /// @param forwarder The ERC-2771 forwarder to trust. May be the zero address.
    14
    ✓ 1
        constructor(address forwarder) ERC2771Context(forwarder) {}
    15
    
                                                    
                                                
    16
        /// @notice Collects the account's received already split funds
    17
        /// and transfers them out of the Drips contract.
    18
        /// @param drips The Drips contract to use.
    19
        /// @param erc20 The used ERC-20 token.
    20
        /// It must preserve amounts, so if some amount of tokens is transferred to
    21
        /// an address, then later the same amount must be transferable from that address.
    22
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    23
        /// or impose any restrictions on holding or transferring tokens are not supported.
    24
        /// If you use such tokens in the protocol, they can get stuck or lost.
    25
        /// @param transferTo The address to send collected funds to
    26
        /// @return amt The collected amount
    27
        function _collectAndTransfer(Drips drips, uint256 accountId, IERC20 erc20, address transferTo)
    28
            internal
    29
    ✓ 14.6M
            returns (uint128 amt)
    30
        {
    31
    ✓ 14.6M
            amt = drips.collect(accountId, erc20);
    32
    ✓ 14.6M
            if (amt > 0) drips.withdraw(erc20, transferTo, amt);
    33
        }
    34
    
                                                    
                                                
    35
        /// @notice Gives funds from the message sender to the receiver.
    36
        /// The receiver can split and collect them immediately.
    37
        /// Transfers the funds to be given from the message sender's wallet to the Drips contract.
    38
        /// @param drips The Drips contract to use.
    39
        /// @param receiver The receiver account ID.
    40
        /// @param erc20 The used ERC-20 token.
    41
        /// It must preserve amounts, so if some amount of tokens is transferred to
    42
        /// an address, then later the same amount must be transferable from that address.
    43
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    44
        /// or impose any restrictions on holding or transferring tokens are not supported.
    45
        /// If you use such tokens in the protocol, they can get stuck or lost.
    46
        /// @param amt The given amount
    47
        function _giveAndTransfer(
    48
            Drips drips,
    49
            uint256 accountId,
    50
            uint256 receiver,
    51
            IERC20 erc20,
    52
            uint128 amt
    53
        ) internal {
    54
    ✓ 2.3M
            if (amt > 0) _transferFromCaller(drips, erc20, amt);
    55
    ✓ 1.6M
            drips.give(accountId, receiver, erc20, amt);
    56
        }
    57
    
                                                    
                                                
    58
        /// @notice Sets the message sender's streams configuration.
    59
        /// Transfers funds between the message sender's wallet and the Drips contract
    60
        /// to fulfil the change of the streams balance.
    61
        /// @param drips The Drips contract to use.
    62
        /// @param erc20 The used ERC-20 token.
    63
        /// It must preserve amounts, so if some amount of tokens is transferred to
    64
        /// an address, then later the same amount must be transferable from that address.
    65
        /// Tokens which rebase the holders' balances, collect taxes on transfers,
    66
        /// or impose any restrictions on holding or transferring tokens are not supported.
    67
        /// If you use such tokens in the protocol, they can get stuck or lost.
    68
        /// @param currReceivers The current streams receivers list.
    69
        /// It must be exactly the same as the last list set for the sender with `setStreams`.
    70
        /// If this is the first update, pass an empty array.
    71
        /// @param balanceDelta The streams balance change to be applied.
    72
        /// If it's positive, the balance is increased by `balanceDelta`.
    73
        /// If it's zero, the balance doesn't change.
    74
        /// If it's negative, the balance is decreased by `balanceDelta`,
    75
        /// but the change is capped at the current balance amount, so it doesn't go below 0.
    76
        /// Passing `type(int128).min` always decreases the current balance to 0.
    77
        /// @param newReceivers The list of the streams receivers of the sender to be set.
    78
        /// Must be sorted by the account IDs and then by the stream configurations,
    79
        /// without identical elements and without 0 amtPerSecs.
    80
        /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it.
    81
        /// The first hint for finding the maximum end time when all streams stop due to funds
    82
        /// running out after the balance is updated and the new receivers list is applied.
    83
        /// Hints have no effect on the results of calling this function, except potentially saving gas.
    84
        /// Hints are Unix timestamps used as the starting points for binary search for the time
    85
        /// when funds run out in the range of timestamps from the current block's to `2^32`.
    86
        /// Hints lower than the current timestamp are ignored.
    87
        /// You can provide zero, one or two hints. The order of hints doesn't matter.
    88
        /// Hints are the most effective when one of them is lower than or equal to
    89
        /// the last timestamp when funds are still streamed, and the other one is strictly larger
    90
        /// than that timestamp,the smaller the difference between such hints, the higher gas savings.
    91
        /// The savings are the highest possible when one of the hints is equal to
    92
        /// the last timestamp when funds are still streamed, and the other one is larger by 1.
    93
        /// It's worth noting that the exact timestamp of the block in which this function is executed
    94
        /// may affect correctness of the hints, especially if they're precise.
    95
        /// Hints don't provide any benefits when balance is not enough to cover
    96
        /// a single second of streaming or is enough to cover all streams until timestamp `2^32`.
    97
        /// Even inaccurate hints can be useful, and providing a single hint
    98
        /// or two hints that don't enclose the time when funds run out can still save some gas.
    99
        /// Providing poor hints that don't reduce the number of binary search steps
    100
        /// may cause slightly higher gas usage than not providing any hints.
    101
        /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it.
    102
        /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details.
    103
        /// @param transferTo The address to send funds to in case of decreasing balance
    104
        /// @return realBalanceDelta The actually applied streams balance change.
    105
        /// It's equal to the passed `balanceDelta`, unless it's negative
    106
        /// and it gets capped at the current balance amount.
    107
        function _setStreamsAndTransfer(
    108
            Drips drips,
    109
            uint256 accountId,
    110
            IERC20 erc20,
    111
            StreamReceiver[] calldata currReceivers,
    112
            int128 balanceDelta,
    113
            StreamReceiver[] calldata newReceivers,
    114
            // slither-disable-next-line similar-names
    115
            uint32 maxEndHint1,
    116
            uint32 maxEndHint2,
    117
            address transferTo
    118
    ✓ 30.3M
        ) internal returns (int128 realBalanceDelta) {
    119
    ✓ 30.3M
            if (balanceDelta > 0) _transferFromCaller(drips, erc20, uint128(balanceDelta));
    120
    ✓ 28.8M
            realBalanceDelta = drips.setStreams(
    121
    ✓ 28.8M
                accountId, erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2
    122
            );
    123
    ✓ 28.4M
            if (realBalanceDelta < 0) drips.withdraw(erc20, transferTo, uint128(-realBalanceDelta));
    124
        }
    125
    
                                                    
                                                
    126
        /// @notice Transfers tokens from the sender to Drips.
    127
        /// @param drips The Drips contract to use.
    128
        /// @param erc20 The used ERC-20 token.
    129
        /// @param amt The transferred amount
    130
        function _transferFromCaller(Drips drips, IERC20 erc20, uint128 amt) internal {
    131
    ✓ 10.9M
            SafeERC20.safeTransferFrom(erc20, _msgSender(), address(drips), amt);
    132
        }
    133
    }
    134
    
                                                    
                                                
    25.0% src/Managed.sol
    Lines covered: 11 / 44 (25.0%)
    1
    // SPDX-License-Identifier: GPL-3.0-only
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {UUPSUpgradeable} from "openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol";
    5
    import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol";
    6
    import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
    7
    import {StorageSlot} from "openzeppelin-contracts/utils/StorageSlot.sol";
    8
    
                                                    
                                                
    9
    using EnumerableSet for EnumerableSet.AddressSet;
    10
    
                                                    
                                                
    11
    /// @notice A mix-in for contract pausing, upgrading and admin management.
    12
    /// It can't be used directly, only via a proxy. It uses the upgrade-safe ERC-1967 storage scheme.
    13
    ///
    14
    /// Managed uses the ERC-1967 admin slot to store the admin address.
    15
    /// All instances of the contracts have admin address `0x00` and are forever paused.
    16
    /// When a proxy uses such contract via delegation, the proxy should define
    17
    /// the initial admin address and the contract is initially unpaused.
    18
    abstract contract Managed is UUPSUpgradeable {
    19
        /// @notice The pointer to the storage slot holding a single `ManagedStorage` structure.
    20
    ✓ 1
        bytes32 private immutable _managedStorageSlot = _erc1967Slot("eip1967.managed.storage");
    21
    
                                                    
                                                
    22
        /// @notice Emitted when a new admin of the contract is proposed.
    23
        /// The proposed admin must call `acceptAdmin` to finalize the change.
    24
        /// @param currentAdmin The current admin address.
    25
        /// @param newAdmin The proposed admin address.
    26
        event NewAdminProposed(address indexed currentAdmin, address indexed newAdmin);
    27
    
                                                    
                                                
    28
        /// @notice Emitted when the pauses role is granted.
    29
        /// @param pauser The address that the pauser role was granted to.
    30
        /// @param admin The address of the admin that triggered the change.
    31
        event PauserGranted(address indexed pauser, address indexed admin);
    32
    
                                                    
                                                
    33
        /// @notice Emitted when the pauses role is revoked.
    34
        /// @param pauser The address that the pauser role was revoked from.
    35
        /// @param admin The address of the admin that triggered the change.
    36
        event PauserRevoked(address indexed pauser, address indexed admin);
    37
    
                                                    
                                                
    38
        /// @notice Emitted when the pause is triggered.
    39
        /// @param pauser The address that triggered the change.
    40
        event Paused(address indexed pauser);
    41
    
                                                    
                                                
    42
        /// @notice Emitted when the pause is lifted.
    43
        /// @param pauser The address that triggered the change.
    44
        event Unpaused(address indexed pauser);
    45
    
                                                    
                                                
    46
        struct ManagedStorage {
    47
            bool isPaused;
    48
            EnumerableSet.AddressSet pausers;
    49
            address proposedAdmin;
    50
        }
    51
    
                                                    
                                                
    52
        /// @notice Throws if called by any caller other than the admin.
    53
        modifier onlyAdmin() {
    54
            require(admin() == msg.sender, "Caller not the admin");
    55
            _;
    56
        }
    57
    
                                                    
                                                
    58
        /// @notice Throws if called by any caller other than the admin or a pauser.
    59
        modifier onlyAdminOrPauser() {
    60
            require(admin() == msg.sender || isPauser(msg.sender), "Caller not the admin or a pauser");
    61
            _;
    62
        }
    63
    
                                                    
                                                
    64
        /// @notice Modifier to make a function callable only when the contract is not paused.
    65
        modifier whenNotPaused() {
    66
    ✓ 43.2M
            require(!isPaused(), "Contract paused");
    67
    ✓ 28.4M
            _;
    68
        }
    69
    
                                                    
                                                
    70
        /// @notice Modifier to make a function callable only when the contract is paused.
    71
        modifier whenPaused() {
    72
            require(isPaused(), "Contract not paused");
    73
            _;
    74
        }
    75
    
                                                    
                                                
    76
        /// @notice Initializes the contract in paused state and with no admin.
    77
        /// The contract instance can be used only as a call delegation target for a proxy.
    78
        constructor() {
    79
    ✓ 1
            _managedStorage().isPaused = true;
    80
        }
    81
    
                                                    
                                                
    82
        /// @notice Returns the current implementation address.
    83
        function implementation() public view returns (address) {
    84
            return _getImplementation();
    85
        }
    86
    
                                                    
                                                
    87
        /// @notice Returns the address of the current admin.
    88
        function admin() public view returns (address) {
    89
            return _getAdmin();
    90
        }
    91
    
                                                    
                                                
    92
        /// @notice Returns the proposed address to change the admin to.
    93
        function proposedAdmin() public view returns (address) {
    94
            return _managedStorage().proposedAdmin;
    95
        }
    96
    
                                                    
                                                
    97
        /// @notice Proposes a change of the admin of the contract.
    98
        /// The proposed new admin must call `acceptAdmin` to finalize the change.
    99
        /// To cancel a proposal propose a different address, e.g. the zero address.
    100
        /// Can only be called by the current admin.
    101
        /// @param newAdmin The proposed admin address.
    102
        function proposeNewAdmin(address newAdmin) public onlyAdmin {
    103
            emit NewAdminProposed(msg.sender, newAdmin);
    104
            _managedStorage().proposedAdmin = newAdmin;
    105
        }
    106
    
                                                    
                                                
    107
        /// @notice Applies a proposed change of the admin of the contract.
    108
        /// Sets the proposed admin to the zero address.
    109
        /// Can only be called by the proposed admin.
    110
        function acceptAdmin() public {
    111
            require(proposedAdmin() == msg.sender, "Caller not the proposed admin");
    112
            _updateAdmin(msg.sender);
    113
        }
    114
    
                                                    
                                                
    115
        /// @notice Changes the admin of the contract to address zero.
    116
        /// It's no longer possible to change the admin or upgrade the contract afterwards.
    117
        /// Can only be called by the current admin.
    118
        function renounceAdmin() public onlyAdmin {
    119
            _updateAdmin(address(0));
    120
        }
    121
    
                                                    
                                                
    122
        /// @notice Sets the current admin of the contract and clears the proposed admin.
    123
        /// @param newAdmin The admin address being set. Can be the zero address.
    124
        function _updateAdmin(address newAdmin) internal {
    125
            emit AdminChanged(admin(), newAdmin);
    126
            _managedStorage().proposedAdmin = address(0);
    127
            StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
    128
        }
    129
    
                                                    
                                                
    130
        /// @notice Grants the pauser role to an address. Callable only by the admin.
    131
        /// @param pauser The granted address.
    132
        function grantPauser(address pauser) public onlyAdmin {
    133
            require(_managedStorage().pausers.add(pauser), "Address already is a pauser");
    134
            emit PauserGranted(pauser, msg.sender);
    135
        }
    136
    
                                                    
                                                
    137
        /// @notice Revokes the pauser role from an address. Callable only by the admin.
    138
        /// @param pauser The revoked address.
    139
        function revokePauser(address pauser) public onlyAdmin {
    140
            require(_managedStorage().pausers.remove(pauser), "Address is not a pauser");
    141
            emit PauserRevoked(pauser, msg.sender);
    142
        }
    143
    
                                                    
                                                
    144
        /// @notice Checks if an address is a pauser.
    145
        /// @param pauser The checked address.
    146
        /// @return isAddrPauser True if the address is a pauser.
    147
        function isPauser(address pauser) public view returns (bool isAddrPauser) {
    148
            return _managedStorage().pausers.contains(pauser);
    149
        }
    150
    
                                                    
                                                
    151
        /// @notice Returns all the addresses with the pauser role.
    152
        /// @return pausersList The list of all the pausers, ordered arbitrarily.
    153
        /// The list's order may change after granting or revoking the pauser role.
    154
        function allPausers() public view returns (address[] memory pausersList) {
    155
            return _managedStorage().pausers.values();
    156
        }
    157
    
                                                    
                                                
    158
        /// @notice Returns true if the contract is paused, and false otherwise.
    159
    ✓ 131.8M
        function isPaused() public view returns (bool) {
    160
    ✓ 131.8M
            return _managedStorage().isPaused;
    161
        }
    162
    
                                                    
                                                
    163
        /// @notice Triggers stopped state. Callable only by the admin or a pauser.
    164
        function pause() public onlyAdminOrPauser whenNotPaused {
    165
            _managedStorage().isPaused = true;
    166
            emit Paused(msg.sender);
    167
        }
    168
    
                                                    
                                                
    169
        /// @notice Returns to normal state. Callable only by the admin or a pauser.
    170
        function unpause() public onlyAdminOrPauser whenPaused {
    171
            _managedStorage().isPaused = false;
    172
            emit Unpaused(msg.sender);
    173
        }
    174
    
                                                    
                                                
    175
        /// @notice Calculates the quasi ERC-1967 slot pointer.
    176
        /// @param name The name of the slot, should be globally unique
    177
        /// @return slot The slot pointer
    178
    ✓ 138.8M
        function _erc1967Slot(string memory name) internal pure returns (bytes32 slot) {
    179
            // The original ERC-1967 subtracts 1 from the hash to get 1 storage slot
    180
            // under an index without a known hash preimage which is enough to store a single address.
    181
            // This implementation subtracts 1024 to get 1024 slots without a known preimage
    182
            // allowing securely storing much larger structures.
    183
    ✓ 138.8M
            return bytes32(uint256(keccak256(bytes(name))) - 1024);
    184
        }
    185
    
                                                    
                                                
    186
        /// @notice Returns the Managed storage.
    187
        /// @return storageRef The storage.
    188
    ✓ 131.8M
        function _managedStorage() internal view returns (ManagedStorage storage storageRef) {
    189
    ✓ 131.8M
            bytes32 slot = _managedStorageSlot;
    190
            // slither-disable-next-line assembly
    191
            assembly {
    192
    ✓ 131.8M
                storageRef.slot := slot
    193
            }
    194
        }
    195
    
                                                    
                                                
    196
        /// @notice Authorizes the contract upgrade. See `UUPSUpgradeable` docs for more details.
    197
        function _authorizeUpgrade(address /* newImplementation */ ) internal view override onlyAdmin {
    198
            return;
    199
        }
    200
    }
    201
    
                                                    
                                                
    202
    /// @notice A generic proxy for contracts implementing `Managed`.
    203
    contract ManagedProxy is ERC1967Proxy {
    204
        constructor(Managed logic, address admin) ERC1967Proxy(address(logic), new bytes(0)) {
    205
            _changeAdmin(admin);
    206
        }
    207
    }
    208
    
                                                    
                                                
    100.0% src/Splits.sol
    Lines covered: 72 / 72 (100.0%)
    1
    // SPDX-License-Identifier: GPL-3.0-only
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
    5
    
                                                    
                                                
    6
    /// @notice A splits receiver
    7
    struct SplitsReceiver {
    8
        /// @notice The account ID.
    9
        uint256 accountId;
    10
        /// @notice The splits weight. Must never be zero.
    11
        /// The account will be getting `weight / _TOTAL_SPLITS_WEIGHT`
    12
        /// share of the funds collected by the splitting account.
    13
        uint32 weight;
    14
    }
    15
    
                                                    
                                                
    16
    /// @notice Splits can keep track of at most `type(uint128).max`
    17
    /// which is `2 ^ 128 - 1` units of each ERC-20 token.
    18
    /// It's up to the caller to guarantee that this limit is never exceeded,
    19
    /// failing to do so may result in a total protocol collapse.
    20
    abstract contract Splits {
    21
        /// @notice Maximum number of splits receivers of a single account.
    22
        /// Limits the cost of splitting.
    23
    ✓ 8.0M
        uint256 internal constant _MAX_SPLITS_RECEIVERS = 200;
    24
        /// @notice The total splits weight of an account.
    25
    ✓ 8.3M
        uint32 internal constant _TOTAL_SPLITS_WEIGHT = 1_000_000;
    26
        /// @notice The amount the contract can keep track of each ERC-20 token.
    27
        // slither-disable-next-line unused-state
    28
        uint128 internal constant _MAX_SPLITS_BALANCE = type(uint128).max;
    29
        /// @notice The storage slot holding a single `SplitsStorage` structure.
    30
        bytes32 private immutable _splitsStorageSlot;
    31
    
                                                    
                                                
    32
        /// @notice Emitted when an account collects funds
    33
        /// @param accountId The account ID.
    34
        /// @param erc20 The used ERC-20 token.
    35
        /// @param collected The collected amount
    36
        event Collected(uint256 indexed accountId, IERC20 indexed erc20, uint128 collected);
    37
    
                                                    
                                                
    38
        /// @notice Emitted when funds are split from an account to a receiver.
    39
        /// This is caused by the account collecting received funds.
    40
        /// @param accountId The account ID.
    41
        /// @param receiver The splits receiver account ID
    42
        /// @param erc20 The used ERC-20 token.
    43
        /// @param amt The amount split to the receiver
    44
        event Split(
    45
            uint256 indexed accountId, uint256 indexed receiver, IERC20 indexed erc20, uint128 amt
    46
        );
    47
    
                                                    
                                                
    48
        /// @notice Emitted when funds are made collectable after splitting.
    49
        /// @param accountId The account ID.
    50
        /// @param erc20 The used ERC-20 token.
    51
        /// @param amt The amount made collectable for the account
    52
        /// on top of what was collectable before.
    53
        event Collectable(uint256 indexed accountId, IERC20 indexed erc20, uint128 amt);
    54
    
                                                    
                                                
    55
        /// @notice Emitted when funds are given from the account to the receiver.
    56
        /// @param accountId The account ID.
    57
        /// @param receiver The receiver account ID.
    58
        /// @param erc20 The used ERC-20 token.
    59
        /// @param amt The given amount
    60
        event Given(
    61
            uint256 indexed accountId, uint256 indexed receiver, IERC20 indexed erc20, uint128 amt
    62
        );
    63
    
                                                    
                                                
    64
        /// @notice Emitted when the account's splits are updated.
    65
        /// @param accountId The account ID.
    66
        /// @param receiversHash The splits receivers list hash
    67
        event SplitsSet(uint256 indexed accountId, bytes32 indexed receiversHash);
    68
    
                                                    
                                                
    69
        /// @notice Emitted when an account is seen in a splits receivers list.
    70
        /// @param receiversHash The splits receivers list hash
    71
        /// @param accountId The account ID.
    72
        /// @param weight The splits weight. Must never be zero.
    73
        /// The account will be getting `weight / _TOTAL_SPLITS_WEIGHT`
    74
        /// share of the funds collected by the splitting account.
    75
        event SplitsReceiverSeen(
    76
            bytes32 indexed receiversHash, uint256 indexed accountId, uint32 weight
    77
        );
    78
    
                                                    
                                                
    79
        struct SplitsStorage {
    80
            /// @notice Account splits states.
    81
            mapping(uint256 accountId => SplitsState) splitsStates;
    82
        }
    83
    
                                                    
                                                
    84
        struct SplitsState {
    85
            /// @notice The account's splits configuration hash, see `hashSplits`.
    86
            bytes32 splitsHash;
    87
            /// @notice The account's splits balances.
    88
            mapping(IERC20 erc20 => SplitsBalance) balances;
    89
        }
    90
    
                                                    
                                                
    91
        struct SplitsBalance {
    92
            /// @notice The not yet split balance, must be split before collecting by the account.
    93
            uint128 splittable;
    94
            /// @notice The already split balance, ready to be collected by the account.
    95
            uint128 collectable;
    96
        }
    97
    
                                                    
                                                
    98
        /// @param splitsStorageSlot The storage slot to holding a single `SplitsStorage` structure.
    99
        constructor(bytes32 splitsStorageSlot) {
    100
    ✓ 1
            _splitsStorageSlot = splitsStorageSlot;
    101
        }
    102
    
                                                    
                                                
    103
        function _addSplittable(uint256 accountId, IERC20 erc20, uint128 amt) internal {
    104
            // This will not overflow if the requirement of tracking in the contract
    105
            // no more than `_MAX_SPLITS_BALANCE` of each token is followed.
    106
    ✓ 6.3M
            _splitsStorage().splitsStates[accountId].balances[erc20].splittable += amt;
    107
        }
    108
    
                                                    
                                                
    109
        /// @notice Returns account's received but not split yet funds.
    110
        /// @param accountId The account ID.
    111
        /// @param erc20 The used ERC-20 token.
    112
        /// @return amt The amount received but not split yet.
    113
    ✓ 16.4M
        function _splittable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) {
    114
    ✓ 16.4M
            return _splitsStorage().splitsStates[accountId].balances[erc20].splittable;
    115
        }
    116
    
                                                    
                                                
    117
        /// @notice Calculate the result of splitting an amount using the current splits configuration.
    118
        /// @param accountId The account ID.
    119
        /// @param currReceivers The list of the account's current splits receivers.
    120
        /// It must be exactly the same as the last list set for the account with `_setSplits`.
    121
        /// If the splits have never been set, pass an empty array.
    122
        /// @param amount The amount being split.
    123
        /// @return collectableAmt The amount made collectable for the account
    124
        /// on top of what was collectable before.
    125
        /// @return splitAmt The amount split to the account's splits receivers
    126
        function _splitResult(uint256 accountId, SplitsReceiver[] memory currReceivers, uint128 amount)
    127
            internal
    128
            view
    129
    ✓ 1.2M
            returns (uint128 collectableAmt, uint128 splitAmt)
    130
        {
    131
    ✓ 1.2M
            _assertCurrSplits(accountId, currReceivers);
    132
    ✓ 1.2M
            if (amount == 0) {
    133
    ✓ 1.1M
                return (0, 0);
    134
            }
    135
            unchecked {
    136
    ✓ 101.6K
                uint256 splitsWeight = 0;
    137
    ✓ 171.9K
                for (uint256 i = currReceivers.length; i != 0;) {
    138
    ✓ 70.3K
                    splitsWeight += currReceivers[--i].weight;
    139
                }
    140
    ✓ 101.6K
                splitAmt = uint128(amount * splitsWeight / _TOTAL_SPLITS_WEIGHT);
    141
    ✓ 101.6K
                collectableAmt = amount - splitAmt;
    142
            }
    143
        }
    144
    
                                                    
                                                
    145
        /// @notice Splits the account's splittable funds among receivers.
    146
        /// The entire splittable balance of the given ERC-20 token is split.
    147
        /// All split funds are split using the current splits configuration.
    148
        /// @param accountId The account ID.
    149
        /// @param erc20 The used ERC-20 token.
    150
        /// @param currReceivers The list of the account's current splits receivers.
    151
        /// It must be exactly the same as the last list set for the account with `_setSplits`.
    152
        /// If the splits have never been set, pass an empty array.
    153
        /// @return collectableAmt The amount made collectable for the account
    154
        /// on top of what was collectable before.
    155
        /// @return splitAmt The amount split to the account's splits receivers
    156
        function _split(uint256 accountId, IERC20 erc20, SplitsReceiver[] memory currReceivers)
    157
            internal
    158
    ✓ 17.2M
            returns (uint128 collectableAmt, uint128 splitAmt)
    159
        {
    160
    ✓ 17.2M
            _assertCurrSplits(accountId, currReceivers);
    161
    ✓ 17.2M
            SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20];
    162
    
                                                    
                                                
    163
    ✓ 17.2M
            collectableAmt = balance.splittable;
    164
    ✓ 17.2M
            if (collectableAmt == 0) {
    165
    ✓ 13.3M
                return (0, 0);
    166
            }
    167
    ✓ 3.9M
            balance.splittable = 0;
    168
    
                                                    
                                                
    169
            unchecked {
    170
    ✓ 3.9M
                uint256 splitsWeight = 0;
    171
    ✓ 6.2M
                for (uint256 i = 0; i < currReceivers.length; i++) {
    172
    ✓ 2.4M
                    splitsWeight += currReceivers[i].weight;
    173
    ✓ 2.4M
                    uint128 currSplitAmt = splitAmt;
    174
    ✓ 2.4M
                    splitAmt = uint128(collectableAmt * splitsWeight / _TOTAL_SPLITS_WEIGHT);
    175
    ✓ 2.4M
                    currSplitAmt = splitAmt - currSplitAmt;
    176
    ✓ 2.4M
                    uint256 receiver = currReceivers[i].accountId;
    177
    ✓ 2.4M
                    _addSplittable(receiver, erc20, currSplitAmt);
    178
    ✓ 2.4M
                    emit Split(accountId, receiver, erc20, currSplitAmt);
    179
                }
    180
    ✓ 3.9M
                collectableAmt -= splitAmt;
    181
                // This will not overflow if the requirement of tracking in the contract
    182
                // no more than `_MAX_SPLITS_BALANCE` of each token is followed.
    183
    ✓ 3.9M
                balance.collectable += collectableAmt;
    184
            }
    185
    ✓ 3.9M
            emit Collectable(accountId, erc20, collectableAmt);
    186
        }
    187
    
                                                    
                                                
    188
        /// @notice Returns account's received funds already split and ready to be collected.
    189
        /// @param accountId The account ID.
    190
        /// @param erc20 The used ERC-20 token.
    191
        /// @return amt The collectable amount.
    192
    ✓ 7.7M
        function _collectable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) {
    193
    ✓ 7.7M
            return _splitsStorage().splitsStates[accountId].balances[erc20].collectable;
    194
        }
    195
    
                                                    
                                                
    196
        /// @notice Collects account's received already split funds.
    197
        /// @param accountId The account ID.
    198
        /// @param erc20 The used ERC-20 token.
    199
        /// @return amt The collected amount
    200
    ✓ 14.6M
        function _collect(uint256 accountId, IERC20 erc20) internal returns (uint128 amt) {
    201
    ✓ 14.6M
            SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20];
    202
    ✓ 14.6M
            amt = balance.collectable;
    203
    ✓ 14.6M
            balance.collectable = 0;
    204
    ✓ 14.6M
            emit Collected(accountId, erc20, amt);
    205
        }
    206
    
                                                    
                                                
    207
        /// @notice Gives funds from the account to the receiver.
    208
        /// The receiver can split and collect them immediately.
    209
        /// @param accountId The account ID.
    210
        /// @param receiver The receiver account ID.
    211
        /// @param erc20 The used ERC-20 token.
    212
        /// @param amt The given amount
    213
        function _give(uint256 accountId, uint256 receiver, IERC20 erc20, uint128 amt) internal {
    214
    ✓ 1.6M
            _addSplittable(receiver, erc20, amt);
    215
    ✓ 1.6M
            emit Given(accountId, receiver, erc20, amt);
    216
        }
    217
    
                                                    
                                                
    218
        /// @notice Sets the account splits configuration.
    219
        /// The configuration is common for all ERC-20 tokens.
    220
        /// Nothing happens to the currently splittable funds, but when they are split
    221
        /// after this function finishes, the new splits configuration will be used.
    222
        /// @param accountId The account ID.
    223
        /// @param receivers The list of the account's splits receivers to be set.
    224
        /// Must be sorted by the account IDs, without duplicate account IDs and without 0 weights.
    225
        /// Each splits receiver will be getting `weight / _TOTAL_SPLITS_WEIGHT`
    226
        /// share of the funds collected by the account.
    227
        /// If the sum of weights of all receivers is less than `_TOTAL_SPLITS_WEIGHT`,
    228
        /// some funds won't be split, but they will be left for the account to collect.
    229
        /// Fractions of tokens are always rounder either up or down depending on the amount
    230
        /// being split, the receiver's position on the list and the other receivers' weights.
    231
        /// It's valid to include the account's own `accountId` in the list of receivers,
    232
        /// but funds split to themselves return to their splittable balance and are not collectable.
    233
        /// This is usually unwanted, because if splitting is repeated,
    234
        /// funds split to themselves will be again split using the current configuration.
    235
        /// Splitting 100% to self effectively blocks splitting unless the configuration is updated.
    236
        function _setSplits(uint256 accountId, SplitsReceiver[] memory receivers) internal {
    237
    ✓ 13.1M
            SplitsState storage state = _splitsStorage().splitsStates[accountId];
    238
    ✓ 13.1M
            bytes32 newSplitsHash = _hashSplits(receivers);
    239
    ✓ 13.1M
            if (newSplitsHash == state.splitsHash) return;
    240
    ✓ 8.0M
            emit SplitsSet(accountId, newSplitsHash);
    241
    ✓ 8.0M
            _assertSplitsValid(receivers, newSplitsHash);
    242
    ✓ 6.5M
            state.splitsHash = newSplitsHash;
    243
        }
    244
    
                                                    
                                                
    245
        /// @notice Validates a list of splits receivers and emits events for them
    246
        /// @param receivers The list of splits receivers
    247
        /// @param receiversHash The hash of the list of splits receivers.
    248
        /// Must be sorted by the account IDs, without duplicate account IDs and without 0 weights.
    249
        function _assertSplitsValid(SplitsReceiver[] memory receivers, bytes32 receiversHash) private {
    250
            unchecked {
    251
    ✓ 8.0M
                require(receivers.length <= _MAX_SPLITS_RECEIVERS, "Too many splits receivers");
    252
    ✓ 8.0M
                uint256 totalWeight = 0;
    253
    ✓ 8.0M
                uint256 prevAccountId = 0;
    254
    ✓ 14.8M
                for (uint256 i = 0; i < receivers.length; i++) {
    255
    ✓ 7.0M
                    SplitsReceiver memory receiver = receivers[i];
    256
    ✓ 7.0M
                    uint32 weight = receiver.weight;
    257
    ✓ 7.0M
                    require(weight != 0, "Splits receiver weight is zero");
    258
    ✓ 6.9M
                    totalWeight += weight;
    259
    ✓ 6.9M
                    uint256 accountId = receiver.accountId;
    260
    ✓ 6.9M
                    if (accountId <= prevAccountId) require(i == 0, "Splits receivers not sorted");
    261
    ✓ 6.9M
                    prevAccountId = accountId;
    262
    ✓ 6.9M
                    emit SplitsReceiverSeen(receiversHash, accountId, weight);
    263
                }
    264
    ✓ 7.9M
                require(totalWeight <= _TOTAL_SPLITS_WEIGHT, "Splits weights sum too high");
    265
            }
    266
        }
    267
    
                                                    
                                                
    268
        /// @notice Asserts that the list of splits receivers is the account's currently used one.
    269
        /// @param accountId The account ID.
    270
        /// @param currReceivers The list of the account's current splits receivers.
    271
        /// If the splits have never been set, pass an empty array.
    272
        function _assertCurrSplits(uint256 accountId, SplitsReceiver[] memory currReceivers)
    273
            internal
    274
            view
    275
        {
    276
            require(
    277
    ✓ 18.3M
                _hashSplits(currReceivers) == _splitsHash(accountId), "Invalid current splits receivers"
    278
            );
    279
        }
    280
    
                                                    
                                                
    281
        /// @notice Current account's splits hash, see `hashSplits`.
    282
        /// @param accountId The account ID.
    283
        /// @return currSplitsHash The current account's splits hash
    284
    ✓ 18.3M
        function _splitsHash(uint256 accountId) internal view returns (bytes32 currSplitsHash) {
    285
    ✓ 18.3M
            return _splitsStorage().splitsStates[accountId].splitsHash;
    286
        }
    287
    
                                                    
                                                
    288
        /// @notice Calculates the hash of the list of splits receivers.
    289
        /// @param receivers The list of the splits receivers.
    290
        /// If the splits have never been set, pass an empty array.
    291
        /// @return receiversHash The hash of the list of splits receivers.
    292
        function _hashSplits(SplitsReceiver[] memory receivers)
    293
            internal
    294
            pure
    295
    ✓ 31.4M
            returns (bytes32 receiversHash)
    296
        {
    297
    ✓ 31.4M
            if (receivers.length == 0) {
    298
    ✓ 21.1M
                return bytes32(0);
    299
            }
    300
    ✓ 10.3M
            return keccak256(abi.encode(receivers));
    301
        }
    302
    
                                                    
                                                
    303
        /// @notice Returns the Splits storage.
    304
        /// @return splitsStorage The storage.
    305
    ✓ 93.5M
        function _splitsStorage() private view returns (SplitsStorage storage splitsStorage) {
    306
    ✓ 93.5M
            bytes32 slot = _splitsStorageSlot;
    307
            // slither-disable-next-line assembly
    308
            assembly {
    309
    ✓ 93.5M
                splitsStorage.slot := slot
    310
            }
    311
        }
    312
    }
    313
    
                                                    
                                                
    99.7% src/Streams.sol
    Lines covered: 367 / 368 (99.7%)
    1
    // SPDX-License-Identifier: GPL-3.0-only
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol";
    5
    
                                                    
                                                
    6
    /// @notice A stream receiver
    7
    struct StreamReceiver {
    8
        /// @notice The account ID.
    9
        uint256 accountId;
    10
        /// @notice The stream configuration.
    11
        StreamConfig config;
    12
    }
    13
    
                                                    
                                                
    14
    /// @notice The sender streams history entry, used when squeezing streams.
    15
    struct StreamsHistory {
    16
        /// @notice Streams receivers list hash, see `_hashStreams`.
    17
        /// If it's non-zero, `receivers` must be empty.
    18
        bytes32 streamsHash;
    19
        /// @notice The streams receivers. If it's non-empty, `streamsHash` must be `0`.
    20
        /// If it's empty, this history entry will be skipped when squeezing streams
    21
        /// and `streamsHash` will be used when verifying the streams history validity.
    22
        /// Skipping a history entry allows cutting gas usage on analysis
    23
        /// of parts of the streams history which are not worth squeezing.
    24
        /// The hash of an empty receivers list is `0`, so when the sender updates
    25
        /// their receivers list to be empty, the new `StreamsHistory` entry will have
    26
        /// both the `streamsHash` equal to `0` and the `receivers` empty making it always skipped.
    27
        /// This is fine, because there can't be any funds to squeeze from that entry anyway.
    28
        StreamReceiver[] receivers;
    29
        /// @notice The time when streams have been configured
    30
        uint32 updateTime;
    31
        /// @notice The maximum end time of streaming.
    32
        uint32 maxEnd;
    33
    }
    34
    
                                                    
                                                
    35
    /// @notice Describes a streams configuration.
    36
    /// It's a 256-bit integer constructed by concatenating the configuration parameters:
    37
    /// `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`.
    38
    /// `streamId` is an arbitrary number used to identify a stream.
    39
    /// It's a part of the configuration but the protocol doesn't use it.
    40
    /// `amtPerSec` is the amount per second being streamed. Must never be zero.
    41
    /// It must have additional `Streams._AMT_PER_SEC_EXTRA_DECIMALS` decimals and can have fractions.
    42
    /// To achieve that its value must be multiplied by `Streams._AMT_PER_SEC_MULTIPLIER`.
    43
    /// `start` is the timestamp when streaming should start.
    44
    /// If zero, use the timestamp when the stream is configured.
    45
    /// `duration` is the duration of streaming.
    46
    /// If zero, stream until balance runs out.
    47
    type StreamConfig is uint256;
    48
    
                                                    
                                                
    49
    using StreamConfigImpl for StreamConfig global;
    50
    
                                                    
                                                
    51
    ✓ 6
    library StreamConfigImpl {
    52
        /// @notice Create a new StreamConfig.
    53
        /// @param streamId_ An arbitrary number used to identify a stream.
    54
        /// It's a part of the configuration but the protocol doesn't use it.
    55
        /// @param amtPerSec_ The amount per second being streamed. Must never be zero.
    56
        /// It must have additional `Streams._AMT_PER_SEC_EXTRA_DECIMALS`
    57
        /// decimals and can have fractions.
    58
        /// To achieve that the passed value must be multiplied by `Streams._AMT_PER_SEC_MULTIPLIER`.
    59
        /// @param start_ The timestamp when streaming should start.
    60
        /// If zero, use the timestamp when the stream is configured.
    61
        /// @param duration_ The duration of streaming. If zero, stream until the balance runs out.
    62
        function create(uint32 streamId_, uint160 amtPerSec_, uint32 start_, uint32 duration_)
    63
            internal
    64
            pure
    65
    ✓ 10.5M
            returns (StreamConfig)
    66
        {
    67
            // By assignment we get `config` value:
    68
            // `zeros (224 bits) | streamId (32 bits)`
    69
    ✓ 10.5M
            uint256 config = streamId_;
    70
            // By bit shifting we get `config` value:
    71
            // `zeros (64 bits) | streamId (32 bits) | zeros (160 bits)`
    72
            // By bit masking we get `config` value:
    73
            // `zeros (64 bits) | streamId (32 bits) | amtPerSec (160 bits)`
    74
    ✓ 10.5M
            config = (config << 160) | amtPerSec_;
    75
            // By bit shifting we get `config` value:
    76
            // `zeros (32 bits) | streamId (32 bits) | amtPerSec (160 bits) | zeros (32 bits)`
    77
            // By bit masking we get `config` value:
    78
            // `zeros (32 bits) | streamId (32 bits) | amtPerSec (160 bits) | start (32 bits)`
    79
    ✓ 10.5M
            config = (config << 32) | start_;
    80
            // By bit shifting we get `config` value:
    81
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | zeros (32 bits)`
    82
            // By bit masking we get `config` value:
    83
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`
    84
    ✓ 10.5M
            config = (config << 32) | duration_;
    85
    ✓ 10.5M
            return StreamConfig.wrap(config);
    86
        }
    87
    
                                                    
                                                
    88
        /// @notice Extracts streamId from a `StreamConfig`
    89
        function streamId(StreamConfig config) internal pure returns (uint32) {
    90
            // `config` has value:
    91
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`
    92
            // By bit shifting we get value:
    93
            // `zeros (224 bits) | streamId (32 bits)`
    94
            // By casting down we get value:
    95
            // `streamId (32 bits)`
    96
            return uint32(StreamConfig.unwrap(config) >> 224);
    97
        }
    98
    
                                                    
                                                
    99
        /// @notice Extracts amtPerSec from a `StreamConfig`
    100
    ✓ 148.5M
        function amtPerSec(StreamConfig config) internal pure returns (uint160) {
    101
            // `config` has value:
    102
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`
    103
            // By bit shifting we get value:
    104
            // `zeros (64 bits) | streamId (32 bits) | amtPerSec (160 bits)`
    105
            // By casting down we get value:
    106
            // `amtPerSec (160 bits)`
    107
    ✓ 148.5M
            return uint160(StreamConfig.unwrap(config) >> 64);
    108
        }
    109
    
                                                    
                                                
    110
        /// @notice Extracts start from a `StreamConfig`
    111
    ✓ 126.3M
        function start(StreamConfig config) internal pure returns (uint32) {
    112
            // `config` has value:
    113
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`
    114
            // By bit shifting we get value:
    115
            // `zeros (32 bits) | streamId (32 bits) | amtPerSec (160 bits) | start (32 bits)`
    116
            // By casting down we get value:
    117
            // `start (32 bits)`
    118
    ✓ 126.3M
            return uint32(StreamConfig.unwrap(config) >> 32);
    119
        }
    120
    
                                                    
                                                
    121
        /// @notice Extracts duration from a `StreamConfig`
    122
    ✓ 126.3M
        function duration(StreamConfig config) internal pure returns (uint32) {
    123
            // `config` has value:
    124
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`
    125
            // By casting down we get value:
    126
            // `duration (32 bits)`
    127
    ✓ 126.3M
            return uint32(StreamConfig.unwrap(config));
    128
        }
    129
    
                                                    
                                                
    130
        /// @notice Compares two `StreamConfig`s.
    131
        /// First compares `streamId`s, then `amtPerSec`s, then `start`s and finally `duration`s.
    132
        /// @return isLower True if `config` is strictly lower than `otherConfig`.
    133
        function lt(StreamConfig config, StreamConfig otherConfig)
    134
            internal
    135
            pure
    136
    ✓ 4.5M
            returns (bool isLower)
    137
        {
    138
            // Both configs have value:
    139
            // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`
    140
            // Comparing them as integers is equivalent to comparing their fields from left to right.
    141
    ✓ 4.5M
            return StreamConfig.unwrap(config) < StreamConfig.unwrap(otherConfig);
    142
        }
    143
    }
    144
    
                                                    
                                                
    145
    /// @notice Streams can keep track of at most `type(int128).max`
    146
    /// which is `2 ^ 127 - 1` units of each ERC-20 token.
    147
    /// It's up to the caller to guarantee that this limit is never exceeded,
    148
    /// failing to do so may result in a total protocol collapse.
    149
    abstract contract Streams {
    150
        /// @notice Maximum number of streams receivers of a single account.
    151
        /// Limits cost of changes in streams configuration.
    152
    ✓ 28.8M
        uint256 internal constant _MAX_STREAMS_RECEIVERS = 100;
    153
        /// @notice The additional decimals for all amtPerSec values.
    154
        uint8 internal constant _AMT_PER_SEC_EXTRA_DECIMALS = 9;
    155
        /// @notice The multiplier for all amtPerSec values. It's `10 ** _AMT_PER_SEC_EXTRA_DECIMALS`.
    156
    ✓ 96.6M
        uint160 internal constant _AMT_PER_SEC_MULTIPLIER = 1_000_000_000;
    157
        /// @notice The amount the contract can keep track of each ERC-20 token.
    158
    ✓ 8.6M
        uint128 internal constant _MAX_STREAMS_BALANCE = uint128(type(int128).max);
    159
        /// @notice On every timestamp `T`, which is a multiple of `cycleSecs`, the receivers
    160
        /// gain access to streams received during `T - cycleSecs` to `T - 1`.
    161
        /// Always higher than 1.
    162
        // slither-disable-next-line naming-convention
    163
        uint32 internal immutable _cycleSecs;
    164
        /// @notice The minimum amtPerSec of a stream. It's 1 token per cycle.
    165
        // slither-disable-next-line naming-convention
    166
        uint160 internal immutable _minAmtPerSec;
    167
        /// @notice The storage slot holding a single `StreamsStorage` structure.
    168
        bytes32 private immutable _streamsStorageSlot;
    169
    
                                                    
                                                
    170
        /// @notice Emitted when the streams configuration of an account is updated.
    171
        /// @param accountId The account ID.
    172
        /// @param erc20 The used ERC-20 token.
    173
        /// @param receiversHash The streams receivers list hash
    174
        /// @param streamsHistoryHash The streams history hash that was valid right before the update.
    175
        /// @param balance The account's streams balance. These funds will be streamed to the receivers.
    176
        /// @param maxEnd The maximum end time of streaming, when funds run out.
    177
        /// If funds run out after the timestamp `type(uint32).max`, it's set to `type(uint32).max`.
    178
        /// If the balance is 0 or there are no receivers, it's set to the current timestamp.
    179
        event StreamsSet(
    180
            uint256 indexed accountId,
    181
            IERC20 indexed erc20,
    182
            bytes32 indexed receiversHash,
    183
            bytes32 streamsHistoryHash,
    184
            uint128 balance,
    185
            uint32 maxEnd
    186
        );
    187
    
                                                    
                                                
    188
        /// @notice Emitted when an account is seen in a streams receivers list.
    189
        /// @param receiversHash The streams receivers list hash
    190
        /// @param accountId The account ID.
    191
        /// @param config The streams configuration.
    192
        event StreamReceiverSeen(
    193
            bytes32 indexed receiversHash, uint256 indexed accountId, StreamConfig config
    194
        );
    195
    
                                                    
                                                
    196
        /// @notice Emitted when streams are received.
    197
        /// @param accountId The account ID.
    198
        /// @param erc20 The used ERC-20 token.
    199
        /// @param amt The received amount.
    200
        /// @param receivableCycles The number of cycles which still can be received.
    201
        event ReceivedStreams(
    202
            uint256 indexed accountId, IERC20 indexed erc20, uint128 amt, uint32 receivableCycles
    203
        );
    204
    
                                                    
                                                
    205
        /// @notice Emitted when streams are squeezed.
    206
        /// @param accountId The squeezing account ID.
    207
        /// @param erc20 The used ERC-20 token.
    208
        /// @param senderId The ID of the streaming account from whom funds are squeezed.
    209
        /// @param amt The squeezed amount.
    210
        /// @param streamsHistoryHashes The history hashes of all squeezed streams history entries.
    211
        /// Each history hash matches `streamsHistoryHash` emitted in its `StreamsSet`
    212
        /// when the squeezed streams configuration was set.
    213
        /// Sorted in the oldest streams configuration to the newest.
    214
        event SqueezedStreams(
    215
            uint256 indexed accountId,
    216
            IERC20 indexed erc20,
    217
            uint256 indexed senderId,
    218
            uint128 amt,
    219
            bytes32[] streamsHistoryHashes
    220
        );
    221
    
                                                    
                                                
    222
        struct StreamsStorage {
    223
            /// @notice Account streams states.
    224
            mapping(IERC20 erc20 => mapping(uint256 accountId => StreamsState)) states;
    225
        }
    226
    
                                                    
                                                
    227
        struct StreamsState {
    228
            /// @notice The streams history hash, see `_hashStreamsHistory`.
    229
            bytes32 streamsHistoryHash;
    230
            /// @notice The next squeezable timestamps.
    231
            /// Each `N`th element of the array is the next squeezable timestamp
    232
            /// of the `N`th sender's streams configuration in effect in the current cycle.
    233
            mapping(uint256 accountId => uint32[2 ** 32]) nextSqueezed;
    234
            /// @notice The streams receivers list hash, see `_hashStreams`.
    235
            bytes32 streamsHash;
    236
            /// @notice The next cycle to be received
    237
            uint32 nextReceivableCycle;
    238
            /// @notice The time when streams have been configured for the last time.
    239
            uint32 updateTime;
    240
            /// @notice The maximum end time of streaming.
    241
            uint32 maxEnd;
    242
            /// @notice The balance when streams have been configured for the last time.
    243
            uint128 balance;
    244
            /// @notice The number of streams configurations seen in the cycle
    245
            /// during which streams have been configured for the last time.
    246
            uint32 lastUpdatedCycleConfigs;
    247
            /// @notice The changes of the received amounts in each cycle.
    248
            /// The keys are cycles, each cycle `C` becomes receivable on timestamp `C * cycleSecs`.
    249
            /// Each cycle is described by 2 deltas, the cycle's `thisCycle` and the previous cycle's
    250
            /// `nextCycle`, so to get the change of the amount sent during the cycle `C`,
    251
            /// you need to calculate `amtDeltas[C].thisCycle + amtDeltas[C-1].nextCycle`.
    252
            /// To calculate the absolute amount streamed in the cycle `C`, you need to take the
    253
            /// amount streamed during the cycle `C - 1` and add the change applied in the cycle `C`.
    254
            /// Values for cycles before `nextReceivableCycle` are guaranteed to be zeroed.
    255
            /// This means that the value of `amtDeltas[nextReceivableCycle].thisCycle` is always
    256
            /// relative to 0 or in other words it's an absolute value independent from other cycles.
    257
            mapping(uint32 cycle => AmtDelta) amtDeltas;
    258
        }
    259
    
                                                    
                                                
    260
        /// @notice The change of the received amounts in the given cycle and the cycle after it.
    261
        struct AmtDelta {
    262
            /// @notice Amount delta applied in this cycle.
    263
            int128 thisCycle;
    264
            /// @notice Amount delta applied on the next cycle.
    265
            int128 nextCycle;
    266
        }
    267
    
                                                    
                                                
    268
        /// @param cycleSecs The length of cycleSecs to be used in the contract instance.
    269
        /// Low value makes funds more available by shortening the average time
    270
        /// of funds being frozen between being taken from the accounts'
    271
        /// streams balance and being receivable by their receivers.
    272
        /// High value makes receiving cheaper by making it process less cycles for a given time range.
    273
        /// Must be higher than 1.
    274
        /// @param streamsStorageSlot The storage slot to holding a single `StreamsStorage` structure.
    275
        constructor(uint32 cycleSecs, bytes32 streamsStorageSlot) {
    276
    ✓ 1
            require(cycleSecs > 1, "Cycle length too low");
    277
    ✓ 1
            _cycleSecs = cycleSecs;
    278
    ✓ 1
            _minAmtPerSec = (_AMT_PER_SEC_MULTIPLIER + cycleSecs - 1) / cycleSecs;
    279
    ✓ 1
            _streamsStorageSlot = streamsStorageSlot;
    280
        }
    281
    
                                                    
                                                
    282
        /// @notice Receive streams from unreceived cycles of the account.
    283
        /// Received streams cycles won't need to be analyzed ever again.
    284
        /// @param accountId The account ID.
    285
        /// @param erc20 The used ERC-20 token.
    286
        /// @param maxCycles The maximum number of received streams cycles.
    287
        /// If too low, receiving will be cheap, but may not cover many cycles.
    288
        /// If too high, receiving may become too expensive to fit in a single transaction.
    289
        /// @return receivedAmt The received amount
    290
        function _receiveStreams(uint256 accountId, IERC20 erc20, uint32 maxCycles)
    291
            internal
    292
    ✓ 13.5M
            returns (uint128 receivedAmt)
    293
        {
    294
    ✓ 13.5M
            uint32 receivableCycles;
    295
    ✓ 13.5M
            uint32 fromCycle;
    296
    ✓ 13.5M
            uint32 toCycle;
    297
    ✓ 13.5M
            int128 finalAmtPerCycle;
    298
            (receivedAmt, receivableCycles, fromCycle, toCycle, finalAmtPerCycle) =
    299
    ✓ 13.5M
                _receiveStreamsResult(accountId, erc20, maxCycles);
    300
    ✓ 13.5M
            if (fromCycle != toCycle) {
    301
    ✓ 7.9M
                StreamsState storage state = _streamsStorage().states[erc20][accountId];
    302
    ✓ 7.9M
                state.nextReceivableCycle = toCycle;
    303
    ✓ 7.9M
                mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas;
    304
                unchecked {
    305
    ✓ 564.4M
                    for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) {
    306
    ✓ 556.5M
                        delete amtDeltas[cycle];
    307
                    }
    308
                    // The next cycle delta must be relative to the last received cycle, which deltas
    309
                    // got zeroed. In other words the next cycle delta must be an absolute value.
    310
    ✓ 7.9M
                    if (finalAmtPerCycle != 0) {
    311
    ✓ 236.3K
                        amtDeltas[toCycle].thisCycle += finalAmtPerCycle;
    312
                    }
    313
                }
    314
            }
    315
    ✓ 13.5M
            emit ReceivedStreams(accountId, erc20, receivedAmt, receivableCycles);
    316
        }
    317
    
                                                    
                                                
    318
        /// @notice Calculate effects of calling `_receiveStreams` with the given parameters.
    319
        /// @param accountId The account ID.
    320
        /// @param erc20 The used ERC-20 token.
    321
        /// @param maxCycles The maximum number of received streams cycles.
    322
        /// If too low, receiving will be cheap, but may not cover many cycles.
    323
        /// If too high, receiving may become too expensive to fit in a single transaction.
    324
        /// @return receivedAmt The amount which would be received
    325
        /// @return receivableCycles The number of cycles which would still be receivable after the call
    326
        /// @return fromCycle The cycle from which funds would be received
    327
        /// @return toCycle The cycle to which funds would be received
    328
        /// @return amtPerCycle The amount per cycle when `toCycle` starts.
    329
        function _receiveStreamsResult(uint256 accountId, IERC20 erc20, uint32 maxCycles)
    330
            internal
    331
            view
    332
            returns (
    333
    ✓ 37.9M
                uint128 receivedAmt,
    334
    ✓ 37.9M
                uint32 receivableCycles,
    335
    ✓ 37.9M
                uint32 fromCycle,
    336
    ✓ 37.9M
                uint32 toCycle,
    337
    ✓ 37.9M
                int128 amtPerCycle
    338
            )
    339
        {
    340
            unchecked {
    341
    ✓ 37.9M
                (fromCycle, toCycle) = _receivableStreamsCyclesRange(accountId, erc20);
    342
    ✓ 37.9M
                if (toCycle - fromCycle > maxCycles) {
    343
    ✓ 321.0K
                    receivableCycles = toCycle - fromCycle - maxCycles;
    344
    ✓ 321.0K
                    toCycle -= receivableCycles;
    345
                }
    346
    ✓ 37.9M
                mapping(uint32 cycle => AmtDelta) storage amtDeltas =
    347
    ✓ 37.9M
                    _streamsStorage().states[erc20][accountId].amtDeltas;
    348
    ✓ 1.8B
                for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) {
    349
    ✓ 1.8B
                    AmtDelta memory amtDelta = amtDeltas[cycle];
    350
    ✓ 1.8B
                    amtPerCycle += amtDelta.thisCycle;
    351
                    // This will not overflow if the requirement of tracking in the contract
    352
                    // no more than `_MAX_STREAMS_BALANCE` of each token is followed.
    353
    ✓ 1.8B
                    receivedAmt += uint128(amtPerCycle);
    354
    ✓ 1.8B
                    amtPerCycle += amtDelta.nextCycle;
    355
                }
    356
            }
    357
        }
    358
    
                                                    
                                                
    359
        /// @notice Counts cycles from which streams can be received.
    360
        /// This function can be used to detect that there are
    361
        /// too many cycles to analyze in a single transaction.
    362
        /// @param accountId The account ID.
    363
        /// @param erc20 The used ERC-20 token.
    364
        /// @return cycles The number of cycles which can be flushed
    365
        function _receivableStreamsCycles(uint256 accountId, IERC20 erc20)
    366
            internal
    367
            view
    368
    ✓ 670.7K
            returns (uint32 cycles)
    369
        {
    370
            unchecked {
    371
    ✓ 670.7K
                (uint32 fromCycle, uint32 toCycle) = _receivableStreamsCyclesRange(accountId, erc20);
    372
    ✓ 670.7K
                return toCycle - fromCycle;
    373
            }
    374
        }
    375
    
                                                    
                                                
    376
        /// @notice Calculates the cycles range from which streams can be received.
    377
        /// @param accountId The account ID.
    378
        /// @param erc20 The used ERC-20 token.
    379
        /// @return fromCycle The cycle from which funds can be received
    380
        /// @return toCycle The cycle to which funds can be received
    381
        function _receivableStreamsCyclesRange(uint256 accountId, IERC20 erc20)
    382
            private
    383
            view
    384
    ✓ 38.6M
            returns (uint32 fromCycle, uint32 toCycle)
    385
        {
    386
    ✓ 38.6M
            fromCycle = _streamsStorage().states[erc20][accountId].nextReceivableCycle;
    387
    ✓ 38.6M
            toCycle = _cycleOf(_currTimestamp());
    388
            // slither-disable-next-line timestamp
    389
    ✓ 38.6M
            if (fromCycle == 0 || toCycle < fromCycle) {
    390
    ✓ 14.4M
                toCycle = fromCycle;
    391
            }
    392
        }
    393
    
                                                    
                                                
    394
        /// @notice Receive streams from the currently running cycle from a single sender.
    395
        /// It doesn't receive streams from the finished cycles, to do that use `_receiveStreams`.
    396
        /// Squeezed funds won't be received in the next calls
    397
        /// to `_squeezeStreams` or `_receiveStreams`.
    398
        /// Only funds streamed before `block.timestamp` can be squeezed.
    399
        /// @param accountId The ID of the account receiving streams to squeeze funds for.
    400
        /// @param erc20 The used ERC-20 token.
    401
        /// @param senderId The ID of the streaming account to squeeze funds from.
    402
        /// @param historyHash The sender's history hash that was valid right before
    403
        /// they set up the sequence of configurations described by `streamsHistory`.
    404
        /// @param streamsHistory The sequence of the sender's streams configurations.
    405
        /// It can start at an arbitrary past configuration, but must describe all the configurations
    406
        /// which have been used since then including the current one, in the chronological order.
    407
        /// Only streams described by `streamsHistory` will be squeezed.
    408
        /// If `streamsHistory` entries have no receivers, they won't be squeezed.
    409
        /// @return amt The squeezed amount.
    410
        function _squeezeStreams(
    411
            uint256 accountId,
    412
            IERC20 erc20,
    413
            uint256 senderId,
    414
            bytes32 historyHash,
    415
            StreamsHistory[] memory streamsHistory
    416
    ✓ 43.2M
        ) internal returns (uint128 amt) {
    417
            unchecked {
    418
    ✓ 43.2M
                uint256 squeezedNum;
    419
    ✓ 43.2M
                uint256[] memory squeezedRevIdxs;
    420
    ✓ 43.2M
                bytes32[] memory historyHashes;
    421
    ✓ 43.2M
                uint256 currCycleConfigs;
    422
                (amt, squeezedNum, squeezedRevIdxs, historyHashes, currCycleConfigs) =
    423
    ✓ 43.2M
                    _squeezeStreamsResult(accountId, erc20, senderId, historyHash, streamsHistory);
    424
    ✓ 43.2M
                bytes32[] memory squeezedHistoryHashes = new bytes32[](squeezedNum);
    425
    ✓ 43.2M
                StreamsState storage state = _streamsStorage().states[erc20][accountId];
    426
    ✓ 43.2M
                uint32[2 ** 32] storage nextSqueezed = state.nextSqueezed[senderId];
    427
    ✓ 63.2M
                for (uint256 i = 0; i < squeezedNum; i++) {
    428
                    // `squeezedRevIdxs` are sorted from the newest configuration to the oldest,
    429
                    // but we need to consume them from the oldest to the newest.
    430
    ✓ 20.0M
                    uint256 revIdx = squeezedRevIdxs[squeezedNum - i - 1];
    431
    ✓ 20.0M
                    squeezedHistoryHashes[i] = historyHashes[historyHashes.length - revIdx];
    432
    ✓ 20.0M
                    nextSqueezed[currCycleConfigs - revIdx] = _currTimestamp();
    433
                }
    434
    ✓ 43.2M
                uint32 cycleStart = _currCycleStart();
    435
    ✓ 43.2M
                _addDeltaRange(
    436
    ✓ 43.2M
                    state, cycleStart, cycleStart + 1, -int160(amt * _AMT_PER_SEC_MULTIPLIER)
    437
                );
    438
    ✓ 43.2M
                emit SqueezedStreams(accountId, erc20, senderId, amt, squeezedHistoryHashes);
    439
            }
    440
        }
    441
    
                                                    
                                                
    442
        /// @notice Calculate effects of calling `_squeezeStreams` with the given parameters.
    443
        /// See its documentation for more details.
    444
        /// @param accountId The ID of the account receiving streams to squeeze funds for.
    445
        /// @param erc20 The used ERC-20 token.
    446
        /// @param senderId The ID of the streaming account to squeeze funds from.
    447
        /// @param historyHash The sender's history hash that was valid right before `streamsHistory`.
    448
        /// @param streamsHistory The sequence of the sender's streams configurations.
    449
        /// @return amt The squeezed amount.
    450
        /// @return squeezedNum The number of squeezed history entries.
    451
        /// @return squeezedRevIdxs The indexes of the squeezed history entries.
    452
        /// The indexes are reversed, meaning that to get the actual index in an array,
    453
        /// they must counted from the end of arrays, as in `arrayLength - squeezedRevIdxs[i]`.
    454
        /// These indexes can be safely used to access `streamsHistory`, `historyHashes`
    455
        /// and `nextSqueezed` regardless of their lengths.
    456
        /// `squeezeRevIdxs` is sorted ascending, from pointing at the most recent entry to the oldest.
    457
        /// @return historyHashes The history hashes valid
    458
        /// for squeezing each of `streamsHistory` entries.
    459
        /// In other words history hashes which had been valid right before each streams
    460
        /// configuration was set, matching `streamsHistoryHash` emitted in its `StreamsSet`.
    461
        /// The first item is always equal to `historyHash`.
    462
        /// @return currCycleConfigs The number of the sender's
    463
        /// streams configurations which have been seen in the current cycle.
    464
        /// This is also the number of used entries in each of the sender's `nextSqueezed` arrays.
    465
        function _squeezeStreamsResult(
    466
            uint256 accountId,
    467
            IERC20 erc20,
    468
            uint256 senderId,
    469
            bytes32 historyHash,
    470
            StreamsHistory[] memory streamsHistory
    471
        )
    472
            internal
    473
            view
    474
            returns (
    475
    ✓ 71.8M
                uint128 amt,
    476
    ✓ 71.8M
                uint256 squeezedNum,
    477
    ✓ 71.8M
                uint256[] memory squeezedRevIdxs,
    478
    ✓ 71.8M
                bytes32[] memory historyHashes,
    479
    ✓ 71.8M
                uint256 currCycleConfigs
    480
            )
    481
        {
    482
            {
    483
    ✓ 71.8M
                StreamsState storage sender = _streamsStorage().states[erc20][senderId];
    484
                historyHashes =
    485
    ✓ 71.8M
                    _verifyStreamsHistory(historyHash, streamsHistory, sender.streamsHistoryHash);
    486
                // If the last update was not in the current cycle,
    487
                // there's only the single latest history entry to squeeze in the current cycle.
    488
    ✓ 71.8M
                currCycleConfigs = 1;
    489
                // slither-disable-next-line timestamp
    490
    ✓ 71.8M
                if (sender.updateTime >= _currCycleStart()) {
    491
    ✓ 2.5M
                    currCycleConfigs = sender.lastUpdatedCycleConfigs;
    492
                }
    493
            }
    494
    ✓ 71.8M
            squeezedRevIdxs = new uint256[](streamsHistory.length);
    495
    ✓ 71.8M
            uint32[2 ** 32] storage nextSqueezed =
    496
    ✓ 71.8M
                _streamsStorage().states[erc20][accountId].nextSqueezed[senderId];
    497
    ✓ 71.8M
            uint32 squeezeEndCap = _currTimestamp();
    498
            unchecked {
    499
    ✓ 135.9M
                for (uint256 i = 1; i <= streamsHistory.length && i <= currCycleConfigs; i++) {
    500
    ✓ 64.2M
                    StreamsHistory memory historyEntry = streamsHistory[streamsHistory.length - i];
    501
    ✓ 64.2M
                    if (historyEntry.receivers.length != 0) {
    502
    ✓ 39.2M
                        uint32 squeezeStartCap = nextSqueezed[currCycleConfigs - i];
    503
    ✓ 39.2M
                        if (squeezeStartCap < _currCycleStart()) squeezeStartCap = _currCycleStart();
    504
    ✓ 39.2M
                        if (squeezeStartCap < historyEntry.updateTime) {
    505
    ✓ 1.4M
                            squeezeStartCap = historyEntry.updateTime;
    506
                        }
    507
    ✓ 39.2M
                        if (squeezeStartCap < squeezeEndCap) {
    508
    ✓ 32.5M
                            squeezedRevIdxs[squeezedNum++] = i;
    509
                            // This will not overflow if the requirement of tracking in the contract
    510
                            // no more than `_MAX_STREAMS_BALANCE` of each token is followed.
    511
    ✓ 32.5M
                            amt += _squeezedAmt(accountId, historyEntry, squeezeStartCap, squeezeEndCap);
    512
                        }
    513
                    }
    514
    ✓ 64.2M
                    squeezeEndCap = historyEntry.updateTime;
    515
                }
    516
            }
    517
        }
    518
    
                                                    
                                                
    519
        /// @notice Verify a streams history and revert if it's invalid.
    520
        /// @param historyHash The account's history hash that was valid right before `streamsHistory`.
    521
        /// @param streamsHistory The sequence of the account's streams configurations.
    522
        /// @param finalHistoryHash The history hash at the end of `streamsHistory`.
    523
        /// @return historyHashes The history hashes valid
    524
        /// for squeezing each of `streamsHistory` entries.
    525
        /// In other words history hashes which had been valid right before each streams
    526
        /// configuration was set, matching `streamsHistoryHash`es emitted in `StreamsSet`.
    527
        /// The first item is always equal to `historyHash` and `finalHistoryHash` is never included.
    528
        function _verifyStreamsHistory(
    529
            bytes32 historyHash,
    530
            StreamsHistory[] memory streamsHistory,
    531
            bytes32 finalHistoryHash
    532
    ✓ 71.8M
        ) private pure returns (bytes32[] memory historyHashes) {
    533
    ✓ 71.8M
            historyHashes = new bytes32[](streamsHistory.length);
    534
    ✓ 399.7M
            for (uint256 i = 0; i < streamsHistory.length; i++) {
    535
    ✓ 327.9M
                StreamsHistory memory historyEntry = streamsHistory[i];
    536
    ✓ 327.9M
                bytes32 streamsHash = historyEntry.streamsHash;
    537
    ✓ 327.9M
                if (historyEntry.receivers.length != 0) {
    538
    ✓ 180.5M
                    require(streamsHash == 0, "Entry with hash and receivers");
    539
    ✓ 180.5M
                    streamsHash = _hashStreams(historyEntry.receivers);
    540
                }
    541
    ✓ 327.9M
                historyHashes[i] = historyHash;
    542
    ✓ 327.9M
                historyHash = _hashStreamsHistory(
    543
    ✓ 327.9M
                    historyHash, streamsHash, historyEntry.updateTime, historyEntry.maxEnd
    544
                );
    545
            }
    546
            // slither-disable-next-line incorrect-equality,timestamp
    547
    ✓ 71.8M
            require(historyHash == finalHistoryHash, "Invalid streams history");
    548
        }
    549
    
                                                    
                                                
    550
        /// @notice Calculate the amount squeezable by an account from a single streams history entry.
    551
        /// @param accountId The ID of the account to squeeze streams for.
    552
        /// @param historyEntry The squeezed history entry.
    553
        /// @param squeezeStartCap The squeezed time range start.
    554
        /// @param squeezeEndCap The squeezed time range end.
    555
        /// @return squeezedAmt The squeezed amount.
    556
        function _squeezedAmt(
    557
            uint256 accountId,
    558
            StreamsHistory memory historyEntry,
    559
            uint32 squeezeStartCap,
    560
            uint32 squeezeEndCap
    561
    ✓ 32.5M
        ) private view returns (uint128 squeezedAmt) {
    562
            unchecked {
    563
    ✓ 32.5M
                StreamReceiver[] memory receivers = historyEntry.receivers;
    564
                // Binary search for the `idx` of the first occurrence of `accountId`
    565
    ✓ 32.5M
                uint256 idx = 0;
    566
    ✓ 72.8M
                for (uint256 idxCap = receivers.length; idx < idxCap;) {
    567
    ✓ 40.3M
                    uint256 idxMid = (idx + idxCap) / 2;
    568
    ✓ 40.3M
                    if (receivers[idxMid].accountId < accountId) {
    569
    ✓ 15.4M
                        idx = idxMid + 1;
    570
                    } else {
    571
    ✓ 24.9M
                        idxCap = idxMid;
    572
                    }
    573
                }
    574
    ✓ 32.5M
                uint32 updateTime = historyEntry.updateTime;
    575
    ✓ 32.5M
                uint32 maxEnd = historyEntry.maxEnd;
    576
    ✓ 32.5M
                uint256 amt = 0;
    577
    ✓ 44.6M
                for (; idx < receivers.length; idx++) {
    578
    ✓ 24.3M
                    StreamReceiver memory receiver = receivers[idx];
    579
    ✓ 24.3M
                    if (receiver.accountId != accountId) break;
    580
    ✓ 12.1M
                    (uint32 start, uint32 end) =
    581
    ✓ 12.1M
                        _streamRange(receiver, updateTime, maxEnd, squeezeStartCap, squeezeEndCap);
    582
                    // This will not overflow if the requirement of tracking in the contract
    583
                    // no more than `_MAX_STREAMS_BALANCE` of each token is followed.
    584
    ✓ 12.1M
                    amt += _streamedAmt(receiver.config.amtPerSec(), start, end);
    585
                }
    586
    ✓ 32.5M
                return uint128(amt);
    587
            }
    588
        }
    589
    
                                                    
                                                
    590
        /// @notice Current account streams state.
    591
        /// @param accountId The account ID.
    592
        /// @param erc20 The used ERC-20 token.
    593
        /// @return streamsHash The current streams receivers list hash, see `_hashStreams`
    594
        /// @return streamsHistoryHash The current streams history hash, see `_hashStreamsHistory`.
    595
        /// @return updateTime The time when streams have been configured for the last time.
    596
        /// @return balance The balance when streams have been configured for the last time.
    597
        /// @return maxEnd The current maximum end time of streaming.
    598
        function _streamsState(uint256 accountId, IERC20 erc20)
    599
            internal
    600
            view
    601
            returns (
    602
    ✓ 33.2M
                bytes32 streamsHash,
    603
    ✓ 33.2M
                bytes32 streamsHistoryHash,
    604
    ✓ 33.2M
                uint32 updateTime,
    605
    ✓ 33.2M
                uint128 balance,
    606
    ✓ 33.2M
                uint32 maxEnd
    607
            )
    608
        {
    609
    ✓ 33.2M
            StreamsState storage state = _streamsStorage().states[erc20][accountId];
    610
            return (
    611
    ✓ 33.2M
                state.streamsHash,
    612
    ✓ 33.2M
                state.streamsHistoryHash,
    613
    ✓ 33.2M
                state.updateTime,
    614
    ✓ 33.2M
                state.balance,
    615
    ✓ 33.2M
                state.maxEnd
    616
            );
    617
        }
    618
    
                                                    
                                                
    619
        /// @notice The account's streams balance at the given timestamp.
    620
        /// @param accountId The account ID.
    621
        /// @param erc20 The used ERC-20 token.
    622
        /// @param currReceivers The current streams receivers list.
    623
        /// It must be exactly the same as the last list set for the account with `_setStreams`.
    624
        /// @param timestamp The timestamp for which balance should be calculated.
    625
        /// It can't be lower than the timestamp of the last call to `_setStreams`.
    626
        /// If it's bigger than `block.timestamp`, then it's a prediction assuming
    627
        /// that `_setStreams` won't be called before `timestamp`.
    628
        /// @return balance The account balance on `timestamp`
    629
        function _balanceAt(
    630
            uint256 accountId,
    631
            IERC20 erc20,
    632
            StreamReceiver[] memory currReceivers,
    633
            uint32 timestamp
    634
    ✓ 9.0M
        ) internal view returns (uint128 balance) {
    635
    ✓ 9.0M
            StreamsState storage state = _streamsStorage().states[erc20][accountId];
    636
    ✓ 9.0M
            require(timestamp >= state.updateTime, "Timestamp before the last update");
    637
    ✓ 9.0M
            _verifyStreamsReceivers(currReceivers, state);
    638
    ✓ 9.0M
            return _calcBalance(state.balance, state.updateTime, state.maxEnd, currReceivers, timestamp);
    639
        }
    640
    
                                                    
                                                
    641
        /// @notice Calculates the streams balance at a given timestamp.
    642
        /// @param lastBalance The balance when streaming started.
    643
        /// @param lastUpdate The timestamp when streaming started.
    644
        /// @param maxEnd The maximum end time of streaming.
    645
        /// @param receivers The list of streams receivers.
    646
        /// @param timestamp The timestamp for which balance should be calculated.
    647
        /// It can't be lower than `lastUpdate`.
    648
        /// If it's bigger than `block.timestamp`, then it's a prediction assuming
    649
        /// that `_setStreams` won't be called before `timestamp`.
    650
        /// @return balance The account balance on `timestamp`
    651
        function _calcBalance(
    652
            uint128 lastBalance,
    653
            uint32 lastUpdate,
    654
            uint32 maxEnd,
    655
            StreamReceiver[] memory receivers,
    656
            uint32 timestamp
    657
    ✓ 37.8M
        ) private view returns (uint128 balance) {
    658
            unchecked {
    659
    ✓ 37.8M
                balance = lastBalance;
    660
    ✓ 73.5M
                for (uint256 i = 0; i < receivers.length; i++) {
    661
    ✓ 35.7M
                    StreamReceiver memory receiver = receivers[i];
    662
    ✓ 35.7M
                    (uint32 start, uint32 end) = _streamRange({
    663
    ✓ 35.7M
                        receiver: receiver,
    664
    ✓ 35.7M
                        updateTime: lastUpdate,
    665
    ✓ 35.7M
                        maxEnd: maxEnd,
    666
    ✓ 35.7M
                        startCap: lastUpdate,
    667
    ✓ 35.7M
                        endCap: timestamp
    668
                    });
    669
    ✓ 35.7M
                    balance -= uint128(_streamedAmt(receiver.config.amtPerSec(), start, end));
    670
                }
    671
            }
    672
        }
    673
    
                                                    
                                                
    674
        /// @notice Sets the account's streams configuration.
    675
        /// @param accountId The account ID.
    676
        /// @param erc20 The used ERC-20 token.
    677
        /// @param currReceivers The current streams receivers list.
    678
        /// It must be exactly the same as the last list set for the account with `_setStreams`.
    679
        /// If this is the first update, pass an empty array.
    680
        /// @param balanceDelta The streams balance change to be applied.
    681
        /// If it's positive, the balance is increased by `balanceDelta`.
    682
        /// If it's zero, the balance doesn't change.
    683
        /// If it's negative, the balance is decreased by `balanceDelta`,
    684
        /// but the change is capped at the current balance amount, so it doesn't go below 0.
    685
        /// Passing `type(int128).min` always decreases the current balance to 0.
    686
        /// @param newReceivers The list of the streams receivers of the account to be set.
    687
        /// Must be sorted by the account IDs and then by the stream configurations,
    688
        /// without identical elements and without 0 amtPerSecs.
    689
        /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it.
    690
        /// The first hint for finding the maximum end time when all streams stop due to funds
    691
        /// running out after the balance is updated and the new receivers list is applied.
    692
        /// Hints have no effect on the results of calling this function, except potentially saving gas.
    693
        /// Hints are Unix timestamps used as the starting points for binary search for the time
    694
        /// when funds run out in the range of timestamps from the current block's to `2^32`.
    695
        /// Hints lower than the current timestamp are ignored.
    696
        /// You can provide zero, one or two hints. The order of hints doesn't matter.
    697
        /// Hints are the most effective when one of them is lower than or equal to
    698
        /// the last timestamp when funds are still streamed, and the other one is strictly larger
    699
        /// than that timestamp,the smaller the difference between such hints, the higher gas savings.
    700
        /// The savings are the highest possible when one of the hints is equal to
    701
        /// the last timestamp when funds are still streamed, and the other one is larger by 1.
    702
        /// It's worth noting that the exact timestamp of the block in which this function is executed
    703
        /// may affect correctness of the hints, especially if they're precise.
    704
        /// Hints don't provide any benefits when balance is not enough to cover
    705
        /// a single second of streaming or is enough to cover all streams until timestamp `2^32`.
    706
        /// Even inaccurate hints can be useful, and providing a single hint
    707
        /// or two hints that don't enclose the time when funds run out can still save some gas.
    708
        /// Providing poor hints that don't reduce the number of binary search steps
    709
        /// may cause slightly higher gas usage than not providing any hints.
    710
        /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it.
    711
        /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details.
    712
        /// @return realBalanceDelta The actually applied streams balance change.
    713
        /// It's equal to the passed `balanceDelta`, unless it's negative
    714
        /// and it gets capped at the current balance amount.
    715
        function _setStreams(
    716
            uint256 accountId,
    717
            IERC20 erc20,
    718
            StreamReceiver[] memory currReceivers,
    719
            int128 balanceDelta,
    720
            StreamReceiver[] memory newReceivers,
    721
            // slither-disable-next-line similar-names
    722
            uint32 maxEndHint1,
    723
            uint32 maxEndHint2
    724
    ✓ 28.8M
        ) internal returns (int128 realBalanceDelta) {
    725
            unchecked {
    726
    ✓ 28.8M
                StreamsState storage state = _streamsStorage().states[erc20][accountId];
    727
    ✓ 28.8M
                _verifyStreamsReceivers(currReceivers, state);
    728
    ✓ 28.8M
                uint32 lastUpdate = state.updateTime;
    729
    ✓ 28.8M
                uint128 newBalance;
    730
    ✓ 28.8M
                uint32 newMaxEnd;
    731
                {
    732
    ✓ 28.8M
                    uint32 currMaxEnd = state.maxEnd;
    733
    ✓ 28.8M
                    int128 currBalance = int128(
    734
    ✓ 28.8M
                        _calcBalance(
    735
    ✓ 28.8M
                            state.balance, lastUpdate, currMaxEnd, currReceivers, _currTimestamp()
    736
                        )
    737
                    );
    738
    ✓ 28.8M
                    realBalanceDelta = balanceDelta;
    739
                    // Cap `realBalanceDelta` at withdrawal of the entire `currBalance`
    740
    ✓ 28.8M
                    if (realBalanceDelta < -currBalance) {
    741
    ✓ 19.0M
                        realBalanceDelta = -currBalance;
    742
                    }
    743
                    // This will not overflow if the requirement of tracking in the contract
    744
                    // no more than `_MAX_STREAMS_BALANCE` of each token is followed.
    745
    ✓ 28.8M
                    newBalance = uint128(currBalance + realBalanceDelta);
    746
    ✓ 28.8M
                    newMaxEnd = _calcMaxEnd(newBalance, newReceivers, maxEndHint1, maxEndHint2);
    747
    ✓ 28.4M
                    _updateReceiverStates(
    748
    ✓ 28.4M
                        _streamsStorage().states[erc20],
    749
    ✓ 28.4M
                        currReceivers,
    750
    ✓ 28.4M
                        lastUpdate,
    751
    ✓ 28.4M
                        currMaxEnd,
    752
    ✓ 28.4M
                        newReceivers,
    753
    ✓ 28.4M
                        newMaxEnd
    754
                    );
    755
                }
    756
    ✓ 28.4M
                state.updateTime = _currTimestamp();
    757
    ✓ 28.4M
                state.maxEnd = newMaxEnd;
    758
    ✓ 28.4M
                state.balance = newBalance;
    759
    ✓ 28.4M
                bytes32 streamsHistory = state.streamsHistoryHash;
    760
                // slither-disable-next-line timestamp
    761
    ✓ 28.4M
                if (streamsHistory != 0 && _cycleOf(lastUpdate) != _cycleOf(_currTimestamp())) {
    762
    ✓ 24.8M
                    state.lastUpdatedCycleConfigs = 2;
    763
                } else {
    764
    ✓ 3.6M
                    state.lastUpdatedCycleConfigs++;
    765
                }
    766
    ✓ 28.4M
                bytes32 newStreamsHash = _hashStreams(newReceivers);
    767
    ✓ 28.4M
                state.streamsHistoryHash =
    768
    ✓ 28.4M
                    _hashStreamsHistory(streamsHistory, newStreamsHash, _currTimestamp(), newMaxEnd);
    769
    ✓ 28.4M
                emit StreamsSet(accountId, erc20, newStreamsHash, streamsHistory, newBalance, newMaxEnd);
    770
                // slither-disable-next-line timestamp
    771
    ✓ 28.4M
                if (newStreamsHash != state.streamsHash) {
    772
    ✓ 10.3M
                    state.streamsHash = newStreamsHash;
    773
    ✓ 25.1M
                    for (uint256 i = 0; i < newReceivers.length; i++) {
    774
    ✓ 14.8M
                        StreamReceiver memory receiver = newReceivers[i];
    775
    ✓ 14.8M
                        emit StreamReceiverSeen(newStreamsHash, receiver.accountId, receiver.config);
    776
                    }
    777
                }
    778
            }
    779
        }
    780
    
                                                    
                                                
    781
        /// @notice Verifies that the provided list of receivers is currently active for the account.
    782
        /// @param currReceivers The verified list of receivers.
    783
        /// @param state The account's state.
    784
        function _verifyStreamsReceivers(
    785
            StreamReceiver[] memory currReceivers,
    786
            StreamsState storage state
    787
        ) private view {
    788
    ✓ 37.8M
            require(_hashStreams(currReceivers) == state.streamsHash, "Invalid streams receivers list");
    789
        }
    790
    
                                                    
                                                
    791
        /// @notice Calculates the maximum end time of all streams.
    792
        /// @param balance The balance when streaming starts.
    793
        /// @param receivers The list of streams receivers.
    794
        /// Must be sorted by the account IDs and then by the stream configurations,
    795
        /// without identical elements and without 0 amtPerSecs.
    796
        /// @param hint1 The first hint for finding the maximum end time.
    797
        /// See `_setStreams` docs for `maxEndHint1` for more details.
    798
        /// @param hint2 The second hint for finding the maximum end time.
    799
        /// See `_setStreams` docs for `maxEndHint2` for more details.
    800
        /// @return maxEnd The maximum end time of streaming.
    801
        function _calcMaxEnd(
    802
            uint128 balance,
    803
            StreamReceiver[] memory receivers,
    804
            uint32 hint1,
    805
            uint32 hint2
    806
    ✓ 28.8M
        ) private view returns (uint32 maxEnd) {
    807
    ✓ 28.8M
            (uint256[] memory configs, uint256 configsLen) = _buildConfigs(receivers);
    808
    
                                                    
                                                
    809
    ✓ 28.4M
            uint256 enoughEnd = _currTimestamp();
    810
            // slither-disable-start incorrect-equality,timestamp
    811
    ✓ 28.4M
            if (configsLen == 0 || balance == 0) {
    812
    ✓ 22.8M
                return uint32(enoughEnd);
    813
            }
    814
    
                                                    
                                                
    815
    ✓ 5.6M
            uint256 notEnoughEnd = type(uint32).max;
    816
    ✓ 5.6M
            if (_isBalanceEnough(balance, configs, configsLen, notEnoughEnd)) {
    817
    ✓ 1.5M
                return uint32(notEnoughEnd);
    818
            }
    819
    
                                                    
                                                
    820
    ✓ 4.1M
            if (hint1 > enoughEnd && hint1 < notEnoughEnd) {
    821
    ✓ 1.0M
                if (_isBalanceEnough(balance, configs, configsLen, hint1)) {
    822
    ✓ 77.6K
                    enoughEnd = hint1;
    823
                } else {
    824
    ✓ 939.1K
                    notEnoughEnd = hint1;
    825
                }
    826
            }
    827
    
                                                    
                                                
    828
    ✓ 4.1M
            if (hint2 > enoughEnd && hint2 < notEnoughEnd) {
    829
    ✓ 712.2K
                if (_isBalanceEnough(balance, configs, configsLen, hint2)) {
    830
    ✓ 56.1K
                    enoughEnd = hint2;
    831
                } else {
    832
    ✓ 656.0K
                    notEnoughEnd = hint2;
    833
                }
    834
            }
    835
    
                                                    
                                                
    836
    ✓ 132.4M
            while (true) {
    837
    ✓ 132.4M
                uint256 end;
    838
                unchecked {
    839
    ✓ 132.4M
                    end = (enoughEnd + notEnoughEnd) / 2;
    840
                }
    841
    ✓ 132.4M
                if (end == enoughEnd) {
    842
    ✓ 4.1M
                    return uint32(end);
    843
                }
    844
    ✓ 128.3M
                if (_isBalanceEnough(balance, configs, configsLen, end)) {
    845
    ✓ 14.9M
                    enoughEnd = end;
    846
                } else {
    847
    ✓ 113.4M
                    notEnoughEnd = end;
    848
                }
    849
            }
    850
            // slither-disable-end incorrect-equality,timestamp
    851
        }
    852
    
                                                    
                                                
    853
        /// @notice Check if a given balance is enough to cover all streams with the given `maxEnd`.
    854
        /// @param balance The balance when streaming starts.
    855
        /// @param configs The list of streams configurations.
    856
        /// @param configsLen The length of `configs`.
    857
        /// @param maxEnd The maximum end time of streaming.
    858
        /// @return isEnough `true` if the balance is enough, `false` otherwise.
    859
        function _isBalanceEnough(
    860
            uint256 balance,
    861
            uint256[] memory configs,
    862
            uint256 configsLen,
    863
            uint256 maxEnd
    864
    ✓ 135.6M
        ) private view returns (bool isEnough) {
    865
            unchecked {
    866
    ✓ 135.6M
                uint256 spent = 0;
    867
    ✓ 177.7M
                for (uint256 i = 0; i < configsLen; i++) {
    868
    ✓ 161.1M
                    (uint256 amtPerSec, uint256 start, uint256 end) = _getConfig(configs, i);
    869
                    // slither-disable-next-line timestamp
    870
    ✓ 161.1M
                    if (maxEnd <= start) {
    871
    ✓ 12.4M
                        continue;
    872
                    }
    873
                    // slither-disable-next-line timestamp
    874
    ✓ 148.7M
                    if (end > maxEnd) {
    875
    ✓ 96.3M
                        end = maxEnd;
    876
                    }
    877
    ✓ 148.7M
                    spent += _streamedAmt(amtPerSec, start, end);
    878
    ✓ 148.7M
                    if (spent > balance) {
    879
    ✓ 119.0M
                        return false;
    880
                    }
    881
                }
    882
    ✓ 16.6M
                return true;
    883
            }
    884
        }
    885
    
                                                    
                                                
    886
        /// @notice Build a preprocessed list of streams configurations from receivers.
    887
        /// @param receivers The list of streams receivers.
    888
        /// Must be sorted by the account IDs and then by the stream configurations,
    889
        /// without identical elements and without 0 amtPerSecs.
    890
        /// @return configs The list of streams configurations
    891
        /// @return configsLen The length of `configs`
    892
        function _buildConfigs(StreamReceiver[] memory receivers)
    893
            private
    894
            view
    895
    ✓ 28.8M
            returns (uint256[] memory configs, uint256 configsLen)
    896
        {
    897
            unchecked {
    898
    ✓ 28.8M
                require(receivers.length <= _MAX_STREAMS_RECEIVERS, "Too many streams receivers");
    899
    ✓ 28.8M
                configs = new uint256[](receivers.length);
    900
    ✓ 56.5M
                for (uint256 i = 0; i < receivers.length; i++) {
    901
    ✓ 28.1M
                    StreamReceiver memory receiver = receivers[i];
    902
    ✓ 28.1M
                    if (i > 0) {
    903
    ✓ 9.0M
                        require(_isOrdered(receivers[i - 1], receiver), "Streams receivers not sorted");
    904
                    }
    905
    ✓ 28.1M
                    configsLen = _addConfig(configs, configsLen, receiver);
    906
                }
    907
            }
    908
        }
    909
    
                                                    
                                                
    910
        /// @notice Preprocess and add a stream receiver to the list of configurations.
    911
        /// @param configs The list of streams configurations
    912
        /// @param configsLen The length of `configs`
    913
        /// @param receiver The added stream receiver.
    914
        /// @return newConfigsLen The new length of `configs`
    915
        function _addConfig(
    916
            uint256[] memory configs,
    917
            uint256 configsLen,
    918
            StreamReceiver memory receiver
    919
    ✓ 28.1M
        ) private view returns (uint256 newConfigsLen) {
    920
    ✓ 28.1M
            uint160 amtPerSec = receiver.config.amtPerSec();
    921
    ✓ 28.1M
            require(amtPerSec >= _minAmtPerSec, "Stream receiver amtPerSec too low");
    922
    ✓ 27.8M
            (uint32 start, uint32 end) =
    923
    ✓ 27.8M
                _streamRangeInFuture(receiver, _currTimestamp(), type(uint32).max);
    924
            // slither-disable-next-line incorrect-equality,timestamp
    925
    ✓ 27.8M
            if (start == end) {
    926
    ✓ 10.8M
                return configsLen;
    927
            }
    928
            // By assignment we get `config` value:
    929
            // `zeros (96 bits) | amtPerSec (160 bits)`
    930
    ✓ 17.0M
            uint256 config = amtPerSec;
    931
            // By bit shifting we get `config` value:
    932
            // `zeros (64 bits) | amtPerSec (160 bits) | zeros (32 bits)`
    933
            // By bit masking we get `config` value:
    934
            // `zeros (64 bits) | amtPerSec (160 bits) | start (32 bits)`
    935
    ✓ 17.0M
            config = (config << 32) | start;
    936
            // By bit shifting we get `config` value:
    937
            // `zeros (32 bits) | amtPerSec (160 bits) | start (32 bits) | zeros (32 bits)`
    938
            // By bit masking we get `config` value:
    939
            // `zeros (32 bits) | amtPerSec (160 bits) | start (32 bits) | end (32 bits)`
    940
    ✓ 17.0M
            config = (config << 32) | end;
    941
    ✓ 17.0M
            configs[configsLen] = config;
    942
            unchecked {
    943
    ✓ 17.0M
                return configsLen + 1;
    944
            }
    945
        }
    946
    
                                                    
                                                
    947
        /// @notice Load a streams configuration from the list.
    948
        /// @param configs The list of streams configurations
    949
        /// @param idx The loaded configuration index. It must be smaller than the `configs` length.
    950
        /// @return amtPerSec The amount per second being streamed.
    951
        /// @return start The timestamp when streaming starts.
    952
        /// @return end The maximum timestamp when streaming ends.
    953
        function _getConfig(uint256[] memory configs, uint256 idx)
    954
            private
    955
            pure
    956
    ✓ 161.1M
            returns (uint256 amtPerSec, uint256 start, uint256 end)
    957
        {
    958
    ✓ 161.1M
            uint256 config;
    959
            // `config` has value:
    960
            // `zeros (32 bits) | amtPerSec (160 bits) | start (32 bits) | end (32 bits)`
    961
            // slither-disable-next-line assembly
    962
            assembly ("memory-safe") {
    963
    ✓ 161.1M
                config := mload(add(32, add(configs, shl(5, idx))))
    964
            }
    965
            // By bit shifting we get value:
    966
            // `zeros (96 bits) | amtPerSec (160 bits)`
    967
    ✓ 161.1M
            amtPerSec = config >> 64;
    968
            // By bit shifting we get value:
    969
            // `zeros (64 bits) | amtPerSec (160 bits) | start (32 bits)`
    970
            // By casting down we get value:
    971
            // `start (32 bits)`
    972
    ✓ 161.1M
            start = uint32(config >> 32);
    973
            // By casting down we get value:
    974
            // `end (32 bits)`
    975
    ✓ 161.1M
            end = uint32(config);
    976
        }
    977
    
                                                    
                                                
    978
        /// @notice Calculates the hash of the streams configuration.
    979
        /// It's used to verify if streams configuration is the previously set one.
    980
        /// @param receivers The list of the streams receivers.
    981
        /// If the streams have never been set, pass an empty array.
    982
        /// @return streamsHash The hash of the streams configuration
    983
        function _hashStreams(StreamReceiver[] memory receivers)
    984
            internal
    985
            pure
    986
    ✓ 285.2M
            returns (bytes32 streamsHash)
    987
        {
    988
    ✓ 285.2M
            if (receivers.length == 0) {
    989
    ✓ 37.9M
                return bytes32(0);
    990
            }
    991
    ✓ 247.3M
            return keccak256(abi.encode(receivers));
    992
        }
    993
    
                                                    
                                                
    994
        /// @notice Calculates the hash of the streams history
    995
        /// after the streams configuration is updated.
    996
        /// @param oldStreamsHistoryHash The history hash
    997
        /// that was valid before the streams were updated.
    998
        /// The `streamsHistoryHash` of an account before they set streams for the first time is `0`.
    999
        /// @param streamsHash The hash of the streams receivers being set.
    1000
        /// @param updateTime The timestamp when the streams were updated.
    1001
        /// @param maxEnd The maximum end of the streams being set.
    1002
        /// @return streamsHistoryHash The hash of the updated streams history.
    1003
        function _hashStreamsHistory(
    1004
            bytes32 oldStreamsHistoryHash,
    1005
            bytes32 streamsHash,
    1006
            uint32 updateTime,
    1007
            uint32 maxEnd
    1008
    ✓ 384.7M
        ) internal pure returns (bytes32 streamsHistoryHash) {
    1009
    ✓ 384.7M
            return keccak256(abi.encode(oldStreamsHistoryHash, streamsHash, updateTime, maxEnd));
    1010
        }
    1011
    
                                                    
                                                
    1012
        /// @notice Applies the effects of the change of the streams on the receivers' streams state.
    1013
        /// Only affects `amtDeltas` and `nextReceivableCycle` of the accounts
    1014
        /// present either in `currReceivers` or in `newReceivers`.
    1015
        /// @param states The streams states for the used ERC-20 token.
    1016
        /// @param currReceivers The list of the streams receivers
    1017
        /// set in the last streams update of the account.
    1018
        /// If this is the first update, pass an empty array.
    1019
        /// @param lastUpdate the last time the sender updated the streams.
    1020
        /// If this is the first update, pass zero.
    1021
        /// @param currMaxEnd The maximum end time of streaming according to the last streams update.
    1022
        /// @param newReceivers  The list of the streams receivers of the account to be set.
    1023
        /// Must be sorted by the account IDs and then by the stream configurations,
    1024
        /// without identical elements and without 0 amtPerSecs.
    1025
        /// @param newMaxEnd The maximum end time of streaming according to the new configuration.
    1026
        // slither-disable-next-line cyclomatic-complexity
    1027
        function _updateReceiverStates(
    1028
            mapping(uint256 accountId => StreamsState) storage states,
    1029
            StreamReceiver[] memory currReceivers,
    1030
            uint32 lastUpdate,
    1031
            uint32 currMaxEnd,
    1032
            StreamReceiver[] memory newReceivers,
    1033
            uint32 newMaxEnd
    1034
        ) private {
    1035
    ✓ 28.4M
            uint256 currIdx = 0;
    1036
    ✓ 28.4M
            uint256 newIdx = 0;
    1037
    ✓ 60.3M
            while (true) {
    1038
    ✓ 60.3M
                bool pickCurr = currIdx < currReceivers.length;
    1039
                // slither-disable-next-line uninitialized-local
    1040
    ✓ 60.3M
                StreamReceiver memory currRecv;
    1041
    ✓ 60.3M
                if (pickCurr) {
    1042
    ✓ 26.1M
                    currRecv = currReceivers[currIdx];
    1043
                }
    1044
    
                                                    
                                                
    1045
    ✓ 60.3M
                bool pickNew = newIdx < newReceivers.length;
    1046
                // slither-disable-next-line uninitialized-local
    1047
    ✓ 60.3M
                StreamReceiver memory newRecv;
    1048
    ✓ 60.3M
                if (pickNew) {
    1049
    ✓ 29.3M
                    newRecv = newReceivers[newIdx];
    1050
                }
    1051
    
                                                    
                                                
    1052
                // Limit picking both curr and new to situations when they
    1053
                // differ only by the stream ID, the start or the duration.
    1054
    ✓ 60.3M
                if (pickCurr && pickNew) {
    1055
                    if (
    1056
    ✓ 23.6M
                        currRecv.accountId != newRecv.accountId
    1057
    ✓ 20.4M
                            || currRecv.config.amtPerSec() != newRecv.config.amtPerSec()
    1058
                    ) {
    1059
    ✓ 4.8M
                        pickCurr = _isOrdered(currRecv, newRecv);
    1060
    ✓ 4.8M
                        pickNew = !pickCurr;
    1061
                    }
    1062
                }
    1063
    
                                                    
                                                
    1064
    ✓ 60.3M
                if (pickCurr && pickNew) {
    1065
                    // Shift the existing stream to fulfil the new configuration
    1066
    ✓ 18.9M
                    StreamsState storage state = states[currRecv.accountId];
    1067
    ✓ 18.9M
                    (uint32 currStart, uint32 currEnd) =
    1068
    ✓ 18.9M
                        _streamRangeInFuture(currRecv, lastUpdate, currMaxEnd);
    1069
    ✓ 18.9M
                    (uint32 newStart, uint32 newEnd) =
    1070
    ✓ 18.9M
                        _streamRangeInFuture(newRecv, _currTimestamp(), newMaxEnd);
    1071
    ✓ 18.9M
                    int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec()));
    1072
                    // Move the start and end times if updated. This has the same effects as calling
    1073
                    // _addDeltaRange(state, currStart, currEnd, -amtPerSec);
    1074
                    // _addDeltaRange(state, newStart, newEnd, amtPerSec);
    1075
                    // but it allows skipping storage access if there's no change to the starts or ends.
    1076
    ✓ 18.9M
                    _addDeltaRange(state, currStart, newStart, -amtPerSec);
    1077
    ✓ 18.9M
                    _addDeltaRange(state, currEnd, newEnd, amtPerSec);
    1078
                    // Ensure that the account receives the updated cycles
    1079
    ✓ 18.9M
                    uint32 currStartCycle = _cycleOf(currStart);
    1080
    ✓ 18.9M
                    uint32 newStartCycle = _cycleOf(newStart);
    1081
                    // The `currStartCycle > newStartCycle` check is just an optimization.
    1082
                    // If it's false, then `state.nextReceivableCycle > newStartCycle` must be
    1083
                    // false too, there's no need to pay for the storage access to check it.
    1084
                    // slither-disable-next-line timestamp
    1085
    ✓ 18.9M
                    if (currStartCycle > newStartCycle && state.nextReceivableCycle > newStartCycle) {
    1086
    ✓ 48.5K
                        state.nextReceivableCycle = newStartCycle;
    1087
                    }
    1088
    ✓ 41.4M
                } else if (pickCurr) {
    1089
                    // Remove an existing stream
    1090
                    // slither-disable-next-line similar-names
    1091
    ✓ 4.1M
                    StreamsState storage state = states[currRecv.accountId];
    1092
    ✓ 4.1M
                    (uint32 start, uint32 end) = _streamRangeInFuture(currRecv, lastUpdate, currMaxEnd);
    1093
                    // slither-disable-next-line similar-names
    1094
    ✓ 4.1M
                    int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec()));
    1095
    ✓ 4.1M
                    _addDeltaRange(state, start, end, -amtPerSec);
    1096
    ✓ 37.3M
                } else if (pickNew) {
    1097
                    // Create a new stream
    1098
    ✓ 8.8M
                    StreamsState storage state = states[newRecv.accountId];
    1099
                    // slither-disable-next-line uninitialized-local
    1100
    ✓ 8.8M
                    (uint32 start, uint32 end) =
    1101
    ✓ 8.8M
                        _streamRangeInFuture(newRecv, _currTimestamp(), newMaxEnd);
    1102
    ✓ 8.8M
                    int256 amtPerSec = int256(uint256(newRecv.config.amtPerSec()));
    1103
    ✓ 8.8M
                    _addDeltaRange(state, start, end, amtPerSec);
    1104
                    // Ensure that the account receives the updated cycles
    1105
    ✓ 8.8M
                    uint32 startCycle = _cycleOf(start);
    1106
                    // slither-disable-next-line timestamp
    1107
    ✓ 8.8M
                    uint32 nextReceivableCycle = state.nextReceivableCycle;
    1108
    ✓ 8.8M
                    if (nextReceivableCycle == 0 || nextReceivableCycle > startCycle) {
    1109
    ✓ 3.2M
                        state.nextReceivableCycle = startCycle;
    1110
                    }
    1111
                } else {
    1112
    ✓ 28.4M
                    break;
    1113
                }
    1114
    
                                                    
                                                
    1115
                unchecked {
    1116
    ✓ 31.9M
                    if (pickCurr) {
    1117
    ✓ 23.0M
                        currIdx++;
    1118
                    }
    1119
    ✓ 31.9M
                    if (pickNew) {
    1120
    ✓ 27.7M
                        newIdx++;
    1121
                    }
    1122
                }
    1123
            }
    1124
        }
    1125
    
                                                    
                                                
    1126
        /// @notice Calculates the time range in the future in which a receiver will be streamed to.
    1127
        /// @param receiver The stream receiver.
    1128
        /// @param maxEnd The maximum end time of streaming.
    1129
        function _streamRangeInFuture(StreamReceiver memory receiver, uint32 updateTime, uint32 maxEnd)
    1130
            private
    1131
            view
    1132
    ✓ 78.5M
            returns (uint32 start, uint32 end)
    1133
        {
    1134
    ✓ 78.5M
            return _streamRange(receiver, updateTime, maxEnd, _currTimestamp(), type(uint32).max);
    1135
        }
    1136
    
                                                    
                                                
    1137
        /// @notice Calculates the time range in which a receiver is to be streamed to.
    1138
        /// This range is capped to provide a view on the stream through a specific time window.
    1139
        /// @param receiver The stream receiver.
    1140
        /// @param updateTime The time when the stream is configured.
    1141
        /// @param maxEnd The maximum end time of streaming.
    1142
        /// @param startCap The timestamp the streaming range start should be capped to.
    1143
        /// @param endCap The timestamp the streaming range end should be capped to.
    1144
        function _streamRange(
    1145
            StreamReceiver memory receiver,
    1146
            uint32 updateTime,
    1147
            uint32 maxEnd,
    1148
            uint32 startCap,
    1149
            uint32 endCap
    1150
    ✓ 126.3M
        ) private pure returns (uint32 start, uint32 end_) {
    1151
    ✓ 126.3M
            start = receiver.config.start();
    1152
            // slither-disable-start timestamp
    1153
    ✓ 126.3M
            if (start == 0) {
    1154
    ✓ 40.8M
                start = updateTime;
    1155
            }
    1156
    ✓ 126.3M
            uint40 end;
    1157
            unchecked {
    1158
    ✓ 126.3M
                end = uint40(start) + receiver.config.duration();
    1159
            }
    1160
            // slither-disable-next-line incorrect-equality
    1161
    ✓ 126.3M
            if (end == start || end > maxEnd) {
    1162
    ✓ 62.5M
                end = maxEnd;
    1163
            }
    1164
    ✓ 126.3M
            if (start < startCap) {
    1165
    ✓ 71.5M
                start = startCap;
    1166
            }
    1167
    ✓ 126.3M
            if (end > endCap) {
    1168
    ✓ 5.0M
                end = endCap;
    1169
            }
    1170
    ✓ 126.3M
            if (end < start) {
    1171
    ✓ 76.2M
                end = start;
    1172
            }
    1173
            // slither-disable-end timestamp
    1174
    ✓ 126.3M
            return (start, uint32(end));
    1175
        }
    1176
    
                                                    
                                                
    1177
        /// @notice Adds funds received by an account in a given time range
    1178
        /// @param state The account state
    1179
        /// @param start The timestamp from which the delta takes effect
    1180
        /// @param end The timestamp until which the delta takes effect
    1181
        /// @param amtPerSec The streaming rate
    1182
        function _addDeltaRange(StreamsState storage state, uint32 start, uint32 end, int256 amtPerSec)
    1183
            private
    1184
        {
    1185
            // slither-disable-next-line incorrect-equality,timestamp
    1186
    ✓ 93.9M
            if (start == end) {
    1187
    ✓ 93.9M
                return;
    1188
            }
    1189
    ✓ 48.3M
            mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas;
    1190
    ✓ 48.3M
            _addDelta(amtDeltas, start, amtPerSec);
    1191
    ✓ 48.3M
            _addDelta(amtDeltas, end, -amtPerSec);
    1192
        }
    1193
    
                                                    
                                                
    1194
        /// @notice Adds delta of funds received by an account at a given time
    1195
        /// @param amtDeltas The account amount deltas
    1196
        /// @param timestamp The timestamp when the deltas need to be added
    1197
        /// @param amtPerSec The streaming rate
    1198
        function _addDelta(
    1199
            mapping(uint32 cycle => AmtDelta) storage amtDeltas,
    1200
            uint256 timestamp,
    1201
            int256 amtPerSec
    1202
        ) private {
    1203
            unchecked {
    1204
                // In order to set a delta on a specific timestamp it must be introduced in two cycles.
    1205
                // These formulas follow the logic from `_streamedAmt`, see it for more details.
    1206
    ✓ 96.6M
                int256 amtPerSecMultiplier = int160(_AMT_PER_SEC_MULTIPLIER);
    1207
    ✓ 96.6M
                int256 fullCycle = (int256(uint256(_cycleSecs)) * amtPerSec) / amtPerSecMultiplier;
    1208
                // slither-disable-next-line weak-prng
    1209
    ✓ 96.6M
                int256 nextCycle = (int256(timestamp % _cycleSecs) * amtPerSec) / amtPerSecMultiplier;
    1210
    ✓ 96.6M
                AmtDelta storage amtDelta = amtDeltas[_cycleOf(uint32(timestamp))];
    1211
                // Any over- or under-flows are fine, they're guaranteed to be fixed by a matching
    1212
                // under- or over-flow from the other call to `_addDelta` made by `_addDeltaRange`.
    1213
                // This is because the total balance of `Streams` can never exceed `type(int128).max`,
    1214
                // so in the end no amtDelta can have delta higher than `type(int128).max`.
    1215
    ✓ 96.6M
                amtDelta.thisCycle += int128(fullCycle - nextCycle);
    1216
    ✓ 96.6M
                amtDelta.nextCycle += int128(nextCycle);
    1217
            }
    1218
        }
    1219
    
                                                    
                                                
    1220
        /// @notice Checks if two receivers fulfil the sortedness requirement of the receivers list.
    1221
        /// @param prev The previous receiver
    1222
        /// @param next The next receiver
    1223
        function _isOrdered(StreamReceiver memory prev, StreamReceiver memory next)
    1224
            private
    1225
            pure
    1226
    ✓ 13.7M
            returns (bool)
    1227
        {
    1228
    ✓ 13.7M
            if (prev.accountId != next.accountId) {
    1229
    ✓ 9.3M
                return prev.accountId < next.accountId;
    1230
            }
    1231
    ✓ 4.5M
            return prev.config.lt(next.config);
    1232
        }
    1233
    
                                                    
                                                
    1234
        /// @notice Calculates the amount streamed over a time range.
    1235
        /// The amount streamed in the `N`th second of each cycle is:
    1236
        /// `(N + 1) * amtPerSec / AMT_PER_SEC_MULTIPLIER - N * amtPerSec / AMT_PER_SEC_MULTIPLIER`.
    1237
        /// For a range of `N`s from `0` to `M` the sum of the streamed amounts is calculated as:
    1238
        /// `M * amtPerSec / AMT_PER_SEC_MULTIPLIER` assuming that `M <= cycleSecs`.
    1239
        /// For an arbitrary time range across multiple cycles the amount
    1240
        /// is calculated as the sum of the amount streamed in the start cycle,
    1241
        /// each of the full cycles in between and the end cycle.
    1242
        /// This algorithm has the following properties:
    1243
        /// - During every second full units are streamed, there are no partially streamed units.
    1244
        /// - Unstreamed fractions are streamed when they add up into full units.
    1245
        /// - Unstreamed fractions don't add up across cycle end boundaries.
    1246
        /// - Some seconds stream 1 unit more to emulate streaming fractions of the unit.
    1247
        /// - Every `N`th second of each cycle streams the same amount.
    1248
        /// - Every full cycle streams the same amount.
    1249
        /// - The amount streamed in a given second is independent from the streaming start and end.
    1250
        /// - Streaming over time ranges `A:B` and then `B:C` is equivalent to streaming over `A:C`.
    1251
        /// - Different streams existing in the system don't interfere with each other.
    1252
        /// @param amtPerSec The streaming rate
    1253
        /// @param start The streaming start time
    1254
        /// @param end The streaming end time
    1255
        /// @return amt The streamed amount
    1256
        function _streamedAmt(uint256 amtPerSec, uint256 start, uint256 end)
    1257
            private
    1258
            view
    1259
    ✓ 196.5M
            returns (uint256 amt)
    1260
        {
    1261
            // This function is written in Yul because it can be called thousands of times
    1262
            // per transaction and it needs to be optimized as much as possible.
    1263
            // As of Solidity 0.8.13, rewriting it in unchecked Solidity triples its gas cost.
    1264
    ✓ 196.5M
            uint256 cycleSecs = _cycleSecs;
    1265
            // slither-disable-next-line assembly
    1266
            assembly {
    1267
    ✓ 196.5M
                let endedCycles := sub(div(end, cycleSecs), div(start, cycleSecs))
    1268
                // slither-disable-next-line divide-before-multiply
    1269
    ✓ 196.5M
                let amtPerCycle := div(mul(cycleSecs, amtPerSec), _AMT_PER_SEC_MULTIPLIER)
    1270
    ✓ 196.5M
                amt := mul(endedCycles, amtPerCycle)
    1271
                // slither-disable-next-line weak-prng
    1272
    ✓ 196.5M
                let amtEnd := div(mul(mod(end, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER)
    1273
    ✓ 196.5M
                amt := add(amt, amtEnd)
    1274
                // slither-disable-next-line weak-prng
    1275
    ✓ 196.5M
                let amtStart := div(mul(mod(start, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER)
    1276
    ✓ 196.5M
                amt := sub(amt, amtStart)
    1277
            }
    1278
        }
    1279
    
                                                    
                                                
    1280
        /// @notice Calculates the cycle containing the given timestamp.
    1281
        /// @param timestamp The timestamp.
    1282
        /// @return cycle The cycle containing the timestamp.
    1283
    ✓ 232.3M
        function _cycleOf(uint32 timestamp) private view returns (uint32 cycle) {
    1284
            unchecked {
    1285
    ✓ 232.3M
                return timestamp / _cycleSecs + 1;
    1286
            }
    1287
        }
    1288
    
                                                    
                                                
    1289
        /// @notice The current timestamp, casted to the contract's internal representation.
    1290
        /// @return timestamp The current timestamp
    1291
    ✓ 595.3M
        function _currTimestamp() private view returns (uint32 timestamp) {
    1292
    ✓ 595.3M
            return uint32(block.timestamp);
    1293
        }
    1294
    
                                                    
                                                
    1295
        /// @notice The current cycle start timestamp, casted to the contract's internal representation.
    1296
        /// @return timestamp The current cycle start timestamp
    1297
    ✓ 191.7M
        function _currCycleStart() private view returns (uint32 timestamp) {
    1298
            unchecked {
    1299
    ✓ 191.7M
                uint32 currTimestamp = _currTimestamp();
    1300
                // slither-disable-next-line weak-prng
    1301
    ✓ 191.7M
                return currTimestamp - (currTimestamp % _cycleSecs);
    1302
            }
    1303
        }
    1304
    
                                                    
                                                
    1305
        /// @notice Returns the Streams storage.
    1306
        /// @return streamsStorage The storage.
    1307
    ✓ 370.6M
        function _streamsStorage() private view returns (StreamsStorage storage streamsStorage) {
    1308
    ✓ 370.6M
            bytes32 slot = _streamsStorageSlot;
    1309
            // slither-disable-next-line assembly
    1310
            assembly {
    1311
    ✓ 370.6M
                streamsStorage.slot := slot
    1312
            }
    1313
        }
    1314
    }
    1315
    
                                                    
                                                
    0.0% src/echidna/Echidna.sol
    Lines covered: 0 / 1 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./EchidnaInvariantTests.sol";
    4
    import "./EchidnaBasicTests.sol";
    5
    import "./EchidnaSplitsTests.sol";
    6
    import "./EchidnaStreamsTests.sol";
    7
    import "./EchidnaSqueezeTests.sol";
    8
    
                                                    
                                                
    9
    /**
    10
     * @title Echidna contract for testing the Drips contract
    11
     * @author Rappie <rappie@perimetersec.io>
    12
     * @dev Running the tests:
    13
     * For Echidna use `echidna . --contract Echidna --config echidna-config.yaml`.
    14
     * For Medusa use `medusa fuzz`.
    15
     * The tests are split into multiple files to be able to toggle the fuzzing
    16
     * of specific features on and off. This is done by commenting out the
    17
     * corresponding inherited contract in the Echidna contract below.
    18
     * Basic tests contain basic features like giving, receiving, splitting and
    19
     * collecting.
    20
     * Splits tests contain tests for splitting.
    21
     * Streams tests contain tests for receiving streams.
    22
     * Squeeze tests contain tests for squeezing.
    23
     * Invariant tests contain tests for invariant properties of the system. These
    24
     * make most sense with all other tests enabled.
    25
     * For further performance improvements, resource heavy tests can be toggled.
    26
     * To do so, change the value of TOGGLE_HEAVY_TESTS_ENABLED in EchidnaConfig.
    27
     */
    28
    contract Echidna is
    29
        EchidnaBasicTests,
    30
        EchidnaSplitsTests,
    31
        EchidnaStreamsTests,
    32
        EchidnaSqueezeTests,
    33
        EchidnaInvariantTests
    34
    {
    35
    
                                                    
                                                
    36
    }
    37
    
                                                    
                                                
    97.5% src/echidna/EchidnaBasicHelpers.sol
    Lines covered: 39 / 40 (97.5%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    
                                                    
                                                
    5
    /**
    6
     * @title Mixin containing basic helper functions
    7
     * @author Rappie <rappie@perimetersec.io>
    8
     */
    9
    contract EchidnaBasicHelpers is EchidnaBase {
    10
        /**
    11
         * @notice Give balance to an account
    12
         * @param fromAccId Account id of the giver
    13
         * @param toAccId Account id of the receiver
    14
         * @param amount Amount to give
    15
         */
    16
        function give(
    17
            uint8 fromAccId,
    18
            uint8 toAccId,
    19
            uint128 amount
    20
        ) public {
    21
    ✓ 2.0M
            address from = getAccount(fromAccId);
    22
    ✓ 2.0M
            address to = getAccount(toAccId);
    23
    
                                                    
                                                
    24
    ✓ 2.0M
            uint256 toDripsAccId = getDripsAccountId(to);
    25
    
                                                    
                                                
    26
    ✓ 2.0M
            hevm.prank(from);
    27
    ✓ 2.0M
            driver.give(toDripsAccId, token, amount);
    28
        }
    29
    
                                                    
                                                
    30
        /**
    31
         * @notice Give a clamped amount to an account
    32
         * @param fromAccId Account id of the giver
    33
         * @param toAccId Account id of the receiver
    34
         * @param amount Amount to give
    35
         */
    36
        function giveClampedAmount(
    37
            uint8 fromAccId,
    38
            uint8 toAccId,
    39
            uint128 amount
    40
        ) public {
    41
    ✓ 1.3M
            address from = getAccount(fromAccId);
    42
    
                                                    
                                                
    43
    ✓ 1.3M
            uint128 min = 1000;
    44
    ✓ 1.3M
            uint128 max = uint128(token.balanceOf(from));
    45
    ✓ 1.3M
            uint128 clampedAmount = min + (amount % (max - min + 1));
    46
    
                                                    
                                                
    47
    ✓ 1.1M
            give(fromAccId, toAccId, clampedAmount);
    48
        }
    49
    
                                                    
                                                
    50
        /**
    51
         * @notice Receive streams
    52
         * @param targetAccId Account id of the receiver
    53
         * @param maxCycles Maximum number of cycles to receive
    54
         * @return Amount received
    55
         * @dev Receiving means moving receivable (already streamed) balance to
    56
         * splittable (available for splitting) balance
    57
         */
    58
        function receiveStreams(uint8 targetAccId, uint32 maxCycles)
    59
            public
    60
    ✓ 11.8M
            returns (uint128)
    61
        {
    62
    ✓ 11.8M
            address target = getAccount(targetAccId);
    63
    ✓ 11.8M
            uint256 targetDripsAccId = getDripsAccountId(target);
    64
    
                                                    
                                                
    65
    ✓ 11.8M
            uint128 receivedAmt = drips.receiveStreams(
    66
    ✓ 11.8M
                targetDripsAccId,
    67
    ✓ 11.8M
                token,
    68
    ✓ 11.8M
                maxCycles
    69
            );
    70
    
                                                    
                                                
    71
    ✓ 11.8M
            return receivedAmt;
    72
        }
    73
    
                                                    
                                                
    74
        /**
    75
         * @notice Receive streams for all possible cycles
    76
         * @param targetAccId Account id of the receiver
    77
         * @return Amount received
    78
         * @dev We pass maxuint32 to receive all possible cycles
    79
         */
    80
        function receiveStreamsAllCycles(uint8 targetAccId)
    81
            public
    82
    ✓ 9.7M
            returns (uint128)
    83
        {
    84
    ✓ 9.7M
            return receiveStreams(targetAccId, type(uint32).max);
    85
        }
    86
    
                                                    
                                                
    87
        /**
    88
         * @notice Split received funds
    89
         * @param targetAccId Account to have their funds split
    90
         * @return Amount collectable and amount split
    91
         * @dev Splitting means moving splittable (available for splitting) balance
    92
         * to collectable (available for collecting) balance
    93
         */
    94
    ✓ 17.2M
        function split(uint8 targetAccId) public returns (uint128, uint128) {
    95
    ✓ 17.2M
            address target = getAccount(targetAccId);
    96
    ✓ 17.2M
            uint256 targetDripsAccId = getDripsAccountId(target);
    97
    
                                                    
                                                
    98
    ✓ 17.2M
            (uint128 collectableAmt, uint128 splitAmt) = drips.split(
    99
    ✓ 17.2M
                targetDripsAccId,
    100
    ✓ 17.2M
                token,
    101
    ✓ 17.2M
                getSplitsReceivers(target)
    102
            );
    103
    
                                                    
                                                
    104
    ✓ 17.2M
            return (collectableAmt, splitAmt);
    105
        }
    106
    
                                                    
                                                
    107
        /**
    108
         * @notice Collect funds
    109
         * @param fromAccId Account id of the giver
    110
         * @param toAccId Account id of the receiver
    111
         * @return Amount collected
    112
         * @dev Collecting means moving withdrawing collectable funds to actual
    113
         * balance of the erc20 token
    114
         */
    115
    ✓ 13.4M
        function collect(uint8 fromAccId, uint8 toAccId) public returns (uint128) {
    116
    ✓ 13.4M
            address from = getAccount(fromAccId);
    117
    ✓ 13.4M
            address to = getAccount(toAccId);
    118
    
                                                    
                                                
    119
    ✓ 13.4M
            hevm.prank(from);
    120
    ✓ 13.4M
            uint128 collected = driver.collect(token, to);
    121
    
                                                    
                                                
    122
    ✓ 13.4M
            return collected;
    123
        }
    124
    
                                                    
                                                
    125
        /**
    126
         * @notice Collect funds to self
    127
         * @param targetAccId Target account
    128
         */
    129
        function collectToSelf(uint8 targetAccId) public {
    130
    ✓ 11.1M
            collect(targetAccId, targetAccId);
    131
        }
    132
    
                                                    
                                                
    133
        /**
    134
         * @notice Split and collect funds to self
    135
         * @param targetAccId Target account
    136
         * @dev Extra helper that narrows the search space for the fuzzer
    137
         */
    138
        function splitAndCollectToSelf(uint8 targetAccId) public {
    139
    ✓ 9.8M
            split(targetAccId);
    140
    ✓ 9.8M
            collectToSelf(targetAccId);
    141
        }
    142
    
                                                    
                                                
    143
        /**
    144
         * @notice Receive streams, split and collect funds to self
    145
         * @param targetAccId Target account
    146
         * @dev Extra helper that narrows the search space for the fuzzer
    147
         */
    148
        function receiveStreamsSplitAndCollectToSelf(uint8 targetAccId) public {
    149
    ✓ 8.3M
            receiveStreamsAllCycles(targetAccId);
    150
    ✓ 8.3M
            splitAndCollectToSelf(targetAccId);
    151
        }
    152
    }
    153
    
                                                    
                                                
    93.1% src/echidna/EchidnaBasicTests.sol
    Lines covered: 54 / 58 (93.1%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    import "./EchidnaBasicHelpers.sol";
    5
    
                                                    
                                                
    6
    /**
    7
     * @title Mixin containing basic tests
    8
     * @author Rappie <rappie@perimetersec.io>
    9
     */
    10
    contract EchidnaBasicTests is EchidnaBase, EchidnaBasicHelpers {
    11
        /**
    12
         * @notice Giving an amount `<=` token balance should never revert
    13
         * @param fromAccId Account id of the giver
    14
         * @param toAccId Account id of the receiver
    15
         * @param amount Amount to give
    16
         */
    17
        function testGiveShouldNotRevert(
    18
            uint8 fromAccId,
    19
            uint8 toAccId,
    20
            uint128 amount
    21
        ) public {
    22
    ✓ 1.6M
            address from = getAccount(fromAccId);
    23
    ✓ 1.6M
            address to = getAccount(toAccId);
    24
    
                                                    
                                                
    25
    ✓ 1.6M
            uint256 toDripsAccId = getDripsAccountId(to);
    26
    
                                                    
                                                
    27
    ✓ 1.6M
            require(amount <= token.balanceOf(from));
    28
    
                                                    
                                                
    29
    ✓ 360.4K
            hevm.prank(from);
    30
    ✓ 360.4K
            try driver.give(toDripsAccId, token, amount) {} catch {
    31
                assert(false);
    32
            }
    33
        }
    34
    
                                                    
                                                
    35
        /**
    36
         * @notice Test internal accounting after receiving streams
    37
         * @param targetAccId Account id of the receiver
    38
         * @param maxCycles Maximum number of cycles to receive
    39
         */
    40
        function testReceiveStreams(uint8 targetAccId, uint32 maxCycles) public {
    41
    ✓ 1.4M
            address target = getAccount(targetAccId);
    42
    ✓ 1.4M
            uint256 targetDripsAccId = getDripsAccountId(target);
    43
    
                                                    
                                                
    44
    ✓ 1.4M
            uint128 splittableBefore = drips.splittable(targetDripsAccId, token);
    45
    ✓ 1.4M
            uint128 receivedAmt = receiveStreams(targetAccId, maxCycles);
    46
    ✓ 1.4M
            uint128 splittableAfter = drips.splittable(targetDripsAccId, token);
    47
    
                                                    
                                                
    48
    ✓ 1.4M
            assert(splittableAfter == splittableBefore + receivedAmt);
    49
    
                                                    
                                                
    50
    ✓ 1.4M
            if (receivedAmt > 0) {
    51
    ✓ 97.7K
                assert(splittableAfter > splittableBefore);
    52
            } else {
    53
    ✓ 1.3M
                assert(splittableAfter == splittableBefore);
    54
            }
    55
        }
    56
    
                                                    
                                                
    57
        /**
    58
         * @notice Receiving streams should never revert
    59
         * @param targetAccId Account id of the receiver
    60
         */
    61
        function testReceiveStreamsShouldNotRevert(uint8 targetAccId) public {
    62
    ✓ 857.2K
            address target = getAccount(targetAccId);
    63
    ✓ 857.2K
            uint256 targetDripsAccId = getDripsAccountId(target);
    64
    
                                                    
                                                
    65
            try
    66
    ✓ 857.2K
                drips.receiveStreams(targetDripsAccId, token, type(uint32).max)
    67
            {} catch {
    68
                assert(false);
    69
            }
    70
        }
    71
    
                                                    
                                                
    72
        /**
    73
         * @notice If there is a receivable amount, there should be at least one
    74
         * receivable cycle
    75
         * @param targetAccId Account id of the receiver
    76
         * @param maxCycles Maximum number of cycles to receive
    77
         */
    78
        function testReceiveStreamsViewConsistency(
    79
            uint8 targetAccId,
    80
            uint32 maxCycles
    81
        ) public {
    82
    ✓ 721.1K
            address target = getAccount(targetAccId);
    83
    ✓ 721.1K
            uint256 targetDripsAccId = getDripsAccountId(target);
    84
    
                                                    
                                                
    85
    ✓ 721.1K
            require(maxCycles > 0);
    86
    
                                                    
                                                
    87
    ✓ 670.7K
            uint128 receivable = drips.receiveStreamsResult(
    88
    ✓ 670.7K
                targetDripsAccId,
    89
    ✓ 670.7K
                token,
    90
    ✓ 670.7K
                maxCycles
    91
            );
    92
    ✓ 670.7K
            uint32 receivableCycles = drips.receivableStreamsCycles(
    93
    ✓ 670.7K
                targetDripsAccId,
    94
    ✓ 670.7K
                token
    95
            );
    96
    
                                                    
                                                
    97
    ✓ 670.7K
            if (receivable > 0) assert(receivableCycles > 0);
    98
    
                                                    
                                                
    99
            // this does not hold because you can have cycles with 0 amount receivable
    100
            // if (receivableCycles > 0) assert(receivable > 0);
    101
        }
    102
    
                                                    
                                                
    103
        /**
    104
         * @notice `drips.receiveStreamsResult` should match actual received amount
    105
         * @param targetAccId Account id of the receiver
    106
         * @param maxCycles Maximum number of cycles to receive
    107
         */
    108
        function testReceiveStreamsViewVsActual(uint8 targetAccId, uint32 maxCycles)
    109
            public
    110
        {
    111
    ✓ 767.7K
            address target = getAccount(targetAccId);
    112
    ✓ 767.7K
            uint256 targetDripsAccId = getDripsAccountId(target);
    113
    
                                                    
                                                
    114
    ✓ 767.7K
            uint128 receivable = drips.receiveStreamsResult(
    115
    ✓ 767.7K
                targetDripsAccId,
    116
    ✓ 767.7K
                token,
    117
    ✓ 767.7K
                maxCycles
    118
            );
    119
    
                                                    
                                                
    120
    ✓ 767.7K
            uint128 received = drips.receiveStreams(
    121
    ✓ 767.7K
                targetDripsAccId,
    122
    ✓ 767.7K
                token,
    123
    ✓ 767.7K
                maxCycles
    124
            );
    125
    
                                                    
                                                
    126
    ✓ 767.7K
            assert(receivable == received);
    127
        }
    128
    
                                                    
                                                
    129
        /**
    130
         * @notice Test internal accounting after collecting
    131
         * @param fromAccId Account id of the collector
    132
         * @param toAccId Account id of the receiving account
    133
         */
    134
        function testCollect(uint8 fromAccId, uint8 toAccId) public {
    135
    ✓ 1.5M
            address from = getAccount(fromAccId);
    136
    ✓ 1.5M
            address to = getAccount(toAccId);
    137
    
                                                    
                                                
    138
    ✓ 1.5M
            uint256 fromDripsAccId = getDripsAccountId(from);
    139
    
                                                    
                                                
    140
    ✓ 1.5M
            uint128 colBalBefore = drips.collectable(fromDripsAccId, token);
    141
    ✓ 1.5M
            uint256 tokenBalBefore = token.balanceOf(to);
    142
    
                                                    
                                                
    143
    ✓ 1.5M
            uint128 collected = collect(fromAccId, toAccId);
    144
    
                                                    
                                                
    145
    ✓ 1.5M
            uint128 colBalAfter = drips.collectable(fromDripsAccId, token);
    146
    ✓ 1.5M
            uint256 tokenBalAfter = token.balanceOf(to);
    147
    
                                                    
                                                
    148
    ✓ 1.5M
            assert(colBalAfter == colBalBefore - collected);
    149
    ✓ 1.5M
            assert(tokenBalAfter == tokenBalBefore + collected);
    150
        }
    151
    
                                                    
                                                
    152
        /**
    153
         * @notice Collecting should never revert
    154
         * @param fromAccId Account id of the collector
    155
         * @param toAccId Account id of the receiving account
    156
         */
    157
        function testCollectShouldNotRevert(uint8 fromAccId, uint8 toAccId) public {
    158
    ✓ 1.2M
            address from = getAccount(fromAccId);
    159
    ✓ 1.2M
            address to = getAccount(toAccId);
    160
    
                                                    
                                                
    161
    ✓ 1.2M
            hevm.prank(from);
    162
    ✓ 1.2M
            try driver.collect(token, to) {} catch {
    163
                assert(false);
    164
            }
    165
        }
    166
    }
    167
    
                                                    
                                                
    95.2% src/echidna/EchidnaInvariantTests.sol
    Lines covered: 60 / 63 (95.2%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./EchidnaBasicHelpers.sol";
    4
    import "./EchidnaSplitsHelpers.sol";
    5
    import "./EchidnaStreamsHelpers.sol";
    6
    import "./EchidnaSqueezeHelpers.sol";
    7
    
                                                    
                                                
    8
    /**
    9
     * @title Mixin containing invariant tests
    10
     * @author Rappie <rappie@perimetersec.io>
    11
     */
    12
    contract EchidnaInvariantTests is
    13
        EchidnaBasicHelpers,
    14
        EchidnaSplitsHelpers,
    15
        EchidnaStreamsHelpers,
    16
        EchidnaSqueezeHelpers
    17
    {
    18
        /**
    19
         * @notice Withdrawing any amount directly from Drips should fail
    20
         * @param amount Amount to withdraw
    21
         */
    22
        function invariantWithdrawShouldAlwaysFail(uint256 amount) public {
    23
    ✓ 1.4M
            require(amount > 0, "withdraw amount must be > 0");
    24
    
                                                    
                                                
    25
    ✓ 1.3M
            try drips.withdraw(token, address(this), amount) {
    26
                assert(false);
    27
            } catch {}
    28
        }
    29
    
                                                    
                                                
    30
        /**
    31
         * @notice `amtPerSec` should never be lower than `drips.minAmtPerSec()`
    32
         * @param targetAccId Account id of the receiver
    33
         * @param index Index of the receiver
    34
         */
    35
        function invariantAmtPerSecVsMinAmtPerSec(uint8 targetAccId, uint256 index)
    36
            public
    37
        {
    38
    ✓ 1.4M
            address target = getAccount(targetAccId);
    39
    
                                                    
                                                
    40
    ✓ 1.4M
            StreamReceiver[] memory receivers = getStreamReceivers(target);
    41
    ✓ 1.4M
            require(receivers.length > 0, "no receivers");
    42
    
                                                    
                                                
    43
    ✓ 670.2K
            index = index % receivers.length;
    44
    ✓ 670.2K
            uint160 amtPerSec = receivers[index].config.amtPerSec();
    45
    
                                                    
                                                
    46
    ✓ 670.2K
            assert(amtPerSec >= drips.minAmtPerSec());
    47
        }
    48
    
                                                    
                                                
    49
        /**
    50
         * @notice The total of all internal balances should match token balance
    51
         * of the Drips contract
    52
         */
    53
        function invariantAccountingVsTokenBalance() public {
    54
    ✓ 721.9K
            uint256 tokenBalance = token.balanceOf(address(drips));
    55
    ✓ 721.9K
            uint256 dripsBalancesTotal = getDripsBalancesTotalForAllUsers();
    56
    
                                                    
                                                
    57
    ✓ 721.9K
            assert(tokenBalance == dripsBalancesTotal);
    58
        }
    59
    
                                                    
                                                
    60
        /**
    61
         * @notice Check internal and external balances after withdrawing all funds
    62
         * from the system
    63
         */
    64
        function invariantWithdrawAllTokens() external heavy {
    65
            // remove all splits to prevent tokens from getting stuck in case
    66
            // there are splits to self
    67
    ✓ 1.5M
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    68
    ✓ 1.5M
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    69
    ✓ 1.5M
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    70
    ✓ 1.5M
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    71
    
                                                    
                                                
    72
    ✓ 1.5M
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    73
    ✓ 1.5M
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    74
    ✓ 1.5M
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    75
    ✓ 1.5M
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    76
    
                                                    
                                                
    77
    ✓ 1.5M
            receiveStreamsSplitAndCollectToSelf(
    78
    ✓ 1.5M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]
    79
            );
    80
    ✓ 1.5M
            receiveStreamsSplitAndCollectToSelf(
    81
    ✓ 1.5M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]
    82
            );
    83
    ✓ 1.5M
            receiveStreamsSplitAndCollectToSelf(
    84
    ✓ 1.5M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]
    85
            );
    86
    ✓ 1.5M
            receiveStreamsSplitAndCollectToSelf(
    87
    ✓ 1.5M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]
    88
            );
    89
    
                                                    
                                                
    90
    ✓ 1.5M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    91
    ✓ 1.5M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    92
    ✓ 1.5M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    93
    ✓ 1.5M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    94
    
                                                    
                                                
    95
    ✓ 1.5M
            uint256 dripsBalance = token.balanceOf(address(drips));
    96
    ✓ 1.5M
            uint256 user0Balance = token.balanceOf(ADDRESS_USER0);
    97
    ✓ 1.5M
            uint256 user1Balance = token.balanceOf(ADDRESS_USER1);
    98
    ✓ 1.5M
            uint256 user2Balance = token.balanceOf(ADDRESS_USER2);
    99
    ✓ 1.5M
            uint256 user3Balance = token.balanceOf(ADDRESS_USER3);
    100
    
                                                    
                                                
    101
    ✓ 1.5M
            uint256 totalUserBalance = user0Balance +
    102
    ✓ 1.5M
                user1Balance +
    103
    ✓ 1.5M
                user2Balance +
    104
    ✓ 1.5M
                user3Balance;
    105
    
                                                    
                                                
    106
    ✓ 1.5M
            assert(dripsBalance == 0);
    107
    ✓ 1.5M
            assert(totalUserBalance == STARTING_BALANCE * 4);
    108
        }
    109
    
                                                    
                                                
    110
        /**
    111
         * @notice Withdrawing all funds from the system should never revert
    112
         */
    113
        function invariantWithdrawAllTokensShouldNotRevert() public heavy {
    114
            try
    115
    ✓ 718.2K
                EchidnaInvariantTests(address(this)).invariantWithdrawAllTokens()
    116
            {} catch {
    117
                assert(false);
    118
            }
    119
        }
    120
    
                                                    
                                                
    121
        /**
    122
         * @notice The sum of all `amtDelta`s for an account should be zero
    123
         * @param targetAccId Target account to perform the test on
    124
         */
    125
        function invariantSumAmtDeltaIsZero(uint8 targetAccId) public heavy {
    126
    ✓ 1.2M
            address target = getAccount(targetAccId);
    127
    ✓ 1.2M
            uint256 targetDripsAccId = getDripsAccountId(target);
    128
    
                                                    
                                                
    129
    ✓ 1.2M
            uint32 maxEnd = getMaxEndForAllUsers();
    130
    
                                                    
                                                
    131
    ✓ 1.2M
            uint32 firstCycle = getCycleFromTimestamp(STARTING_TIMESTAMP);
    132
    ✓ 1.2M
            uint32 lastCycle = getCycleFromTimestamp(maxEnd);
    133
    
                                                    
                                                
    134
    ✓ 1.2M
            require(maxEnd > 0, "no cycles");
    135
    ✓ 953.1K
            require(firstCycle != lastCycle, "only one cycle");
    136
    
                                                    
                                                
    137
            // limit amount of cycles for gas & memory savings
    138
    ✓ 932.4K
            require(lastCycle - firstCycle < 1000, "too many cycles");
    139
    
                                                    
                                                
    140
    ✓ 602.1K
            int256 sumAmtDelta = 0;
    141
    
                                                    
                                                
    142
    ✓ 139.4M
            for (uint32 cycle = firstCycle; cycle <= lastCycle; cycle++) {
    143
    ✓ 138.8M
                (int128 thisCycle, int128 nextCycle) = drips.getAmtDeltaForCycle(
    144
    ✓ 138.8M
                    targetDripsAccId,
    145
    ✓ 138.8M
                    token,
    146
    ✓ 138.8M
                    cycle
    147
                );
    148
    
                                                    
                                                
    149
    ✓ 138.8M
                sumAmtDelta += int256(thisCycle);
    150
    ✓ 138.8M
                sumAmtDelta += int256(nextCycle);
    151
            }
    152
    
                                                    
                                                
    153
    ✓ 602.1K
            assert(sumAmtDelta == 0);
    154
        }
    155
    }
    156
    
                                                    
                                                
    97.7% src/echidna/EchidnaSplitsHelpers.sol
    Lines covered: 43 / 44 (97.7%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    
                                                    
                                                
    5
    /**
    6
     * @title Mixin containing helpers for splitting
    7
     * @author Rappie <rappie@perimetersec.io>
    8
     */
    9
    contract EchidnaSplitsHelpers is EchidnaBase {
    10
        /**
    11
         * @notice Internal helper function to set splits receivers
    12
         * @param senderAccId Account id of the sender
    13
         * @param unsortedReceivers Receivers list to set
    14
         * @dev This function also sorts the receivers list
    15
         */
    16
        function _setSplits(
    17
            uint8 senderAccId,
    18
            SplitsReceiver[] memory unsortedReceivers
    19
        ) internal {
    20
    ✓ 13.4M
            address sender = getAccount(senderAccId);
    21
    
                                                    
                                                
    22
    ✓ 13.4M
            SplitsReceiver[] memory newReceivers = bubbleSortSplitsReceivers(
    23
    ✓ 13.4M
                unsortedReceivers
    24
            );
    25
    
                                                    
                                                
    26
    ✓ 13.1M
            updateSplitsReceivers(sender, newReceivers);
    27
    
                                                    
                                                
    28
    ✓ 13.1M
            hevm.prank(sender);
    29
    ✓ 13.1M
            driver.setSplits(newReceivers);
    30
        }
    31
    
                                                    
                                                
    32
        /**
    33
         * @notice Set splits, overwriting the current receivers list
    34
         * @param senderAccId Account id of the sender
    35
         * @param receiverAccId Account id of the receiver in the receivers list
    36
         * @param weight Weight of the receiver
    37
         */
    38
        function setSplits(
    39
            uint8 senderAccId,
    40
            uint8 receiverAccId,
    41
            uint32 weight
    42
        ) public {
    43
    ✓ 3.7M
            address sender = getAccount(senderAccId);
    44
    ✓ 3.7M
            address receiver = getAccount(receiverAccId);
    45
    ✓ 3.7M
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    46
    
                                                    
                                                
    47
    ✓ 3.7M
            SplitsReceiver[] memory receivers = new SplitsReceiver[](1);
    48
    ✓ 3.7M
            receivers[0] = SplitsReceiver({
    49
    ✓ 3.7M
                accountId: receiverDripsAccId,
    50
    ✓ 3.7M
                weight: weight
    51
            });
    52
    ✓ 3.7M
            updateSplitsReceivers(sender, receivers);
    53
    
                                                    
                                                
    54
    ✓ 3.7M
            _setSplits(senderAccId, receivers);
    55
        }
    56
    
                                                    
                                                
    57
        /**
    58
         * @notice Set splits, overwriting the current receivers list
    59
         * @param senderAccId Account id of the sender
    60
         * @param receiverAccId Account id of the receiver in the receivers list
    61
         * @param weight Weight of the receiver
    62
         * @dev This function clamps the weight between the minimum and maximum
    63
         * allowed values
    64
         */
    65
        function setSplitsWithClamping(
    66
            uint8 senderAccId,
    67
            uint8 receiverAccId,
    68
            uint32 weight
    69
        ) public {
    70
    ✓ 2.4M
            weight = clampSplitWeight(weight, 0); // there are no existing weights
    71
    ✓ 2.4M
            setSplits(senderAccId, receiverAccId, weight);
    72
        }
    73
    
                                                    
                                                
    74
        /**
    75
         * @notice Add a splits receiver to the existing list of receivers
    76
         * @param senderAccId Account id of the sender
    77
         * @param receiverAccId Account id of the receiver to add
    78
         * @param weight Weight of the receiver
    79
         */
    80
        function addSplitsReceiver(
    81
            uint8 senderAccId,
    82
            uint8 receiverAccId,
    83
            uint32 weight
    84
        ) public {
    85
    ✓ 2.7M
            address sender = getAccount(senderAccId);
    86
    ✓ 2.7M
            address receiver = getAccount(receiverAccId);
    87
    ✓ 2.7M
            uint256 senderDripsAccId = getDripsAccountId(sender);
    88
    ✓ 2.7M
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    89
    
                                                    
                                                
    90
    ✓ 2.7M
            SplitsReceiver[] memory oldReceivers = getSplitsReceivers(sender);
    91
    
                                                    
                                                
    92
    ✓ 2.7M
            SplitsReceiver memory addedReceiver = SplitsReceiver({
    93
    ✓ 2.7M
                accountId: receiverDripsAccId,
    94
    ✓ 2.7M
                weight: weight
    95
            });
    96
    
                                                    
                                                
    97
    ✓ 2.7M
            SplitsReceiver[] memory newReceivers = new SplitsReceiver[](
    98
    ✓ 2.7M
                oldReceivers.length + 1
    99
            );
    100
    ✓ 4.0M
            for (uint256 i = 0; i < oldReceivers.length; i++) {
    101
    ✓ 1.2M
                newReceivers[i] = oldReceivers[i];
    102
            }
    103
    ✓ 2.7M
            newReceivers[newReceivers.length - 1] = addedReceiver;
    104
    
                                                    
                                                
    105
    ✓ 2.7M
            _setSplits(senderAccId, newReceivers);
    106
        }
    107
    
                                                    
                                                
    108
        /**
    109
         * @notice Add a splits receiver to the existing list of receivers
    110
         * @param senderAccId Account id of the sender
    111
         * @param receiverAccId Account id of the receiver to add
    112
         * @param weight Weight of the receiver
    113
         * @dev This function clamps the weight between the minimum and maximum
    114
         * allowed values
    115
         */
    116
        function addSplitsReceiverWithClamping(
    117
            uint8 senderAccId,
    118
            uint8 receiverAccId,
    119
            uint32 weight
    120
        ) public {
    121
    ✓ 2.0M
            address sender = getAccount(senderAccId);
    122
    
                                                    
                                                
    123
            // sum all the existing weights
    124
    ✓ 2.0M
            uint32 existingWeights;
    125
    ✓ 2.0M
            SplitsReceiver[] memory receivers = getSplitsReceivers(sender);
    126
    ✓ 3.0M
            for (uint256 i = 0; i < receivers.length; i++) {
    127
    ✓ 1.0M
                existingWeights += receivers[i].weight;
    128
            }
    129
    
                                                    
                                                
    130
            // we can't add a receiver if it makes the total weight go over the
    131
            // maximum allowed
    132
    ✓ 2.0M
            if (existingWeights >= drips.TOTAL_SPLITS_WEIGHT()) return;
    133
    
                                                    
                                                
    134
    ✓ 2.0M
            weight = clampSplitWeight(weight, existingWeights);
    135
    
                                                    
                                                
    136
    ✓ 2.0M
            addSplitsReceiver(senderAccId, receiverAccId, weight);
    137
        }
    138
    
                                                    
                                                
    139
        /**
    140
         * @notice Remove any existing splits
    141
         * @param targetAccId Target account id
    142
         */
    143
        function removeAllSplits(uint8 targetAccId) public {
    144
    ✓ 6.9M
            SplitsReceiver[] memory receivers = new SplitsReceiver[](0);
    145
    ✓ 6.9M
            _setSplits(targetAccId, receivers);
    146
        }
    147
    
                                                    
                                                
    148
        /**
    149
         * @notice Clamp the weight between the minimum and maximum allowed values
    150
         * @param weight Weight to clamp
    151
         * @param existingWeights Sum of all the existing weights
    152
         * @return Clamped weight
    153
         */
    154
        function clampSplitWeight(uint32 weight, uint32 existingWeights)
    155
            public
    156
            view
    157
    ✓ 4.5M
            returns (uint32)
    158
        {
    159
    ✓ 4.5M
            return (weight % (drips.TOTAL_SPLITS_WEIGHT() - existingWeights)) + 1;
    160
        }
    161
    }
    162
    
                                                    
                                                
    95.5% src/echidna/EchidnaSplitsTests.sol
    Lines covered: 84 / 88 (95.5%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    import "./EchidnaBasicHelpers.sol";
    5
    import "./EchidnaSplitsHelpers.sol";
    6
    
                                                    
                                                
    7
    /**
    8
     * @title Mixin containing tests for splitting
    9
     * @author Rappie <rappie@perimetersec.io>
    10
     */
    11
    contract EchidnaSplitsTests is
    12
        EchidnaBase,
    13
        EchidnaBasicHelpers,
    14
        EchidnaSplitsHelpers
    15
    {
    16
        /**
    17
         * @notice Test internal accounting for splittable amount after splitting
    18
         * @param targetAccId Account id execute split on
    19
         */
    20
        function testSplittableAfterSplit(uint8 targetAccId) public {
    21
    ✓ 983.0K
            address target = getAccount(targetAccId);
    22
    ✓ 983.0K
            uint256 targetDripsAccId = getDripsAccountId(target);
    23
    
                                                    
                                                
    24
    ✓ 983.0K
            uint128 splittableBefore = drips.splittable(targetDripsAccId, token);
    25
    
                                                    
                                                
    26
            // check if we are splitting to ourselves
    27
    ✓ 983.0K
            uint32 splitToSelfWeight;
    28
    ✓ 983.0K
            SplitsReceiver[] memory receivers = getSplitsReceivers(target);
    29
    ✓ 1.2M
            for (uint256 i = 0; i < receivers.length; i++) {
    30
    ✓ 251.4K
                if (receivers[i].accountId == targetDripsAccId) {
    31
    ✓ 83.4K
                    splitToSelfWeight += receivers[i].weight;
    32
                }
    33
            }
    34
    
                                                    
                                                
    35
            // calculate amount to split to self
    36
    ✓ 983.0K
            uint128 splitToSelfAmount = uint128(
    37
    ✓ 983.0K
                (splittableBefore * splitToSelfWeight) / drips.TOTAL_SPLITS_WEIGHT()
    38
            );
    39
    
                                                    
                                                
    40
    ✓ 983.0K
            (uint128 collectableAmt, uint128 splitAmt) = split(targetAccId);
    41
    
                                                    
                                                
    42
    ✓ 983.0K
            uint128 splittableAfter = drips.splittable(targetDripsAccId, token);
    43
    
                                                    
                                                
    44
            // sanity check
    45
    ✓ 983.0K
            assert((splitAmt + collectableAmt) <= splittableBefore);
    46
    
                                                    
                                                
    47
    ✓ 983.0K
            if (splitToSelfWeight == 0) {
    48
                // if we're not splitting to ourselves, things are simple
    49
                assert(
    50
    ✓ 899.6K
                    splittableAfter == splittableBefore - splitAmt - collectableAmt
    51
                );
    52
            } else {
    53
                // if we ARE splitting to ourselves, there are rounding errors
    54
                // to take into account.
    55
    
                                                    
                                                
    56
                // calculate expected amount after the split
    57
    ✓ 83.4K
                uint128 expectedSplittableAfter = splittableBefore -
    58
    ✓ 83.4K
                    splitAmt -
    59
    ✓ 83.4K
                    collectableAmt +
    60
    ✓ 83.4K
                    splitToSelfAmount;
    61
    
                                                    
                                                
    62
                // calculate difference between expected and actual
    63
    ✓ 83.4K
                int256 difference = int256(uint256(splittableAfter)) -
    64
    ✓ 83.4K
                    int256(uint256(expectedSplittableAfter));
    65
    
                                                    
                                                
    66
                // check if difference is within tolerance
    67
                assert(
    68
    ✓ 83.4K
                    difference >= -int256(SPLIT_ROUNDING_TOLERANCE) &&
    69
    ✓ 83.4K
                        difference <= int256(SPLIT_ROUNDING_TOLERANCE)
    70
                );
    71
            }
    72
        }
    73
    
                                                    
                                                
    74
        /**
    75
         * @notice Test internal accounting for collectable amount after splitting
    76
         * @param targetAccId Account id execute split on
    77
         */
    78
        function testCollectableAfterSplit(uint8 targetAccId) public {
    79
    ✓ 919.3K
            address target = getAccount(targetAccId);
    80
    ✓ 919.3K
            uint256 targetDripsAccId = getDripsAccountId(target);
    81
    
                                                    
                                                
    82
    ✓ 919.3K
            uint128 colBalBefore = drips.collectable(targetDripsAccId, token);
    83
    ✓ 919.3K
            (uint128 collectableAmt, ) = split(targetAccId);
    84
    ✓ 919.3K
            uint128 colBalAfter = drips.collectable(targetDripsAccId, token);
    85
    
                                                    
                                                
    86
    ✓ 919.3K
            assert(colBalAfter == colBalBefore + collectableAmt);
    87
        }
    88
    
                                                    
                                                
    89
        /**
    90
         * @notice After splitting, all receivers should have their splittable
    91
         * amount increased by the amount they were supposed to receive
    92
         * @param targetAccId Account id execute split on
    93
         */
    94
        function testReceiversReceivedSplit(uint8 targetAccId) public {
    95
    ✓ 1.5M
            address target = getAccount(targetAccId);
    96
    ✓ 1.5M
            uint256 targetDripsAccId = getDripsAccountId(target);
    97
    
                                                    
                                                
    98
    ✓ 1.5M
            uint128 amountToBeSplit = drips.splittable(targetDripsAccId, token);
    99
    ✓ 1.5M
            SplitsReceiver[] memory receivers = getSplitsReceivers(target);
    100
    
                                                    
                                                
    101
            // storage for all receivers
    102
    ✓ 1.5M
            uint128[] memory splittableBefore = new uint128[](receivers.length);
    103
    ✓ 1.5M
            uint128[] memory splittableAfter = new uint128[](receivers.length);
    104
    ✓ 1.5M
            uint32[] memory weights = new uint32[](receivers.length);
    105
    ✓ 1.5M
            uint128[] memory amounts = new uint128[](receivers.length);
    106
    
                                                    
                                                
    107
    ✓ 2.2M
            for (uint256 i = 0; i < receivers.length; i++) {
    108
                // store splittable before
    109
    ✓ 714.2K
                splittableBefore[i] = drips.splittable(
    110
    ✓ 714.2K
                    receivers[i].accountId,
    111
    ✓ 714.2K
                    token
    112
                );
    113
    
                                                    
                                                
    114
                // calculate amount the receiver should get
    115
    ✓ 714.2K
                weights[i] = receivers[i].weight;
    116
    ✓ 714.2K
                amounts[i] = uint128(
    117
    ✓ 714.2K
                    (amountToBeSplit * weights[i]) / drips.TOTAL_SPLITS_WEIGHT()
    118
                );
    119
            }
    120
    
                                                    
                                                
    121
            // split
    122
    ✓ 1.5M
            (uint128 collectableAmt, uint128 splitAmt) = split(targetAccId);
    123
    
                                                    
                                                
    124
            // store splittable after
    125
    ✓ 2.2M
            for (uint256 i = 0; i < receivers.length; i++) {
    126
    ✓ 714.2K
                splittableAfter[i] = drips.splittable(
    127
    ✓ 714.2K
                    receivers[i].accountId,
    128
    ✓ 714.2K
                    token
    129
                );
    130
            }
    131
    
                                                    
                                                
    132
    ✓ 2.2M
            for (uint256 i = 0; i < receivers.length; i++) {
    133
                // calculate expected amount after the split
    134
    ✓ 714.2K
                uint128 expectedAfter;
    135
    ✓ 714.2K
                if (receivers[i].accountId != targetDripsAccId) {
    136
                    // splitting so someone else is trivial
    137
    ✓ 621.3K
                    expectedAfter = splittableBefore[i] + amounts[i];
    138
                } else {
    139
                    // splitting to self needs to take into account that the splittable
    140
                    // amount before contains the actual amount that was split to all
    141
                    // the receivers. we should end up with only the amount that was
    142
                    // split to ourselves
    143
                    expectedAfter =
    144
    ✓ 92.9K
                        splittableBefore[i] -
    145
    ✓ 92.9K
                        amountToBeSplit +
    146
    ✓ 92.9K
                        amounts[i];
    147
                }
    148
    
                                                    
                                                
    149
                // calculate difference between expected and actual
    150
    ✓ 714.2K
                int256 difference = int256(uint256(splittableAfter[i])) -
    151
    ✓ 714.2K
                    int256(uint256(expectedAfter));
    152
    
                                                    
                                                
    153
                // check if difference is within tolerance
    154
                assert(
    155
    ✓ 714.2K
                    difference >= -int256(SPLIT_ROUNDING_TOLERANCE) &&
    156
    ✓ 714.2K
                        difference <= int256(SPLIT_ROUNDING_TOLERANCE)
    157
                );
    158
            }
    159
        }
    160
    
                                                    
                                                
    161
        /**
    162
         * @notice `drips.splitResult` should match actual split amount
    163
         * @param targetAccId Account id execute split on
    164
         */
    165
        function testSplitViewVsActual(uint8 targetAccId) public {
    166
    ✓ 1.2M
            address target = getAccount(targetAccId);
    167
    ✓ 1.2M
            uint256 targetDripsAccId = getDripsAccountId(target);
    168
    
                                                    
                                                
    169
    ✓ 1.2M
            uint128 splittable = drips.splittable(targetDripsAccId, token);
    170
    
                                                    
                                                
    171
    ✓ 1.2M
            (uint128 collectableAmtView, uint128 splitAmtView) = drips.splitResult(
    172
    ✓ 1.2M
                targetDripsAccId,
    173
    ✓ 1.2M
                getSplitsReceivers(target),
    174
    ✓ 1.2M
                splittable
    175
            );
    176
    
                                                    
                                                
    177
    ✓ 1.2M
            (uint128 collectableAmtActual, uint128 splitAmtActual) = split(
    178
    ✓ 1.2M
                targetAccId
    179
            );
    180
    
                                                    
                                                
    181
    ✓ 1.2M
            assert(collectableAmtView == collectableAmtActual);
    182
    ✓ 1.2M
            assert(splitAmtView == splitAmtActual);
    183
        }
    184
    
                                                    
                                                
    185
        /**
    186
         * @notice Splitting should never revert
    187
         * @param targetAccId Account id execute split on
    188
         */
    189
        function testSplitShouldNotRevert(uint8 targetAccId) public {
    190
    ✓ 1.3M
            address target = getAccount(targetAccId);
    191
    ✓ 1.3M
            uint256 targetDripsAccId = getDripsAccountId(target);
    192
    
                                                    
                                                
    193
    ✓ 1.3M
            try EchidnaBasicHelpers(address(this)).split(targetAccId) {} catch {
    194
                assert(false);
    195
            }
    196
        }
    197
    
                                                    
                                                
    198
        /**
    199
         * @notice Setting splits with sane defaults should not revert
    200
         * @param senderAccId Account id of the sender
    201
         * @param receiverAccId Account id of the receiver in the receivers list
    202
         * @param weight Weight of the receiver
    203
         */
    204
        function testSetSplitsShouldNotRevert(
    205
            uint8 senderAccId,
    206
            uint8 receiverAccId,
    207
            uint32 weight
    208
        ) public {
    209
            try
    210
    ✓ 1.3M
                EchidnaSplitsHelpers(address(this)).setSplitsWithClamping(
    211
    ✓ 1.3M
                    senderAccId,
    212
    ✓ 1.3M
                    receiverAccId,
    213
    ✓ 1.3M
                    weight
    214
                )
    215
            {} catch {
    216
                assert(false);
    217
            }
    218
        }
    219
    
                                                    
                                                
    220
        /**
    221
         * @notice Adding splits with sane defaults should not revert
    222
         * @param senderAccId Account id of the sender
    223
         * @param receiverAccId Account id of the receiver in the receivers list
    224
         * @param weight Weight of the receiver
    225
         */
    226
        function testAddSplitsShouldNotRevert(
    227
            uint8 senderAccId,
    228
            uint8 receiverAccId,
    229
            uint32 weight
    230
        ) public {
    231
            try
    232
    ✓ 820.0K
                EchidnaSplitsHelpers(address(this)).addSplitsReceiverWithClamping(
    233
    ✓ 820.0K
                    senderAccId,
    234
    ✓ 820.0K
                    receiverAccId,
    235
    ✓ 820.0K
                    weight
    236
                )
    237
            {} catch (bytes memory reason) {
    238
    ✓ 145.8K
                bytes4 errorSelector = bytes4(reason);
    239
    ✓ 145.8K
                if (errorSelector == EchidnaStorage.DuplicateError.selector) {
    240
                    // ignore this case, it means we tried to add a duplicate stream
    241
                } else {
    242
                    assert(false);
    243
                }
    244
            }
    245
        }
    246
    }
    247
    
                                                    
                                                
    98.5% src/echidna/EchidnaSqueezeHelpers.sol
    Lines covered: 66 / 67 (98.5%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    import "./EchidnaBasicHelpers.sol";
    5
    
                                                    
                                                
    6
    /**
    7
     * @title Mixin containing helpers for squeezing
    8
     * @author Rappie <rappie@perimetersec.io>
    9
     */
    10
    contract EchidnaSqueezeHelpers is EchidnaBase, EchidnaBasicHelpers {
    11
        /**
    12
         * @notice Internal helper function to squeeze streams
    13
         * @param receiverAccId Account id of the receiver
    14
         * @param senderAccId Account id of the sender
    15
         * @param historyHash Hash of the streams history
    16
         * @param history Streams history array
    17
         * @return Amount squeezed
    18
         */
    19
        function _squeeze(
    20
            uint8 receiverAccId,
    21
            uint8 senderAccId,
    22
            bytes32 historyHash,
    23
            StreamsHistory[] memory history
    24
    ✓ 43.2M
        ) internal returns (uint128) {
    25
    ✓ 43.2M
            address receiver = getAccount(receiverAccId);
    26
    ✓ 43.2M
            address sender = getAccount(senderAccId);
    27
    ✓ 43.2M
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    28
    ✓ 43.2M
            uint256 senderDripsAccId = getDripsAccountId(sender);
    29
    
                                                    
                                                
    30
    ✓ 43.2M
            uint128 amount = drips.squeezeStreams(
    31
    ✓ 43.2M
                receiverDripsAccId,
    32
    ✓ 43.2M
                token,
    33
    ✓ 43.2M
                senderDripsAccId,
    34
    ✓ 43.2M
                historyHash,
    35
    ✓ 43.2M
                history
    36
            );
    37
    
                                                    
                                                
    38
    ✓ 43.2M
            return amount;
    39
        }
    40
    
                                                    
                                                
    41
        /**
    42
         * @notice Squeeze streams with default history (all StreamHistory entries)
    43
         * @param receiverAccId Account id of the receiver
    44
         * @param senderAccId Account id of the sender
    45
         * @return Amount squeezed
    46
         */
    47
        function squeezeWithDefaultHistory(uint8 receiverAccId, uint8 senderAccId)
    48
            public
    49
    ✓ 38.5M
            returns (uint128)
    50
        {
    51
            return
    52
    ✓ 38.5M
                _squeeze(
    53
    ✓ 38.5M
                    receiverAccId,
    54
    ✓ 38.5M
                    senderAccId,
    55
    ✓ 38.5M
                    bytes32(0),
    56
    ✓ 38.5M
                    getStreamsHistory(getAccount(senderAccId))
    57
                );
    58
        }
    59
    
                                                    
                                                
    60
        /**
    61
         * @notice Squeeze streams with a fuzzed history
    62
         * @param receiverAccId Account id of the receiver
    63
         * @param senderAccId Account id of the sender
    64
         * @param hashIndex Index of the history hash to use
    65
         * @param receiversRandomSeed Random seed used for fuzzing the history
    66
         * @return Amount squeezed
    67
         * @dev This function will use the seed to make random changes to the history.
    68
         * These include changing the starting point of the history, and hashing
    69
         * certain history entries to leave them out of the squeeze.
    70
         */
    71
        function squeezeWithFuzzedHistory(
    72
            uint8 receiverAccId,
    73
            uint8 senderAccId,
    74
            uint256 hashIndex,
    75
            bytes32 receiversRandomSeed
    76
    ✓ 4.2M
        ) public returns (uint128) {
    77
    ✓ 4.2M
            address receiver = getAccount(receiverAccId);
    78
    ✓ 4.2M
            address sender = getAccount(senderAccId);
    79
    ✓ 4.2M
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    80
    ✓ 4.2M
            uint256 senderDripsAccId = getDripsAccountId(sender);
    81
    
                                                    
                                                
    82
            (
    83
    ✓ 4.2M
                bytes32 historyHash,
    84
    ✓ 4.2M
                StreamsHistory[] memory history
    85
    ✓ 4.2M
            ) = getFuzzedStreamsHistory(
    86
    ✓ 4.2M
                    senderAccId,
    87
    ✓ 4.2M
                    hashIndex,
    88
    ✓ 4.2M
                    receiversRandomSeed
    89
                );
    90
    
                                                    
                                                
    91
    ✓ 3.4M
            return _squeeze(receiverAccId, senderAccId, historyHash, history);
    92
        }
    93
    
                                                    
                                                
    94
        /**
    95
         * @notice Squeeze streams sent to self
    96
         * @param targetAccId Account id of the sender
    97
         */
    98
        function squeezeToSelf(uint8 targetAccId) public {
    99
    ✓ 1.2M
            squeezeWithDefaultHistory(targetAccId, targetAccId);
    100
        }
    101
    
                                                    
                                                
    102
        /**
    103
         * @notice Squeeze streams from all possible senders to target
    104
         * @param targetAccId Account id of the receiver
    105
         * @dev This can be used to test extracting all value from the system
    106
         */
    107
        function squeezeAllSenders(uint8 targetAccId) public {
    108
    ✓ 7.8M
            squeezeWithDefaultHistory(
    109
    ✓ 7.8M
                targetAccId,
    110
    ✓ 7.8M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]
    111
            );
    112
    ✓ 7.8M
            squeezeWithDefaultHistory(
    113
    ✓ 7.8M
                targetAccId,
    114
    ✓ 7.8M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]
    115
            );
    116
    ✓ 7.8M
            squeezeWithDefaultHistory(
    117
    ✓ 7.8M
                targetAccId,
    118
    ✓ 7.8M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]
    119
            );
    120
    ✓ 7.8M
            squeezeWithDefaultHistory(
    121
    ✓ 7.8M
                targetAccId,
    122
    ✓ 7.8M
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]
    123
            );
    124
        }
    125
    
                                                    
                                                
    126
        /**
    127
         * @notice Squeeze all senders, receive streams, split and collect funds to self
    128
         * @param targetAccId Target account
    129
         * @dev Extra helper that narrows the search space for the fuzzer
    130
         */
    131
        function squeezeAllAndReceiveAndSplitAndCollectToSelf(uint8 targetAccId)
    132
            public
    133
        {
    134
    ✓ 996.4K
            squeezeAllSenders(targetAccId);
    135
    ✓ 996.4K
            receiveStreamsSplitAndCollectToSelf(targetAccId);
    136
        }
    137
    
                                                    
                                                
    138
        /**
    139
         * @notice Helper to create a fuzzed version of a sender's streams history
    140
         * @param targetAccId Account id of the sender
    141
         * @param hashIndex Index of the history entry to be used as the starting point
    142
         * @param receiversRandomSeed Random seed used to determine which history entries
    143
         * to leave out of the squeeze (by hashing them)
    144
         * @return Hash of the history, and the fuzzed streams history array
    145
         */
    146
        function getFuzzedStreamsHistory(
    147
            uint8 targetAccId,
    148
            uint256 hashIndex,
    149
            bytes32 receiversRandomSeed
    150
    ✓ 4.2M
        ) internal returns (bytes32, StreamsHistory[] memory) {
    151
    ✓ 4.2M
            address target = getAccount(targetAccId);
    152
    
                                                    
                                                
    153
            // get the history structs and hashes
    154
    ✓ 4.2M
            StreamsHistory[] memory historyStructs = getStreamsHistory(target);
    155
    ✓ 4.2M
            bytes32[] memory historyHashes = getStreamsHistoryHashes(target);
    156
    
                                                    
                                                
    157
            // having a hashed history requires at least 2 history entries
    158
    ✓ 4.2M
            require(historyStructs.length >= 2, "need at least 2 history entries");
    159
    
                                                    
                                                
    160
            // hashIndex must be within bounds and cant be the last entry
    161
    ✓ 3.4M
            hashIndex = hashIndex % (historyHashes.length - 1);
    162
    
                                                    
                                                
    163
            // get the history hash at the index
    164
    ✓ 3.4M
            bytes32 historyHash = historyHashes[hashIndex];
    165
    
                                                    
                                                
    166
            // create a history array with all entries after the hashIndex
    167
    ✓ 3.4M
            StreamsHistory[] memory history = new StreamsHistory[](
    168
    ✓ 3.4M
                historyStructs.length - 1 - hashIndex
    169
            );
    170
    ✓ 13.3M
            for (uint256 i = hashIndex + 1; i < historyStructs.length; i++) {
    171
    ✓ 9.9M
                history[i - hashIndex - 1] = historyStructs[i];
    172
            }
    173
    
                                                    
                                                
    174
            // hash receivers based on 'receiversRandomSeed'
    175
    ✓ 13.3M
            for (uint256 i = 0; i < history.length; i++) {
    176
    ✓ 9.9M
                receiversRandomSeed = keccak256(bytes.concat(receiversRandomSeed));
    177
    ✓ 9.9M
                bool hashBool = (uint256(receiversRandomSeed) % 2) == 0
    178
    ✓ 4.9M
                    ? false
    179
    ✓ 5.0M
                    : true;
    180
    
                                                    
                                                
    181
    ✓ 9.9M
                if (hashBool) {
    182
    ✓ 5.0M
                    history[i].streamsHash = drips.hashStreams(
    183
    ✓ 5.0M
                        history[i].receivers
    184
                    );
    185
    ✓ 5.0M
                    history[i].receivers = new StreamReceiver[](0);
    186
                }
    187
            }
    188
    
                                                    
                                                
    189
    ✓ 3.4M
            return (historyHash, history);
    190
        }
    191
    }
    192
    
                                                    
                                                
    96.8% src/echidna/EchidnaSqueezeTests.sol
    Lines covered: 92 / 95 (96.8%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./EchidnaBasicHelpers.sol";
    4
    import "./EchidnaSplitsHelpers.sol";
    5
    import "./EchidnaStreamsHelpers.sol";
    6
    import "./EchidnaSqueezeHelpers.sol";
    7
    
                                                    
                                                
    8
    /**
    9
     * @title Mixin containing tests for squeezing
    10
     * @author Rappie <rappie@perimetersec.io>
    11
     */
    12
    contract EchidnaSqueezeTests is
    13
        EchidnaBasicHelpers,
    14
        EchidnaSplitsHelpers,
    15
        EchidnaStreamsHelpers,
    16
        EchidnaSqueezeHelpers
    17
    {
    18
        /**
    19
         * @notice Test internal accounting after squeezing
    20
         * @param receiverAccId Account id of the receiver
    21
         * @param senderAccId Account id of the sender
    22
         */
    23
        function testSqueeze(uint8 receiverAccId, uint8 senderAccId) public {
    24
    ✓ 2.4M
            address receiver = getAccount(receiverAccId);
    25
    ✓ 2.4M
            address sender = getAccount(senderAccId);
    26
    
                                                    
                                                
    27
    ✓ 2.4M
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    28
    
                                                    
                                                
    29
    ✓ 2.4M
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    30
    ✓ 2.4M
            uint128 splittableBefore = drips.splittable(receiverDripsAccId, token);
    31
    
                                                    
                                                
    32
    ✓ 2.4M
            uint128 squeezedAmt = squeezeWithDefaultHistory(
    33
    ✓ 2.4M
                receiverAccId,
    34
    ✓ 2.4M
                senderAccId
    35
            );
    36
    
                                                    
                                                
    37
    ✓ 2.4M
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    38
    ✓ 2.4M
            uint128 splittableAfter = drips.splittable(receiverDripsAccId, token);
    39
    
                                                    
                                                
    40
    ✓ 2.4M
            assert(squeezableAfter == squeezableBefore - squeezedAmt);
    41
    ✓ 2.4M
            assert(splittableAfter == splittableBefore + squeezedAmt);
    42
    
                                                    
                                                
    43
    ✓ 2.4M
            if (squeezedAmt > 0) {
    44
    ✓ 11.7K
                assert(squeezableAfter < squeezableBefore);
    45
    ✓ 11.7K
                assert(splittableAfter > splittableBefore);
    46
            } else {
    47
    ✓ 2.3M
                assert(squeezableAfter == squeezableBefore);
    48
    ✓ 2.3M
                assert(splittableAfter == splittableBefore);
    49
            }
    50
        }
    51
    
                                                    
                                                
    52
        /**
    53
         * @notice `drips.squeezeStreamsResult` should match actual squeezed amount
    54
         * @param receiverAccId Account id of the receiver
    55
         * @param senderAccId Account id of the sender
    56
         */
    57
        function testSqueezeViewVsActual(uint8 receiverAccId, uint8 senderAccId)
    58
            public
    59
        {
    60
    ✓ 1.1M
            address receiver = getAccount(receiverAccId);
    61
    ✓ 1.1M
            address sender = getAccount(senderAccId);
    62
    
                                                    
                                                
    63
    ✓ 1.1M
            uint128 squeezable = getSqueezableAmount(sender, receiver);
    64
    ✓ 1.1M
            uint128 squeezed = squeezeWithDefaultHistory(
    65
    ✓ 1.1M
                receiverAccId,
    66
    ✓ 1.1M
                senderAccId
    67
            );
    68
    
                                                    
                                                
    69
    ✓ 1.1M
            assert(squeezable == squeezed);
    70
        }
    71
    
                                                    
                                                
    72
        /**
    73
         * @notice Squeezable amount should be equal to receivable amount in the future
    74
         * @param targetAccId Account id of the receiver
    75
         */
    76
        function testSqueezableVsReceived(uint8 targetAccId) public heavy {
    77
    ✓ 1.3M
            address target = getAccount(targetAccId);
    78
    
                                                    
                                                
    79
            // store the current squeezable and receivable amount
    80
    ✓ 1.3M
            uint128 squeezable = getTotalSqueezableAmountForUser(target);
    81
    ✓ 1.3M
            uint128 receivableBefore = getReceivableAmountForUser(target);
    82
    
                                                    
                                                
    83
            // remove all streaming balance from the system, so that warping to
    84
            // the future will not increase the receivable/squeezable amount
    85
    ✓ 1.3M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    86
    ✓ 1.3M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    87
    ✓ 1.3M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    88
    ✓ 1.3M
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    89
    
                                                    
                                                
    90
            // warp to the point in time where the streams are receivable
    91
    ✓ 1.3M
            hevm.warp(getCurrentCycleEnd() + 1);
    92
    
                                                    
                                                
    93
    ✓ 1.3M
            uint128 receivableAfter = getReceivableAmountForUser(target);
    94
    
                                                    
                                                
    95
            // sanity check
    96
    ✓ 1.3M
            assert(receivableAfter >= receivableBefore);
    97
    
                                                    
                                                
    98
    ✓ 1.3M
            uint128 receiveableDelta = receivableAfter - receivableBefore;
    99
    
                                                    
                                                
    100
            // squeezable before should match receivable now
    101
    ✓ 1.3M
            assert(squeezable == receiveableDelta);
    102
        }
    103
    
                                                    
                                                
    104
        /**
    105
         * @notice Squeezing with a fully hashed history should do nothing
    106
         * @param receiverAccId Account id of the receiver
    107
         * @param senderAccId Account id of the sender
    108
         */
    109
        function testSqueezeWithFullyHashedHistory(
    110
            uint8 receiverAccId,
    111
            uint8 senderAccId
    112
        ) public {
    113
    ✓ 1.2M
            address receiver = getAccount(receiverAccId);
    114
    ✓ 1.2M
            address sender = getAccount(senderAccId);
    115
    
                                                    
                                                
    116
    ✓ 1.2M
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    117
    
                                                    
                                                
    118
    ✓ 1.2M
            StreamsHistory[] memory history = getStreamsHistory(sender);
    119
    ✓ 6.3M
            for (uint256 i = 0; i < history.length; i++) {
    120
    ✓ 5.0M
                history[i].streamsHash = drips.hashStreams(history[i].receivers);
    121
    ✓ 5.0M
                history[i].receivers = new StreamReceiver[](0);
    122
            }
    123
    
                                                    
                                                
    124
    ✓ 1.2M
            uint128 squeezedAmt = _squeeze(
    125
    ✓ 1.2M
                receiverAccId,
    126
    ✓ 1.2M
                senderAccId,
    127
    ✓ 1.2M
                bytes32(0),
    128
    ✓ 1.2M
                history
    129
            );
    130
    
                                                    
                                                
    131
    ✓ 1.2M
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    132
    
                                                    
                                                
    133
    ✓ 1.2M
            assert(squeezedAmt == 0);
    134
    ✓ 1.2M
            assert(squeezableAfter == squeezableBefore);
    135
        }
    136
    
                                                    
                                                
    137
        /**
    138
         * @notice Squeezing the same part(s) of history should only work the first time
    139
         * @param receiverAccId Account id of the receiver
    140
         * @param senderAccId Account id of the sender
    141
         * @param hashIndex Index of the history entry to squeeze
    142
         * @param receiversRandomSeed Random seed used to determine which history entries
    143
         * to leave out of the squeeze (by hashing them)
    144
         */
    145
        function testSqueezeTwice(
    146
            uint8 receiverAccId,
    147
            uint8 senderAccId,
    148
            uint256 hashIndex,
    149
            bytes32 receiversRandomSeed
    150
        ) external {
    151
    ✓ 1.4M
            address receiver = getAccount(receiverAccId);
    152
    ✓ 1.4M
            address sender = getAccount(senderAccId);
    153
    
                                                    
                                                
    154
    ✓ 1.4M
            uint128 amount0 = squeezeWithFuzzedHistory(
    155
    ✓ 1.4M
                receiverAccId,
    156
    ✓ 1.4M
                senderAccId,
    157
    ✓ 1.4M
                hashIndex,
    158
    ✓ 1.4M
                receiversRandomSeed
    159
            );
    160
    
                                                    
                                                
    161
    ✓ 880.5K
            uint128 amount1 = squeezeWithFuzzedHistory(
    162
    ✓ 880.5K
                receiverAccId,
    163
    ✓ 880.5K
                senderAccId,
    164
    ✓ 880.5K
                hashIndex,
    165
    ✓ 880.5K
                receiversRandomSeed
    166
            );
    167
    
                                                    
                                                
    168
    ✓ 880.5K
            assert(amount1 == 0);
    169
        }
    170
    
                                                    
                                                
    171
        /**
    172
         * @notice Already streamed (and therefore squeezable) balance should not be
    173
         * affected changing the stream receivers
    174
         * @param receiverAccId Account id of the receiver
    175
         * @param senderAccId Account id of the sender
    176
         * @param amountPerSec Amount per second to stream
    177
         * @param startTime Start time for the stream
    178
         * @param duration Duration for the stream
    179
         * @param balanceDelta Amount to update stream balance with
    180
         */
    181
        function testSqueezableAmountCantBeUndone(
    182
            uint8 receiverAccId,
    183
            uint8 senderAccId,
    184
            uint160 amountPerSec,
    185
            uint32 startTime,
    186
            uint32 duration,
    187
            int128 balanceDelta
    188
        ) external {
    189
    ✓ 1.1M
            address receiver = getAccount(receiverAccId);
    190
    ✓ 1.1M
            address sender = getAccount(senderAccId);
    191
    
                                                    
                                                
    192
    ✓ 1.1M
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    193
    
                                                    
                                                
    194
    ✓ 1.1M
            setStreams(
    195
    ✓ 1.1M
                receiverAccId,
    196
    ✓ 1.1M
                senderAccId,
    197
    ✓ 1.1M
                amountPerSec,
    198
    ✓ 1.1M
                startTime,
    199
    ✓ 1.1M
                duration,
    200
    ✓ 1.1M
                balanceDelta
    201
            );
    202
    
                                                    
                                                
    203
    ✓ 673.7K
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    204
    
                                                    
                                                
    205
    ✓ 673.7K
            assert(squeezableAfter == squeezableBefore);
    206
        }
    207
    
                                                    
                                                
    208
        /**
    209
         * @notice Already streamed (and therefore squeezable) balance should not be
    210
         * affected by withdrawing all streaming balance
    211
         * @param receiverAccId Account id of the receiver
    212
         * @param senderAccId Account id of the sender
    213
         */
    214
        function testSqueezableAmountCantBeWithdrawn(
    215
            uint8 receiverAccId,
    216
            uint8 senderAccId
    217
        ) external {
    218
    ✓ 1.0M
            address receiver = getAccount(receiverAccId);
    219
    ✓ 1.0M
            address sender = getAccount(senderAccId);
    220
    
                                                    
                                                
    221
    ✓ 1.0M
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    222
    
                                                    
                                                
    223
    ✓ 1.0M
            setStreamBalanceWithdrawAll(senderAccId);
    224
    
                                                    
                                                
    225
    ✓ 1.0M
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    226
    
                                                    
                                                
    227
    ✓ 1.0M
            assert(squeezableAfter == squeezableBefore);
    228
        }
    229
    
                                                    
                                                
    230
        /**
    231
         * @notice Squeezing with default history (all history entries) should
    232
         * not revert
    233
         * @param receiverAccId Account id of the receiver
    234
         * @param senderAccId Account id of the sender
    235
         */
    236
        function testSqueezeWithDefaultHistoryShouldNotRevert(
    237
            uint8 receiverAccId,
    238
            uint8 senderAccId
    239
        ) public {
    240
            try
    241
    ✓ 1.5M
                EchidnaSqueezeHelpers(address(this)).squeezeWithDefaultHistory(
    242
    ✓ 1.5M
                    receiverAccId,
    243
    ✓ 1.5M
                    senderAccId
    244
                )
    245
            {} catch {
    246
                assert(false);
    247
            }
    248
        }
    249
    
                                                    
                                                
    250
        /**
    251
         * @notice Squeezing with fuzzed history should not revert
    252
         * @param receiverAccId Account id of the receiver
    253
         * @param senderAccId Account id of the sender
    254
         * @param hashIndex Index of the history entry to squeeze
    255
         * @param receiversRandomSeed Random seed used to determine which history entries
    256
         * to leave out of the squeeze (by hashing them)
    257
         */
    258
        function testSqueezeWithFuzzedHistoryShouldNotRevert(
    259
            uint8 receiverAccId,
    260
            uint8 senderAccId,
    261
            uint256 hashIndex,
    262
            bytes32 receiversRandomSeed
    263
        ) public {
    264
    ✓ 892.8K
            address sender = getAccount(senderAccId);
    265
            require(
    266
    ✓ 892.8K
                getStreamsHistory(sender).length >= 2,
    267
                "need at least 2 history entries"
    268
            );
    269
    
                                                    
                                                
    270
            try
    271
    ✓ 778.2K
                EchidnaSqueezeHelpers(address(this)).squeezeWithFuzzedHistory(
    272
    ✓ 778.2K
                    receiverAccId,
    273
    ✓ 778.2K
                    senderAccId,
    274
    ✓ 778.2K
                    hashIndex,
    275
    ✓ 778.2K
                    receiversRandomSeed
    276
                )
    277
            {} catch {
    278
                assert(false);
    279
            }
    280
        }
    281
    
                                                    
                                                
    282
    }
    283
    
                                                    
                                                
    99.3% src/echidna/EchidnaStreamsHelpers.sol
    Lines covered: 143 / 144 (99.3%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    
                                                    
                                                
    5
    /**
    6
     * @title Mixin containing helpers for streams
    7
     * @author Rappie <rappie@perimetersec.io>
    8
     */
    9
    contract EchidnaStreamsHelpers is EchidnaBase {
    10
        // Internal variables to store maxEndHint1 and maxEndHint2. These are used
    11
        // as hints to the Drips contract to speed up the setStreams call.
    12
        //
    13
        // Instead of fuzzing these directly, we use the `setMaxEndHints` helper
    14
        // function to set these values. This makes fuzzing these toggleable and
    15
        // also makes debugging easier because the values don't have to be passed
    16
        // as arguments to all functions calling `setStreams`.
    17
        //
    18
        uint32 internal maxEndHint1;
    19
        uint32 internal maxEndHint2;
    20
    
                                                    
                                                
    21
        /**
    22
         * @notice Internal helper function to set streams receivers
    23
         * @param from Account to set streams for
    24
         * @param currReceivers Current stream receivers (Drips needs this)
    25
         * @param balanceDelta Balance delta to set
    26
         * @param unsortedNewReceivers New receivers list to set
    27
         * @return Real balance delta
    28
         * @dev This function also sorts the receivers list
    29
         */
    30
        function _setStreams(
    31
            address from,
    32
            StreamReceiver[] memory currReceivers,
    33
            int128 balanceDelta,
    34
            StreamReceiver[] memory unsortedNewReceivers
    35
    ✓ 30.5M
        ) internal returns (int128) {
    36
    ✓ 30.5M
            StreamReceiver[] memory newReceivers = bubbleSortStreamReceivers(
    37
    ✓ 30.5M
                unsortedNewReceivers
    38
            );
    39
    
                                                    
                                                
    40
    ✓ 30.3M
            hevm.prank(from);
    41
    ✓ 30.3M
            int128 realBalanceDelta = driver.setStreams(
    42
    ✓ 30.3M
                token,
    43
    ✓ 30.3M
                currReceivers,
    44
    ✓ 30.3M
                balanceDelta,
    45
    ✓ 30.3M
                newReceivers,
    46
    ✓ 30.3M
                maxEndHint1,
    47
    ✓ 30.3M
                maxEndHint2,
    48
    ✓ 30.3M
                from
    49
            );
    50
    
                                                    
                                                
    51
    ✓ 28.4M
            updateStreamReceivers(from, newReceivers);
    52
    
                                                    
                                                
    53
    ✓ 28.4M
            return realBalanceDelta;
    54
        }
    55
    
                                                    
                                                
    56
        /**
    57
         * @notice Set streams, overwriting the current receivers list
    58
         * @param fromAccId Account id of the sender
    59
         * @param toAccId Account id of the receiver in the receivers list
    60
         * @param amountPerSec Amount per second to stream
    61
         * @param startTime Start time of the stream
    62
         * @param duration Duration of the stream
    63
         * @param balanceDelta Balance delta to set
    64
         * @return Real balance delta
    65
         */
    66
        function setStreams(
    67
            uint8 fromAccId,
    68
            uint8 toAccId,
    69
            uint160 amountPerSec,
    70
            uint32 startTime,
    71
            uint32 duration,
    72
            int128 balanceDelta
    73
    ✓ 5.1M
        ) public returns (int128) {
    74
    ✓ 5.1M
            address from = getAccount(fromAccId);
    75
    ✓ 5.1M
            address to = getAccount(toAccId);
    76
    
                                                    
                                                
    77
    ✓ 5.1M
            StreamReceiver[] memory receivers = new StreamReceiver[](1);
    78
    ✓ 5.1M
            receivers[0] = StreamReceiver(
    79
    ✓ 5.1M
                getDripsAccountId(to),
    80
    ✓ 5.1M
                StreamConfigImpl.create(
    81
    ✓ 5.1M
                    0, // streamId is arbitrary and can be ignored
    82
    ✓ 5.1M
                    amountPerSec,
    83
    ✓ 5.1M
                    startTime,
    84
    ✓ 5.1M
                    duration
    85
                )
    86
            );
    87
    
                                                    
                                                
    88
    ✓ 5.1M
            int128 realBalanceDelta = _setStreams(
    89
    ✓ 5.1M
                from,
    90
    ✓ 5.1M
                getStreamReceivers(from),
    91
    ✓ 5.1M
                balanceDelta,
    92
    ✓ 5.1M
                receivers
    93
            );
    94
    
                                                    
                                                
    95
    ✓ 4.1M
            return realBalanceDelta;
    96
        }
    97
    
                                                    
                                                
    98
        /**
    99
         * @notice Set streams, overwriting the current receivers list
    100
         * @param fromAccId Account id of the sender
    101
         * @param toAccId Account id of the receiver in the receivers list
    102
         * @param amountPerSec Amount per second to stream
    103
         * @param startTime Start time of the stream
    104
         * @param duration Duration of the stream
    105
         * @param balanceDelta Balance delta to set
    106
         * @return Real balance delta
    107
         * @dev This function clamps the amountPerSec, startTime, duration and
    108
         * balanceDelta between the minimum and maximum allowed values
    109
         */
    110
        function setStreamsWithClamping(
    111
            uint8 fromAccId,
    112
            uint8 toAccId,
    113
            uint160 amountPerSec,
    114
            uint32 startTime,
    115
            uint32 duration,
    116
            int128 balanceDelta
    117
    ✓ 3.0M
        ) public returns (int128) {
    118
    ✓ 3.0M
            address from = getAccount(fromAccId);
    119
    ✓ 3.0M
            address to = getAccount(toAccId);
    120
    
                                                    
                                                
    121
    ✓ 3.0M
            amountPerSec = clampAmountPerSec(amountPerSec);
    122
    ✓ 3.0M
            startTime = clampStartTime(startTime);
    123
    ✓ 3.0M
            duration = clampDuration(duration);
    124
    ✓ 3.0M
            balanceDelta = clampBalanceDelta(balanceDelta, from);
    125
    
                                                    
                                                
    126
    ✓ 3.0M
            setStreams(
    127
    ✓ 3.0M
                fromAccId,
    128
    ✓ 3.0M
                toAccId,
    129
    ✓ 3.0M
                amountPerSec,
    130
    ✓ 3.0M
                startTime,
    131
    ✓ 3.0M
                duration,
    132
    ✓ 3.0M
                balanceDelta
    133
            );
    134
        }
    135
    
                                                    
                                                
    136
        /**
    137
         * @notice Add a stream receiver to the existing list of receivers
    138
         * @param fromAccId Account id of the sender
    139
         * @param toAccId Account id of the receiver to add
    140
         * @param amountPerSec Amount per second to stream
    141
         * @param startTime Start time of the stream
    142
         * @param duration Duration of the stream
    143
         * @param balanceDelta Balance delta to set
    144
         * @return Real balance delta
    145
         */
    146
        function addStream(
    147
            uint8 fromAccId,
    148
            uint8 toAccId,
    149
            uint160 amountPerSec,
    150
            uint32 startTime,
    151
            uint32 duration,
    152
            int128 balanceDelta
    153
    ✓ 5.4M
        ) public returns (int128) {
    154
    ✓ 5.4M
            address from = getAccount(fromAccId);
    155
    ✓ 5.4M
            address to = getAccount(toAccId);
    156
    
                                                    
                                                
    157
    ✓ 5.4M
            StreamReceiver[] memory oldReceivers = getStreamReceivers(from);
    158
    
                                                    
                                                
    159
    ✓ 5.4M
            StreamReceiver memory addedReceiver = StreamReceiver(
    160
    ✓ 5.4M
                getDripsAccountId(to),
    161
    ✓ 5.4M
                StreamConfigImpl.create(
    162
    ✓ 5.4M
                    0, // streamId is arbitrary and can be ignored
    163
    ✓ 5.4M
                    amountPerSec,
    164
    ✓ 5.4M
                    startTime,
    165
    ✓ 5.4M
                    duration
    166
                )
    167
            );
    168
    
                                                    
                                                
    169
    ✓ 5.4M
            StreamReceiver[] memory newReceivers = new StreamReceiver[](
    170
    ✓ 5.4M
                oldReceivers.length + 1
    171
            );
    172
    ✓ 11.1M
            for (uint256 i = 0; i < oldReceivers.length; i++) {
    173
    ✓ 5.7M
                newReceivers[i] = oldReceivers[i];
    174
            }
    175
    ✓ 5.4M
            newReceivers[newReceivers.length - 1] = addedReceiver;
    176
    
                                                    
                                                
    177
    ✓ 5.4M
            int128 realBalanceDelta = _setStreams(
    178
    ✓ 5.4M
                from,
    179
    ✓ 5.4M
                oldReceivers,
    180
    ✓ 5.4M
                balanceDelta,
    181
    ✓ 5.4M
                newReceivers
    182
            );
    183
    
                                                    
                                                
    184
    ✓ 4.8M
            return realBalanceDelta;
    185
        }
    186
    
                                                    
                                                
    187
        /**
    188
         * @notice Add a stream receiver to the existing list of receivers
    189
         * @param fromAccId Account id of the sender
    190
         * @param toAccId Account id of the receiver to add
    191
         * @param amountPerSec Amount per second to stream
    192
         * @param startTime Start time of the stream
    193
         * @param duration Duration of the stream
    194
         * @param balanceDelta Balance delta to set
    195
         * @dev This function clamps the amountPerSec, startTime, duration and
    196
         * balanceDelta between the minimum and maximum allowed values
    197
         */
    198
        function addStreamWithClamping(
    199
            uint8 fromAccId,
    200
            uint8 toAccId,
    201
            uint160 amountPerSec,
    202
            uint32 startTime,
    203
            uint32 duration,
    204
            int128 balanceDelta
    205
    ✓ 2.3M
        ) public returns (int128) {
    206
    ✓ 2.3M
            address from = getAccount(fromAccId);
    207
    
                                                    
                                                
    208
    ✓ 2.3M
            amountPerSec = clampAmountPerSec(amountPerSec);
    209
    ✓ 2.3M
            startTime = clampStartTime(startTime);
    210
    ✓ 2.3M
            duration = clampDuration(duration);
    211
    ✓ 2.3M
            balanceDelta = clampBalanceDelta(balanceDelta, from);
    212
    
                                                    
                                                
    213
            return
    214
    ✓ 2.3M
                addStream(
    215
    ✓ 2.3M
                    fromAccId,
    216
    ✓ 2.3M
                    toAccId,
    217
    ✓ 2.3M
                    amountPerSec,
    218
    ✓ 2.3M
                    startTime,
    219
    ✓ 2.3M
                    duration,
    220
    ✓ 2.3M
                    balanceDelta
    221
                );
    222
        }
    223
    
                                                    
                                                
    224
        /**
    225
         * @notice Add a stream receiver to the existing list of receivers, making sure
    226
         * it is immediately squeezable in a transaction after this call
    227
         * @param fromAccId Account id of the sender
    228
         * @param toAccId Account id of the receiver to add
    229
         * @param amountPerSec Amount per second to stream
    230
         * @dev This is meant as a helper to quickly seed the corpus with situations
    231
         * where there is something to squeeze in the next transaction
    232
         */
    233
        function addStreamImmediatelySqueezable(
    234
            uint8 fromAccId,
    235
            uint8 toAccId,
    236
            uint160 amountPerSec
    237
        ) public {
    238
    ✓ 1.6M
            address receiver = getAccount(toAccId);
    239
    ✓ 1.6M
            address sender = getAccount(fromAccId);
    240
    
                                                    
                                                
    241
            // calculate amount per second so there will be something to squeeze
    242
            // this cycle
    243
    ✓ 1.6M
            uint160 minAmtPerSec = drips.minAmtPerSec() * SECONDS_PER_CYCLE;
    244
            amountPerSec =
    245
    ✓ 1.6M
                minAmtPerSec +
    246
    ✓ 1.6M
                (amountPerSec % (MAX_AMOUNT_PER_SEC - minAmtPerSec + 1));
    247
    
                                                    
                                                
    248
            // deposit 100 times the amount of 'amountPerSec' so chances are high
    249
            // that there is enough balance to stream this cycle
    250
    ✓ 1.6M
            int128 balanceDelta = (int128(uint128(amountPerSec)) * 100) / 1e9;
    251
    ✓ 1.6M
            if (uint128(balanceDelta) > token.balanceOf(sender)) {
    252
    ✓ 1.3M
                balanceDelta = int128(uint128(token.balanceOf(sender)));
    253
            }
    254
    
                                                    
                                                
    255
            // add the stream
    256
    ✓ 1.6M
            addStream(fromAccId, toAccId, amountPerSec, 0, 0, balanceDelta);
    257
    
                                                    
                                                
    258
            // warp 1 second forward so there is something to squeeze
    259
    ✓ 1.5M
            hevm.warp(block.timestamp + 1);
    260
        }
    261
    
                                                    
                                                
    262
        /**
    263
         * @notice Remove a stream receiver from the existing list of receivers
    264
         * @param targetAccId Account id of the receiver to remove
    265
         * @param indexSeed Random seed used to determine which receiver to remove
    266
         */
    267
        function removeStream(uint8 targetAccId, uint256 indexSeed) public {
    268
    ✓ 2.3M
            address target = getAccount(targetAccId);
    269
    
                                                    
                                                
    270
    ✓ 2.3M
            StreamReceiver[] memory oldReceivers = getStreamReceivers(target);
    271
    
                                                    
                                                
    272
    ✓ 2.3M
            uint256 index = indexSeed % oldReceivers.length;
    273
    
                                                    
                                                
    274
    ✓ 1.4M
            StreamReceiver[] memory newReceivers = new StreamReceiver[](
    275
    ✓ 1.4M
                oldReceivers.length - 1
    276
            );
    277
    ✓ 1.4M
            uint256 j = 0;
    278
    ✓ 3.5M
            for (uint256 i = 0; i < oldReceivers.length; i++) {
    279
    ✓ 2.1M
                if (i != index) {
    280
    ✓ 669.1K
                    newReceivers[j] = oldReceivers[i];
    281
    ✓ 669.1K
                    j++;
    282
                }
    283
            }
    284
    
                                                    
                                                
    285
    ✓ 1.4M
            _setStreams(target, oldReceivers, 0, newReceivers);
    286
        }
    287
    
                                                    
                                                
    288
        /**
    289
         * @notice Update stream balance by calling `setStreams` with the same
    290
         * receivers list
    291
         * @param targetAccId Account id of the sender
    292
         * @param balanceDelta Balance delta to set
    293
         * @return Real balance delta
    294
         */
    295
        function setStreamBalance(uint8 targetAccId, int128 balanceDelta)
    296
            public
    297
    ✓ 4.2M
            returns (int128)
    298
        {
    299
    ✓ 4.2M
            address target = getAccount(targetAccId);
    300
    
                                                    
                                                
    301
    ✓ 4.2M
            int128 realBalanceDelta = _setStreams(
    302
    ✓ 4.2M
                target,
    303
    ✓ 4.2M
                getStreamReceivers(target),
    304
    ✓ 4.2M
                balanceDelta,
    305
    ✓ 4.2M
                getStreamReceivers(target)
    306
            );
    307
    
                                                    
                                                
    308
    ✓ 3.6M
            return realBalanceDelta;
    309
        }
    310
    
                                                    
                                                
    311
        /**
    312
         * @notice Update stream balance by calling `setStreams` with the same
    313
         * receivers list
    314
         * @param targetAccId Account id of the sender
    315
         * @param balanceDelta Balance delta to set
    316
         * @dev This function clamps the balanceDelta between the minimum and
    317
         * maximum allowed values
    318
         */
    319
        function setStreamBalanceWithClamping(
    320
            uint8 targetAccId,
    321
            int128 balanceDelta
    322
        ) public {
    323
    ✓ 2.2M
            address target = getAccount(targetAccId);
    324
    ✓ 2.2M
            balanceDelta = clampBalanceDelta(balanceDelta, target);
    325
    ✓ 2.2M
            setStreamBalance(targetAccId, balanceDelta);
    326
        }
    327
    
                                                    
                                                
    328
        /**
    329
         * @notice Withdraw all stream balance by calling `setStreams` with the same
    330
         * receivers list and using min int128 as balance delta
    331
         * @param targetAccId Account id of the sender
    332
         * @return Real balance delta
    333
         */
    334
        function setStreamBalanceWithdrawAll(uint8 targetAccId)
    335
            public
    336
    ✓ 14.4M
            returns (int128)
    337
        {
    338
    ✓ 14.4M
            address target = getAccount(targetAccId);
    339
    ✓ 14.4M
            uint256 targetDripsAccId = getDripsAccountId(target);
    340
    
                                                    
                                                
    341
    ✓ 14.4M
            int128 realBalanceDelta = _setStreams(
    342
    ✓ 14.4M
                target,
    343
    ✓ 14.4M
                getStreamReceivers(target),
    344
    ✓ 14.4M
                type(int128).min,
    345
    ✓ 14.4M
                getStreamReceivers(target)
    346
            );
    347
        }
    348
    
                                                    
                                                
    349
        /**
    350
         * @notice Helper function to update the values used as maxEnd hints
    351
         * @param _maxEndHint1 New value for maxEndHint1
    352
         * @param _maxEndHint2 New value for maxEndHint2
    353
         * @dev Can be toggled on/off with TOGGLE_MAXENDHINTS_ENABLED
    354
         */
    355
        function setMaxEndHints(uint32 _maxEndHint1, uint32 _maxEndHint2) public {
    356
    ✓ 1.0M
            require(TOGGLE_MAXENDHINTS_ENABLED);
    357
    ✓ 1.0M
            maxEndHint1 = _maxEndHint1;
    358
    ✓ 1.0M
            maxEndHint2 = _maxEndHint2;
    359
        }
    360
    
                                                    
                                                
    361
        /**
    362
         * @notice Clamp the amountPerSec between the minimum and maximum allowed values
    363
         * @param amountPerSec Amount per second to clamp
    364
         * @return Clamped amountPerSec
    365
         */
    366
        function clampAmountPerSec(uint160 amountPerSec)
    367
            internal
    368
    ✓ 6.8M
            returns (uint160)
    369
        {
    370
            return
    371
    ✓ 6.8M
                drips.minAmtPerSec() +
    372
    ✓ 6.8M
                (amountPerSec % (MAX_AMOUNT_PER_SEC - drips.minAmtPerSec() + 1));
    373
        }
    374
    
                                                    
                                                
    375
        /**
    376
         * @notice Clamp the startTime between the minimum and maximum allowed values
    377
         * @param startTime Start time to clamp
    378
         * @return Clamped startTime
    379
         */
    380
    ✓ 5.3M
        function clampStartTime(uint32 startTime) internal returns (uint32) {
    381
    ✓ 5.3M
            if (startTime == 0) return 0;
    382
    
                                                    
                                                
    383
            // We want to make sure that the start time does not go below 1
    384
    ✓ 4.6M
            uint32 minStartTime;
    385
    ✓ 4.6M
            if (CYCLE_FUZZING_BUFFER_SECONDS >= block.timestamp) {
    386
    ✓ 70.5K
                minStartTime = 1;
    387
            } else {
    388
                minStartTime =
    389
    ✓ 4.5M
                    uint32(block.timestamp) -
    390
                    CYCLE_FUZZING_BUFFER_SECONDS;
    391
            }
    392
    
                                                    
                                                
    393
    ✓ 4.6M
            uint32 maxStartTime = uint32(block.timestamp) +
    394
                CYCLE_FUZZING_BUFFER_SECONDS;
    395
    
                                                    
                                                
    396
    ✓ 4.6M
            return minStartTime + (startTime % (maxStartTime - minStartTime + 1));
    397
        }
    398
    
                                                    
                                                
    399
        /**
    400
         * @notice Clamp the duration between the minimum and maximum allowed values
    401
         * @param duration Duration to clamp
    402
         * @return Clamped duration
    403
         */
    404
    ✓ 5.3M
        function clampDuration(uint32 duration) internal returns (uint32) {
    405
    ✓ 5.3M
            if (duration == 0) return 0;
    406
    
                                                    
                                                
    407
    ✓ 5.1M
            return duration % (MAX_STREAM_DURATION + 1);
    408
        }
    409
    
                                                    
                                                
    410
        /**
    411
         * @notice Clamp the balanceDelta between the minimum and maximum allowed values
    412
         * @param balanceDelta Balance delta to clamp
    413
         * @param from Account performing the setStreams action
    414
         * @return Clamped balanceDelta
    415
         */
    416
        function clampBalanceDelta(int128 balanceDelta, address from)
    417
            internal
    418
    ✓ 9.0M
            returns (int128)
    419
        {
    420
    ✓ 9.0M
            if (balanceDelta > 0) {
    421
                balanceDelta =
    422
    ✓ 5.7M
                    balanceDelta %
    423
    ✓ 5.7M
                    (int128(uint128(token.balanceOf(from))) + 1);
    424
            } else {
    425
    ✓ 3.3M
                balanceDelta = balanceDelta % int128(uint128(STARTING_BALANCE));
    426
            }
    427
    ✓ 9.0M
            return balanceDelta;
    428
        }
    429
    }
    430
    
                                                    
                                                
    94.0% src/echidna/EchidnaStreamsTests.sol
    Lines covered: 94 / 100 (94.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./base/EchidnaBase.sol";
    4
    import "./EchidnaStreamsHelpers.sol";
    5
    
                                                    
                                                
    6
    /**
    7
     * @title Mixin containing tests for streams
    8
     * @author Rappie <rappie@perimetersec.io>
    9
     */
    10
    contract EchidnaStreamsTests is EchidnaBase, EchidnaStreamsHelpers {
    11
        /**
    12
         * @notice Test internal accounting after updating stream balance
    13
         * @param targetAccId Account id of the sender
    14
         * @param balanceDelta Amount to update stream balance with
    15
         */
    16
        function testSetStreamBalance(uint8 targetAccId, int128 balanceDelta)
    17
            public
    18
        {
    19
    ✓ 1.0M
            address target = getAccount(targetAccId);
    20
    ✓ 1.0M
            uint256 targetDripsAccId = getDripsAccountId(target);
    21
    
                                                    
                                                
    22
    ✓ 1.0M
            uint256 tokenBalanceBefore = token.balanceOf(target);
    23
    ✓ 1.0M
            uint128 streamBalanceBefore = drips.balanceAt(
    24
    ✓ 1.0M
                targetDripsAccId,
    25
    ✓ 1.0M
                token,
    26
    ✓ 1.0M
                getStreamReceivers(target),
    27
    ✓ 1.0M
                uint32(block.timestamp)
    28
            );
    29
    
                                                    
                                                
    30
    ✓ 1.0M
            int128 realBalanceDelta = setStreamBalance(targetAccId, balanceDelta);
    31
    
                                                    
                                                
    32
    ✓ 725.5K
            uint256 tokenBalanceAfter = token.balanceOf(target);
    33
    ✓ 725.5K
            uint128 streamBalanceAfter = drips.balanceAt(
    34
    ✓ 725.5K
                targetDripsAccId,
    35
    ✓ 725.5K
                token,
    36
    ✓ 725.5K
                getStreamReceivers(target),
    37
    ✓ 725.5K
                uint32(block.timestamp)
    38
            );
    39
    
                                                    
                                                
    40
    ✓ 725.5K
            if (balanceDelta >= 0) {
    41
    ✓ 358.2K
                assert(realBalanceDelta == balanceDelta);
    42
            } else {
    43
    ✓ 367.3K
                assert(realBalanceDelta <= 0);
    44
    ✓ 367.3K
                assert(realBalanceDelta >= balanceDelta);
    45
            }
    46
    
                                                    
                                                
    47
            assert(
    48
    ✓ 725.5K
                int256(tokenBalanceAfter) ==
    49
    ✓ 725.5K
                    int256(tokenBalanceBefore) - realBalanceDelta
    50
            );
    51
            assert(
    52
    ✓ 725.5K
                int128(streamBalanceAfter) ==
    53
    ✓ 725.5K
                    int128(streamBalanceBefore) + realBalanceDelta
    54
            );
    55
        }
    56
    
                                                    
                                                
    57
        /**
    58
         * @notice Check balances before and after adding a stream, warping to the
    59
         * future, and receiving the stream.
    60
         * @param fromAccId Account id of the sender
    61
         * @param toAccId Account id to be added as receiver
    62
         * @param amtPerSecAdded Amount per second for the stream to be added
    63
         * @dev This test is resource heavy because it contains lots of logic and
    64
         * warping to the future.
    65
         */
    66
        function testBalanceAtInFuture(
    67
            uint8 fromAccId,
    68
            uint8 toAccId,
    69
            uint160 amtPerSecAdded
    70
        ) public heavy {
    71
    ✓ 1.5M
            address from = getAccount(fromAccId);
    72
    ✓ 1.5M
            address to = getAccount(toAccId);
    73
    ✓ 1.5M
            uint256 fromDripsAccId = getDripsAccountId(from);
    74
    ✓ 1.5M
            uint256 toDripsAccId = getDripsAccountId(to);
    75
    
                                                    
                                                
    76
    ✓ 1.5M
            amtPerSecAdded = clampAmountPerSec(amtPerSecAdded);
    77
    
                                                    
                                                
    78
            // the timestamps we are comparing
    79
    ✓ 1.5M
            uint256 currentTimestamp = block.timestamp;
    80
    ✓ 1.5M
            uint256 futureTimestamp = getCurrentCycleEnd() + 1;
    81
    
                                                    
                                                
    82
            // retrieve initial balances
    83
    ✓ 1.5M
            uint128 balanceInitial = getStreamBalanceForUser(
    84
    ✓ 1.5M
                from,
    85
    ✓ 1.5M
                uint32(block.timestamp)
    86
            );
    87
    ✓ 1.5M
            uint128 receivableInitial = getReceivableAmountForAllUsers();
    88
    
                                                    
                                                
    89
            // look at balances in the future if we wouldnt do anything
    90
    ✓ 1.5M
            hevm.warp(futureTimestamp);
    91
    ✓ 1.5M
            uint128 balanceBaseline = getStreamBalanceForUser(
    92
    ✓ 1.5M
                from,
    93
    ✓ 1.5M
                uint32(block.timestamp)
    94
            );
    95
    ✓ 1.5M
            uint128 receivableBaseline = getReceivableAmountForAllUsers();
    96
    ✓ 1.5M
            hevm.warp(currentTimestamp);
    97
    
                                                    
                                                
    98
            // add a stream
    99
            // make sure we add enough balance to complete the cycle
    100
    ✓ 1.5M
            uint128 balanceAdded = uint128(amtPerSecAdded) * SECONDS_PER_CYCLE;
    101
    ✓ 1.5M
            balanceAdded = uint128(clampBalanceDelta(int128(balanceAdded), from));
    102
    ✓ 1.5M
            require(balanceAdded / amtPerSecAdded >= SECONDS_PER_CYCLE);
    103
    ✓ 708.7K
            addStream(
    104
    ✓ 708.7K
                fromAccId,
    105
    ✓ 708.7K
                toAccId,
    106
    ✓ 708.7K
                amtPerSecAdded,
    107
    ✓ 708.7K
                0,
    108
    ✓ 708.7K
                0,
    109
    ✓ 708.7K
                int128(balanceAdded)
    110
            );
    111
    
                                                    
                                                
    112
            // retrieve balances after adding stream
    113
    ✓ 698.9K
            uint128 balanceBefore = getStreamBalanceForUser(
    114
    ✓ 698.9K
                from,
    115
    ✓ 698.9K
                uint32(block.timestamp)
    116
            );
    117
    ✓ 698.9K
            uint128 receivableBefore = getReceivableAmountForAllUsers();
    118
    
                                                    
                                                
    119
            // jump to future
    120
    ✓ 698.9K
            hevm.warp(futureTimestamp);
    121
    
                                                    
                                                
    122
            // retrieve balances in the future after adding the stream
    123
    ✓ 698.9K
            uint128 balanceAfter = getStreamBalanceForUser(
    124
    ✓ 698.9K
                from,
    125
    ✓ 698.9K
                uint32(block.timestamp)
    126
            );
    127
    ✓ 698.9K
            uint128 receivableAfter = getReceivableAmountForAllUsers();
    128
    
                                                    
                                                
    129
            // sanity checks
    130
    ✓ 698.9K
            assert(balanceInitial >= balanceBaseline);
    131
    ✓ 698.9K
            assert(balanceBefore >= balanceAfter);
    132
    ✓ 698.9K
            assert(receivableAfter >= receivableBaseline);
    133
    
                                                    
                                                
    134
            // the amount that would have been streamed if we do nothing
    135
    ✓ 445.7K
            uint128 baselineBalanceStreamed = balanceInitial - balanceBaseline;
    136
    
                                                    
                                                
    137
            // calculate expected balance change including the effect of the
    138
            // added stream
    139
    ✓ 445.7K
            uint128 expectedBalanceChange = balanceBefore -
    140
    ✓ 445.7K
                balanceAfter -
    141
    ✓ 445.7K
                baselineBalanceStreamed;
    142
    ✓ 445.7K
            uint128 expectedReceivedChange = receivableAfter - receivableBaseline;
    143
    
                                                    
                                                
    144
    ✓ 445.7K
            assert(expectedBalanceChange == expectedReceivedChange);
    145
        }
    146
    
                                                    
                                                
    147
        /**
    148
         * @notice Setting streams with sane defaults should not revert
    149
         * @param fromAccId Account id of the sender
    150
         * @param toAccId Account id of the receiver
    151
         * @param amountPerSec Amount per second for the stream
    152
         * @param startTime Start time for the stream
    153
         * @param duration Duration for the stream
    154
         * @param balanceDelta Amount to update stream balance with
    155
         */
    156
        function testSetStreamsShouldNotRevert(
    157
            uint8 fromAccId,
    158
            uint8 toAccId,
    159
            uint160 amountPerSec,
    160
            uint32 startTime,
    161
            uint32 duration,
    162
            int128 balanceDelta
    163
        ) public {
    164
            try
    165
    ✓ 1.5M
                EchidnaStreamsHelpers(address(this)).setStreamsWithClamping(
    166
    ✓ 1.5M
                    fromAccId,
    167
    ✓ 1.5M
                    toAccId,
    168
    ✓ 1.5M
                    amountPerSec,
    169
    ✓ 1.5M
                    startTime,
    170
    ✓ 1.5M
                    duration,
    171
    ✓ 1.5M
                    balanceDelta
    172
                )
    173
            {} catch {
    174
                assert(false);
    175
            }
    176
        }
    177
    
                                                    
                                                
    178
        /**
    179
         * @notice Adding streams with sane defaults should not revert
    180
         * @param fromAccId Account id of the sender
    181
         * @param toAccId Account id of the receiver
    182
         * @param amountPerSec Amount per second for the stream
    183
         * @param startTime Start time for the stream
    184
         * @param duration Duration for the stream
    185
         * @param balanceDelta Amount to update stream balance with
    186
         */
    187
        function testAddStreamShouldNotRevert(
    188
            uint8 fromAccId,
    189
            uint8 toAccId,
    190
            uint160 amountPerSec,
    191
            uint32 startTime,
    192
            uint32 duration,
    193
            int128 balanceDelta
    194
        ) public {
    195
            try
    196
    ✓ 1.2M
                EchidnaStreamsHelpers(address(this)).addStreamWithClamping(
    197
    ✓ 1.2M
                    fromAccId,
    198
    ✓ 1.2M
                    toAccId,
    199
    ✓ 1.2M
                    amountPerSec,
    200
    ✓ 1.2M
                    startTime,
    201
    ✓ 1.2M
                    duration,
    202
    ✓ 1.2M
                    balanceDelta
    203
                )
    204
            {} catch (bytes memory reason) {
    205
    ✓ 45.1K
                bytes4 errorSelector = bytes4(reason);
    206
    ✓ 45.1K
                if (errorSelector == EchidnaStorage.DuplicateError.selector) {
    207
                    // ignore this case, it means we tried to add a duplicate stream
    208
                } else {
    209
                    assert(false);
    210
                }
    211
            }
    212
        }
    213
    
                                                    
                                                
    214
        /*
    215
         * @notice Removing streams should not revert
    216
         * @param targetAccId Account id of the sender
    217
         * @param indexSeed Random seed used to determine which receiver to remove
    218
         */
    219
        function testRemoveStreamShouldNotRevert(
    220
            uint8 targetAccId,
    221
            uint256 indexSeed
    222
        ) public {
    223
    ✓ 1.0M
            address target = getAccount(targetAccId);
    224
    ✓ 1.0M
            require(getStreamReceivers(target).length > 0);
    225
    
                                                    
                                                
    226
            try
    227
    ✓ 712.5K
                EchidnaStreamsHelpers(address(this)).removeStream(
    228
    ✓ 712.5K
                    targetAccId,
    229
    ✓ 712.5K
                    indexSeed
    230
                )
    231
            {} catch {
    232
                assert(false);
    233
            }
    234
        }
    235
    
                                                    
                                                
    236
        /**
    237
         * @notice Updating stream balance with sane defaults should not revert
    238
         * @param targetAccId Account id of the sender
    239
         * @param balanceDelta Amount to update stream balance with
    240
         */
    241
        function testSetStreamBalanceShouldNotRevert(
    242
            uint8 targetAccId,
    243
            int128 balanceDelta
    244
        ) public {
    245
            try
    246
    ✓ 997.0K
                EchidnaStreamsHelpers(address(this)).setStreamBalanceWithClamping(
    247
    ✓ 997.0K
                    targetAccId,
    248
    ✓ 997.0K
                    balanceDelta
    249
                )
    250
            {} catch {
    251
                assert(false);
    252
            }
    253
        }
    254
    
                                                    
                                                
    255
        /**
    256
         * @notice Withdrawing all stream balance should not revert
    257
         * @param targetAccId Account id of the sender
    258
         */
    259
        function testSetStreamBalanceWithdrawAllShouldNotRevert(uint8 targetAccId)
    260
            public
    261
        {
    262
            try
    263
    ✓ 1.2M
                EchidnaStreamsHelpers(address(this)).setStreamBalanceWithdrawAll(
    264
    ✓ 1.2M
                    targetAccId
    265
                )
    266
            {} catch {
    267
                assert(false);
    268
            }
    269
        }
    270
    }
    271
    
                                                    
                                                
    97.5% src/echidna/base/EchidnaAccounting.sol
    Lines covered: 78 / 80 (97.5%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./EchidnaStorage.sol";
    4
    
                                                    
                                                
    5
    /**
    6
     * @title Mixin for handling accounting related functions
    7
     * @author Rappie <rappie@perimetersec.io>
    8
     */
    9
    contract EchidnaAccounting is EchidnaStorage {
    10
        /**
    11
         * @notice Get the total amount of drips balances for all users
    12
         * @return Total drips balances for all users
    13
         * @dev This is the total amount of drips balances for all users, including
    14
         * the current stream balance, the receivable amount, the collectable
    15
         * amount, the splittable amount, and the squeezable amount.
    16
         */
    17
    ✓ 721.9K
        function getDripsBalancesTotalForAllUsers() internal returns (uint256) {
    18
    ✓ 721.9K
            uint256 user0Total = getDripsBalancesTotalForUser(ADDRESS_USER0);
    19
    ✓ 721.9K
            uint256 user1Total = getDripsBalancesTotalForUser(ADDRESS_USER1);
    20
    ✓ 721.9K
            uint256 user2Total = getDripsBalancesTotalForUser(ADDRESS_USER2);
    21
    ✓ 721.9K
            uint256 user3Total = getDripsBalancesTotalForUser(ADDRESS_USER3);
    22
    
                                                    
                                                
    23
    ✓ 721.9K
            return user0Total + user1Total + user2Total + user3Total;
    24
        }
    25
    
                                                    
                                                
    26
        /**
    27
         * @notice Get the total amount of drips balances for a user
    28
         * @param target The user to query
    29
         * @return Total drips balances for the target user
    30
         * @dev This is the total amount of drips balances for a user, including
    31
         * the current stream balance, the receivable amount, the collectable
    32
         * amount, the splittable amount, and the squeezable amount.
    33
         */
    34
        function getDripsBalancesTotalForUser(address target)
    35
            internal
    36
    ✓ 2.9M
            returns (uint256)
    37
        {
    38
    ✓ 2.9M
            uint256 targetDripsAccId = getDripsAccountId(target);
    39
    
                                                    
                                                
    40
    ✓ 2.9M
            uint128 balance = getCurrentStreamBalanceForUser(target);
    41
    ✓ 2.9M
            uint128 squeezable = getTotalSqueezableAmountForUser(target);
    42
    ✓ 2.9M
            uint128 receivable = getReceivableAmountForUser(target);
    43
    ✓ 2.9M
            uint128 collectable = drips.collectable(targetDripsAccId, token);
    44
    ✓ 2.9M
            uint128 splittable = drips.splittable(targetDripsAccId, token);
    45
    
                                                    
                                                
    46
    ✓ 2.9M
            return balance + squeezable + receivable + collectable + splittable;
    47
        }
    48
    
                                                    
                                                
    49
        /**
    50
         * @notice Get the streamable balance for a user
    51
         * @param target The user to query
    52
         * @param timestamp The point in time to get 'balanceAt' from
    53
         * @return Streamable balance for the target user
    54
         * @dev This is the streamable balance for a user, which is the current
    55
         * stream balance minus the receivable amount.
    56
         */
    57
        function getStreamBalanceForUser(address target, uint32 timestamp)
    58
            internal
    59
    ✓ 7.3M
            returns (uint128)
    60
        {
    61
    ✓ 7.3M
            uint256 targetDripsAccId = getDripsAccountId(target);
    62
    
                                                    
                                                
    63
    ✓ 7.3M
            uint128 balance;
    64
            try
    65
    ✓ 7.3M
                drips.balanceAt(
    66
    ✓ 7.3M
                    targetDripsAccId,
    67
    ✓ 7.3M
                    token,
    68
    ✓ 7.3M
                    getStreamReceivers(target),
    69
    ✓ 7.3M
                    timestamp
    70
                )
    71
            returns (uint128 _balance) {
    72
    ✓ 7.3M
                balance = _balance;
    73
            } catch {
    74
                // this should not happen, so put an assert here to be sure
    75
                assert(false);
    76
            }
    77
    
                                                    
                                                
    78
    ✓ 7.3M
            return balance;
    79
        }
    80
    
                                                    
                                                
    81
        /**
    82
         * @notice Get the current stream balance for a user
    83
         * @param target The user to query
    84
         * @return Current stream balance for the target user
    85
         */
    86
        function getCurrentStreamBalanceForUser(address target)
    87
            internal
    88
    ✓ 2.9M
            returns (uint128)
    89
        {
    90
    ✓ 2.9M
            return getStreamBalanceForUser(target, uint32(block.timestamp));
    91
        }
    92
    
                                                    
                                                
    93
        /**
    94
         * @notice Get the receivable amount for all users
    95
         * @return Receivable amount for all users
    96
         * @dev This is the receivable amount for all users, which means the amount
    97
         * that has already been streamed to the users but not yet collected.
    98
         */
    99
    ✓ 4.4M
        function getReceivableAmountForAllUsers() internal returns (uint128) {
    100
    ✓ 4.4M
            uint128 receivable;
    101
    ✓ 4.4M
            receivable += getReceivableAmountForUser(ADDRESS_USER0);
    102
    ✓ 4.4M
            receivable += getReceivableAmountForUser(ADDRESS_USER1);
    103
    ✓ 4.4M
            receivable += getReceivableAmountForUser(ADDRESS_USER2);
    104
    ✓ 4.4M
            receivable += getReceivableAmountForUser(ADDRESS_USER3);
    105
    ✓ 4.4M
            return receivable;
    106
        }
    107
    
                                                    
                                                
    108
        /**
    109
         * @notice Get the receivable amount for a user
    110
         * @param target The user to query
    111
         * @return Receivable amount for the target user
    112
         * @dev This is the receivable amount for a user, which means the amount
    113
         * that has already been streamed to the user but not yet collected.
    114
         */
    115
        function getReceivableAmountForUser(address target)
    116
            internal
    117
    ✓ 23.0M
            returns (uint128)
    118
        {
    119
    ✓ 23.0M
            uint128 receivable = drips.receiveStreamsResult(
    120
    ✓ 23.0M
                getDripsAccountId(target),
    121
    ✓ 23.0M
                token,
    122
    ✓ 23.0M
                type(uint32).max
    123
            );
    124
    ✓ 23.0M
            return receivable;
    125
        }
    126
    
                                                    
                                                
    127
        /**
    128
         * @notice Get the squeezable amount for all users
    129
         * @param target The user to query
    130
         * @return Squeezable amount for all users
    131
         * @dev This is the squeezable amount for a user, which means the amount
    132
         * that has already been streamed in the current cycle that is not yet
    133
         * receivable.
    134
         */
    135
        function getTotalSqueezableAmountForUser(address target)
    136
            internal
    137
    ✓ 4.1M
            returns (uint128)
    138
        {
    139
    ✓ 4.1M
            uint128 amount = 0;
    140
    ✓ 4.1M
            amount += getSqueezableAmount(ADDRESS_USER0, target);
    141
    ✓ 4.1M
            amount += getSqueezableAmount(ADDRESS_USER1, target);
    142
    ✓ 4.1M
            amount += getSqueezableAmount(ADDRESS_USER2, target);
    143
    ✓ 4.1M
            amount += getSqueezableAmount(ADDRESS_USER3, target);
    144
    
                                                    
                                                
    145
    ✓ 4.1M
            return amount;
    146
        }
    147
    
                                                    
                                                
    148
        /**
    149
         * @notice Get the squeezable amount for a user
    150
         * @param sender The sender of the stream(s)
    151
         * @param receiver The receiver of the stream(s)
    152
         * @return Squeezable amount for the target user
    153
         * @dev This is the squeezable amount for a user, which means the amount
    154
         * that has already been streamed in the current cycle that is not yet
    155
         * receivable.
    156
         */
    157
        function getSqueezableAmount(address sender, address receiver)
    158
            internal
    159
    ✓ 28.6M
            returns (uint128)
    160
        {
    161
    ✓ 28.6M
            uint256 senderDripsAccId = getDripsAccountId(sender);
    162
    ✓ 28.6M
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    163
    
                                                    
                                                
    164
    ✓ 28.6M
            uint128 amount = drips.squeezeStreamsResult(
    165
    ✓ 28.6M
                receiverDripsAccId,
    166
    ✓ 28.6M
                token,
    167
    ✓ 28.6M
                senderDripsAccId,
    168
    ✓ 28.6M
                bytes32(0),
    169
    ✓ 28.6M
                getStreamsHistory(sender)
    170
            );
    171
    
                                                    
                                                
    172
    ✓ 28.6M
            return amount;
    173
        }
    174
    
                                                    
                                                
    175
        /**
    176
         * @notice Get the 'maxEnd' farthest in the future for all users
    177
         * @return 'maxEnd' fartherst in the future for all users
    178
         * @dev This is the 'maxEnd' farthest in the future for all users, which
    179
         * means the farthest in the future that any stream ends for any user.
    180
         */
    181
    ✓ 1.2M
        function getMaxEndForAllUsers() internal returns (uint32) {
    182
    ✓ 1.2M
            uint32 maxMaxEnd;
    183
    
                                                    
                                                
    184
    ✓ 1.2M
            uint32 maxEndUser0 = getMaxEndForUser(ADDRESS_USER0);
    185
    ✓ 1.2M
            if (maxEndUser0 > maxMaxEnd) maxMaxEnd = maxEndUser0;
    186
    ✓ 1.2M
            uint32 maxEndUser1 = getMaxEndForUser(ADDRESS_USER1);
    187
    ✓ 1.2M
            if (maxEndUser1 > maxMaxEnd) maxMaxEnd = maxEndUser1;
    188
    ✓ 1.2M
            uint32 maxEndUser2 = getMaxEndForUser(ADDRESS_USER2);
    189
    ✓ 1.2M
            if (maxEndUser2 > maxMaxEnd) maxMaxEnd = maxEndUser2;
    190
    ✓ 1.2M
            uint32 maxEndUser3 = getMaxEndForUser(ADDRESS_USER3);
    191
    ✓ 1.2M
            if (maxEndUser3 > maxMaxEnd) maxMaxEnd = maxEndUser3;
    192
    
                                                    
                                                
    193
    ✓ 1.2M
            return maxMaxEnd;
    194
        }
    195
    
                                                    
                                                
    196
        /**
    197
         * @notice Get the 'maxEnd' for a user
    198
         * @param target The user to query
    199
         * @return The 'maxEnd' for the target user
    200
         */
    201
    ✓ 4.8M
        function getMaxEndForUser(address target) internal returns (uint32) {
    202
    ✓ 4.8M
            uint256 targetDripsAccId = getDripsAccountId(target);
    203
    ✓ 4.8M
            (, , , , uint32 maxEnd) = drips.streamsState(targetDripsAccId, token);
    204
    ✓ 4.8M
            return maxEnd;
    205
        }
    206
    
                                                    
                                                
    207
        /**
    208
         * @notice Get the timestamp on which the current cycle started
    209
         * @return Timestamp on which the current cycle started
    210
         */
    211
    ✓ 2.8M
        function getCurrentCycleStart() internal returns (uint32) {
    212
    ✓ 2.8M
            uint32 currTimestamp = uint32(block.timestamp);
    213
    ✓ 2.8M
            return currTimestamp - (currTimestamp % SECONDS_PER_CYCLE);
    214
        }
    215
    
                                                    
                                                
    216
        /**
    217
         * @notice Get the timestamp on which the current cycle ends
    218
         * @return Timestamp on which the current cycle ends
    219
         */
    220
    ✓ 2.8M
        function getCurrentCycleEnd() internal returns (uint32) {
    221
    ✓ 2.8M
            return getCurrentCycleStart() + SECONDS_PER_CYCLE - 1;
    222
        }
    223
    
                                                    
                                                
    224
        /**
    225
         * @notice Get the cycle number for a given timestamp
    226
         * @param timestamp The timestamp to get the cycle number for
    227
         * @return timestamp Cycle number for the given timestamp
    228
         */
    229
        function getCycleFromTimestamp(uint256 timestamp)
    230
            internal
    231
    ✓ 2.4M
            returns (uint32)
    232
        {
    233
    ✓ 2.4M
            return uint32(timestamp / SECONDS_PER_CYCLE + 1);
    234
        }
    235
    }
    236
    
                                                    
                                                
    0.0% src/echidna/base/EchidnaBase.sol
    Lines covered: 0 / 1 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "./EchidnaSetup.sol";
    4
    import "./EchidnaStorage.sol";
    5
    import "./EchidnaAccounting.sol";
    6
    
                                                    
                                                
    7
    /**
    8
     * @title Mixin grouping together all base contracts
    9
     * @author Rappie <rappie@perimetersec.io>
    10
     */
    11
    contract EchidnaBase is
    12
        EchidnaSetup,
    13
        EchidnaStorage,
    14
        EchidnaAccounting
    15
    {}
    16
    
                                                    
                                                
    92.0% src/echidna/base/EchidnaConfig.sol
    Lines covered: 23 / 25 (92.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import {IERC20, ERC20PresetFixedSupply} from "openzeppelin-contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol";
    4
    import {ManagedProxy} from "src/Managed.sol";
    5
    import {Drips, SplitsReceiver, StreamReceiver, StreamConfig, StreamConfigImpl, StreamsHistory} from "src/Drips.sol";
    6
    import {AddressDriver} from "src/AddressDriver.sol";
    7
    
                                                    
                                                
    8
    import {DripsEchidna} from "../tools/DripsEchidna.sol";
    9
    import {AddressDriverEchidna} from "../tools/AddressDriverEchidna.sol";
    10
    
                                                    
                                                
    11
    import "../tools/Debugger.sol";
    12
    
                                                    
                                                
    13
    /**
    14
     * @title Mixin containing the configuration for the fuzzing campaign
    15
     * @author Rappie <rappie@perimetersec.io>
    16
     */
    17
    contract EchidnaConfig {
    18
        // Addresses used for the simulated users
    19
    ✓ 106.9M
        address internal constant ADDRESS_USER0 = address(0x10000);
    20
    ✓ 72.7M
        address internal constant ADDRESS_USER1 = address(0x20000);
    21
    ✓ 66.6M
        address internal constant ADDRESS_USER2 = address(0x30000);
    22
    ✓ 80.5M
        address internal constant ADDRESS_USER3 = address(0x40000);
    23
    
                                                    
                                                
    24
        // Mappings from address to account id
    25
        mapping(address => uint8) internal ADDRESS_TO_ACCOUNT_ID;
    26
        mapping(address => uint256) internal ADDRESS_TO_DRIPS_ACCOUNT_ID;
    27
    
                                                    
                                                
    28
        // Variable to store the timestamp when fuzzing starts
    29
        uint256 internal STARTING_TIMESTAMP;
    30
    
                                                    
                                                
    31
        // Starting token balance of the simulated users
    32
    ✓ 6.8M
        uint256 internal constant STARTING_BALANCE = 1_000_000_000e18;
    33
    
                                                    
                                                
    34
        // Amount of seconds in a Drips cycle
    35
    ✓ 6.8M
        uint32 internal constant SECONDS_PER_CYCLE = 10;
    36
    
                                                    
                                                
    37
        // Buffers to be used as fuzzing boundaries
    38
    ✓ 5.1M
        uint32 internal constant CYCLE_FUZZING_BUFFER_CYCLES = 10;
    39
        uint32 internal constant CYCLE_FUZZING_BUFFER_SECONDS =
    40
    ✓ 5.1M
            CYCLE_FUZZING_BUFFER_CYCLES * SECONDS_PER_CYCLE;
    41
    
                                                    
                                                
    42
        // Maximum amount of streamable funds per second that make sense based
    43
        // on the starting balance of the simulated users
    44
        uint160 internal constant MAX_AMOUNT_PER_SEC =
    45
    ✓ 6.8M
            (uint160(STARTING_BALANCE) / uint160(SECONDS_PER_CYCLE)) * 1e9;
    46
    
                                                    
                                                
    47
        // Sensible maximum amount of seconds for a stream
    48
        uint32 internal constant MAX_STREAM_DURATION = CYCLE_FUZZING_BUFFER_SECONDS;
    49
    
                                                    
                                                
    50
        // Due to the inner workings of the splitting algorithm Drips uses,
    51
        // it is unpredictable wether a split will be rounded up or down. To
    52
        // remedy this, allow a tolerance for the expected split amount.
    53
    ✓ 714.2K
        uint256 internal constant SPLIT_ROUNDING_TOLERANCE = 1;
    54
    
                                                    
                                                
    55
        // Toggles for certain tests
    56
        bool internal constant TOGGLE_EXPERIMENTAL_TESTS_ENABLED = true;
    57
    ✓ 1.5M
        bool internal constant TOGGLE_HEAVY_TESTS_ENABLED = true;
    58
    ✓ 1.0M
        bool internal constant TOGGLE_MAXENDHINTS_ENABLED = true;
    59
    
                                                    
                                                
    60
        // Modifier to toggle experimental tests
    61
        modifier experimental() {
    62
            if (!TOGGLE_EXPERIMENTAL_TESTS_ENABLED) return;
    63
            _;
    64
        }
    65
    
                                                    
                                                
    66
        // Modifier to toggle performance heavy tests
    67
        modifier heavy() {
    68
    ✓ 1.5M
            if (!TOGGLE_HEAVY_TESTS_ENABLED) return;
    69
            _;
    70
        }
    71
    
                                                    
                                                
    72
        constructor() {
    73
    ✓ 1
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0] = 0;
    74
    ✓ 1
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1] = 64;
    75
    ✓ 1
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2] = 128;
    76
    ✓ 1
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3] = 192;
    77
        }
    78
    
                                                    
                                                
    79
        /*
    80
         * @notice Get the address for a certain account id
    81
         * @param rawId The raw account id
    82
         * @return The address for the account id
    83
         * @dev In this case we have 4 users, spread over the range of 256 (8 bits).
    84
         */
    85
    ✓ 326.7M
        function getAccount(uint8 rawId) internal pure returns (address) {
    86
    ✓ 326.7M
            uint256 id = uint256(rawId) / 64;
    87
    
                                                    
                                                
    88
    ✓ 326.7M
            if (id == 0) return ADDRESS_USER0;
    89
    ✓ 219.9M
            if (id == 1) return ADDRESS_USER1;
    90
    ✓ 147.2M
            if (id == 2) return ADDRESS_USER2;
    91
    ✓ 80.5M
            if (id == 3) return ADDRESS_USER3;
    92
    
                                                    
                                                
    93
            require(false, "Unknown account ID");
    94
        }
    95
    }
    96
    
                                                    
                                                
    96.3% src/echidna/base/EchidnaSetup.sol
    Lines covered: 26 / 27 (96.3%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    import "../tools/IHevm.sol";
    4
    import "./EchidnaConfig.sol";
    5
    
                                                    
                                                
    6
    /**
    7
     * @title Mixin containing the deployment and setup
    8
     * @author Rappie <rappie@perimetersec.io>
    9
     */
    10
    contract EchidnaSetup is EchidnaConfig {
    11
    ✓ 1
        IHevm hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    12
    
                                                    
                                                
    13
        ERC20PresetFixedSupply token;
    14
        DripsEchidna drips;
    15
        AddressDriverEchidna driver;
    16
    
                                                    
                                                
    17
        constructor() EchidnaConfig() {
    18
            // Deploy ERC20 token
    19
    ✓ 1
            token = new ERC20PresetFixedSupply(
    20
                "Test Token",
    21
                "TEST",
    22
    ✓ 1
                STARTING_BALANCE * 4,
    23
    ✓ 1
                address(this)
    24
            );
    25
    
                                                    
                                                
    26
            // Deploy Drips
    27
    ✓ 1
            drips = new DripsEchidna(SECONDS_PER_CYCLE);
    28
    ✓ 1
            drips.unpause_noModifiers();
    29
    
                                                    
                                                
    30
            // Deploy AddressDriver
    31
    ✓ 1
            uint32 driverId = drips.registerDriver(address(this));
    32
    ✓ 1
            driver = new AddressDriverEchidna(
    33
    ✓ 1
                drips,
    34
    ✓ 1
                address(0),
    35
    ✓ 1
                driverId
    36
            );
    37
    ✓ 1
            driver.unpause_noModifiers();
    38
    ✓ 1
            drips.updateDriverAddress(driverId, address(driver));
    39
    
                                                    
                                                
    40
            // Set up token balances
    41
    ✓ 1
            token.transfer(ADDRESS_USER0, STARTING_BALANCE);
    42
    ✓ 1
            hevm.prank(ADDRESS_USER0);
    43
    ✓ 1
            token.approve(address(driver), type(uint256).max);
    44
    ✓ 1
            token.transfer(ADDRESS_USER1, STARTING_BALANCE);
    45
    ✓ 1
            hevm.prank(ADDRESS_USER1);
    46
    ✓ 1
            token.approve(address(driver), type(uint256).max);
    47
    ✓ 1
            token.transfer(ADDRESS_USER2, STARTING_BALANCE);
    48
    ✓ 1
            hevm.prank(ADDRESS_USER2);
    49
    ✓ 1
            token.approve(address(driver), type(uint256).max);
    50
    ✓ 1
            token.transfer(ADDRESS_USER3, STARTING_BALANCE);
    51
    ✓ 1
            hevm.prank(ADDRESS_USER3);
    52
    ✓ 1
            token.approve(address(driver), type(uint256).max);
    53
    
                                                    
                                                
    54
            // Store starting timestamp
    55
    ✓ 1
            STARTING_TIMESTAMP = block.timestamp;
    56
        }
    57
    }
    58
    
                                                    
                                                
    98.6% src/echidna/base/EchidnaStorage.sol
    Lines covered: 73 / 74 (98.6%)
    1
    // SPDX-License-Identifier: MIT
    2
    import "./EchidnaSetup.sol";
    3
    
                                                    
                                                
    4
    /**
    5
     * @title Mixin for storing stream receivers and streams history
    6
     * @author Rappie <rappie@perimetersec.io>
    7
     */
    8
    contract EchidnaStorage is EchidnaSetup {
    9
        error DuplicateError();
    10
    
                                                    
                                                
    11
        // Mapping from address to current stream receivers
    12
        mapping(address => StreamReceiver[]) internal userToStreamReceivers;
    13
    
                                                    
                                                
    14
        // Mappings from address to full streams history and hashes
    15
        mapping(address => StreamsHistory[]) internal userToStreamsHistory;
    16
        mapping(address => bytes32[]) internal userToStreamsHistoryHashes;
    17
    
                                                    
                                                
    18
        // Mapping from address to current splits receivers
    19
        mapping(address => SplitsReceiver[]) internal userToSplitsReceivers;
    20
    
                                                    
                                                
    21
        /**
    22
         * @notice Update the stream receivers for a given account.
    23
         * @param sender Account to update
    24
         * @param receivers New stream receivers
    25
         * @dev This function will generate a new hash for the stream receivers,
    26
         * update the stream receivers for the given account, and add a new entry
    27
         * to the streams history and streams history hashes for the given
    28
         * account. This is stored for later use.
    29
         */
    30
        function updateStreamReceivers(
    31
            address sender,
    32
            StreamReceiver[] memory receivers
    33
        ) internal {
    34
            // Hash the new stream receivers
    35
    ✓ 28.4M
            bytes32 receiversHash = drips.hashStreams(receivers);
    36
    
                                                    
                                                
    37
            // Update the stream receivers for 'sender'
    38
    ✓ 28.4M
            delete userToStreamReceivers[sender];
    39
    ✓ 56.1M
            for (uint256 i = 0; i < receivers.length; i++) {
    40
    ✓ 27.7M
                userToStreamReceivers[sender].push(receivers[i]);
    41
            }
    42
    
                                                    
                                                
    43
            // Add a new entry to the streams history
    44
    ✓ 28.4M
            uint256 nextIndex = userToStreamsHistory[sender].length;
    45
    ✓ 28.4M
            userToStreamsHistory[sender].push();
    46
    ✓ 28.4M
            userToStreamsHistory[sender][nextIndex].streamsHash = bytes32(0);
    47
    ✓ 56.1M
            for (uint256 i = 0; i < receivers.length; i++) {
    48
    ✓ 27.7M
                userToStreamsHistory[sender][nextIndex].receivers.push(
    49
    ✓ 27.7M
                    receivers[i]
    50
                );
    51
            }
    52
    ✓ 28.4M
            (, , uint32 updateTime, , uint32 maxEnd) = drips.streamsState(
    53
    ✓ 28.4M
                getDripsAccountId(sender),
    54
    ✓ 28.4M
                token
    55
            );
    56
    ✓ 28.4M
            userToStreamsHistory[sender][nextIndex].updateTime = updateTime;
    57
    ✓ 28.4M
            userToStreamsHistory[sender][nextIndex].maxEnd = maxEnd;
    58
    
                                                    
                                                
    59
            // Generate starting hash. If there is no previous history, use 0
    60
    ✓ 28.4M
            bytes32 startingHash;
    61
    ✓ 28.4M
            if (nextIndex == 0) {
    62
    ✓ 3.2M
                startingHash = bytes32(0);
    63
            } else {
    64
    ✓ 25.2M
                startingHash = userToStreamsHistoryHashes[sender][nextIndex - 1];
    65
            }
    66
    
                                                    
                                                
    67
            // Add new entry to the streams history hashes
    68
    ✓ 28.4M
            bytes32 historyHash = drips.hashStreamsHistory(
    69
    ✓ 28.4M
                startingHash,
    70
    ✓ 28.4M
                receiversHash,
    71
    ✓ 28.4M
                updateTime,
    72
    ✓ 28.4M
                maxEnd
    73
            );
    74
    ✓ 28.4M
            userToStreamsHistoryHashes[sender].push(historyHash);
    75
        }
    76
    
                                                    
                                                
    77
        /**
    78
         * @notice Update the splits receivers for a given account.
    79
         * @param sender Account to update
    80
         * @param receivers New splits receivers
    81
         */
    82
        function updateSplitsReceivers(
    83
            address sender,
    84
            SplitsReceiver[] memory receivers
    85
        ) internal {
    86
    ✓ 16.8M
            delete userToSplitsReceivers[sender];
    87
    ✓ 27.5M
            for (uint256 i = 0; i < receivers.length; i++) {
    88
    ✓ 10.7M
                userToSplitsReceivers[sender].push(receivers[i]);
    89
            }
    90
        }
    91
    
                                                    
                                                
    92
        /**
    93
         * @notice Get the stream receivers for a given account.
    94
         * @param sender Account to get stream receivers for
    95
         * @return Stream receivers for the given account
    96
         */
    97
        function getStreamReceivers(address sender)
    98
            internal
    99
    ✓ 61.3M
            returns (StreamReceiver[] memory)
    100
        {
    101
    ✓ 61.3M
            return userToStreamReceivers[sender];
    102
        }
    103
    
                                                    
                                                
    104
        /**
    105
         * @notice Get the streams history for a given account.
    106
         * @param sender Account to get streams history for
    107
         * @return Streams history for the given account
    108
         */
    109
        function getStreamsHistory(address sender)
    110
            internal
    111
    ✓ 73.5M
            returns (StreamsHistory[] memory)
    112
        {
    113
    ✓ 73.5M
            return userToStreamsHistory[sender];
    114
        }
    115
    
                                                    
                                                
    116
        /**
    117
         * @notice Get the streams history hashes for a given account.
    118
         * @param sender Account to get streams history hashes for
    119
         * @return Streams history hashes for the given account
    120
         */
    121
        function getStreamsHistoryHashes(address sender)
    122
            internal
    123
    ✓ 4.2M
            returns (bytes32[] memory)
    124
        {
    125
    ✓ 4.2M
            return userToStreamsHistoryHashes[sender];
    126
        }
    127
    
                                                    
                                                
    128
        /**
    129
         * @notice Get the splits receivers for a given account.
    130
         * @param sender Account to get splits receivers for
    131
         * @return Splits receivers for the given account
    132
         */
    133
        function getSplitsReceivers(address sender)
    134
            internal
    135
    ✓ 25.5M
            returns (SplitsReceiver[] memory)
    136
        {
    137
    ✓ 25.5M
            return userToSplitsReceivers[sender];
    138
        }
    139
    
                                                    
                                                
    140
        /**
    141
         * @notice Sort the stream receivers.
    142
         * @param unsorted Unsorted stream receivers
    143
         * @return Sorted stream receivers
    144
         * @dev This function will sort the stream receivers by account ID,
    145
         * then by config, then by stream ID. It will revert if there are
    146
         * duplicate stream receivers.
    147
         */
    148
        function bubbleSortStreamReceivers(StreamReceiver[] memory unsorted)
    149
            internal
    150
    ✓ 30.5M
            returns (StreamReceiver[] memory)
    151
        {
    152
    ✓ 30.5M
            uint256 n = unsorted.length;
    153
    ✓ 30.5M
            if (n <= 1) return unsorted;
    154
    
                                                    
                                                
    155
    ✓ 6.7M
            StreamReceiver[] memory sorted = unsorted;
    156
    ✓ 16.1M
            for (uint256 i = 0; i < n - 1; i++) {
    157
    ✓ 22.7M
                for (uint256 j = 0; j < n - i - 1; j++) {
    158
    ✓ 13.3M
                    if (bubbleSortStreamReceiverGT(sorted[j], sorted[j + 1])) {
    159
    ✓ 2.6M
                        StreamReceiver memory temp = sorted[j];
    160
    ✓ 2.6M
                        sorted[j] = sorted[j + 1];
    161
    ✓ 2.6M
                        sorted[j + 1] = temp;
    162
                    }
    163
                }
    164
            }
    165
    ✓ 6.5M
            return sorted;
    166
        }
    167
    
                                                    
                                                
    168
        /**
    169
         * @notice Sort the splits receivers.
    170
         * @param unsorted Unsorted splits receivers
    171
         * @return Sorted splits receivers
    172
         */
    173
        function bubbleSortSplitsReceivers(SplitsReceiver[] memory unsorted)
    174
            internal
    175
    ✓ 13.4M
            returns (SplitsReceiver[] memory)
    176
        {
    177
    ✓ 13.4M
            uint256 n = unsorted.length;
    178
    ✓ 13.4M
            if (n <= 1) return unsorted;
    179
    
                                                    
                                                
    180
    ✓ 1.2M
            SplitsReceiver[] memory sorted = unsorted;
    181
    ✓ 2.1M
            for (uint256 i = 0; i < n - 1; i++) {
    182
    ✓ 2.3M
                for (uint256 j = 0; j < n - i - 1; j++) {
    183
    ✓ 1.3M
                    if (bubbleSortSplitsReceiverGT(sorted[j], sorted[j + 1])) {
    184
    ✓ 280.4K
                        SplitsReceiver memory temp = sorted[j];
    185
    ✓ 280.4K
                        sorted[j] = sorted[j + 1];
    186
    ✓ 280.4K
                        sorted[j + 1] = temp;
    187
                    }
    188
                }
    189
            }
    190
    ✓ 855.7K
            return sorted;
    191
        }
    192
    
                                                    
                                                
    193
        /**
    194
         * @notice Compare two stream receivers.
    195
         * @param a First stream receiver
    196
         * @param b Second stream receiver
    197
         * @return True if `a` is greater than `b`, false otherwise
    198
         * @dev This function will compare two stream receivers by account ID,
    199
         * then by config. It will revert if there are duplicate stream receivers.
    200
         */
    201
        function bubbleSortStreamReceiverGT(
    202
            StreamReceiver memory a,
    203
            StreamReceiver memory b
    204
    ✓ 13.3M
        ) internal returns (bool) {
    205
    ✓ 13.3M
            if (a.accountId != b.accountId) {
    206
    ✓ 8.4M
                return a.accountId > b.accountId;
    207
            }
    208
    ✓ 4.9M
            if (StreamConfig.unwrap(a.config) != StreamConfig.unwrap(b.config)) {
    209
                return
    210
    ✓ 4.8M
                    StreamConfig.unwrap(a.config) > StreamConfig.unwrap(b.config);
    211
            }
    212
    ✓ 173.9K
    ⟲ 173.9K
            revert DuplicateError();
    213
        }
    214
    
                                                    
                                                
    215
        /**
    216
         * @notice Compare two splits receivers.
    217
         * @param a First splits receiver
    218
         * @param b Second splits receiver
    219
         * @return True if `a` is greater than `b`, false otherwise
    220
         * @dev This function will compare two splits receivers by account ID.
    221
         * It will revert if there are duplicate splits receivers.
    222
         */
    223
        function bubbleSortSplitsReceiverGT(
    224
            SplitsReceiver memory a,
    225
            SplitsReceiver memory b
    226
    ✓ 1.3M
        ) internal returns (bool) {
    227
    ✓ 1.3M
            if (a.accountId != b.accountId) {
    228
    ✓ 1.0M
                return a.accountId > b.accountId;
    229
            }
    230
    ✓ 296.5K
    ⟲ 296.5K
            revert DuplicateError();
    231
        }
    232
    
                                                    
                                                
    233
        /**
    234
         * @notice Get the drips account ID for a given address.
    235
         * @param account Address to get drips account ID for
    236
         * @return Drips account ID for the given address
    237
         * @dev Caches the value to increase performance
    238
         */
    239
    ✓ 303.7M
        function getDripsAccountId(address account) internal returns (uint256) {
    240
    ✓ 303.7M
            if (ADDRESS_TO_DRIPS_ACCOUNT_ID[account] == 0) {
    241
    ✓ 4.6M
                ADDRESS_TO_DRIPS_ACCOUNT_ID[account] = driver.calcAccountId(
    242
    ✓ 4.6M
                    account
    243
                );
    244
            }
    245
    ✓ 303.7M
            return ADDRESS_TO_DRIPS_ACCOUNT_ID[account];
    246
        }
    247
    }
    248
    
                                                    
                                                
    100.0% src/echidna/tools/AddressDriverEchidna.sol
    Lines covered: 2 / 2 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    import {AddressDriver} from "src/AddressDriver.sol";
    3
    
                                                    
                                                
    4
    import {DripsEchidna} from "./DripsEchidna.sol";
    5
    import {ManagedEchidna} from "./ManagedEchidna.sol";
    6
    
                                                    
                                                
    7
    /**
    8
     * @title Wrapper around AddressDriver with minor fuzzing helpers
    9
     * @author Rappie <rappie@perimetersec.io>
    10
     */
    11
    ✓ 64.9M
    contract AddressDriverEchidna is AddressDriver, ManagedEchidna {
    12
        constructor(
    13
            DripsEchidna drips_,
    14
            address forwarder,
    15
            uint32 driverId_
    16
    ✓ 1
        ) AddressDriver(drips_, forwarder, driverId_) {}
    17
    }
    18
    
                                                    
                                                
    100.0% src/echidna/tools/Debugger.sol
    Lines covered: 1 / 1 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    ✓ 6
    library Debugger {
    4
        event Debug(string debugString);
    5
        event Debug(string description, string data);
    6
        event Debug(string prefix, string description, string data);
    7
        event Debug(string description, bytes32 data);
    8
        event Debug(string prefix, string description, bytes32 data);
    9
        event Debug(string description, uint256 data);
    10
        event Debug(string prefix, string description, uint256 data);
    11
        event Debug(string description, int256 data);
    12
        event Debug(string prefix, string description, int256 data);
    13
        event Debug(string description, address data);
    14
        event Debug(string prefix, string description, address data);
    15
        event Debug(string description, bool data);
    16
        event Debug(string prefix, string description, bool data);
    17
    
                                                    
                                                
    18
        function log(string memory debugString) internal {
    19
            emit Debug(debugString);
    20
        }
    21
    
                                                    
                                                
    22
        function log(string memory description, string memory data) internal {
    23
            emit Debug(description, data);
    24
        }
    25
    
                                                    
                                                
    26
        function log(
    27
            string memory prefix,
    28
            string memory description,
    29
            string memory data
    30
        ) internal {
    31
            emit Debug(prefix, description, data);
    32
        }
    33
    
                                                    
                                                
    34
        function log(string memory description, bytes32 data) internal {
    35
            emit Debug(description, data);
    36
        }
    37
    
                                                    
                                                
    38
        function log(
    39
            string memory prefix,
    40
            string memory description,
    41
            bytes32 data
    42
        ) internal {
    43
            emit Debug(prefix, description, data);
    44
        }
    45
    
                                                    
                                                
    46
        function log(string memory description, uint256 data) internal {
    47
            emit Debug(description, data);
    48
        }
    49
    
                                                    
                                                
    50
        function log(
    51
            string memory prefix,
    52
            string memory description,
    53
            uint256 data
    54
        ) internal {
    55
            emit Debug(prefix, description, data);
    56
        }
    57
    
                                                    
                                                
    58
        function log(string memory description, int256 data) internal {
    59
            emit Debug(description, data);
    60
        }
    61
    
                                                    
                                                
    62
        function log(
    63
            string memory prefix,
    64
            string memory description,
    65
            int256 data
    66
        ) internal {
    67
            emit Debug(prefix, description, data);
    68
        }
    69
    
                                                    
                                                
    70
        function log(string memory description, address data) internal {
    71
            emit Debug(description, data);
    72
        }
    73
    
                                                    
                                                
    74
        function log(
    75
            string memory prefix,
    76
            string memory description,
    77
            address data
    78
        ) internal {
    79
            emit Debug(prefix, description, data);
    80
        }
    81
    
                                                    
                                                
    82
        function log(string memory description, bool data) internal {
    83
            emit Debug(description, data);
    84
        }
    85
    
                                                    
                                                
    86
        function log(
    87
            string memory prefix,
    88
            string memory description,
    89
            bool data
    90
        ) internal {
    91
            emit Debug(prefix, description, data);
    92
        }
    93
    }
    94
    
                                                    
                                                
    100.0% src/echidna/tools/DripsEchidna.sol
    Lines covered: 8 / 8 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    import {IERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol";
    3
    import {Drips} from "src/Drips.sol";
    4
    
                                                    
                                                
    5
    import {ManagedEchidna} from "./ManagedEchidna.sol";
    6
    
                                                    
                                                
    7
    /**
    8
     * @title Wrapper around Drips with minor fuzzing helpers
    9
     * @author Rappie <rappie@perimetersec.io>
    10
     */
    11
    contract DripsEchidna is Drips, ManagedEchidna {
    12
    ✓ 1
        constructor(uint32 cycleSecs_) Drips(cycleSecs_) {}
    13
    
                                                    
                                                
    14
        /**
    15
         * @notice Get the amtDelta for a given user and cycle
    16
         * @param accountId The account to get the amtDelta for
    17
         * @param erc20 The ERC20 token
    18
         * @param cycle The cycle to get the amtDelta for
    19
         * @return The amtDelta for this cycle and the next cycle
    20
         */
    21
        function getAmtDeltaForCycle(
    22
            uint256 accountId,
    23
            IERC20 erc20,
    24
            uint32 cycle
    25
    ✓ 138.8M
        ) public view returns (int128, int128) {
    26
            // Manually calculate the storage slot because it is a private variable
    27
            // in Streams
    28
    ✓ 138.8M
            StreamsStorage storage streamsStorage;
    29
    ✓ 138.8M
            bytes32 slot = _erc1967Slot("eip1967.streams.storage");
    30
            assembly {
    31
    ✓ 138.8M
                streamsStorage.slot := slot
    32
            }
    33
    
                                                    
                                                
    34
            // Return the amtDelta for the given cycle
    35
    ✓ 138.8M
            StreamsState storage state = streamsStorage.states[erc20][accountId];
    36
    ✓ 138.8M
            mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas;
    37
    ✓ 138.8M
            return (amtDeltas[cycle].thisCycle, amtDeltas[cycle].nextCycle);
    38
        }
    39
    }
    40
    
                                                    
                                                
    0.0% src/echidna/tools/IHevm.sol
    Lines covered: 0 / 0 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    
                                                    
                                                
    3
    // https://github.com/ethereum/hevm/blob/main/doc/src/controlling-the-unit-testing-environment.md#cheat-codes
    4
    
                                                    
                                                
    5
    interface IHevm {
    6
        function warp(uint256 x) external;
    7
    
                                                    
                                                
    8
        function roll(uint256 x) external;
    9
    
                                                    
                                                
    10
        function store(
    11
            address c,
    12
            bytes32 loc,
    13
            bytes32 val
    14
        ) external;
    15
    
                                                    
                                                
    16
        function load(address c, bytes32 loc) external returns (bytes32 val);
    17
    
                                                    
                                                
    18
        function sign(uint256 sk, bytes32 digest)
    19
            external
    20
            returns (
    21
                uint8 v,
    22
                bytes32 r,
    23
                bytes32 s
    24
            );
    25
    
                                                    
                                                
    26
        function addr(uint256 sk) external returns (address addr);
    27
    
                                                    
                                                
    28
        function ffi(string[] calldata) external returns (bytes memory);
    29
    
                                                    
                                                
    30
        function prank(address sender) external;
    31
    }
    32
    
                                                    
                                                
    66.7% src/echidna/tools/ManagedEchidna.sol
    Lines covered: 2 / 3 (66.7%)
    1
    // SPDX-License-Identifier: MIT
    2
    import {Managed} from "src/Managed.sol";
    3
    
                                                    
                                                
    4
    /**
    5
     * @title Wrapper around Managed with minor fuzzing helpers
    6
     * @author Rappie <rappie@perimetersec.io>
    7
     */
    8
    contract ManagedEchidna is Managed {
    9
        constructor() Managed() {}
    10
    
                                                    
                                                
    11
        /**
    12
         * @notice Helper function to unpause the contract as any user
    13
         */
    14
        function unpause_noModifiers() public {
    15
    ✓ 1
            _managedStorage().isPaused = false;
    16
    ✓ 1
            emit Unpaused(msg.sender);
    17
        }
    18
    }
    19
    
                                                    
                                                
    100.0% test/recon/CryticTester.sol
    Lines covered: 1 / 1 (100.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {Properties} from "./Properties.sol";
    5
    
                                                    
                                                
    6
    // echidna test/recon/CryticTester.sol --contract CryticTester --config echidna.yaml
    7
    ✓ 88.9M
    contract CryticTester is Properties {}
    8
    
                                                    
                                                
    57.1% test/recon/Properties.sol
    Lines covered: 4 / 7 (57.1%)
    1
    // SPDX-License-Identifier: MIT
    2
    pragma solidity ^0.8.20;
    3
    
                                                    
                                                
    4
    import {Echidna} from "src/echidna/Echidna.sol";
    5
    
                                                    
                                                
    6
    /// @notice scfuzzbench adaptation layer: canary checks on top of the
    7
    /// Perimeter Drips fuzzing 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
    ✓ 980.9K
            if (!b) {
    18
    ✓ 36.0K
                emit AssertionFailed(reason);
    19
    ✓ 36.0K
                assert(false);
    20
            }
    21
        }
    22
    
                                                    
                                                
    23
        function invariant_canary() public returns (bool) {
    24
            t(false, INVARIANT_CANARY_GLOBAL_INVARIANT_FAILURE);
    25
            return true;
    26
        }
    27
    
                                                    
                                                
    28
        function assert_canary_ASSERTION_CANARY(uint256 entropy) public {
    29
    ✓ 980.9K
            t(entropy > 0, ASSERTION_CANARY);
    30
        }
    31
    }
    32