Echidna Coverage Report

Files

    Files
    47
    Total Lines
    7965
    Coverage
    87.0%
    1808 / 2063 lines
    Actions
    0.0% lib/openzeppelin-contracts/contracts/interfaces/IERC1967.sol
    Lines covered: 0 / 1 (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
    87.0% lib/openzeppelin-contracts/contracts/metatx/ERC2771Context.sol
    Lines covered: 7 / 8 (87.0%)
    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
            _trustedForwarder = trustedForwarder;
    18
        }
    19
    20
        function isTrustedForwarder(address forwarder) public view virtual returns (bool) {
    21
            return forwarder == _trustedForwarder;
    22
        }
    23
    24
        function _msgSender() internal view virtual override returns (address sender) {
    25
            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
                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 / 5 (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 / 30 (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 / 14 (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
    8.0% lib/openzeppelin-contracts/contracts/proxy/utils/UUPSUpgradeable.sol
    Lines covered: 1 / 12 (8.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
        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
    61.0% lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol
    Lines covered: 47 / 77 (61.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
            _name = name_;
    56
            _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
        function balanceOf(address account) public view virtual override returns (uint256) {
    102
            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
        function transfer(address to, uint256 amount) public virtual override returns (bool) {
    114
            address owner = _msgSender();
    115
            _transfer(owner, to, amount);
    116
            return true;
    117
        }
    118
    119
        /**
    120
         * @dev See {IERC20-allowance}.
    121
         */
    122
        function allowance(address owner, address spender) public view virtual override returns (uint256) {
    123
            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
        function approve(address spender, uint256 amount) public virtual override returns (bool) {
    137
            address owner = _msgSender();
    138
            _approve(owner, spender, amount);
    139
            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
        function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
    159
            address spender = _msgSender();
    160
            _spendAllowance(from, spender, amount);
    161
            _transfer(from, to, amount);
    162
            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
            require(from != address(0), "ERC20: transfer from the zero address");
    224
            require(to != address(0), "ERC20: transfer to the zero address");
    225
    226
            _beforeTokenTransfer(from, to, amount);
    227
    228
            uint256 fromBalance = _balances[from];
    229
            require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
    230
            unchecked {
    231
                _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
                _balances[to] += amount;
    235
            }
    236
    237
            emit Transfer(from, to, amount);
    238
    239
            _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
            require(account != address(0), "ERC20: mint to the zero address");
    253
    254
            _beforeTokenTransfer(address(0), account, amount);
    255
    256
            _totalSupply += amount;
    257
            unchecked {
    258
                // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
    259
                _balances[account] += amount;
    260
            }
    261
            emit Transfer(address(0), account, amount);
    262
    263
            _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
            require(owner != address(0), "ERC20: approve from the zero address");
    310
            require(spender != address(0), "ERC20: approve to the zero address");
    311
    312
            _allowances[owner][spender] = amount;
    313
            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
            uint256 currentAllowance = allowance(owner, spender);
    326
            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
        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
        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 / 5 (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
    contract ERC20PresetFixedSupply is ERC20Burnable {
    22
        /**
    23
         * @dev Mints `initialSupply` amount of token and transfers them to `owner`.
    24
         *
    25
         * See {ERC20-constructor}.
    26
         */
    27
        constructor(string memory name, string memory symbol, uint256 initialSupply, address owner) ERC20(name, symbol) {
    28
            _mint(owner, initialSupply);
    29
        }
    30
    }
    31
    87.0% lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol
    Lines covered: 7 / 8 (87.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
    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
            _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
            _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
            bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
    123
            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.0% lib/openzeppelin-contracts/contracts/utils/Address.sol
    Lines covered: 18 / 29 (62.0%)
    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
    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
        ) internal returns (bytes memory) {
    104
            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
        ) internal returns (bytes memory) {
    134
            require(address(this).balance >= value, "Address: insufficient balance for call");
    135
            (bool success, bytes memory returndata) = target.call{value: value}(data);
    136
            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
        ) internal view returns (bytes memory) {
    201
            if (success) {
    202
                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
                return returndata;
    208
            } else {
    209
                _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
            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
                    let returndata_size := mload(returndata)
    238
                    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
        function _msgSender() internal view virtual returns (address) {
    18
            return msg.sender;
    19
        }
    20
    21
        function _msgData() internal view virtual returns (bytes calldata) {
    22
            return msg.data;
    23
        }
    24
    }
    25
    0.0% lib/openzeppelin-contracts/contracts/utils/StorageSlot.sol
    Lines covered: 0 / 5 (0.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
    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
    0.0% lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol
    Lines covered: 0 / 35 (0.0%)
    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
    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
    81.0% src/AddressDriver.sol
    Lines covered: 26 / 32 (81.0%)
    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
        constructor(Drips drips_, address forwarder, uint32 driverId_) DriverTransferUtils(forwarder) {
    22
            drips = drips_;
    23
            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
        function calcAccountId(address addr) public view returns (uint256 accountId) {
    32
            // By assignment we get `accountId` value:
    33
            // `zeros (224 bits) | driverId (32 bits)`
    34
            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
            accountId = (accountId << 224) | uint160(addr);
    40
        }
    41
    42
        /// @notice Calculates the account ID for the message sender
    43
        /// @return accountId The account ID
    44
        function _callerAccountId() internal view returns (uint256 accountId) {
    45
            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
        function collect(IERC20 erc20, address transferTo) public whenNotPaused returns (uint128 amt) {
    59
            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
            _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
        ) public whenNotPaused returns (int128 realBalanceDelta) {
    135
            return _setStreamsAndTransfer(
    136
                drips,
    137
                _callerAccountId(),
    138
                erc20,
    139
                currReceivers,
    140
                balanceDelta,
    141
                newReceivers,
    142
                maxEndHint1,
    143
                maxEndHint2,
    144
                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
            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: 127 / 146 (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
        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
        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
        uint160 public immutable minAmtPerSec;
    82
        /// @notice The ERC-1967 storage slot holding a single `DripsStorage` structure.
    83
        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
            Streams(cycleSecs_, _erc1967Slot("eip1967.streams.storage"))
    140
            Splits(_erc1967Slot("eip1967.splits.storage"))
    141
        {
    142
            cycleSecs = Streams._cycleSecs;
    143
            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
            uint32 driverId = uint32(accountId >> DRIVER_ID_OFFSET);
    156
            _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
            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
        function registerDriver(address driverAddr) public whenNotPaused returns (uint32 driverId) {
    178
            require(driverAddr != address(0), "Driver registered for 0 address");
    179
            DripsStorage storage dripsStorage = _dripsStorage();
    180
            driverId = dripsStorage.nextDriverId++;
    181
            dripsStorage.driverAddresses[driverId] = driverAddr;
    182
            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
        function driverAddress(uint32 driverId) public view returns (address driverAddr) {
    190
            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
            _assertCallerIsDriver(driverId);
    200
            _dripsStorage().driverAddresses[driverId] = newDriverAddr;
    201
            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
            returns (uint128 streamsBalance, uint128 splitsBalance)
    229
        {
    230
            Balance storage balance = _dripsStorage().balances[erc20];
    231
            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
            _verifyBalanceIncrease(erc20, amt);
    242
            _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
            _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
            _verifyBalanceIncrease(erc20, amt);
    262
            _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
            _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
            Balance storage balance = _dripsStorage().balances[erc20];
    280
            balance.streams -= amt;
    281
            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
            (uint256 streamsBalance, uint128 splitsBalance) = balances(erc20);
    291
            uint256 newTotalBalance = streamsBalance + splitsBalance + amt;
    292
            require(newTotalBalance <= MAX_TOTAL_BALANCE, "Total balance too high");
    293
            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
            (uint128 streamsBalance, uint128 splitsBalance) = balances(erc20);
    313
            uint256 withdrawable = _tokenBalance(erc20) - streamsBalance - splitsBalance;
    314
            require(amt <= withdrawable, "Withdrawal amount too high");
    315
            emit Withdrawn(erc20, receiver, amt);
    316
            erc20.safeTransfer(receiver, amt);
    317
        }
    318
    319
        function _tokenBalance(IERC20 erc20) internal view returns (uint256) {
    320
            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
            returns (uint32 cycles)
    338
        {
    339
            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
            returns (uint128 receivableAmt)
    358
        {
    359
            (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
            returns (uint128 receivedAmt)
    380
        {
    381
            receivedAmt = Streams._receiveStreams(accountId, erc20, maxCycles);
    382
            if (receivedAmt != 0) {
    383
                _moveBalanceFromStreamsToSplits(erc20, receivedAmt);
    384
                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
        ) public whenNotPaused returns (uint128 amt) {
    415
            amt = Streams._squeezeStreams(accountId, erc20, senderId, historyHash, streamsHistory);
    416
            if (amt != 0) {
    417
                _moveBalanceFromStreamsToSplits(erc20, amt);
    418
                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
        ) public view returns (uint128 amt) {
    442
            (amt,,,,) =
    443
                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
        function splittable(uint256 accountId, IERC20 erc20) public view returns (uint128 amt) {
    456
            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
            returns (uint128 collectableAmt, uint128 splitAmt)
    474
        {
    475
            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
            returns (uint128 collectableAmt, uint128 splitAmt)
    505
        {
    506
            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
        function collectable(uint256 accountId, IERC20 erc20) public view returns (uint128 amt) {
    519
            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
            onlyDriver(accountId)
    537
            returns (uint128 amt)
    538
        {
    539
            amt = Splits._collect(accountId, erc20);
    540
            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
            onlyDriver(accountId)
    561
        {
    562
            if (amt != 0) _increaseSplitsBalance(erc20, amt);
    563
            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
                bytes32 streamsHash,
    584
                bytes32 streamsHistoryHash,
    585
                uint32 updateTime,
    586
                uint128 balance,
    587
                uint32 maxEnd
    588
            )
    589
        {
    590
            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
        ) public view returns (uint128 balance) {
    614
            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
        ) public whenNotPaused onlyDriver(accountId) returns (int128 realBalanceDelta) {
    679
            if (balanceDelta > 0) _increaseStreamsBalance(erc20, uint128(balanceDelta));
    680
            realBalanceDelta = Streams._setStreams(
    681
                accountId, erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2
    682
            );
    683
            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
            returns (bytes32 streamsHash)
    697
        {
    698
            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
        ) public pure returns (bytes32 streamsHistoryHash) {
    716
            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
            onlyDriver(accountId)
    743
        {
    744
            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
        function _dripsStorage() internal view returns (DripsStorage storage storageRef) {
    787
            bytes32 slot = _dripsStorageSlot;
    788
            // slither-disable-next-line assembly
    789
            assembly {
    790
                storageRef.slot := slot
    791
            }
    792
        }
    793
    }
    794
    100.0% src/DriverTransferUtils.sol
    Lines covered: 16 / 16 (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
        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
            returns (uint128 amt)
    30
        {
    31
            amt = drips.collect(accountId, erc20);
    32
            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
            if (amt > 0) _transferFromCaller(drips, erc20, amt);
    55
            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
        ) internal returns (int128 realBalanceDelta) {
    119
            if (balanceDelta > 0) _transferFromCaller(drips, erc20, uint128(balanceDelta));
    120
            realBalanceDelta = drips.setStreams(
    121
                accountId, erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2
    122
            );
    123
            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
            SafeERC20.safeTransferFrom(erc20, _msgSender(), address(drips), amt);
    132
        }
    133
    }
    134
    22.0% src/Managed.sol
    Lines covered: 12 / 53 (22.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
        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
            require(!isPaused(), "Contract paused");
    67
            _;
    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
            _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
        function isPaused() public view returns (bool) {
    160
            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
        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
            return bytes32(uint256(keccak256(bytes(name))) - 1024);
    184
        }
    185
    186
        /// @notice Returns the Managed storage.
    187
        /// @return storageRef The storage.
    188
        function _managedStorage() internal view returns (ManagedStorage storage storageRef) {
    189
            bytes32 slot = _managedStorageSlot;
    190
            // slither-disable-next-line assembly
    191
            assembly {
    192
                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: 87 / 87 (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
        uint256 internal constant _MAX_SPLITS_RECEIVERS = 200;
    24
        /// @notice The total splits weight of an account.
    25
        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
            _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
            _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
        function _splittable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) {
    114
            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
            returns (uint128 collectableAmt, uint128 splitAmt)
    130
        {
    131
            _assertCurrSplits(accountId, currReceivers);
    132
            if (amount == 0) {
    133
                return (0, 0);
    134
            }
    135
            unchecked {
    136
                uint256 splitsWeight = 0;
    137
                for (uint256 i = currReceivers.length; i != 0;) {
    138
                    splitsWeight += currReceivers[--i].weight;
    139
                }
    140
                splitAmt = uint128(amount * splitsWeight / _TOTAL_SPLITS_WEIGHT);
    141
                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
            returns (uint128 collectableAmt, uint128 splitAmt)
    159
        {
    160
            _assertCurrSplits(accountId, currReceivers);
    161
            SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20];
    162
    163
            collectableAmt = balance.splittable;
    164
            if (collectableAmt == 0) {
    165
                return (0, 0);
    166
            }
    167
            balance.splittable = 0;
    168
    169
            unchecked {
    170
                uint256 splitsWeight = 0;
    171
                for (uint256 i = 0; i < currReceivers.length; i++) {
    172
                    splitsWeight += currReceivers[i].weight;
    173
                    uint128 currSplitAmt = splitAmt;
    174
                    splitAmt = uint128(collectableAmt * splitsWeight / _TOTAL_SPLITS_WEIGHT);
    175
                    currSplitAmt = splitAmt - currSplitAmt;
    176
                    uint256 receiver = currReceivers[i].accountId;
    177
                    _addSplittable(receiver, erc20, currSplitAmt);
    178
                    emit Split(accountId, receiver, erc20, currSplitAmt);
    179
                }
    180
                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
                balance.collectable += collectableAmt;
    184
            }
    185
            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
        function _collectable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) {
    193
            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
        function _collect(uint256 accountId, IERC20 erc20) internal returns (uint128 amt) {
    201
            SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20];
    202
            amt = balance.collectable;
    203
            balance.collectable = 0;
    204
            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
            _addSplittable(receiver, erc20, amt);
    215
            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
            SplitsState storage state = _splitsStorage().splitsStates[accountId];
    238
            bytes32 newSplitsHash = _hashSplits(receivers);
    239
            if (newSplitsHash == state.splitsHash) return;
    240
            emit SplitsSet(accountId, newSplitsHash);
    241
            _assertSplitsValid(receivers, newSplitsHash);
    242
            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
                require(receivers.length <= _MAX_SPLITS_RECEIVERS, "Too many splits receivers");
    252
                uint256 totalWeight = 0;
    253
                uint256 prevAccountId = 0;
    254
                for (uint256 i = 0; i < receivers.length; i++) {
    255
                    SplitsReceiver memory receiver = receivers[i];
    256
                    uint32 weight = receiver.weight;
    257
                    require(weight != 0, "Splits receiver weight is zero");
    258
                    totalWeight += weight;
    259
                    uint256 accountId = receiver.accountId;
    260
                    if (accountId <= prevAccountId) require(i == 0, "Splits receivers not sorted");
    261
                    prevAccountId = accountId;
    262
                    emit SplitsReceiverSeen(receiversHash, accountId, weight);
    263
                }
    264
                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
                _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
        function _splitsHash(uint256 accountId) internal view returns (bytes32 currSplitsHash) {
    285
            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
            returns (bytes32 receiversHash)
    296
        {
    297
            if (receivers.length == 0) {
    298
                return bytes32(0);
    299
            }
    300
            return keccak256(abi.encode(receivers));
    301
        }
    302
    303
        /// @notice Returns the Splits storage.
    304
        /// @return splitsStorage The storage.
    305
        function _splitsStorage() private view returns (SplitsStorage storage splitsStorage) {
    306
            bytes32 slot = _splitsStorageSlot;
    307
            // slither-disable-next-line assembly
    308
            assembly {
    309
                splitsStorage.slot := slot
    310
            }
    311
        }
    312
    }
    313
    99.0% src/Streams.sol
    Lines covered: 414 / 416 (99.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 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
    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
            returns (StreamConfig)
    66
        {
    67
            // By assignment we get `config` value:
    68
            // `zeros (224 bits) | streamId (32 bits)`
    69
            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
            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
            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
            config = (config << 32) | duration_;
    85
            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
        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
            return uint160(StreamConfig.unwrap(config) >> 64);
    108
        }
    109
    110
        /// @notice Extracts start from a `StreamConfig`
    111
        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
            return uint32(StreamConfig.unwrap(config) >> 32);
    119
        }
    120
    121
        /// @notice Extracts duration from a `StreamConfig`
    122
        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
            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
            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
            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
        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
        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
        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
            require(cycleSecs > 1, "Cycle length too low");
    277
            _cycleSecs = cycleSecs;
    278
            _minAmtPerSec = (_AMT_PER_SEC_MULTIPLIER + cycleSecs - 1) / cycleSecs;
    279
            _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
            returns (uint128 receivedAmt)
    293
        {
    294
            uint32 receivableCycles;
    295
            uint32 fromCycle;
    296
            uint32 toCycle;
    297
            int128 finalAmtPerCycle;
    298
            (receivedAmt, receivableCycles, fromCycle, toCycle, finalAmtPerCycle) =
    299
                _receiveStreamsResult(accountId, erc20, maxCycles);
    300
            if (fromCycle != toCycle) {
    301
                StreamsState storage state = _streamsStorage().states[erc20][accountId];
    302
                state.nextReceivableCycle = toCycle;
    303
                mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas;
    304
                unchecked {
    305
                    for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) {
    306
                        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
                    if (finalAmtPerCycle != 0) {
    311
                        amtDeltas[toCycle].thisCycle += finalAmtPerCycle;
    312
                    }
    313
                }
    314
            }
    315
            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
                uint128 receivedAmt,
    334
                uint32 receivableCycles,
    335
                uint32 fromCycle,
    336
                uint32 toCycle,
    337
                int128 amtPerCycle
    338
            )
    339
        {
    340
            unchecked {
    341
                (fromCycle, toCycle) = _receivableStreamsCyclesRange(accountId, erc20);
    342
                if (toCycle - fromCycle > maxCycles) {
    343
                    receivableCycles = toCycle - fromCycle - maxCycles;
    344
                    toCycle -= receivableCycles;
    345
                }
    346
                mapping(uint32 cycle => AmtDelta) storage amtDeltas =
    347
                    _streamsStorage().states[erc20][accountId].amtDeltas;
    348
                for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) {
    349
                    AmtDelta memory amtDelta = amtDeltas[cycle];
    350
                    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
                    receivedAmt += uint128(amtPerCycle);
    354
                    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
            returns (uint32 cycles)
    369
        {
    370
            unchecked {
    371
                (uint32 fromCycle, uint32 toCycle) = _receivableStreamsCyclesRange(accountId, erc20);
    372
                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
            returns (uint32 fromCycle, uint32 toCycle)
    385
        {
    386
            fromCycle = _streamsStorage().states[erc20][accountId].nextReceivableCycle;
    387
            toCycle = _cycleOf(_currTimestamp());
    388
            // slither-disable-next-line timestamp
    389
            if (fromCycle == 0 || toCycle < fromCycle) {
    390
                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
        ) internal returns (uint128 amt) {
    417
            unchecked {
    418
                uint256 squeezedNum;
    419
                uint256[] memory squeezedRevIdxs;
    420
                bytes32[] memory historyHashes;
    421
                uint256 currCycleConfigs;
    422
                (amt, squeezedNum, squeezedRevIdxs, historyHashes, currCycleConfigs) =
    423
                    _squeezeStreamsResult(accountId, erc20, senderId, historyHash, streamsHistory);
    424
                bytes32[] memory squeezedHistoryHashes = new bytes32[](squeezedNum);
    425
                StreamsState storage state = _streamsStorage().states[erc20][accountId];
    426
                uint32[2 ** 32] storage nextSqueezed = state.nextSqueezed[senderId];
    427
                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
                    uint256 revIdx = squeezedRevIdxs[squeezedNum - i - 1];
    431
                    squeezedHistoryHashes[i] = historyHashes[historyHashes.length - revIdx];
    432
                    nextSqueezed[currCycleConfigs - revIdx] = _currTimestamp();
    433
                }
    434
                uint32 cycleStart = _currCycleStart();
    435
                _addDeltaRange(
    436
                    state, cycleStart, cycleStart + 1, -int160(amt * _AMT_PER_SEC_MULTIPLIER)
    437
                );
    438
                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
                uint128 amt,
    476
                uint256 squeezedNum,
    477
                uint256[] memory squeezedRevIdxs,
    478
                bytes32[] memory historyHashes,
    479
                uint256 currCycleConfigs
    480
            )
    481
        {
    482
            {
    483
                StreamsState storage sender = _streamsStorage().states[erc20][senderId];
    484
                historyHashes =
    485
                    _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
                currCycleConfigs = 1;
    489
                // slither-disable-next-line timestamp
    490
                if (sender.updateTime >= _currCycleStart()) {
    491
                    currCycleConfigs = sender.lastUpdatedCycleConfigs;
    492
                }
    493
            }
    494
            squeezedRevIdxs = new uint256[](streamsHistory.length);
    495
            uint32[2 ** 32] storage nextSqueezed =
    496
                _streamsStorage().states[erc20][accountId].nextSqueezed[senderId];
    497
            uint32 squeezeEndCap = _currTimestamp();
    498
            unchecked {
    499
                for (uint256 i = 1; i <= streamsHistory.length && i <= currCycleConfigs; i++) {
    500
                    StreamsHistory memory historyEntry = streamsHistory[streamsHistory.length - i];
    501
                    if (historyEntry.receivers.length != 0) {
    502
                        uint32 squeezeStartCap = nextSqueezed[currCycleConfigs - i];
    503
                        if (squeezeStartCap < _currCycleStart()) squeezeStartCap = _currCycleStart();
    504
                        if (squeezeStartCap < historyEntry.updateTime) {
    505
                            squeezeStartCap = historyEntry.updateTime;
    506
                        }
    507
                        if (squeezeStartCap < squeezeEndCap) {
    508
                            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
                            amt += _squeezedAmt(accountId, historyEntry, squeezeStartCap, squeezeEndCap);
    512
                        }
    513
                    }
    514
                    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
        ) private pure returns (bytes32[] memory historyHashes) {
    533
            historyHashes = new bytes32[](streamsHistory.length);
    534
            for (uint256 i = 0; i < streamsHistory.length; i++) {
    535
                StreamsHistory memory historyEntry = streamsHistory[i];
    536
                bytes32 streamsHash = historyEntry.streamsHash;
    537
                if (historyEntry.receivers.length != 0) {
    538
                    require(streamsHash == 0, "Entry with hash and receivers");
    539
                    streamsHash = _hashStreams(historyEntry.receivers);
    540
                }
    541
                historyHashes[i] = historyHash;
    542
                historyHash = _hashStreamsHistory(
    543
                    historyHash, streamsHash, historyEntry.updateTime, historyEntry.maxEnd
    544
                );
    545
            }
    546
            // slither-disable-next-line incorrect-equality,timestamp
    547
            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
        ) private view returns (uint128 squeezedAmt) {
    562
            unchecked {
    563
                StreamReceiver[] memory receivers = historyEntry.receivers;
    564
                // Binary search for the `idx` of the first occurrence of `accountId`
    565
                uint256 idx = 0;
    566
                for (uint256 idxCap = receivers.length; idx < idxCap;) {
    567
                    uint256 idxMid = (idx + idxCap) / 2;
    568
                    if (receivers[idxMid].accountId < accountId) {
    569
                        idx = idxMid + 1;
    570
                    } else {
    571
                        idxCap = idxMid;
    572
                    }
    573
                }
    574
                uint32 updateTime = historyEntry.updateTime;
    575
                uint32 maxEnd = historyEntry.maxEnd;
    576
                uint256 amt = 0;
    577
                for (; idx < receivers.length; idx++) {
    578
                    StreamReceiver memory receiver = receivers[idx];
    579
                    if (receiver.accountId != accountId) break;
    580
                    (uint32 start, uint32 end) =
    581
                        _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
                    amt += _streamedAmt(receiver.config.amtPerSec(), start, end);
    585
                }
    586
                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
                bytes32 streamsHash,
    603
                bytes32 streamsHistoryHash,
    604
                uint32 updateTime,
    605
                uint128 balance,
    606
                uint32 maxEnd
    607
            )
    608
        {
    609
            StreamsState storage state = _streamsStorage().states[erc20][accountId];
    610
            return (
    611
                state.streamsHash,
    612
                state.streamsHistoryHash,
    613
                state.updateTime,
    614
                state.balance,
    615
                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
        ) internal view returns (uint128 balance) {
    635
            StreamsState storage state = _streamsStorage().states[erc20][accountId];
    636
            require(timestamp >= state.updateTime, "Timestamp before the last update");
    637
            _verifyStreamsReceivers(currReceivers, state);
    638
            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
        ) private view returns (uint128 balance) {
    658
            unchecked {
    659
                balance = lastBalance;
    660
                for (uint256 i = 0; i < receivers.length; i++) {
    661
                    StreamReceiver memory receiver = receivers[i];
    662
                    (uint32 start, uint32 end) = _streamRange({
    663
                        receiver: receiver,
    664
                        updateTime: lastUpdate,
    665
                        maxEnd: maxEnd,
    666
                        startCap: lastUpdate,
    667
                        endCap: timestamp
    668
                    });
    669
                    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
        ) internal returns (int128 realBalanceDelta) {
    725
            unchecked {
    726
                StreamsState storage state = _streamsStorage().states[erc20][accountId];
    727
                _verifyStreamsReceivers(currReceivers, state);
    728
                uint32 lastUpdate = state.updateTime;
    729
                uint128 newBalance;
    730
                uint32 newMaxEnd;
    731
                {
    732
                    uint32 currMaxEnd = state.maxEnd;
    733
                    int128 currBalance = int128(
    734
                        _calcBalance(
    735
                            state.balance, lastUpdate, currMaxEnd, currReceivers, _currTimestamp()
    736
                        )
    737
                    );
    738
                    realBalanceDelta = balanceDelta;
    739
                    // Cap `realBalanceDelta` at withdrawal of the entire `currBalance`
    740
                    if (realBalanceDelta < -currBalance) {
    741
                        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
                    newBalance = uint128(currBalance + realBalanceDelta);
    746
                    newMaxEnd = _calcMaxEnd(newBalance, newReceivers, maxEndHint1, maxEndHint2);
    747
                    _updateReceiverStates(
    748
                        _streamsStorage().states[erc20],
    749
                        currReceivers,
    750
                        lastUpdate,
    751
                        currMaxEnd,
    752
                        newReceivers,
    753
                        newMaxEnd
    754
                    );
    755
                }
    756
                state.updateTime = _currTimestamp();
    757
                state.maxEnd = newMaxEnd;
    758
                state.balance = newBalance;
    759
                bytes32 streamsHistory = state.streamsHistoryHash;
    760
                // slither-disable-next-line timestamp
    761
                if (streamsHistory != 0 && _cycleOf(lastUpdate) != _cycleOf(_currTimestamp())) {
    762
                    state.lastUpdatedCycleConfigs = 2;
    763
                } else {
    764
                    state.lastUpdatedCycleConfigs++;
    765
                }
    766
                bytes32 newStreamsHash = _hashStreams(newReceivers);
    767
                state.streamsHistoryHash =
    768
                    _hashStreamsHistory(streamsHistory, newStreamsHash, _currTimestamp(), newMaxEnd);
    769
                emit StreamsSet(accountId, erc20, newStreamsHash, streamsHistory, newBalance, newMaxEnd);
    770
                // slither-disable-next-line timestamp
    771
                if (newStreamsHash != state.streamsHash) {
    772
                    state.streamsHash = newStreamsHash;
    773
                    for (uint256 i = 0; i < newReceivers.length; i++) {
    774
                        StreamReceiver memory receiver = newReceivers[i];
    775
                        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
            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
        ) private view returns (uint32 maxEnd) {
    807
            (uint256[] memory configs, uint256 configsLen) = _buildConfigs(receivers);
    808
    809
            uint256 enoughEnd = _currTimestamp();
    810
            // slither-disable-start incorrect-equality,timestamp
    811
            if (configsLen == 0 || balance == 0) {
    812
                return uint32(enoughEnd);
    813
            }
    814
    815
            uint256 notEnoughEnd = type(uint32).max;
    816
            if (_isBalanceEnough(balance, configs, configsLen, notEnoughEnd)) {
    817
                return uint32(notEnoughEnd);
    818
            }
    819
    820
            if (hint1 > enoughEnd && hint1 < notEnoughEnd) {
    821
                if (_isBalanceEnough(balance, configs, configsLen, hint1)) {
    822
                    enoughEnd = hint1;
    823
                } else {
    824
                    notEnoughEnd = hint1;
    825
                }
    826
            }
    827
    828
            if (hint2 > enoughEnd && hint2 < notEnoughEnd) {
    829
                if (_isBalanceEnough(balance, configs, configsLen, hint2)) {
    830
                    enoughEnd = hint2;
    831
                } else {
    832
                    notEnoughEnd = hint2;
    833
                }
    834
            }
    835
    836
            while (true) {
    837
                uint256 end;
    838
                unchecked {
    839
                    end = (enoughEnd + notEnoughEnd) / 2;
    840
                }
    841
                if (end == enoughEnd) {
    842
                    return uint32(end);
    843
                }
    844
                if (_isBalanceEnough(balance, configs, configsLen, end)) {
    845
                    enoughEnd = end;
    846
                } else {
    847
                    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
        ) private view returns (bool isEnough) {
    865
            unchecked {
    866
                uint256 spent = 0;
    867
                for (uint256 i = 0; i < configsLen; i++) {
    868
                    (uint256 amtPerSec, uint256 start, uint256 end) = _getConfig(configs, i);
    869
                    // slither-disable-next-line timestamp
    870
                    if (maxEnd <= start) {
    871
                        continue;
    872
                    }
    873
                    // slither-disable-next-line timestamp
    874
                    if (end > maxEnd) {
    875
                        end = maxEnd;
    876
                    }
    877
                    spent += _streamedAmt(amtPerSec, start, end);
    878
                    if (spent > balance) {
    879
                        return false;
    880
                    }
    881
                }
    882
                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
            returns (uint256[] memory configs, uint256 configsLen)
    896
        {
    897
            unchecked {
    898
                require(receivers.length <= _MAX_STREAMS_RECEIVERS, "Too many streams receivers");
    899
                configs = new uint256[](receivers.length);
    900
                for (uint256 i = 0; i < receivers.length; i++) {
    901
                    StreamReceiver memory receiver = receivers[i];
    902
                    if (i > 0) {
    903
                        require(_isOrdered(receivers[i - 1], receiver), "Streams receivers not sorted");
    904
                    }
    905
                    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
        ) private view returns (uint256 newConfigsLen) {
    920
            uint160 amtPerSec = receiver.config.amtPerSec();
    921
            require(amtPerSec >= _minAmtPerSec, "Stream receiver amtPerSec too low");
    922
            (uint32 start, uint32 end) =
    923
                _streamRangeInFuture(receiver, _currTimestamp(), type(uint32).max);
    924
            // slither-disable-next-line incorrect-equality,timestamp
    925
            if (start == end) {
    926
                return configsLen;
    927
            }
    928
            // By assignment we get `config` value:
    929
            // `zeros (96 bits) | amtPerSec (160 bits)`
    930
            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
            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
            config = (config << 32) | end;
    941
            configs[configsLen] = config;
    942
            unchecked {
    943
                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
            returns (uint256 amtPerSec, uint256 start, uint256 end)
    957
        {
    958
            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
                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
            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
            start = uint32(config >> 32);
    973
            // By casting down we get value:
    974
            // `end (32 bits)`
    975
            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
            returns (bytes32 streamsHash)
    987
        {
    988
            if (receivers.length == 0) {
    989
                return bytes32(0);
    990
            }
    991
            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
        ) internal pure returns (bytes32 streamsHistoryHash) {
    1009
            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
            uint256 currIdx = 0;
    1036
            uint256 newIdx = 0;
    1037
            while (true) {
    1038
                bool pickCurr = currIdx < currReceivers.length;
    1039
                // slither-disable-next-line uninitialized-local
    1040
                StreamReceiver memory currRecv;
    1041
                if (pickCurr) {
    1042
                    currRecv = currReceivers[currIdx];
    1043
                }
    1044
    1045
                bool pickNew = newIdx < newReceivers.length;
    1046
                // slither-disable-next-line uninitialized-local
    1047
                StreamReceiver memory newRecv;
    1048
                if (pickNew) {
    1049
                    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
                if (pickCurr && pickNew) {
    1055
                    if (
    1056
                        currRecv.accountId != newRecv.accountId
    1057
                            || currRecv.config.amtPerSec() != newRecv.config.amtPerSec()
    1058
                    ) {
    1059
                        pickCurr = _isOrdered(currRecv, newRecv);
    1060
                        pickNew = !pickCurr;
    1061
                    }
    1062
                }
    1063
    1064
                if (pickCurr && pickNew) {
    1065
                    // Shift the existing stream to fulfil the new configuration
    1066
                    StreamsState storage state = states[currRecv.accountId];
    1067
                    (uint32 currStart, uint32 currEnd) =
    1068
                        _streamRangeInFuture(currRecv, lastUpdate, currMaxEnd);
    1069
                    (uint32 newStart, uint32 newEnd) =
    1070
                        _streamRangeInFuture(newRecv, _currTimestamp(), newMaxEnd);
    1071
                    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
                    _addDeltaRange(state, currStart, newStart, -amtPerSec);
    1077
                    _addDeltaRange(state, currEnd, newEnd, amtPerSec);
    1078
                    // Ensure that the account receives the updated cycles
    1079
                    uint32 currStartCycle = _cycleOf(currStart);
    1080
                    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
                    if (currStartCycle > newStartCycle && state.nextReceivableCycle > newStartCycle) {
    1086
                        state.nextReceivableCycle = newStartCycle;
    1087
                    }
    1088
                } else if (pickCurr) {
    1089
                    // Remove an existing stream
    1090
                    // slither-disable-next-line similar-names
    1091
                    StreamsState storage state = states[currRecv.accountId];
    1092
                    (uint32 start, uint32 end) = _streamRangeInFuture(currRecv, lastUpdate, currMaxEnd);
    1093
                    // slither-disable-next-line similar-names
    1094
                    int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec()));
    1095
                    _addDeltaRange(state, start, end, -amtPerSec);
    1096
                } else if (pickNew) {
    1097
                    // Create a new stream
    1098
                    StreamsState storage state = states[newRecv.accountId];
    1099
                    // slither-disable-next-line uninitialized-local
    1100
                    (uint32 start, uint32 end) =
    1101
                        _streamRangeInFuture(newRecv, _currTimestamp(), newMaxEnd);
    1102
                    int256 amtPerSec = int256(uint256(newRecv.config.amtPerSec()));
    1103
                    _addDeltaRange(state, start, end, amtPerSec);
    1104
                    // Ensure that the account receives the updated cycles
    1105
                    uint32 startCycle = _cycleOf(start);
    1106
                    // slither-disable-next-line timestamp
    1107
                    uint32 nextReceivableCycle = state.nextReceivableCycle;
    1108
                    if (nextReceivableCycle == 0 || nextReceivableCycle > startCycle) {
    1109
                        state.nextReceivableCycle = startCycle;
    1110
                    }
    1111
                } else {
    1112
                    break;
    1113
                }
    1114
    1115
                unchecked {
    1116
                    if (pickCurr) {
    1117
                        currIdx++;
    1118
                    }
    1119
                    if (pickNew) {
    1120
                        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
            returns (uint32 start, uint32 end)
    1133
        {
    1134
            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
        ) private pure returns (uint32 start, uint32 end_) {
    1151
            start = receiver.config.start();
    1152
            // slither-disable-start timestamp
    1153
            if (start == 0) {
    1154
                start = updateTime;
    1155
            }
    1156
            uint40 end;
    1157
            unchecked {
    1158
                end = uint40(start) + receiver.config.duration();
    1159
            }
    1160
            // slither-disable-next-line incorrect-equality
    1161
            if (end == start || end > maxEnd) {
    1162
                end = maxEnd;
    1163
            }
    1164
            if (start < startCap) {
    1165
                start = startCap;
    1166
            }
    1167
            if (end > endCap) {
    1168
                end = endCap;
    1169
            }
    1170
            if (end < start) {
    1171
                end = start;
    1172
            }
    1173
            // slither-disable-end timestamp
    1174
            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
            if (start == end) {
    1187
                return;
    1188
            }
    1189
            mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas;
    1190
            _addDelta(amtDeltas, start, amtPerSec);
    1191
            _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
                int256 amtPerSecMultiplier = int160(_AMT_PER_SEC_MULTIPLIER);
    1207
                int256 fullCycle = (int256(uint256(_cycleSecs)) * amtPerSec) / amtPerSecMultiplier;
    1208
                // slither-disable-next-line weak-prng
    1209
                int256 nextCycle = (int256(timestamp % _cycleSecs) * amtPerSec) / amtPerSecMultiplier;
    1210
                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
                amtDelta.thisCycle += int128(fullCycle - nextCycle);
    1216
                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
            returns (bool)
    1227
        {
    1228
            if (prev.accountId != next.accountId) {
    1229
                return prev.accountId < next.accountId;
    1230
            }
    1231
            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
            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
            uint256 cycleSecs = _cycleSecs;
    1265
            // slither-disable-next-line assembly
    1266
            assembly {
    1267
                let endedCycles := sub(div(end, cycleSecs), div(start, cycleSecs))
    1268
                // slither-disable-next-line divide-before-multiply
    1269
                let amtPerCycle := div(mul(cycleSecs, amtPerSec), _AMT_PER_SEC_MULTIPLIER)
    1270
                amt := mul(endedCycles, amtPerCycle)
    1271
                // slither-disable-next-line weak-prng
    1272
                let amtEnd := div(mul(mod(end, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER)
    1273
                amt := add(amt, amtEnd)
    1274
                // slither-disable-next-line weak-prng
    1275
                let amtStart := div(mul(mod(start, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER)
    1276
                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
        function _cycleOf(uint32 timestamp) private view returns (uint32 cycle) {
    1284
            unchecked {
    1285
                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
        function _currTimestamp() private view returns (uint32 timestamp) {
    1292
            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
        function _currCycleStart() private view returns (uint32 timestamp) {
    1298
            unchecked {
    1299
                uint32 currTimestamp = _currTimestamp();
    1300
                // slither-disable-next-line weak-prng
    1301
                return currTimestamp - (currTimestamp % _cycleSecs);
    1302
            }
    1303
        }
    1304
    1305
        /// @notice Returns the Streams storage.
    1306
        /// @return streamsStorage The storage.
    1307
        function _streamsStorage() private view returns (StreamsStorage storage streamsStorage) {
    1308
            bytes32 slot = _streamsStorageSlot;
    1309
            // slither-disable-next-line assembly
    1310
            assembly {
    1311
                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.0% src/echidna/EchidnaBasicHelpers.sol
    Lines covered: 48 / 49 (97.0%)
    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
            address from = getAccount(fromAccId);
    22
            address to = getAccount(toAccId);
    23
    24
            uint256 toDripsAccId = getDripsAccountId(to);
    25
    26
            hevm.prank(from);
    27
            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
            address from = getAccount(fromAccId);
    42
    43
            uint128 min = 1000;
    44
            uint128 max = uint128(token.balanceOf(from));
    45
            uint128 clampedAmount = min + (amount % (max - min + 1));
    46
    47
            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
            returns (uint128)
    61
        {
    62
            address target = getAccount(targetAccId);
    63
            uint256 targetDripsAccId = getDripsAccountId(target);
    64
    65
            uint128 receivedAmt = drips.receiveStreams(
    66
                targetDripsAccId,
    67
                token,
    68
                maxCycles
    69
            );
    70
    71
            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
            returns (uint128)
    83
        {
    84
            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
        function split(uint8 targetAccId) public returns (uint128, uint128) {
    95
            address target = getAccount(targetAccId);
    96
            uint256 targetDripsAccId = getDripsAccountId(target);
    97
    98
            (uint128 collectableAmt, uint128 splitAmt) = drips.split(
    99
                targetDripsAccId,
    100
                token,
    101
                getSplitsReceivers(target)
    102
            );
    103
    104
            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
        function collect(uint8 fromAccId, uint8 toAccId) public returns (uint128) {
    116
            address from = getAccount(fromAccId);
    117
            address to = getAccount(toAccId);
    118
    119
            hevm.prank(from);
    120
            uint128 collected = driver.collect(token, to);
    121
    122
            return collected;
    123
        }
    124
    125
        /**
    126
         * @notice Collect funds to self
    127
         * @param targetAccId Target account
    128
         */
    129
        function collectToSelf(uint8 targetAccId) public {
    130
            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
            split(targetAccId);
    140
            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
            receiveStreamsAllCycles(targetAccId);
    150
            splitAndCollectToSelf(targetAccId);
    151
        }
    152
    }
    153
    94.0% src/echidna/EchidnaBasicTests.sol
    Lines covered: 65 / 69 (94.0%)
    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
            address from = getAccount(fromAccId);
    23
            address to = getAccount(toAccId);
    24
    25
            uint256 toDripsAccId = getDripsAccountId(to);
    26
    27
            require(amount <= token.balanceOf(from));
    28
    29
            hevm.prank(from);
    30
            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
            address target = getAccount(targetAccId);
    42
            uint256 targetDripsAccId = getDripsAccountId(target);
    43
    44
            uint128 splittableBefore = drips.splittable(targetDripsAccId, token);
    45
            uint128 receivedAmt = receiveStreams(targetAccId, maxCycles);
    46
            uint128 splittableAfter = drips.splittable(targetDripsAccId, token);
    47
    48
            assert(splittableAfter == splittableBefore + receivedAmt);
    49
    50
            if (receivedAmt > 0) {
    51
                assert(splittableAfter > splittableBefore);
    52
            } else {
    53
                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
            address target = getAccount(targetAccId);
    63
            uint256 targetDripsAccId = getDripsAccountId(target);
    64
    65
            try
    66
                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
            address target = getAccount(targetAccId);
    83
            uint256 targetDripsAccId = getDripsAccountId(target);
    84
    85
            require(maxCycles > 0);
    86
    87
            uint128 receivable = drips.receiveStreamsResult(
    88
                targetDripsAccId,
    89
                token,
    90
                maxCycles
    91
            );
    92
            uint32 receivableCycles = drips.receivableStreamsCycles(
    93
                targetDripsAccId,
    94
                token
    95
            );
    96
    97
            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
            address target = getAccount(targetAccId);
    112
            uint256 targetDripsAccId = getDripsAccountId(target);
    113
    114
            uint128 receivable = drips.receiveStreamsResult(
    115
                targetDripsAccId,
    116
                token,
    117
                maxCycles
    118
            );
    119
    120
            uint128 received = drips.receiveStreams(
    121
                targetDripsAccId,
    122
                token,
    123
                maxCycles
    124
            );
    125
    126
            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
            address from = getAccount(fromAccId);
    136
            address to = getAccount(toAccId);
    137
    138
            uint256 fromDripsAccId = getDripsAccountId(from);
    139
    140
            uint128 colBalBefore = drips.collectable(fromDripsAccId, token);
    141
            uint256 tokenBalBefore = token.balanceOf(to);
    142
    143
            uint128 collected = collect(fromAccId, toAccId);
    144
    145
            uint128 colBalAfter = drips.collectable(fromDripsAccId, token);
    146
            uint256 tokenBalAfter = token.balanceOf(to);
    147
    148
            assert(colBalAfter == colBalBefore - collected);
    149
            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
            address from = getAccount(fromAccId);
    159
            address to = getAccount(toAccId);
    160
    161
            hevm.prank(from);
    162
            try driver.collect(token, to) {} catch {
    163
                assert(false);
    164
            }
    165
        }
    166
    }
    167
    95.0% src/echidna/EchidnaInvariantTests.sol
    Lines covered: 68 / 71 (95.0%)
    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
            require(amount > 0, "withdraw amount must be > 0");
    24
    25
            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
            address target = getAccount(targetAccId);
    39
    40
            StreamReceiver[] memory receivers = getStreamReceivers(target);
    41
            require(receivers.length > 0, "no receivers");
    42
    43
            index = index % receivers.length;
    44
            uint160 amtPerSec = receivers[index].config.amtPerSec();
    45
    46
            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
            uint256 tokenBalance = token.balanceOf(address(drips));
    55
            uint256 dripsBalancesTotal = getDripsBalancesTotalForAllUsers();
    56
    57
            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
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    68
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    69
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    70
            removeAllSplits(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    71
    72
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    73
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    74
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    75
            squeezeAllSenders(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    76
    77
            receiveStreamsSplitAndCollectToSelf(
    78
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]
    79
            );
    80
            receiveStreamsSplitAndCollectToSelf(
    81
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]
    82
            );
    83
            receiveStreamsSplitAndCollectToSelf(
    84
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]
    85
            );
    86
            receiveStreamsSplitAndCollectToSelf(
    87
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]
    88
            );
    89
    90
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    91
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    92
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    93
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    94
    95
            uint256 dripsBalance = token.balanceOf(address(drips));
    96
            uint256 user0Balance = token.balanceOf(ADDRESS_USER0);
    97
            uint256 user1Balance = token.balanceOf(ADDRESS_USER1);
    98
            uint256 user2Balance = token.balanceOf(ADDRESS_USER2);
    99
            uint256 user3Balance = token.balanceOf(ADDRESS_USER3);
    100
    101
            uint256 totalUserBalance = user0Balance +
    102
                user1Balance +
    103
                user2Balance +
    104
                user3Balance;
    105
    106
            assert(dripsBalance == 0);
    107
            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
                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
            address target = getAccount(targetAccId);
    127
            uint256 targetDripsAccId = getDripsAccountId(target);
    128
    129
            uint32 maxEnd = getMaxEndForAllUsers();
    130
    131
            uint32 firstCycle = getCycleFromTimestamp(STARTING_TIMESTAMP);
    132
            uint32 lastCycle = getCycleFromTimestamp(maxEnd);
    133
    134
            require(maxEnd > 0, "no cycles");
    135
            require(firstCycle != lastCycle, "only one cycle");
    136
    137
            // limit amount of cycles for gas & memory savings
    138
            require(lastCycle - firstCycle < 1000, "too many cycles");
    139
    140
            int256 sumAmtDelta = 0;
    141
    142
            for (uint32 cycle = firstCycle; cycle <= lastCycle; cycle++) {
    143
                (int128 thisCycle, int128 nextCycle) = drips.getAmtDeltaForCycle(
    144
                    targetDripsAccId,
    145
                    token,
    146
                    cycle
    147
                );
    148
    149
                sumAmtDelta += int256(thisCycle);
    150
                sumAmtDelta += int256(nextCycle);
    151
            }
    152
    153
            assert(sumAmtDelta == 0);
    154
        }
    155
    }
    156
    98.0% src/echidna/EchidnaSplitsHelpers.sol
    Lines covered: 54 / 55 (98.0%)
    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
            address sender = getAccount(senderAccId);
    21
    22
            SplitsReceiver[] memory newReceivers = bubbleSortSplitsReceivers(
    23
                unsortedReceivers
    24
            );
    25
    26
            updateSplitsReceivers(sender, newReceivers);
    27
    28
            hevm.prank(sender);
    29
            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
            address sender = getAccount(senderAccId);
    44
            address receiver = getAccount(receiverAccId);
    45
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    46
    47
            SplitsReceiver[] memory receivers = new SplitsReceiver[](1);
    48
            receivers[0] = SplitsReceiver({
    49
                accountId: receiverDripsAccId,
    50
                weight: weight
    51
            });
    52
            updateSplitsReceivers(sender, receivers);
    53
    54
            _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
            weight = clampSplitWeight(weight, 0); // there are no existing weights
    71
            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
            address sender = getAccount(senderAccId);
    86
            address receiver = getAccount(receiverAccId);
    87
            uint256 senderDripsAccId = getDripsAccountId(sender);
    88
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    89
    90
            SplitsReceiver[] memory oldReceivers = getSplitsReceivers(sender);
    91
    92
            SplitsReceiver memory addedReceiver = SplitsReceiver({
    93
                accountId: receiverDripsAccId,
    94
                weight: weight
    95
            });
    96
    97
            SplitsReceiver[] memory newReceivers = new SplitsReceiver[](
    98
                oldReceivers.length + 1
    99
            );
    100
            for (uint256 i = 0; i < oldReceivers.length; i++) {
    101
                newReceivers[i] = oldReceivers[i];
    102
            }
    103
            newReceivers[newReceivers.length - 1] = addedReceiver;
    104
    105
            _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
            address sender = getAccount(senderAccId);
    122
    123
            // sum all the existing weights
    124
            uint32 existingWeights;
    125
            SplitsReceiver[] memory receivers = getSplitsReceivers(sender);
    126
            for (uint256 i = 0; i < receivers.length; i++) {
    127
                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
            if (existingWeights >= drips.TOTAL_SPLITS_WEIGHT()) return;
    133
    134
            weight = clampSplitWeight(weight, existingWeights);
    135
    136
            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
            SplitsReceiver[] memory receivers = new SplitsReceiver[](0);
    145
            _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
            returns (uint32)
    158
        {
    159
            return (weight % (drips.TOTAL_SPLITS_WEIGHT() - existingWeights)) + 1;
    160
        }
    161
    }
    162
    96.0% src/echidna/EchidnaSplitsTests.sol
    Lines covered: 99 / 103 (96.0%)
    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
            address target = getAccount(targetAccId);
    22
            uint256 targetDripsAccId = getDripsAccountId(target);
    23
    24
            uint128 splittableBefore = drips.splittable(targetDripsAccId, token);
    25
    26
            // check if we are splitting to ourselves
    27
            uint32 splitToSelfWeight;
    28
            SplitsReceiver[] memory receivers = getSplitsReceivers(target);
    29
            for (uint256 i = 0; i < receivers.length; i++) {
    30
                if (receivers[i].accountId == targetDripsAccId) {
    31
                    splitToSelfWeight += receivers[i].weight;
    32
                }
    33
            }
    34
    35
            // calculate amount to split to self
    36
            uint128 splitToSelfAmount = uint128(
    37
                (splittableBefore * splitToSelfWeight) / drips.TOTAL_SPLITS_WEIGHT()
    38
            );
    39
    40
            (uint128 collectableAmt, uint128 splitAmt) = split(targetAccId);
    41
    42
            uint128 splittableAfter = drips.splittable(targetDripsAccId, token);
    43
    44
            // sanity check
    45
            assert((splitAmt + collectableAmt) <= splittableBefore);
    46
    47
            if (splitToSelfWeight == 0) {
    48
                // if we're not splitting to ourselves, things are simple
    49
                assert(
    50
                    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
                uint128 expectedSplittableAfter = splittableBefore -
    58
                    splitAmt -
    59
                    collectableAmt +
    60
                    splitToSelfAmount;
    61
    62
                // calculate difference between expected and actual
    63
                int256 difference = int256(uint256(splittableAfter)) -
    64
                    int256(uint256(expectedSplittableAfter));
    65
    66
                // check if difference is within tolerance
    67
                assert(
    68
                    difference >= -int256(SPLIT_ROUNDING_TOLERANCE) &&
    69
                        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
            address target = getAccount(targetAccId);
    80
            uint256 targetDripsAccId = getDripsAccountId(target);
    81
    82
            uint128 colBalBefore = drips.collectable(targetDripsAccId, token);
    83
            (uint128 collectableAmt, ) = split(targetAccId);
    84
            uint128 colBalAfter = drips.collectable(targetDripsAccId, token);
    85
    86
            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
            address target = getAccount(targetAccId);
    96
            uint256 targetDripsAccId = getDripsAccountId(target);
    97
    98
            uint128 amountToBeSplit = drips.splittable(targetDripsAccId, token);
    99
            SplitsReceiver[] memory receivers = getSplitsReceivers(target);
    100
    101
            // storage for all receivers
    102
            uint128[] memory splittableBefore = new uint128[](receivers.length);
    103
            uint128[] memory splittableAfter = new uint128[](receivers.length);
    104
            uint32[] memory weights = new uint32[](receivers.length);
    105
            uint128[] memory amounts = new uint128[](receivers.length);
    106
    107
            for (uint256 i = 0; i < receivers.length; i++) {
    108
                // store splittable before
    109
                splittableBefore[i] = drips.splittable(
    110
                    receivers[i].accountId,
    111
                    token
    112
                );
    113
    114
                // calculate amount the receiver should get
    115
                weights[i] = receivers[i].weight;
    116
                amounts[i] = uint128(
    117
                    (amountToBeSplit * weights[i]) / drips.TOTAL_SPLITS_WEIGHT()
    118
                );
    119
            }
    120
    121
            // split
    122
            (uint128 collectableAmt, uint128 splitAmt) = split(targetAccId);
    123
    124
            // store splittable after
    125
            for (uint256 i = 0; i < receivers.length; i++) {
    126
                splittableAfter[i] = drips.splittable(
    127
                    receivers[i].accountId,
    128
                    token
    129
                );
    130
            }
    131
    132
            for (uint256 i = 0; i < receivers.length; i++) {
    133
                // calculate expected amount after the split
    134
                uint128 expectedAfter;
    135
                if (receivers[i].accountId != targetDripsAccId) {
    136
                    // splitting so someone else is trivial
    137
                    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
                        splittableBefore[i] -
    145
                        amountToBeSplit +
    146
                        amounts[i];
    147
                }
    148
    149
                // calculate difference between expected and actual
    150
                int256 difference = int256(uint256(splittableAfter[i])) -
    151
                    int256(uint256(expectedAfter));
    152
    153
                // check if difference is within tolerance
    154
                assert(
    155
                    difference >= -int256(SPLIT_ROUNDING_TOLERANCE) &&
    156
                        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
            address target = getAccount(targetAccId);
    167
            uint256 targetDripsAccId = getDripsAccountId(target);
    168
    169
            uint128 splittable = drips.splittable(targetDripsAccId, token);
    170
    171
            (uint128 collectableAmtView, uint128 splitAmtView) = drips.splitResult(
    172
                targetDripsAccId,
    173
                getSplitsReceivers(target),
    174
                splittable
    175
            );
    176
    177
            (uint128 collectableAmtActual, uint128 splitAmtActual) = split(
    178
                targetAccId
    179
            );
    180
    181
            assert(collectableAmtView == collectableAmtActual);
    182
            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
            address target = getAccount(targetAccId);
    191
            uint256 targetDripsAccId = getDripsAccountId(target);
    192
    193
            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
                EchidnaSplitsHelpers(address(this)).setSplitsWithClamping(
    211
                    senderAccId,
    212
                    receiverAccId,
    213
                    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
                EchidnaSplitsHelpers(address(this)).addSplitsReceiverWithClamping(
    233
                    senderAccId,
    234
                    receiverAccId,
    235
                    weight
    236
                )
    237
            {} catch (bytes memory reason) {
    238
                bytes4 errorSelector = bytes4(reason);
    239
                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.0% src/echidna/EchidnaSqueezeHelpers.sol
    Lines covered: 75 / 76 (98.0%)
    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
        ) internal returns (uint128) {
    25
            address receiver = getAccount(receiverAccId);
    26
            address sender = getAccount(senderAccId);
    27
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    28
            uint256 senderDripsAccId = getDripsAccountId(sender);
    29
    30
            uint128 amount = drips.squeezeStreams(
    31
                receiverDripsAccId,
    32
                token,
    33
                senderDripsAccId,
    34
                historyHash,
    35
                history
    36
            );
    37
    38
            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
            returns (uint128)
    50
        {
    51
            return
    52
                _squeeze(
    53
                    receiverAccId,
    54
                    senderAccId,
    55
                    bytes32(0),
    56
                    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
        ) public returns (uint128) {
    77
            address receiver = getAccount(receiverAccId);
    78
            address sender = getAccount(senderAccId);
    79
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    80
            uint256 senderDripsAccId = getDripsAccountId(sender);
    81
    82
            (
    83
                bytes32 historyHash,
    84
                StreamsHistory[] memory history
    85
            ) = getFuzzedStreamsHistory(
    86
                    senderAccId,
    87
                    hashIndex,
    88
                    receiversRandomSeed
    89
                );
    90
    91
            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
            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
            squeezeWithDefaultHistory(
    109
                targetAccId,
    110
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]
    111
            );
    112
            squeezeWithDefaultHistory(
    113
                targetAccId,
    114
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]
    115
            );
    116
            squeezeWithDefaultHistory(
    117
                targetAccId,
    118
                ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]
    119
            );
    120
            squeezeWithDefaultHistory(
    121
                targetAccId,
    122
                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
            squeezeAllSenders(targetAccId);
    135
            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
        ) internal returns (bytes32, StreamsHistory[] memory) {
    151
            address target = getAccount(targetAccId);
    152
    153
            // get the history structs and hashes
    154
            StreamsHistory[] memory historyStructs = getStreamsHistory(target);
    155
            bytes32[] memory historyHashes = getStreamsHistoryHashes(target);
    156
    157
            // having a hashed history requires at least 2 history entries
    158
            require(historyStructs.length >= 2, "need at least 2 history entries");
    159
    160
            // hashIndex must be within bounds and cant be the last entry
    161
            hashIndex = hashIndex % (historyHashes.length - 1);
    162
    163
            // get the history hash at the index
    164
            bytes32 historyHash = historyHashes[hashIndex];
    165
    166
            // create a history array with all entries after the hashIndex
    167
            StreamsHistory[] memory history = new StreamsHistory[](
    168
                historyStructs.length - 1 - hashIndex
    169
            );
    170
            for (uint256 i = hashIndex + 1; i < historyStructs.length; i++) {
    171
                history[i - hashIndex - 1] = historyStructs[i];
    172
            }
    173
    174
            // hash receivers based on 'receiversRandomSeed'
    175
            for (uint256 i = 0; i < history.length; i++) {
    176
                receiversRandomSeed = keccak256(bytes.concat(receiversRandomSeed));
    177
                bool hashBool = (uint256(receiversRandomSeed) % 2) == 0
    178
                    ? false
    179
                    : true;
    180
    181
                if (hashBool) {
    182
                    history[i].streamsHash = drips.hashStreams(
    183
                        history[i].receivers
    184
                    );
    185
                    history[i].receivers = new StreamReceiver[](0);
    186
                }
    187
            }
    188
    189
            return (historyHash, history);
    190
        }
    191
    }
    192
    97.0% src/echidna/EchidnaSqueezeTests.sol
    Lines covered: 110 / 113 (97.0%)
    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
            address receiver = getAccount(receiverAccId);
    25
            address sender = getAccount(senderAccId);
    26
    27
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    28
    29
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    30
            uint128 splittableBefore = drips.splittable(receiverDripsAccId, token);
    31
    32
            uint128 squeezedAmt = squeezeWithDefaultHistory(
    33
                receiverAccId,
    34
                senderAccId
    35
            );
    36
    37
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    38
            uint128 splittableAfter = drips.splittable(receiverDripsAccId, token);
    39
    40
            assert(squeezableAfter == squeezableBefore - squeezedAmt);
    41
            assert(splittableAfter == splittableBefore + squeezedAmt);
    42
    43
            if (squeezedAmt > 0) {
    44
                assert(squeezableAfter < squeezableBefore);
    45
                assert(splittableAfter > splittableBefore);
    46
            } else {
    47
                assert(squeezableAfter == squeezableBefore);
    48
                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
            address receiver = getAccount(receiverAccId);
    61
            address sender = getAccount(senderAccId);
    62
    63
            uint128 squeezable = getSqueezableAmount(sender, receiver);
    64
            uint128 squeezed = squeezeWithDefaultHistory(
    65
                receiverAccId,
    66
                senderAccId
    67
            );
    68
    69
            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
            address target = getAccount(targetAccId);
    78
    79
            // store the current squeezable and receivable amount
    80
            uint128 squeezable = getTotalSqueezableAmountForUser(target);
    81
            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
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0]);
    86
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1]);
    87
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2]);
    88
            setStreamBalanceWithdrawAll(ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER3]);
    89
    90
            // warp to the point in time where the streams are receivable
    91
            hevm.warp(getCurrentCycleEnd() + 1);
    92
    93
            uint128 receivableAfter = getReceivableAmountForUser(target);
    94
    95
            // sanity check
    96
            assert(receivableAfter >= receivableBefore);
    97
    98
            uint128 receiveableDelta = receivableAfter - receivableBefore;
    99
    100
            // squeezable before should match receivable now
    101
            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
            address receiver = getAccount(receiverAccId);
    114
            address sender = getAccount(senderAccId);
    115
    116
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    117
    118
            StreamsHistory[] memory history = getStreamsHistory(sender);
    119
            for (uint256 i = 0; i < history.length; i++) {
    120
                history[i].streamsHash = drips.hashStreams(history[i].receivers);
    121
                history[i].receivers = new StreamReceiver[](0);
    122
            }
    123
    124
            uint128 squeezedAmt = _squeeze(
    125
                receiverAccId,
    126
                senderAccId,
    127
                bytes32(0),
    128
                history
    129
            );
    130
    131
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    132
    133
            assert(squeezedAmt == 0);
    134
            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
            address receiver = getAccount(receiverAccId);
    152
            address sender = getAccount(senderAccId);
    153
    154
            uint128 amount0 = squeezeWithFuzzedHistory(
    155
                receiverAccId,
    156
                senderAccId,
    157
                hashIndex,
    158
                receiversRandomSeed
    159
            );
    160
    161
            uint128 amount1 = squeezeWithFuzzedHistory(
    162
                receiverAccId,
    163
                senderAccId,
    164
                hashIndex,
    165
                receiversRandomSeed
    166
            );
    167
    168
            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
            address receiver = getAccount(receiverAccId);
    190
            address sender = getAccount(senderAccId);
    191
    192
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    193
    194
            setStreams(
    195
                receiverAccId,
    196
                senderAccId,
    197
                amountPerSec,
    198
                startTime,
    199
                duration,
    200
                balanceDelta
    201
            );
    202
    203
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    204
    205
            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
            address receiver = getAccount(receiverAccId);
    219
            address sender = getAccount(senderAccId);
    220
    221
            uint128 squeezableBefore = getSqueezableAmount(sender, receiver);
    222
    223
            setStreamBalanceWithdrawAll(senderAccId);
    224
    225
            uint128 squeezableAfter = getSqueezableAmount(sender, receiver);
    226
    227
            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
                EchidnaSqueezeHelpers(address(this)).squeezeWithDefaultHistory(
    242
                    receiverAccId,
    243
                    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
            address sender = getAccount(senderAccId);
    265
            require(
    266
                getStreamsHistory(sender).length >= 2,
    267
                "need at least 2 history entries"
    268
            );
    269
    270
            try
    271
                EchidnaSqueezeHelpers(address(this)).squeezeWithFuzzedHistory(
    272
                    receiverAccId,
    273
                    senderAccId,
    274
                    hashIndex,
    275
                    receiversRandomSeed
    276
                )
    277
            {} catch {
    278
                assert(false);
    279
            }
    280
        }
    281
    282
    }
    283
    98.0% src/echidna/EchidnaStreamsHelpers.sol
    Lines covered: 163 / 165 (98.0%)
    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
        ) internal returns (int128) {
    36
            StreamReceiver[] memory newReceivers = bubbleSortStreamReceivers(
    37
                unsortedNewReceivers
    38
            );
    39
    40
            hevm.prank(from);
    41
            int128 realBalanceDelta = driver.setStreams(
    42
                token,
    43
                currReceivers,
    44
                balanceDelta,
    45
                newReceivers,
    46
                maxEndHint1,
    47
                maxEndHint2,
    48
                from
    49
            );
    50
    51
            updateStreamReceivers(from, newReceivers);
    52
    53
            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
        ) public returns (int128) {
    74
            address from = getAccount(fromAccId);
    75
            address to = getAccount(toAccId);
    76
    77
            StreamReceiver[] memory receivers = new StreamReceiver[](1);
    78
            receivers[0] = StreamReceiver(
    79
                getDripsAccountId(to),
    80
                StreamConfigImpl.create(
    81
                    0, // streamId is arbitrary and can be ignored
    82
                    amountPerSec,
    83
                    startTime,
    84
                    duration
    85
                )
    86
            );
    87
    88
            int128 realBalanceDelta = _setStreams(
    89
                from,
    90
                getStreamReceivers(from),
    91
                balanceDelta,
    92
                receivers
    93
            );
    94
    95
            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
        ) public returns (int128) {
    118
            address from = getAccount(fromAccId);
    119
            address to = getAccount(toAccId);
    120
    121
            amountPerSec = clampAmountPerSec(amountPerSec);
    122
            startTime = clampStartTime(startTime);
    123
            duration = clampDuration(duration);
    124
            balanceDelta = clampBalanceDelta(balanceDelta, from);
    125
    126
            setStreams(
    127
                fromAccId,
    128
                toAccId,
    129
                amountPerSec,
    130
                startTime,
    131
                duration,
    132
                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
        ) public returns (int128) {
    154
            address from = getAccount(fromAccId);
    155
            address to = getAccount(toAccId);
    156
    157
            StreamReceiver[] memory oldReceivers = getStreamReceivers(from);
    158
    159
            StreamReceiver memory addedReceiver = StreamReceiver(
    160
                getDripsAccountId(to),
    161
                StreamConfigImpl.create(
    162
                    0, // streamId is arbitrary and can be ignored
    163
                    amountPerSec,
    164
                    startTime,
    165
                    duration
    166
                )
    167
            );
    168
    169
            StreamReceiver[] memory newReceivers = new StreamReceiver[](
    170
                oldReceivers.length + 1
    171
            );
    172
            for (uint256 i = 0; i < oldReceivers.length; i++) {
    173
                newReceivers[i] = oldReceivers[i];
    174
            }
    175
            newReceivers[newReceivers.length - 1] = addedReceiver;
    176
    177
            int128 realBalanceDelta = _setStreams(
    178
                from,
    179
                oldReceivers,
    180
                balanceDelta,
    181
                newReceivers
    182
            );
    183
    184
            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
        ) public returns (int128) {
    206
            address from = getAccount(fromAccId);
    207
    208
            amountPerSec = clampAmountPerSec(amountPerSec);
    209
            startTime = clampStartTime(startTime);
    210
            duration = clampDuration(duration);
    211
            balanceDelta = clampBalanceDelta(balanceDelta, from);
    212
    213
            return
    214
                addStream(
    215
                    fromAccId,
    216
                    toAccId,
    217
                    amountPerSec,
    218
                    startTime,
    219
                    duration,
    220
                    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
            address receiver = getAccount(toAccId);
    239
            address sender = getAccount(fromAccId);
    240
    241
            // calculate amount per second so there will be something to squeeze
    242
            // this cycle
    243
            uint160 minAmtPerSec = drips.minAmtPerSec() * SECONDS_PER_CYCLE;
    244
            amountPerSec =
    245
                minAmtPerSec +
    246
                (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
            int128 balanceDelta = (int128(uint128(amountPerSec)) * 100) / 1e9;
    251
            if (uint128(balanceDelta) > token.balanceOf(sender)) {
    252
                balanceDelta = int128(uint128(token.balanceOf(sender)));
    253
            }
    254
    255
            // add the stream
    256
            addStream(fromAccId, toAccId, amountPerSec, 0, 0, balanceDelta);
    257
    258
            // warp 1 second forward so there is something to squeeze
    259
            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
            address target = getAccount(targetAccId);
    269
    270
            StreamReceiver[] memory oldReceivers = getStreamReceivers(target);
    271
    272
            uint256 index = indexSeed % oldReceivers.length;
    273
    274
            StreamReceiver[] memory newReceivers = new StreamReceiver[](
    275
                oldReceivers.length - 1
    276
            );
    277
            uint256 j = 0;
    278
            for (uint256 i = 0; i < oldReceivers.length; i++) {
    279
                if (i != index) {
    280
                    newReceivers[j] = oldReceivers[i];
    281
                    j++;
    282
                }
    283
            }
    284
    285
            _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
            returns (int128)
    298
        {
    299
            address target = getAccount(targetAccId);
    300
    301
            int128 realBalanceDelta = _setStreams(
    302
                target,
    303
                getStreamReceivers(target),
    304
                balanceDelta,
    305
                getStreamReceivers(target)
    306
            );
    307
    308
            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
            address target = getAccount(targetAccId);
    324
            balanceDelta = clampBalanceDelta(balanceDelta, target);
    325
            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
            returns (int128)
    337
        {
    338
            address target = getAccount(targetAccId);
    339
            uint256 targetDripsAccId = getDripsAccountId(target);
    340
    341
            int128 realBalanceDelta = _setStreams(
    342
                target,
    343
                getStreamReceivers(target),
    344
                type(int128).min,
    345
                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
            require(TOGGLE_MAXENDHINTS_ENABLED);
    357
            maxEndHint1 = _maxEndHint1;
    358
            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
            returns (uint160)
    369
        {
    370
            return
    371
                drips.minAmtPerSec() +
    372
                (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
        function clampStartTime(uint32 startTime) internal returns (uint32) {
    381
            if (startTime == 0) return 0;
    382
    383
            // We want to make sure that the start time does not go below 1
    384
            uint32 minStartTime;
    385
            if (CYCLE_FUZZING_BUFFER_SECONDS >= block.timestamp) {
    386
                minStartTime = 1;
    387
            } else {
    388
                minStartTime =
    389
                    uint32(block.timestamp) -
    390
                    CYCLE_FUZZING_BUFFER_SECONDS;
    391
            }
    392
    393
            uint32 maxStartTime = uint32(block.timestamp) +
    394
                CYCLE_FUZZING_BUFFER_SECONDS;
    395
    396
            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
        function clampDuration(uint32 duration) internal returns (uint32) {
    405
            if (duration == 0) return 0;
    406
    407
            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
            returns (int128)
    419
        {
    420
            if (balanceDelta > 0) {
    421
                balanceDelta =
    422
                    balanceDelta %
    423
                    (int128(uint128(token.balanceOf(from))) + 1);
    424
            } else {
    425
                balanceDelta = balanceDelta % int128(uint128(STARTING_BALANCE));
    426
            }
    427
            return balanceDelta;
    428
        }
    429
    }
    430
    94.0% src/echidna/EchidnaStreamsTests.sol
    Lines covered: 112 / 118 (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
            address target = getAccount(targetAccId);
    20
            uint256 targetDripsAccId = getDripsAccountId(target);
    21
    22
            uint256 tokenBalanceBefore = token.balanceOf(target);
    23
            uint128 streamBalanceBefore = drips.balanceAt(
    24
                targetDripsAccId,
    25
                token,
    26
                getStreamReceivers(target),
    27
                uint32(block.timestamp)
    28
            );
    29
    30
            int128 realBalanceDelta = setStreamBalance(targetAccId, balanceDelta);
    31
    32
            uint256 tokenBalanceAfter = token.balanceOf(target);
    33
            uint128 streamBalanceAfter = drips.balanceAt(
    34
                targetDripsAccId,
    35
                token,
    36
                getStreamReceivers(target),
    37
                uint32(block.timestamp)
    38
            );
    39
    40
            if (balanceDelta >= 0) {
    41
                assert(realBalanceDelta == balanceDelta);
    42
            } else {
    43
                assert(realBalanceDelta <= 0);
    44
                assert(realBalanceDelta >= balanceDelta);
    45
            }
    46
    47
            assert(
    48
                int256(tokenBalanceAfter) ==
    49
                    int256(tokenBalanceBefore) - realBalanceDelta
    50
            );
    51
            assert(
    52
                int128(streamBalanceAfter) ==
    53
                    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
            address from = getAccount(fromAccId);
    72
            address to = getAccount(toAccId);
    73
            uint256 fromDripsAccId = getDripsAccountId(from);
    74
            uint256 toDripsAccId = getDripsAccountId(to);
    75
    76
            amtPerSecAdded = clampAmountPerSec(amtPerSecAdded);
    77
    78
            // the timestamps we are comparing
    79
            uint256 currentTimestamp = block.timestamp;
    80
            uint256 futureTimestamp = getCurrentCycleEnd() + 1;
    81
    82
            // retrieve initial balances
    83
            uint128 balanceInitial = getStreamBalanceForUser(
    84
                from,
    85
                uint32(block.timestamp)
    86
            );
    87
            uint128 receivableInitial = getReceivableAmountForAllUsers();
    88
    89
            // look at balances in the future if we wouldnt do anything
    90
            hevm.warp(futureTimestamp);
    91
            uint128 balanceBaseline = getStreamBalanceForUser(
    92
                from,
    93
                uint32(block.timestamp)
    94
            );
    95
            uint128 receivableBaseline = getReceivableAmountForAllUsers();
    96
            hevm.warp(currentTimestamp);
    97
    98
            // add a stream
    99
            // make sure we add enough balance to complete the cycle
    100
            uint128 balanceAdded = uint128(amtPerSecAdded) * SECONDS_PER_CYCLE;
    101
            balanceAdded = uint128(clampBalanceDelta(int128(balanceAdded), from));
    102
            require(balanceAdded / amtPerSecAdded >= SECONDS_PER_CYCLE);
    103
            addStream(
    104
                fromAccId,
    105
                toAccId,
    106
                amtPerSecAdded,
    107
                0,
    108
                0,
    109
                int128(balanceAdded)
    110
            );
    111
    112
            // retrieve balances after adding stream
    113
            uint128 balanceBefore = getStreamBalanceForUser(
    114
                from,
    115
                uint32(block.timestamp)
    116
            );
    117
            uint128 receivableBefore = getReceivableAmountForAllUsers();
    118
    119
            // jump to future
    120
            hevm.warp(futureTimestamp);
    121
    122
            // retrieve balances in the future after adding the stream
    123
            uint128 balanceAfter = getStreamBalanceForUser(
    124
                from,
    125
                uint32(block.timestamp)
    126
            );
    127
            uint128 receivableAfter = getReceivableAmountForAllUsers();
    128
    129
            // sanity checks
    130
            assert(balanceInitial >= balanceBaseline);
    131
            assert(balanceBefore >= balanceAfter);
    132
            assert(receivableAfter >= receivableBaseline);
    133
    134
            // the amount that would have been streamed if we do nothing
    135
            uint128 baselineBalanceStreamed = balanceInitial - balanceBaseline;
    136
    137
            // calculate expected balance change including the effect of the
    138
            // added stream
    139
            uint128 expectedBalanceChange = balanceBefore -
    140
                balanceAfter -
    141
                baselineBalanceStreamed;
    142
            uint128 expectedReceivedChange = receivableAfter - receivableBaseline;
    143
    144
            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
                EchidnaStreamsHelpers(address(this)).setStreamsWithClamping(
    166
                    fromAccId,
    167
                    toAccId,
    168
                    amountPerSec,
    169
                    startTime,
    170
                    duration,
    171
                    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
                EchidnaStreamsHelpers(address(this)).addStreamWithClamping(
    197
                    fromAccId,
    198
                    toAccId,
    199
                    amountPerSec,
    200
                    startTime,
    201
                    duration,
    202
                    balanceDelta
    203
                )
    204
            {} catch (bytes memory reason) {
    205
                bytes4 errorSelector = bytes4(reason);
    206
                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
            address target = getAccount(targetAccId);
    224
            require(getStreamReceivers(target).length > 0);
    225
    226
            try
    227
                EchidnaStreamsHelpers(address(this)).removeStream(
    228
                    targetAccId,
    229
                    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
                EchidnaStreamsHelpers(address(this)).setStreamBalanceWithClamping(
    247
                    targetAccId,
    248
                    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
                EchidnaStreamsHelpers(address(this)).setStreamBalanceWithdrawAll(
    264
                    targetAccId
    265
                )
    266
            {} catch {
    267
                assert(false);
    268
            }
    269
        }
    270
    }
    271
    97.0% src/echidna/base/EchidnaAccounting.sol
    Lines covered: 87 / 89 (97.0%)
    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
        function getDripsBalancesTotalForAllUsers() internal returns (uint256) {
    18
            uint256 user0Total = getDripsBalancesTotalForUser(ADDRESS_USER0);
    19
            uint256 user1Total = getDripsBalancesTotalForUser(ADDRESS_USER1);
    20
            uint256 user2Total = getDripsBalancesTotalForUser(ADDRESS_USER2);
    21
            uint256 user3Total = getDripsBalancesTotalForUser(ADDRESS_USER3);
    22
    23
            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
            returns (uint256)
    37
        {
    38
            uint256 targetDripsAccId = getDripsAccountId(target);
    39
    40
            uint128 balance = getCurrentStreamBalanceForUser(target);
    41
            uint128 squeezable = getTotalSqueezableAmountForUser(target);
    42
            uint128 receivable = getReceivableAmountForUser(target);
    43
            uint128 collectable = drips.collectable(targetDripsAccId, token);
    44
            uint128 splittable = drips.splittable(targetDripsAccId, token);
    45
    46
            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
            returns (uint128)
    60
        {
    61
            uint256 targetDripsAccId = getDripsAccountId(target);
    62
    63
            uint128 balance;
    64
            try
    65
                drips.balanceAt(
    66
                    targetDripsAccId,
    67
                    token,
    68
                    getStreamReceivers(target),
    69
                    timestamp
    70
                )
    71
            returns (uint128 _balance) {
    72
                balance = _balance;
    73
            } catch {
    74
                // this should not happen, so put an assert here to be sure
    75
                assert(false);
    76
            }
    77
    78
            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
            returns (uint128)
    89
        {
    90
            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
        function getReceivableAmountForAllUsers() internal returns (uint128) {
    100
            uint128 receivable;
    101
            receivable += getReceivableAmountForUser(ADDRESS_USER0);
    102
            receivable += getReceivableAmountForUser(ADDRESS_USER1);
    103
            receivable += getReceivableAmountForUser(ADDRESS_USER2);
    104
            receivable += getReceivableAmountForUser(ADDRESS_USER3);
    105
            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
            returns (uint128)
    118
        {
    119
            uint128 receivable = drips.receiveStreamsResult(
    120
                getDripsAccountId(target),
    121
                token,
    122
                type(uint32).max
    123
            );
    124
            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
            returns (uint128)
    138
        {
    139
            uint128 amount = 0;
    140
            amount += getSqueezableAmount(ADDRESS_USER0, target);
    141
            amount += getSqueezableAmount(ADDRESS_USER1, target);
    142
            amount += getSqueezableAmount(ADDRESS_USER2, target);
    143
            amount += getSqueezableAmount(ADDRESS_USER3, target);
    144
    145
            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
            returns (uint128)
    160
        {
    161
            uint256 senderDripsAccId = getDripsAccountId(sender);
    162
            uint256 receiverDripsAccId = getDripsAccountId(receiver);
    163
    164
            uint128 amount = drips.squeezeStreamsResult(
    165
                receiverDripsAccId,
    166
                token,
    167
                senderDripsAccId,
    168
                bytes32(0),
    169
                getStreamsHistory(sender)
    170
            );
    171
    172
            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
        function getMaxEndForAllUsers() internal returns (uint32) {
    182
            uint32 maxMaxEnd;
    183
    184
            uint32 maxEndUser0 = getMaxEndForUser(ADDRESS_USER0);
    185
            if (maxEndUser0 > maxMaxEnd) maxMaxEnd = maxEndUser0;
    186
            uint32 maxEndUser1 = getMaxEndForUser(ADDRESS_USER1);
    187
            if (maxEndUser1 > maxMaxEnd) maxMaxEnd = maxEndUser1;
    188
            uint32 maxEndUser2 = getMaxEndForUser(ADDRESS_USER2);
    189
            if (maxEndUser2 > maxMaxEnd) maxMaxEnd = maxEndUser2;
    190
            uint32 maxEndUser3 = getMaxEndForUser(ADDRESS_USER3);
    191
            if (maxEndUser3 > maxMaxEnd) maxMaxEnd = maxEndUser3;
    192
    193
            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
        function getMaxEndForUser(address target) internal returns (uint32) {
    202
            uint256 targetDripsAccId = getDripsAccountId(target);
    203
            (, , , , uint32 maxEnd) = drips.streamsState(targetDripsAccId, token);
    204
            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
        function getCurrentCycleStart() internal returns (uint32) {
    212
            uint32 currTimestamp = uint32(block.timestamp);
    213
            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
        function getCurrentCycleEnd() internal returns (uint32) {
    221
            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
            returns (uint32)
    232
        {
    233
            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
    88.0% src/echidna/base/EchidnaConfig.sol
    Lines covered: 23 / 26 (88.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
        address internal constant ADDRESS_USER0 = address(0x10000);
    20
        address internal constant ADDRESS_USER1 = address(0x20000);
    21
        address internal constant ADDRESS_USER2 = address(0x30000);
    22
        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
        uint256 internal constant STARTING_BALANCE = 1_000_000_000e18;
    33
    34
        // Amount of seconds in a Drips cycle
    35
        uint32 internal constant SECONDS_PER_CYCLE = 10;
    36
    37
        // Buffers to be used as fuzzing boundaries
    38
        uint32 internal constant CYCLE_FUZZING_BUFFER_CYCLES = 10;
    39
        uint32 internal constant CYCLE_FUZZING_BUFFER_SECONDS =
    40
            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
            (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
        uint256 internal constant SPLIT_ROUNDING_TOLERANCE = 1;
    54
    55
        // Toggles for certain tests
    56
        bool internal constant TOGGLE_EXPERIMENTAL_TESTS_ENABLED = true;
    57
        bool internal constant TOGGLE_HEAVY_TESTS_ENABLED = true;
    58
        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
            if (!TOGGLE_HEAVY_TESTS_ENABLED) return;
    69
            _;
    70
        }
    71
    72
        constructor() {
    73
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER0] = 0;
    74
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER1] = 64;
    75
            ADDRESS_TO_ACCOUNT_ID[ADDRESS_USER2] = 128;
    76
            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
        function getAccount(uint8 rawId) internal pure returns (address) {
    86
            uint256 id = uint256(rawId) / 64;
    87
    88
            if (id == 0) return ADDRESS_USER0;
    89
            if (id == 1) return ADDRESS_USER1;
    90
            if (id == 2) return ADDRESS_USER2;
    91
            if (id == 3) return ADDRESS_USER3;
    92
    93
            require(false, "Unknown account ID");
    94
        }
    95
    }
    96
    96.0% src/echidna/base/EchidnaSetup.sol
    Lines covered: 27 / 28 (96.0%)
    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
        IHevm hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    12
    13
        ERC20PresetFixedSupply token;
    14
        DripsEchidna drips;
    15
        AddressDriverEchidna driver;
    16
    17
        constructor() EchidnaConfig() {
    18
            // Deploy ERC20 token
    19
            token = new ERC20PresetFixedSupply(
    20
                "Test Token",
    21
                "TEST",
    22
                STARTING_BALANCE * 4,
    23
                address(this)
    24
            );
    25
    26
            // Deploy Drips
    27
            drips = new DripsEchidna(SECONDS_PER_CYCLE);
    28
            drips.unpause_noModifiers();
    29
    30
            // Deploy AddressDriver
    31
            uint32 driverId = drips.registerDriver(address(this));
    32
            driver = new AddressDriverEchidna(
    33
                drips,
    34
                address(0),
    35
                driverId
    36
            );
    37
            driver.unpause_noModifiers();
    38
            drips.updateDriverAddress(driverId, address(driver));
    39
    40
            // Set up token balances
    41
            token.transfer(ADDRESS_USER0, STARTING_BALANCE);
    42
            hevm.prank(ADDRESS_USER0);
    43
            token.approve(address(driver), type(uint256).max);
    44
            token.transfer(ADDRESS_USER1, STARTING_BALANCE);
    45
            hevm.prank(ADDRESS_USER1);
    46
            token.approve(address(driver), type(uint256).max);
    47
            token.transfer(ADDRESS_USER2, STARTING_BALANCE);
    48
            hevm.prank(ADDRESS_USER2);
    49
            token.approve(address(driver), type(uint256).max);
    50
            token.transfer(ADDRESS_USER3, STARTING_BALANCE);
    51
            hevm.prank(ADDRESS_USER3);
    52
            token.approve(address(driver), type(uint256).max);
    53
    54
            // Store starting timestamp
    55
            STARTING_TIMESTAMP = block.timestamp;
    56
        }
    57
    }
    58
    98.0% src/echidna/base/EchidnaStorage.sol
    Lines covered: 85 / 86 (98.0%)
    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
            bytes32 receiversHash = drips.hashStreams(receivers);
    36
    37
            // Update the stream receivers for 'sender'
    38
            delete userToStreamReceivers[sender];
    39
            for (uint256 i = 0; i < receivers.length; i++) {
    40
                userToStreamReceivers[sender].push(receivers[i]);
    41
            }
    42
    43
            // Add a new entry to the streams history
    44
            uint256 nextIndex = userToStreamsHistory[sender].length;
    45
            userToStreamsHistory[sender].push();
    46
            userToStreamsHistory[sender][nextIndex].streamsHash = bytes32(0);
    47
            for (uint256 i = 0; i < receivers.length; i++) {
    48
                userToStreamsHistory[sender][nextIndex].receivers.push(
    49
                    receivers[i]
    50
                );
    51
            }
    52
            (, , uint32 updateTime, , uint32 maxEnd) = drips.streamsState(
    53
                getDripsAccountId(sender),
    54
                token
    55
            );
    56
            userToStreamsHistory[sender][nextIndex].updateTime = updateTime;
    57
            userToStreamsHistory[sender][nextIndex].maxEnd = maxEnd;
    58
    59
            // Generate starting hash. If there is no previous history, use 0
    60
            bytes32 startingHash;
    61
            if (nextIndex == 0) {
    62
                startingHash = bytes32(0);
    63
            } else {
    64
                startingHash = userToStreamsHistoryHashes[sender][nextIndex - 1];
    65
            }
    66
    67
            // Add new entry to the streams history hashes
    68
            bytes32 historyHash = drips.hashStreamsHistory(
    69
                startingHash,
    70
                receiversHash,
    71
                updateTime,
    72
                maxEnd
    73
            );
    74
            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
            delete userToSplitsReceivers[sender];
    87
            for (uint256 i = 0; i < receivers.length; i++) {
    88
                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
            returns (StreamReceiver[] memory)
    100
        {
    101
            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
            returns (StreamsHistory[] memory)
    112
        {
    113
            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
            returns (bytes32[] memory)
    124
        {
    125
            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
            returns (SplitsReceiver[] memory)
    136
        {
    137
            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
            returns (StreamReceiver[] memory)
    151
        {
    152
            uint256 n = unsorted.length;
    153
            if (n <= 1) return unsorted;
    154
    155
            StreamReceiver[] memory sorted = unsorted;
    156
            for (uint256 i = 0; i < n - 1; i++) {
    157
                for (uint256 j = 0; j < n - i - 1; j++) {
    158
                    if (bubbleSortStreamReceiverGT(sorted[j], sorted[j + 1])) {
    159
                        StreamReceiver memory temp = sorted[j];
    160
                        sorted[j] = sorted[j + 1];
    161
                        sorted[j + 1] = temp;
    162
                    }
    163
                }
    164
            }
    165
            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
            returns (SplitsReceiver[] memory)
    176
        {
    177
            uint256 n = unsorted.length;
    178
            if (n <= 1) return unsorted;
    179
    180
            SplitsReceiver[] memory sorted = unsorted;
    181
            for (uint256 i = 0; i < n - 1; i++) {
    182
                for (uint256 j = 0; j < n - i - 1; j++) {
    183
                    if (bubbleSortSplitsReceiverGT(sorted[j], sorted[j + 1])) {
    184
                        SplitsReceiver memory temp = sorted[j];
    185
                        sorted[j] = sorted[j + 1];
    186
                        sorted[j + 1] = temp;
    187
                    }
    188
                }
    189
            }
    190
            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
        ) internal returns (bool) {
    205
            if (a.accountId != b.accountId) {
    206
                return a.accountId > b.accountId;
    207
            }
    208
            if (StreamConfig.unwrap(a.config) != StreamConfig.unwrap(b.config)) {
    209
                return
    210
                    StreamConfig.unwrap(a.config) > StreamConfig.unwrap(b.config);
    211
            }
    212
            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
        ) internal returns (bool) {
    227
            if (a.accountId != b.accountId) {
    228
                return a.accountId > b.accountId;
    229
            }
    230
            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
        function getDripsAccountId(address account) internal returns (uint256) {
    240
            if (ADDRESS_TO_DRIPS_ACCOUNT_ID[account] == 0) {
    241
                ADDRESS_TO_DRIPS_ACCOUNT_ID[account] = driver.calcAccountId(
    242
                    account
    243
                );
    244
            }
    245
            return ADDRESS_TO_DRIPS_ACCOUNT_ID[account];
    246
        }
    247
    }
    248
    100.0% src/echidna/tools/AddressDriverEchidna.sol
    Lines covered: 3 / 3 (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
    contract AddressDriverEchidna is AddressDriver, ManagedEchidna {
    12
        constructor(
    13
            DripsEchidna drips_,
    14
            address forwarder,
    15
            uint32 driverId_
    16
        ) AddressDriver(drips_, forwarder, driverId_) {}
    17
    }
    18
    0.0% src/echidna/tools/Debugger.sol
    Lines covered: 0 / 1 (0.0%)
    1
    // SPDX-License-Identifier: MIT
    2
    3
    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: 10 / 10 (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
        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
        ) public view returns (int128, int128) {
    26
            // Manually calculate the storage slot because it is a private variable
    27
            // in Streams
    28
            StreamsStorage storage streamsStorage;
    29
            bytes32 slot = _erc1967Slot("eip1967.streams.storage");
    30
            assembly {
    31
                streamsStorage.slot := slot
    32
            }
    33
    34
            // Return the amtDelta for the given cycle
    35
            StreamsState storage state = streamsStorage.states[erc20][accountId];
    36
            mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas;
    37
            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
    60.0% src/echidna/tools/ManagedEchidna.sol
    Lines covered: 3 / 5 (60.0%)
    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
            _managedStorage().isPaused = false;
    16
            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
    contract CryticTester is Properties {}
    8
    88.0% test/recon/Properties.sol
    Lines covered: 8 / 9 (88.0%)
    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
            if (!b) {
    18
                emit AssertionFailed(reason);
    19
                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
            t(entropy > 0, ASSERTION_CANARY);
    30
        }
    31
    }
    32