Overview
ETH Balance
0 ETH
Eth Value
$0.00More Info
Private Name Tags
ContractCreator
Latest 1 internal transaction
Advanced mode:
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
17684572 | 494 days ago | Contract Creation | 0 ETH |
Loading...
Loading
This contract may be a proxy contract. Click on More Options and select Is this a proxy? to confirm and enable the "Read as Proxy" & "Write as Proxy" tabs.
Similar Match Source Code This contract matches the deployed Bytecode of the Source Code for Contract 0x4F76Fa2B...f4B603523 The constructor portion of the code might be different and could alter the actual behaviour of the contract
Contract Name:
AddressDriver
Compiler Version
v0.8.20+commit.a1b79de6
Optimization Enabled:
Yes with 7700 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.20; import {AccountMetadata, Drips, StreamReceiver, IERC20, SplitsReceiver} from "./Drips.sol"; import {Managed} from "./Managed.sol"; import {DriverTransferUtils} from "./DriverTransferUtils.sol"; /// @notice A Drips driver implementing address-based account identification. /// Each address can use `AddressDriver` to control a single account ID derived from that address. /// No registration is required, an `AddressDriver`-based account ID /// for each address is available upfront. contract AddressDriver is DriverTransferUtils, Managed { /// @notice The Drips address used by this driver. Drips public immutable drips; /// @notice The driver ID which this driver uses when calling Drips. uint32 public immutable driverId; /// @param drips_ The Drips contract to use. /// @param forwarder The ERC-2771 forwarder to trust. May be the zero address. /// @param driverId_ The driver ID to use when calling Drips. constructor(Drips drips_, address forwarder, uint32 driverId_) DriverTransferUtils(forwarder) { drips = drips_; driverId = driverId_; } /// @notice Returns the address of the Drips contract to use for ERC-20 transfers. function _drips() internal view override returns (Drips) { return drips; } /// @notice Calculates the account ID for an address. /// Every account ID is a 256-bit integer constructed by concatenating: /// `driverId (32 bits) | zeros (64 bits) | addr (160 bits)`. /// @param addr The address /// @return accountId The account ID function calcAccountId(address addr) public view returns (uint256 accountId) { // By assignment we get `accountId` value: // `zeros (224 bits) | driverId (32 bits)` accountId = driverId; // By bit shifting we get `accountId` value: // `driverId (32 bits) | zeros (224 bits)` // By bit masking we get `accountId` value: // `driverId (32 bits) | zeros (64 bits) | addr (160 bits)` accountId = (accountId << 224) | uint160(addr); } /// @notice Calculates the account ID for the message sender /// @return accountId The account ID function _callerAccountId() internal view returns (uint256 accountId) { return calcAccountId(_msgSender()); } /// @notice Collects the account's received already split funds /// and transfers them out of the Drips contract. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param transferTo The address to send collected funds to /// @return amt The collected amount function collect(IERC20 erc20, address transferTo) public whenNotPaused returns (uint128 amt) { return _collectAndTransfer(_callerAccountId(), erc20, transferTo); } /// @notice Gives funds from the message sender to the receiver. /// The receiver can split and collect them immediately. /// Transfers the funds to be given from the message sender's wallet to the Drips contract. /// @param receiver The receiver account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param amt The given amount function give(uint256 receiver, IERC20 erc20, uint128 amt) public whenNotPaused { _giveAndTransfer(_callerAccountId(), receiver, erc20, amt); } /// @notice Sets the message sender's streams configuration. /// Transfers funds between the message sender's wallet and the Drips contract /// to fulfil the change of the streams balance. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param currReceivers The current streams receivers list. /// It must be exactly the same as the last list set for the sender with `setStreams`. /// If this is the first update, pass an empty array. /// @param balanceDelta The streams balance change to be applied. /// Positive to add funds to the streams balance, negative to remove them. /// @param newReceivers The list of the streams receivers of the sender to be set. /// Must be sorted by the receivers' addresses, deduplicated and without 0 amtPerSecs. /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The first hint for finding the maximum end time when all streams stop due to funds /// running out after the balance is updated and the new receivers list is applied. /// Hints have no effect on the results of calling this function, except potentially saving gas. /// Hints are Unix timestamps used as the starting points for binary search for the time /// when funds run out in the range of timestamps from the current block's to `2^32`. /// Hints lower than the current timestamp are ignored. /// You can provide zero, one or two hints. The order of hints doesn't matter. /// Hints are the most effective when one of them is lower than or equal to /// the last timestamp when funds are still streamed, and the other one is strictly larger /// than that timestamp,the smaller the difference between such hints, the higher gas savings. /// The savings are the highest possible when one of the hints is equal to /// the last timestamp when funds are still streamed, and the other one is larger by 1. /// It's worth noting that the exact timestamp of the block in which this function is executed /// may affect correctness of the hints, especially if they're precise. /// Hints don't provide any benefits when balance is not enough to cover /// a single second of streaming or is enough to cover all streams until timestamp `2^32`. /// Even inaccurate hints can be useful, and providing a single hint /// or two hints that don't enclose the time when funds run out can still save some gas. /// Providing poor hints that don't reduce the number of binary search steps /// may cause slightly higher gas usage than not providing any hints. /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details. /// @param transferTo The address to send funds to in case of decreasing balance /// @return realBalanceDelta The actually applied streams balance change. function setStreams( IERC20 erc20, StreamReceiver[] calldata currReceivers, int128 balanceDelta, StreamReceiver[] calldata newReceivers, // slither-disable-next-line similar-names uint32 maxEndHint1, uint32 maxEndHint2, address transferTo ) public whenNotPaused returns (int128 realBalanceDelta) { return _setStreamsAndTransfer( _callerAccountId(), erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2, transferTo ); } /// @notice Sets the account splits configuration. /// The configuration is common for all ERC-20 tokens. /// Nothing happens to the currently splittable funds, but when they are split /// after this function finishes, the new splits configuration will be used. /// Because anybody can call `split` on `Drips`, calling this function may be frontrun /// and all the currently splittable funds will be split using the old splits configuration. /// @param receivers The list of the account's splits receivers to be set. /// Must be sorted by the splits receivers' addresses, deduplicated and without 0 weights. /// Each splits receiver will be getting `weight / TOTAL_SPLITS_WEIGHT` /// share of the funds collected by the account. /// If the sum of weights of all receivers is less than `_TOTAL_SPLITS_WEIGHT`, /// some funds won't be split, but they will be left for the account to collect. /// It's valid to include the account's own `accountId` in the list of receivers, /// but funds split to themselves return to their splittable balance and are not collectable. /// This is usually unwanted, because if splitting is repeated, /// funds split to themselves will be again split using the current configuration. /// Splitting 100% to self effectively blocks splitting unless the configuration is updated. function setSplits(SplitsReceiver[] calldata receivers) public whenNotPaused { drips.setSplits(_callerAccountId(), receivers); } /// @notice Emits the account metadata for the message sender. /// The keys and the values are not standardized by the protocol, it's up to the users /// to establish and follow conventions to ensure compatibility with the consumers. /// @param accountMetadata The list of account metadata. function emitAccountMetadata(AccountMetadata[] calldata accountMetadata) public whenNotPaused { drips.emitAccountMetadata(_callerAccountId(), accountMetadata); } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.20; import { Streams, StreamConfig, StreamsHistory, StreamConfigImpl, StreamReceiver } from "./Streams.sol"; import {Managed} from "./Managed.sol"; import {Splits, SplitsReceiver} from "./Splits.sol"; import {IERC20, SafeERC20} from "openzeppelin-contracts/token/ERC20/utils/SafeERC20.sol"; using SafeERC20 for IERC20; /// @notice The account metadata. /// The key and the value are not standardized by the protocol, it's up to the users /// to establish and follow conventions to ensure compatibility with the consumers. struct AccountMetadata { /// @param key The metadata key bytes32 key; /// @param value The metadata value bytes value; } /// @notice Drips protocol contract. Automatically streams and splits funds between accounts. /// /// The account can transfer some funds to their streams balance in the contract /// and configure a list of receivers, to whom they want to stream these funds. /// As soon as the streams balance is enough to cover at least 1 second of streaming /// to the configured receivers, the funds start streaming automatically. /// Every second funds are deducted from the streams balance and moved to their receivers. /// The process stops automatically when the streams balance is not enough to cover another second. /// /// Every account has a receiver balance, in which they have funds received from other accounts. /// The streamed funds are added to the receiver balances in global cycles. /// Every `cycleSecs` seconds the protocol adds streamed funds to the receivers' balances, /// so recently streamed funds may not be receivable immediately. /// `cycleSecs` is a constant configured when the Drips contract is deployed. /// The receiver balance is independent from the streams balance, /// to stream received funds they need to be first collected and then added to the streams balance. /// /// The account can share collected funds with other accounts by using splits. /// When collecting, the account gives each of their splits receivers /// a fraction of the received funds. /// Funds received from splits are available for collection immediately regardless of the cycle. /// They aren't exempt from being split, so they too can be split when collected. /// Accounts can build chains and networks of splits between each other. /// Anybody can request collection of funds for any account, /// which can be used to enforce the flow of funds in the network of splits. /// /// The concept of something happening periodically, e.g. every second or every `cycleSecs` are /// only high-level abstractions for the account, Ethereum isn't really capable of scheduling work. /// The actual implementation emulates that behavior by calculating the results of the scheduled /// events based on how many seconds have passed and only when the account needs their outcomes. /// /// The contract can store at most `type(int128).max` which is `2 ^ 127 - 1` units of each token. contract Drips is Managed, Streams, Splits { /// @notice Maximum number of streams receivers of a single account. /// Limits cost of changes in streams configuration. uint256 public constant MAX_STREAMS_RECEIVERS = _MAX_STREAMS_RECEIVERS; /// @notice The additional decimals for all amtPerSec values. uint8 public constant AMT_PER_SEC_EXTRA_DECIMALS = _AMT_PER_SEC_EXTRA_DECIMALS; /// @notice The multiplier for all amtPerSec values. uint160 public constant AMT_PER_SEC_MULTIPLIER = _AMT_PER_SEC_MULTIPLIER; /// @notice Maximum number of splits receivers of a single account. /// Limits the cost of splitting. uint256 public constant MAX_SPLITS_RECEIVERS = _MAX_SPLITS_RECEIVERS; /// @notice The total splits weight of an account uint32 public constant TOTAL_SPLITS_WEIGHT = _TOTAL_SPLITS_WEIGHT; /// @notice The offset of the controlling driver ID in the account ID. /// In other words the controlling driver ID is the highest 32 bits of the account ID. /// Every account ID is a 256-bit integer constructed by concatenating: /// `driverId (32 bits) | driverCustomData (224 bits)`. uint8 public constant DRIVER_ID_OFFSET = 224; /// @notice The total amount the protocol can store of each token. /// It's the minimum of _MAX_STREAMS_BALANCE and _MAX_SPLITS_BALANCE. uint128 public constant MAX_TOTAL_BALANCE = _MAX_STREAMS_BALANCE; /// @notice On every timestamp `T`, which is a multiple of `cycleSecs`, the receivers /// gain access to steams received during `T - cycleSecs` to `T - 1`. /// Always higher than 1. uint32 public immutable cycleSecs; /// @notice The minimum amtPerSec of a stream. It's 1 token per cycle. uint160 public immutable minAmtPerSec; /// @notice The ERC-1967 storage slot holding a single `DripsStorage` structure. bytes32 private immutable _dripsStorageSlot = _erc1967Slot("eip1967.drips.storage"); /// @notice Emitted when a driver is registered /// @param driverId The driver ID /// @param driverAddr The driver address event DriverRegistered(uint32 indexed driverId, address indexed driverAddr); /// @notice Emitted when a driver address is updated /// @param driverId The driver ID /// @param oldDriverAddr The old driver address /// @param newDriverAddr The new driver address event DriverAddressUpdated( uint32 indexed driverId, address indexed oldDriverAddr, address indexed newDriverAddr ); /// @notice Emitted when funds are withdrawn. /// @param erc20 The used ERC-20 token. /// @param receiver The address that the funds are sent to. /// @param amt The withdrawn amount. event Withdrawn(IERC20 indexed erc20, address indexed receiver, uint256 amt); /// @notice Emitted by the account to broadcast metadata. /// The key and the value are not standardized by the protocol, it's up to the users /// to establish and follow conventions to ensure compatibility with the consumers. /// @param accountId The ID of the account emitting metadata /// @param key The metadata key /// @param value The metadata value event AccountMetadataEmitted(uint256 indexed accountId, bytes32 indexed key, bytes value); struct DripsStorage { /// @notice The next driver ID that will be used when registering. uint32 nextDriverId; /// @notice Driver addresses. mapping(uint32 driverId => address) driverAddresses; /// @notice The balance of each token currently stored in the protocol. mapping(IERC20 erc20 => Balance) balances; } /// @notice The balance currently stored in the protocol. struct Balance { /// @notice The balance currently stored in streaming. uint128 streams; /// @notice The balance currently stored in splitting. uint128 splits; } /// @param cycleSecs_ The length of cycleSecs to be used in the contract instance. /// Low value makes funds more available by shortening the average time /// of funds being frozen between being taken from the accounts' /// streams balance and being receivable by their receivers. /// High value makes receiving cheaper by making it process less cycles for a given time range. /// Must be higher than 1. constructor(uint32 cycleSecs_) Streams(cycleSecs_, _erc1967Slot("eip1967.streams.storage")) Splits(_erc1967Slot("eip1967.splits.storage")) { cycleSecs = Streams._cycleSecs; minAmtPerSec = Streams._minAmtPerSec; } /// @notice A modifier making functions callable only by the driver controlling the account. /// @param accountId The account ID. modifier onlyDriver(uint256 accountId) { // `accountId` has value: // `driverId (32 bits) | driverCustomData (224 bits)` // By bit shifting we get value: // `zeros (224 bits) | driverId (32 bits)` // By casting down we get value: // `driverId (32 bits)` uint32 driverId = uint32(accountId >> DRIVER_ID_OFFSET); _assertCallerIsDriver(driverId); _; } /// @notice Verifies that the caller controls the given driver ID and reverts otherwise. /// @param driverId The driver ID. function _assertCallerIsDriver(uint32 driverId) internal view { require(driverAddress(driverId) == msg.sender, "Callable only by the driver"); } /// @notice Registers a driver. /// The driver is assigned a unique ID and a range of account IDs it can control. /// That range consists of all 2^224 account IDs with highest 32 bits equal to the driver ID. /// Every account ID is a 256-bit integer constructed by concatenating: /// `driverId (32 bits) | driverCustomData (224 bits)`. /// Every driver ID is assigned only to a single address, /// but a single address can have multiple driver IDs assigned to it. /// @param driverAddr The address of the driver. Must not be zero address. /// It should be a smart contract capable of dealing with the Drips API. /// It shouldn't be an EOA because the API requires making multiple calls per transaction. /// @return driverId The registered driver ID. function registerDriver(address driverAddr) public whenNotPaused returns (uint32 driverId) { require(driverAddr != address(0), "Driver registered for 0 address"); DripsStorage storage dripsStorage = _dripsStorage(); driverId = dripsStorage.nextDriverId++; dripsStorage.driverAddresses[driverId] = driverAddr; emit DriverRegistered(driverId, driverAddr); } /// @notice Returns the driver address. /// @param driverId The driver ID to look up. /// @return driverAddr The address of the driver. /// If the driver hasn't been registered yet, returns address 0. function driverAddress(uint32 driverId) public view returns (address driverAddr) { return _dripsStorage().driverAddresses[driverId]; } /// @notice Updates the driver address. Must be called from the current driver address. /// @param driverId The driver ID. /// @param newDriverAddr The new address of the driver. /// It should be a smart contract capable of dealing with the Drips API. /// It shouldn't be an EOA because the API requires making multiple calls per transaction. function updateDriverAddress(uint32 driverId, address newDriverAddr) public whenNotPaused { _assertCallerIsDriver(driverId); _dripsStorage().driverAddresses[driverId] = newDriverAddr; emit DriverAddressUpdated(driverId, msg.sender, newDriverAddr); } /// @notice Returns the driver ID which will be assigned for the next registered driver. /// @return driverId The next driver ID. function nextDriverId() public view returns (uint32 driverId) { return _dripsStorage().nextDriverId; } /// @notice Returns the amount currently stored in the protocol of the given token. /// The sum of streaming and splitting balances can never exceed `MAX_TOTAL_BALANCE`. /// The amount of tokens held by the Drips contract exceeding the sum of /// streaming and splitting balances can be `withdraw`n. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @return streamsBalance The balance currently stored in streaming. /// @return splitsBalance The balance currently stored in splitting. function balances(IERC20 erc20) public view returns (uint128 streamsBalance, uint128 splitsBalance) { Balance storage balance = _dripsStorage().balances[erc20]; return (balance.streams, balance.splits); } /// @notice Increases the balance of the given token currently stored in streams. /// No funds are transferred, all the tokens are expected to be already held by Drips. /// The new total balance is verified to have coverage in the held tokens /// and to be within the limit of `MAX_TOTAL_BALANCE`. /// @param erc20 The used ERC-20 token. /// @param amt The amount to increase the streams balance by. function _increaseStreamsBalance(IERC20 erc20, uint128 amt) internal { _verifyBalanceIncrease(erc20, amt); _dripsStorage().balances[erc20].streams += amt; } /// @notice Decreases the balance of the given token currently stored in streams. /// No funds are transferred, but the tokens held by Drips /// above the total balance become withdrawable. /// @param erc20 The used ERC-20 token. /// @param amt The amount to decrease the streams balance by. function _decreaseStreamsBalance(IERC20 erc20, uint128 amt) internal { _dripsStorage().balances[erc20].streams -= amt; } /// @notice Increases the balance of the given token currently stored in streams. /// No funds are transferred, all the tokens are expected to be already held by Drips. /// The new total balance is verified to have coverage in the held tokens /// and to be within the limit of `MAX_TOTAL_BALANCE`. /// @param erc20 The used ERC-20 token. /// @param amt The amount to increase the streams balance by. function _increaseSplitsBalance(IERC20 erc20, uint128 amt) internal { _verifyBalanceIncrease(erc20, amt); _dripsStorage().balances[erc20].splits += amt; } /// @notice Decreases the balance of the given token currently stored in splits. /// No funds are transferred, but the tokens held by Drips /// above the total balance become withdrawable. /// @param erc20 The used ERC-20 token. /// @param amt The amount to decrease the splits balance by. function _decreaseSplitsBalance(IERC20 erc20, uint128 amt) internal { _dripsStorage().balances[erc20].splits -= amt; } /// @notice Moves the balance of the given token currently stored in streams to splits. /// No funds are transferred, all the tokens are already held by Drips. /// @param erc20 The used ERC-20 token. /// @param amt The amount to decrease the splits balance by. function _moveBalanceFromStreamsToSplits(IERC20 erc20, uint128 amt) internal { Balance storage balance = _dripsStorage().balances[erc20]; balance.streams -= amt; balance.splits += amt; } /// @notice Verifies that the balance of streams or splits can be increased by the given amount. /// The sum of streaming and splitting balances is checked to not exceed /// `MAX_TOTAL_BALANCE` or the amount of tokens held by the Drips. /// @param erc20 The used ERC-20 token. /// @param amt The amount to increase the streams or splits balance by. function _verifyBalanceIncrease(IERC20 erc20, uint128 amt) internal view { (uint256 streamsBalance, uint128 splitsBalance) = balances(erc20); uint256 newTotalBalance = streamsBalance + splitsBalance + amt; require(newTotalBalance <= MAX_TOTAL_BALANCE, "Total balance too high"); require(newTotalBalance <= _tokenBalance(erc20), "Token balance too low"); } /// @notice Transfers withdrawable funds to an address. /// The withdrawable funds are held by the Drips contract, /// but not used in the protocol, so they are free to be transferred out. /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn /// or used in the protocol before any 3rd parties have a chance to do that. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param receiver The address to send withdrawn funds to. /// @param amt The withdrawn amount. /// It must be at most the difference between the balance of the token held by the Drips /// contract address and the sum of balances managed by the protocol as indicated by `balances`. function withdraw(IERC20 erc20, address receiver, uint256 amt) public { (uint128 streamsBalance, uint128 splitsBalance) = balances(erc20); uint256 withdrawable = _tokenBalance(erc20) - streamsBalance - splitsBalance; require(amt <= withdrawable, "Withdrawal amount too high"); emit Withdrawn(erc20, receiver, amt); erc20.safeTransfer(receiver, amt); } function _tokenBalance(IERC20 erc20) internal view returns (uint256) { return erc20.balanceOf(address(this)); } /// @notice Counts cycles from which streams can be collected. /// This function can be used to detect that there are /// too many cycles to analyze in a single transaction. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @return cycles The number of cycles which can be flushed function receivableStreamsCycles(uint256 accountId, IERC20 erc20) public view returns (uint32 cycles) { return Streams._receivableStreamsCycles(accountId, erc20); } /// @notice Calculate effects of calling `receiveStreams` with the given parameters. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param maxCycles The maximum number of received streams cycles. /// If too low, receiving will be cheap, but may not cover many cycles. /// If too high, receiving may become too expensive to fit in a single transaction. /// @return receivableAmt The amount which would be received function receiveStreamsResult(uint256 accountId, IERC20 erc20, uint32 maxCycles) public view returns (uint128 receivableAmt) { (receivableAmt,,,,) = Streams._receiveStreamsResult(accountId, erc20, maxCycles); } /// @notice Receive streams for the account. /// Received streams cycles won't need to be analyzed ever again. /// Calling this function does not collect but makes the funds ready to be split and collected. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param maxCycles The maximum number of received streams cycles. /// If too low, receiving will be cheap, but may not cover many cycles. /// If too high, receiving may become too expensive to fit in a single transaction. /// @return receivedAmt The received amount function receiveStreams(uint256 accountId, IERC20 erc20, uint32 maxCycles) public whenNotPaused returns (uint128 receivedAmt) { receivedAmt = Streams._receiveStreams(accountId, erc20, maxCycles); if (receivedAmt != 0) { _moveBalanceFromStreamsToSplits(erc20, receivedAmt); Splits._addSplittable(accountId, erc20, receivedAmt); } } /// @notice Receive streams from the currently running cycle from a single sender. /// It doesn't receive streams from the finished cycles, to do that use `receiveStreams`. /// Squeezed funds won't be received in the next calls to `squeezeStreams` or `receiveStreams`. /// Only funds streamed before `block.timestamp` can be squeezed. /// @param accountId The ID of the account receiving streams to squeeze funds for. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param senderId The ID of the streaming account to squeeze funds from. /// @param historyHash The sender's history hash that was valid right before /// they set up the sequence of configurations described by `streamsHistory`. /// @param streamsHistory The sequence of the sender's streams configurations. /// It can start at an arbitrary past configuration, but must describe all the configurations /// which have been used since then including the current one, in the chronological order. /// Only streams described by `streamsHistory` will be squeezed. /// If `streamsHistory` entries have no receivers, they won't be squeezed. /// @return amt The squeezed amount. function squeezeStreams( uint256 accountId, IERC20 erc20, uint256 senderId, bytes32 historyHash, StreamsHistory[] memory streamsHistory ) public whenNotPaused returns (uint128 amt) { amt = Streams._squeezeStreams(accountId, erc20, senderId, historyHash, streamsHistory); if (amt != 0) { _moveBalanceFromStreamsToSplits(erc20, amt); Splits._addSplittable(accountId, erc20, amt); } } /// @notice Calculate effects of calling `squeezeStreams` with the given parameters. /// See its documentation for more details. /// @param accountId The ID of the account receiving streams to squeeze funds for. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param senderId The ID of the streaming account to squeeze funds from. /// @param historyHash The sender's history hash that was valid right before `streamsHistory`. /// @param streamsHistory The sequence of the sender's streams configurations. /// @return amt The squeezed amount. function squeezeStreamsResult( uint256 accountId, IERC20 erc20, uint256 senderId, bytes32 historyHash, StreamsHistory[] memory streamsHistory ) public view returns (uint128 amt) { (amt,,,,) = Streams._squeezeStreamsResult(accountId, erc20, senderId, historyHash, streamsHistory); } /// @notice Returns account's received but not split yet funds. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @return amt The amount received but not split yet. function splittable(uint256 accountId, IERC20 erc20) public view returns (uint128 amt) { return Splits._splittable(accountId, erc20); } /// @notice Calculate the result of splitting an amount using the current splits configuration. /// @param accountId The account ID. /// @param currReceivers The list of the account's current splits receivers. /// It must be exactly the same as the last list set for the account with `setSplits`. /// @param amount The amount being split. /// @return collectableAmt The amount made collectable for the account /// on top of what was collectable before. /// @return splitAmt The amount split to the account's splits receivers function splitResult(uint256 accountId, SplitsReceiver[] memory currReceivers, uint128 amount) public view returns (uint128 collectableAmt, uint128 splitAmt) { return Splits._splitResult(accountId, currReceivers, amount); } /// @notice Splits the account's splittable funds among receivers. /// The entire splittable balance of the given ERC-20 token is split. /// All split funds are split using the current splits configuration. /// Because the account can update their splits configuration at any time, /// it is possible that calling this function will be frontrun, /// and all the splittable funds will become splittable only using the new configuration. /// The account must be trusted with how funds sent to them will be splits, /// in the end they can do with their funds whatever they want by changing the configuration. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param currReceivers The list of the account's current splits receivers. /// It must be exactly the same as the last list set for the account with `setSplits`. /// @return collectableAmt The amount made collectable for the account /// on top of what was collectable before. /// @return splitAmt The amount split to the account's splits receivers function split(uint256 accountId, IERC20 erc20, SplitsReceiver[] memory currReceivers) public whenNotPaused returns (uint128 collectableAmt, uint128 splitAmt) { return Splits._split(accountId, erc20, currReceivers); } /// @notice Returns account's received funds already split and ready to be collected. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @return amt The collectable amount. function collectable(uint256 accountId, IERC20 erc20) public view returns (uint128 amt) { return Splits._collectable(accountId, erc20); } /// @notice Collects account's received already split funds and makes them withdrawable. /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn /// or used in the protocol before any 3rd parties have a chance to do that. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @return amt The collected amount function collect(uint256 accountId, IERC20 erc20) public whenNotPaused onlyDriver(accountId) returns (uint128 amt) { amt = Splits._collect(accountId, erc20); if (amt != 0) _decreaseSplitsBalance(erc20, amt); } /// @notice Gives funds from the account to the receiver. /// The receiver can split and collect them immediately. /// Requires that the tokens used to give are already sent to Drips and are withdrawable. /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn /// or used in the protocol before any 3rd parties have a chance to do that. /// @param accountId The account ID. /// @param receiver The receiver account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param amt The given amount function give(uint256 accountId, uint256 receiver, IERC20 erc20, uint128 amt) public whenNotPaused onlyDriver(accountId) { if (amt != 0) _increaseSplitsBalance(erc20, amt); Splits._give(accountId, receiver, erc20, amt); } /// @notice Current account streams state. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @return streamsHash The current streams receivers list hash, see `hashStreams` /// @return streamsHistoryHash The current streams history hash, see `hashStreamsHistory`. /// @return updateTime The time when streams have been configured for the last time. /// @return balance The balance when streams have been configured for the last time. /// @return maxEnd The current maximum end time of streaming. function streamsState(uint256 accountId, IERC20 erc20) public view returns ( bytes32 streamsHash, bytes32 streamsHistoryHash, uint32 updateTime, uint128 balance, uint32 maxEnd ) { return Streams._streamsState(accountId, erc20); } /// @notice The account's streams balance at the given timestamp. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param currReceivers The current streams receivers list. /// It must be exactly the same as the last list set for the account with `setStreams`. /// @param timestamp The timestamps for which balance should be calculated. /// It can't be lower than the timestamp of the last call to `setStreams`. /// If it's bigger than `block.timestamp`, then it's a prediction assuming /// that `setStreams` won't be called before `timestamp`. /// @return balance The account balance on `timestamp` function balanceAt( uint256 accountId, IERC20 erc20, StreamReceiver[] memory currReceivers, uint32 timestamp ) public view returns (uint128 balance) { return Streams._balanceAt(accountId, erc20, currReceivers, timestamp); } /// @notice Sets the account's streams configuration. /// Requires that the tokens used to increase the streams balance /// are already sent to Drips and are withdrawable. /// If the streams balance is decreased, the released tokens become withdrawable. /// Anybody can call `withdraw`, so all withdrawable funds should be withdrawn /// or used in the protocol before any 3rd parties have a chance to do that. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param currReceivers The current streams receivers list. /// It must be exactly the same as the last list set for the account with `setStreams`. /// If this is the first update, pass an empty array. /// @param balanceDelta The streams balance change to be applied. /// Positive to add funds to the streams balance, negative to remove them. /// @param newReceivers The list of the streams receivers of the account to be set. /// Must be sorted by the receivers' addresses, deduplicated and without 0 amtPerSecs. /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The first hint for finding the maximum end time when all streams stop due to funds /// running out after the balance is updated and the new receivers list is applied. /// Hints have no effect on the results of calling this function, except potentially saving gas. /// Hints are Unix timestamps used as the starting points for binary search for the time /// when funds run out in the range of timestamps from the current block's to `2^32`. /// Hints lower than the current timestamp are ignored. /// You can provide zero, one or two hints. The order of hints doesn't matter. /// Hints are the most effective when one of them is lower than or equal to /// the last timestamp when funds are still streamed, and the other one is strictly larger /// than that timestamp,the smaller the difference between such hints, the higher gas savings. /// The savings are the highest possible when one of the hints is equal to /// the last timestamp when funds are still streamed, and the other one is larger by 1. /// It's worth noting that the exact timestamp of the block in which this function is executed /// may affect correctness of the hints, especially if they're precise. /// Hints don't provide any benefits when balance is not enough to cover /// a single second of streaming or is enough to cover all streams until timestamp `2^32`. /// Even inaccurate hints can be useful, and providing a single hint /// or two hints that don't enclose the time when funds run out can still save some gas. /// Providing poor hints that don't reduce the number of binary search steps /// may cause slightly higher gas usage than not providing any hints. /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details. /// @return realBalanceDelta The actually applied streams balance change. /// If it's lower than zero, it's the negative of the amount that became withdrawable. function setStreams( uint256 accountId, IERC20 erc20, StreamReceiver[] memory currReceivers, int128 balanceDelta, StreamReceiver[] memory newReceivers, // slither-disable-next-line similar-names uint32 maxEndHint1, uint32 maxEndHint2 ) public whenNotPaused onlyDriver(accountId) returns (int128 realBalanceDelta) { if (balanceDelta > 0) _increaseStreamsBalance(erc20, uint128(balanceDelta)); realBalanceDelta = Streams._setStreams( accountId, erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2 ); if (realBalanceDelta < 0) _decreaseStreamsBalance(erc20, uint128(-realBalanceDelta)); } /// @notice Calculates the hash of the streams configuration. /// It's used to verify if streams configuration is the previously set one. /// @param receivers The list of the streams receivers. /// Must be sorted by the receivers' addresses, deduplicated and without 0 amtPerSecs. /// If the streams have never been updated, pass an empty array. /// @return streamsHash The hash of the streams configuration function hashStreams(StreamReceiver[] memory receivers) public pure returns (bytes32 streamsHash) { return Streams._hashStreams(receivers); } /// @notice Calculates the hash of the streams history /// after the streams configuration is updated. /// @param oldStreamsHistoryHash The history hash /// that was valid before the streams were updated. /// The `streamsHistoryHash` of the account before they set streams for the first time is `0`. /// @param streamsHash The hash of the streams receivers being set. /// @param updateTime The timestamp when the streams were updated. /// @param maxEnd The maximum end of the streams being set. /// @return streamsHistoryHash The hash of the updated streams history. function hashStreamsHistory( bytes32 oldStreamsHistoryHash, bytes32 streamsHash, uint32 updateTime, uint32 maxEnd ) public pure returns (bytes32 streamsHistoryHash) { return Streams._hashStreamsHistory(oldStreamsHistoryHash, streamsHash, updateTime, maxEnd); } /// @notice Sets the account splits configuration. /// The configuration is common for all ERC-20 tokens. /// Nothing happens to the currently splittable funds, but when they are split /// after this function finishes, the new splits configuration will be used. /// Because anybody can call `split`, calling this function may be frontrun /// and all the currently splittable funds will be split using the old splits configuration. /// @param accountId The account ID. /// @param receivers The list of the account's splits receivers to be set. /// Must be sorted by the splits receivers' addresses, deduplicated and without 0 weights. /// Each splits receiver will be getting `weight / TOTAL_SPLITS_WEIGHT` /// share of the funds collected by the account. /// If the sum of weights of all receivers is less than `_TOTAL_SPLITS_WEIGHT`, /// some funds won't be split, but they will be left for the account to collect. /// It's valid to include the account's own `accountId` in the list of receivers, /// but funds split to themselves return to their splittable balance and are not collectable. /// This is usually unwanted, because if splitting is repeated, /// funds split to themselves will be again split using the current configuration. /// Splitting 100% to self effectively blocks splitting unless the configuration is updated. function setSplits(uint256 accountId, SplitsReceiver[] memory receivers) public whenNotPaused onlyDriver(accountId) { Splits._setSplits(accountId, receivers); } /// @notice Current account's splits hash, see `hashSplits`. /// @param accountId The account ID. /// @return currSplitsHash The current account's splits hash function splitsHash(uint256 accountId) public view returns (bytes32 currSplitsHash) { return Splits._splitsHash(accountId); } /// @notice Calculates the hash of the list of splits receivers. /// @param receivers The list of the splits receivers. /// Must be sorted by the splits receivers' addresses, deduplicated and without 0 weights. /// @return receiversHash The hash of the list of splits receivers. function hashSplits(SplitsReceiver[] memory receivers) public pure returns (bytes32 receiversHash) { return Splits._hashSplits(receivers); } /// @notice Emits account metadata. /// The keys and the values are not standardized by the protocol, it's up to the users /// to establish and follow conventions to ensure compatibility with the consumers. /// @param accountId The account ID. /// @param accountMetadata The list of account metadata. function emitAccountMetadata(uint256 accountId, AccountMetadata[] calldata accountMetadata) public whenNotPaused onlyDriver(accountId) { unchecked { for (uint256 i = 0; i < accountMetadata.length; i++) { AccountMetadata calldata metadata = accountMetadata[i]; emit AccountMetadataEmitted(accountId, metadata.key, metadata.value); } } } /// @notice Returns the Drips storage. /// @return storageRef The storage. function _dripsStorage() internal view returns (DripsStorage storage storageRef) { bytes32 slot = _dripsStorageSlot; // slither-disable-next-line assembly assembly { storageRef.slot := slot } } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.20; import {UUPSUpgradeable} from "openzeppelin-contracts/proxy/utils/UUPSUpgradeable.sol"; import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol"; import {StorageSlot} from "openzeppelin-contracts/utils/StorageSlot.sol"; using EnumerableSet for EnumerableSet.AddressSet; /// @notice A mix-in for contract pausing, upgrading and admin management. /// It can't be used directly, only via a proxy. It uses the upgrade-safe ERC-1967 storage scheme. /// /// Managed uses the ERC-1967 admin slot to store the admin address. /// All instances of the contracts have admin address `0x00` and are forever paused. /// When a proxy uses such contract via delegation, the proxy should define /// the initial admin address and the contract is initially unpaused. abstract contract Managed is UUPSUpgradeable { /// @notice The pointer to the storage slot holding a single `ManagedStorage` structure. bytes32 private immutable _managedStorageSlot = _erc1967Slot("eip1967.managed.storage"); /// @notice Emitted when a new admin of the contract is proposed. /// The proposed admin must call `acceptAdmin` to finalize the change. /// @param currentAdmin The current admin address. /// @param newAdmin The proposed admin address. event NewAdminProposed(address indexed currentAdmin, address indexed newAdmin); /// @notice Emitted when the pauses role is granted. /// @param pauser The address that the pauser role was granted to. /// @param admin The address of the admin that triggered the change. event PauserGranted(address indexed pauser, address indexed admin); /// @notice Emitted when the pauses role is revoked. /// @param pauser The address that the pauser role was revoked from. /// @param admin The address of the admin that triggered the change. event PauserRevoked(address indexed pauser, address indexed admin); /// @notice Emitted when the pause is triggered. /// @param pauser The address that triggered the change. event Paused(address indexed pauser); /// @notice Emitted when the pause is lifted. /// @param pauser The address that triggered the change. event Unpaused(address indexed pauser); struct ManagedStorage { bool isPaused; EnumerableSet.AddressSet pausers; address proposedAdmin; } /// @notice Throws if called by any caller other than the admin. modifier onlyAdmin() { require(admin() == msg.sender, "Caller not the admin"); _; } /// @notice Throws if called by any caller other than the admin or a pauser. modifier onlyAdminOrPauser() { require(admin() == msg.sender || isPauser(msg.sender), "Caller not the admin or a pauser"); _; } /// @notice Modifier to make a function callable only when the contract is not paused. modifier whenNotPaused() { require(!isPaused(), "Contract paused"); _; } /// @notice Modifier to make a function callable only when the contract is paused. modifier whenPaused() { require(isPaused(), "Contract not paused"); _; } /// @notice Initializes the contract in paused state and with no admin. /// The contract instance can be used only as a call delegation target for a proxy. constructor() { _managedStorage().isPaused = true; } /// @notice Returns the current implementation address. function implementation() public view returns (address) { return _getImplementation(); } /// @notice Returns the address of the current admin. function admin() public view returns (address) { return _getAdmin(); } /// @notice Returns the proposed address to change the admin to. function proposedAdmin() public view returns (address) { return _managedStorage().proposedAdmin; } /// @notice Proposes a change of the admin of the contract. /// The proposed new admin must call `acceptAdmin` to finalize the change. /// To cancel a proposal propose a different address, e.g. the zero address. /// Can only be called by the current admin. /// @param newAdmin The proposed admin address. function proposeNewAdmin(address newAdmin) public onlyAdmin { emit NewAdminProposed(msg.sender, newAdmin); _managedStorage().proposedAdmin = newAdmin; } /// @notice Applies a proposed change of the admin of the contract. /// Sets the proposed admin to the zero address. /// Can only be called by the proposed admin. function acceptAdmin() public { require(proposedAdmin() == msg.sender, "Caller not the proposed admin"); _updateAdmin(msg.sender); } /// @notice Changes the admin of the contract to address zero. /// It's no longer possible to change the admin or upgrade the contract afterwards. /// Can only be called by the current admin. function renounceAdmin() public onlyAdmin { _updateAdmin(address(0)); } /// @notice Sets the current admin of the contract and clears the proposed admin. /// @param newAdmin The admin address being set. Can be the zero address. function _updateAdmin(address newAdmin) internal { emit AdminChanged(admin(), newAdmin); _managedStorage().proposedAdmin = address(0); StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /// @notice Grants the pauser role to an address. Callable only by the admin. /// @param pauser The granted address. function grantPauser(address pauser) public onlyAdmin { require(_managedStorage().pausers.add(pauser), "Address already is a pauser"); emit PauserGranted(pauser, msg.sender); } /// @notice Revokes the pauser role from an address. Callable only by the admin. /// @param pauser The revoked address. function revokePauser(address pauser) public onlyAdmin { require(_managedStorage().pausers.remove(pauser), "Address is not a pauser"); emit PauserRevoked(pauser, msg.sender); } /// @notice Checks if an address is a pauser. /// @param pauser The checked address. /// @return isAddrPauser True if the address is a pauser. function isPauser(address pauser) public view returns (bool isAddrPauser) { return _managedStorage().pausers.contains(pauser); } /// @notice Returns all the addresses with the pauser role. /// @return pausersList The list of all the pausers, ordered arbitrarily. /// The list's order may change after granting or revoking the pauser role. function allPausers() public view returns (address[] memory pausersList) { return _managedStorage().pausers.values(); } /// @notice Returns true if the contract is paused, and false otherwise. function isPaused() public view returns (bool) { return _managedStorage().isPaused; } /// @notice Triggers stopped state. Callable only by the admin or a pauser. function pause() public onlyAdminOrPauser whenNotPaused { _managedStorage().isPaused = true; emit Paused(msg.sender); } /// @notice Returns to normal state. Callable only by the admin or a pauser. function unpause() public onlyAdminOrPauser whenPaused { _managedStorage().isPaused = false; emit Unpaused(msg.sender); } /// @notice Calculates the quasi ERC-1967 slot pointer. /// @param name The name of the slot, should be globally unique /// @return slot The slot pointer function _erc1967Slot(string memory name) internal pure returns (bytes32 slot) { // The original ERC-1967 subtracts 1 from the hash to get 1 storage slot // under an index without a known hash preimage which is enough to store a single address. // This implementation subtracts 1024 to get 1024 slots without a known preimage // allowing securely storing much larger structures. return bytes32(uint256(keccak256(bytes(name))) - 1024); } /// @notice Returns the Managed storage. /// @return storageRef The storage. function _managedStorage() internal view returns (ManagedStorage storage storageRef) { bytes32 slot = _managedStorageSlot; // slither-disable-next-line assembly assembly { storageRef.slot := slot } } /// @notice Authorizes the contract upgrade. See `UUPSUpgradeable` docs for more details. function _authorizeUpgrade(address /* newImplementation */ ) internal view override onlyAdmin { return; } } /// @notice A generic proxy for contracts implementing `Managed`. contract ManagedProxy is ERC1967Proxy { constructor(Managed logic, address admin) ERC1967Proxy(address(logic), new bytes(0)) { _changeAdmin(admin); } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.20; import {Drips, StreamReceiver, IERC20, SafeERC20} from "./Drips.sol"; import {ERC2771Context} from "openzeppelin-contracts/metatx/ERC2771Context.sol"; /// @notice ERC-20 token transfer utilities for drivers. /// Encapsulates the logic for token transfers made by drivers implementing user identities. /// All funds going into Drips are transferred ad-hoc from the caller (`msg.sender`), /// and all funds going out of Drips are transferred in full to the provided address. /// Compatible with `Caller` by supporting ERC-2771. abstract contract DriverTransferUtils is ERC2771Context { /// @param forwarder The ERC-2771 forwarder to trust. May be the zero address. constructor(address forwarder) ERC2771Context(forwarder) {} /// @notice Returns the address of the Drips contract to use for ERC-20 transfers. function _drips() internal virtual returns (Drips); /// @notice Collects the account's received already split funds /// and transfers them out of the Drips contract. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param transferTo The address to send collected funds to /// @return amt The collected amount function _collectAndTransfer(uint256 accountId, IERC20 erc20, address transferTo) internal returns (uint128 amt) { amt = _drips().collect(accountId, erc20); if (amt > 0) _drips().withdraw(erc20, transferTo, amt); } /// @notice Gives funds from the message sender to the receiver. /// The receiver can split and collect them immediately. /// Transfers the funds to be given from the message sender's wallet to the Drips contract. /// @param receiver The receiver account ID. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param amt The given amount function _giveAndTransfer(uint256 accountId, uint256 receiver, IERC20 erc20, uint128 amt) internal { if (amt > 0) _transferFromCaller(erc20, amt); _drips().give(accountId, receiver, erc20, amt); } /// @notice Sets the message sender's streams configuration. /// Transfers funds between the message sender's wallet and the Drips contract /// to fulfil the change of the streams balance. /// @param erc20 The used ERC-20 token. /// It must preserve amounts, so if some amount of tokens is transferred to /// an address, then later the same amount must be transferable from that address. /// Tokens which rebase the holders' balances, collect taxes on transfers, /// or impose any restrictions on holding or transferring tokens are not supported. /// If you use such tokens in the protocol, they can get stuck or lost. /// @param currReceivers The current streams receivers list. /// It must be exactly the same as the last list set for the sender with `setStreams`. /// If this is the first update, pass an empty array. /// @param balanceDelta The streams balance change to be applied. /// Positive to add funds to the streams balance, negative to remove them. /// @param newReceivers The list of the streams receivers of the sender to be set. /// Must be sorted by the receivers' addresses, deduplicated and without 0 amtPerSecs. /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The first hint for finding the maximum end time when all streams stop due to funds /// running out after the balance is updated and the new receivers list is applied. /// Hints have no effect on the results of calling this function, except potentially saving gas. /// Hints are Unix timestamps used as the starting points for binary search for the time /// when funds run out in the range of timestamps from the current block's to `2^32`. /// Hints lower than the current timestamp are ignored. /// You can provide zero, one or two hints. The order of hints doesn't matter. /// Hints are the most effective when one of them is lower than or equal to /// the last timestamp when funds are still streamed, and the other one is strictly larger /// than that timestamp,the smaller the difference between such hints, the higher gas savings. /// The savings are the highest possible when one of the hints is equal to /// the last timestamp when funds are still streamed, and the other one is larger by 1. /// It's worth noting that the exact timestamp of the block in which this function is executed /// may affect correctness of the hints, especially if they're precise. /// Hints don't provide any benefits when balance is not enough to cover /// a single second of streaming or is enough to cover all streams until timestamp `2^32`. /// Even inaccurate hints can be useful, and providing a single hint /// or two hints that don't enclose the time when funds run out can still save some gas. /// Providing poor hints that don't reduce the number of binary search steps /// may cause slightly higher gas usage than not providing any hints. /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details. /// @param transferTo The address to send funds to in case of decreasing balance /// @return realBalanceDelta The actually applied streams balance change. function _setStreamsAndTransfer( uint256 accountId, IERC20 erc20, StreamReceiver[] calldata currReceivers, int128 balanceDelta, StreamReceiver[] calldata newReceivers, // slither-disable-next-line similar-names uint32 maxEndHint1, uint32 maxEndHint2, address transferTo ) internal returns (int128 realBalanceDelta) { if (balanceDelta > 0) _transferFromCaller(erc20, uint128(balanceDelta)); realBalanceDelta = _drips().setStreams( accountId, erc20, currReceivers, balanceDelta, newReceivers, maxEndHint1, maxEndHint2 ); if (realBalanceDelta < 0) _drips().withdraw(erc20, transferTo, uint128(-realBalanceDelta)); } /// @notice Transfers tokens from the sender to Drips. /// @param erc20 The used ERC-20 token. /// @param amt The transferred amount function _transferFromCaller(IERC20 erc20, uint128 amt) internal { SafeERC20.safeTransferFrom(erc20, _msgSender(), address(_drips()), amt); } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.20; import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; /// @notice A stream receiver struct StreamReceiver { /// @notice The account ID. uint256 accountId; /// @notice The stream configuration. StreamConfig config; } /// @notice The sender streams history entry, used when squeezing streams. struct StreamsHistory { /// @notice Streams receivers list hash, see `_hashStreams`. /// If it's non-zero, `receivers` must be empty. bytes32 streamsHash; /// @notice The streams receivers. If it's non-empty, `streamsHash` must be `0`. /// If it's empty, this history entry will be skipped when squeezing streams /// and `streamsHash` will be used when verifying the streams history validity. /// Skipping a history entry allows cutting gas usage on analysis /// of parts of the streams history which are not worth squeezing. /// The hash of an empty receivers list is `0`, so when the sender updates /// their receivers list to be empty, the new `StreamsHistory` entry will have /// both the `streamsHash` equal to `0` and the `receivers` empty making it always skipped. /// This is fine, because there can't be any funds to squeeze from that entry anyway. StreamReceiver[] receivers; /// @notice The time when streams have been configured uint32 updateTime; /// @notice The maximum end time of streaming. uint32 maxEnd; } /// @notice Describes a streams configuration. /// It's a 256-bit integer constructed by concatenating the configuration parameters: /// `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)`. /// `streamId` is an arbitrary number used to identify a stream. /// It's a part of the configuration but the protocol doesn't use it. /// `amtPerSec` is the amount per second being streamed. Must never be zero. /// It must have additional `Streams._AMT_PER_SEC_EXTRA_DECIMALS` decimals and can have fractions. /// To achieve that its value must be multiplied by `Streams._AMT_PER_SEC_MULTIPLIER`. /// `start` is the timestamp when streaming should start. /// If zero, use the timestamp when the stream is configured. /// `duration` is the duration of streaming. /// If zero, stream until balance runs out. type StreamConfig is uint256; using StreamConfigImpl for StreamConfig global; library StreamConfigImpl { /// @notice Create a new StreamConfig. /// @param streamId_ An arbitrary number used to identify a stream. /// It's a part of the configuration but the protocol doesn't use it. /// @param amtPerSec_ The amount per second being streamed. Must never be zero. /// It must have additional `Streams._AMT_PER_SEC_EXTRA_DECIMALS` /// decimals and can have fractions. /// To achieve that the passed value must be multiplied by `Streams._AMT_PER_SEC_MULTIPLIER`. /// @param start_ The timestamp when streaming should start. /// If zero, use the timestamp when the stream is configured. /// @param duration_ The duration of streaming. If zero, stream until the balance runs out. function create(uint32 streamId_, uint160 amtPerSec_, uint32 start_, uint32 duration_) internal pure returns (StreamConfig) { // By assignment we get `config` value: // `zeros (224 bits) | streamId (32 bits)` uint256 config = streamId_; // By bit shifting we get `config` value: // `zeros (64 bits) | streamId (32 bits) | zeros (160 bits)` // By bit masking we get `config` value: // `zeros (64 bits) | streamId (32 bits) | amtPerSec (160 bits)` config = (config << 160) | amtPerSec_; // By bit shifting we get `config` value: // `zeros (32 bits) | streamId (32 bits) | amtPerSec (160 bits) | zeros (32 bits)` // By bit masking we get `config` value: // `zeros (32 bits) | streamId (32 bits) | amtPerSec (160 bits) | start (32 bits)` config = (config << 32) | start_; // By bit shifting we get `config` value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | zeros (32 bits)` // By bit masking we get `config` value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)` config = (config << 32) | duration_; return StreamConfig.wrap(config); } /// @notice Extracts streamId from a `StreamConfig` function streamId(StreamConfig config) internal pure returns (uint32) { // `config` has value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)` // By bit shifting we get value: // `zeros (224 bits) | streamId (32 bits)` // By casting down we get value: // `streamId (32 bits)` return uint32(StreamConfig.unwrap(config) >> 224); } /// @notice Extracts amtPerSec from a `StreamConfig` function amtPerSec(StreamConfig config) internal pure returns (uint160) { // `config` has value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)` // By bit shifting we get value: // `zeros (64 bits) | streamId (32 bits) | amtPerSec (160 bits)` // By casting down we get value: // `amtPerSec (160 bits)` return uint160(StreamConfig.unwrap(config) >> 64); } /// @notice Extracts start from a `StreamConfig` function start(StreamConfig config) internal pure returns (uint32) { // `config` has value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)` // By bit shifting we get value: // `zeros (32 bits) | streamId (32 bits) | amtPerSec (160 bits) | start (32 bits)` // By casting down we get value: // `start (32 bits)` return uint32(StreamConfig.unwrap(config) >> 32); } /// @notice Extracts duration from a `StreamConfig` function duration(StreamConfig config) internal pure returns (uint32) { // `config` has value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)` // By casting down we get value: // `duration (32 bits)` return uint32(StreamConfig.unwrap(config)); } /// @notice Compares two `StreamConfig`s. /// First compares `streamId`s, then `amtPerSec`s, then `start`s and finally `duration`s. /// @return isLower True if `config` is strictly lower than `otherConfig`. function lt(StreamConfig config, StreamConfig otherConfig) internal pure returns (bool isLower) { // Both configs have value: // `streamId (32 bits) | amtPerSec (160 bits) | start (32 bits) | duration (32 bits)` // Comparing them as integers is equivalent to comparing their fields from left to right. return StreamConfig.unwrap(config) < StreamConfig.unwrap(otherConfig); } } /// @notice Streams can keep track of at most `type(int128).max` /// which is `2 ^ 127 - 1` units of each ERC-20 token. /// It's up to the caller to guarantee that this limit is never exceeded, /// failing to do so may result in a total protocol collapse. abstract contract Streams { /// @notice Maximum number of streams receivers of a single account. /// Limits cost of changes in streams configuration. uint256 internal constant _MAX_STREAMS_RECEIVERS = 100; /// @notice The additional decimals for all amtPerSec values. uint8 internal constant _AMT_PER_SEC_EXTRA_DECIMALS = 9; /// @notice The multiplier for all amtPerSec values. It's `10 ** _AMT_PER_SEC_EXTRA_DECIMALS`. uint160 internal constant _AMT_PER_SEC_MULTIPLIER = 1_000_000_000; /// @notice The amount the contract can keep track of each ERC-20 token. uint128 internal constant _MAX_STREAMS_BALANCE = uint128(type(int128).max); /// @notice On every timestamp `T`, which is a multiple of `cycleSecs`, the receivers /// gain access to streams received during `T - cycleSecs` to `T - 1`. /// Always higher than 1. // slither-disable-next-line naming-convention uint32 internal immutable _cycleSecs; /// @notice The minimum amtPerSec of a stream. It's 1 token per cycle. // slither-disable-next-line naming-convention uint160 internal immutable _minAmtPerSec; /// @notice The storage slot holding a single `StreamsStorage` structure. bytes32 private immutable _streamsStorageSlot; /// @notice Emitted when the streams configuration of an account is updated. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param receiversHash The streams receivers list hash /// @param streamsHistoryHash The streams history hash that was valid right before the update. /// @param balance The account's streams balance. These funds will be streamed to the receivers. /// @param maxEnd The maximum end time of streaming, when funds run out. /// If funds run out after the timestamp `type(uint32).max`, it's set to `type(uint32).max`. /// If the balance is 0 or there are no receivers, it's set to the current timestamp. event StreamsSet( uint256 indexed accountId, IERC20 indexed erc20, bytes32 indexed receiversHash, bytes32 streamsHistoryHash, uint128 balance, uint32 maxEnd ); /// @notice Emitted when an account is seen in a streams receivers list. /// @param receiversHash The streams receivers list hash /// @param accountId The account ID. /// @param config The streams configuration. event StreamReceiverSeen( bytes32 indexed receiversHash, uint256 indexed accountId, StreamConfig config ); /// @notice Emitted when streams are received. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param amt The received amount. /// @param receivableCycles The number of cycles which still can be received. event ReceivedStreams( uint256 indexed accountId, IERC20 indexed erc20, uint128 amt, uint32 receivableCycles ); /// @notice Emitted when streams are squeezed. /// @param accountId The squeezing account ID. /// @param erc20 The used ERC-20 token. /// @param senderId The ID of the streaming account from whom funds are squeezed. /// @param amt The squeezed amount. /// @param streamsHistoryHashes The history hashes of all squeezed streams history entries. /// Each history hash matches `streamsHistoryHash` emitted in its `StreamsSet` /// when the squeezed streams configuration was set. /// Sorted in the oldest streams configuration to the newest. event SqueezedStreams( uint256 indexed accountId, IERC20 indexed erc20, uint256 indexed senderId, uint128 amt, bytes32[] streamsHistoryHashes ); struct StreamsStorage { /// @notice Account streams states. mapping(IERC20 erc20 => mapping(uint256 accountId => StreamsState)) states; } struct StreamsState { /// @notice The streams history hash, see `_hashStreamsHistory`. bytes32 streamsHistoryHash; /// @notice The next squeezable timestamps. /// Each `N`th element of the array is the next squeezable timestamp /// of the `N`th sender's streams configuration in effect in the current cycle. mapping(uint256 accountId => uint32[2 ** 32]) nextSqueezed; /// @notice The streams receivers list hash, see `_hashStreams`. bytes32 streamsHash; /// @notice The next cycle to be received uint32 nextReceivableCycle; /// @notice The time when streams have been configured for the last time. uint32 updateTime; /// @notice The maximum end time of streaming. uint32 maxEnd; /// @notice The balance when streams have been configured for the last time. uint128 balance; /// @notice The number of streams configurations seen in the current cycle uint32 currCycleConfigs; /// @notice The changes of received amounts on specific cycle. /// The keys are cycles, each cycle `C` becomes receivable on timestamp `C * cycleSecs`. /// Values for cycles before `nextReceivableCycle` are guaranteed to be zeroed. /// This means that the value of `amtDeltas[nextReceivableCycle].thisCycle` is always /// relative to 0 or in other words it's an absolute value independent from other cycles. mapping(uint32 cycle => AmtDelta) amtDeltas; } struct AmtDelta { /// @notice Amount delta applied on this cycle int128 thisCycle; /// @notice Amount delta applied on the next cycle int128 nextCycle; } /// @param cycleSecs The length of cycleSecs to be used in the contract instance. /// Low value makes funds more available by shortening the average time /// of funds being frozen between being taken from the accounts' /// streams balance and being receivable by their receivers. /// High value makes receiving cheaper by making it process less cycles for a given time range. /// Must be higher than 1. /// @param streamsStorageSlot The storage slot to holding a single `StreamsStorage` structure. constructor(uint32 cycleSecs, bytes32 streamsStorageSlot) { require(cycleSecs > 1, "Cycle length too low"); _cycleSecs = cycleSecs; _minAmtPerSec = (_AMT_PER_SEC_MULTIPLIER + cycleSecs - 1) / cycleSecs; _streamsStorageSlot = streamsStorageSlot; } /// @notice Receive streams from unreceived cycles of the account. /// Received streams cycles won't need to be analyzed ever again. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param maxCycles The maximum number of received streams cycles. /// If too low, receiving will be cheap, but may not cover many cycles. /// If too high, receiving may become too expensive to fit in a single transaction. /// @return receivedAmt The received amount function _receiveStreams(uint256 accountId, IERC20 erc20, uint32 maxCycles) internal returns (uint128 receivedAmt) { uint32 receivableCycles; uint32 fromCycle; uint32 toCycle; int128 finalAmtPerCycle; (receivedAmt, receivableCycles, fromCycle, toCycle, finalAmtPerCycle) = _receiveStreamsResult(accountId, erc20, maxCycles); if (fromCycle != toCycle) { StreamsState storage state = _streamsStorage().states[erc20][accountId]; state.nextReceivableCycle = toCycle; mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas; unchecked { for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) { delete amtDeltas[cycle]; } // The next cycle delta must be relative to the last received cycle, which deltas // got zeroed. In other words the next cycle delta must be an absolute value. if (finalAmtPerCycle != 0) { amtDeltas[toCycle].thisCycle += finalAmtPerCycle; } } } emit ReceivedStreams(accountId, erc20, receivedAmt, receivableCycles); } /// @notice Calculate effects of calling `_receiveStreams` with the given parameters. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param maxCycles The maximum number of received streams cycles. /// If too low, receiving will be cheap, but may not cover many cycles. /// If too high, receiving may become too expensive to fit in a single transaction. /// @return receivedAmt The amount which would be received /// @return receivableCycles The number of cycles which would still be receivable after the call /// @return fromCycle The cycle from which funds would be received /// @return toCycle The cycle to which funds would be received /// @return amtPerCycle The amount per cycle when `toCycle` starts. function _receiveStreamsResult(uint256 accountId, IERC20 erc20, uint32 maxCycles) internal view returns ( uint128 receivedAmt, uint32 receivableCycles, uint32 fromCycle, uint32 toCycle, int128 amtPerCycle ) { unchecked { (fromCycle, toCycle) = _receivableStreamsCyclesRange(accountId, erc20); if (toCycle - fromCycle > maxCycles) { receivableCycles = toCycle - fromCycle - maxCycles; toCycle -= receivableCycles; } mapping(uint32 cycle => AmtDelta) storage amtDeltas = _streamsStorage().states[erc20][accountId].amtDeltas; for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) { AmtDelta memory amtDelta = amtDeltas[cycle]; amtPerCycle += amtDelta.thisCycle; receivedAmt += uint128(amtPerCycle); amtPerCycle += amtDelta.nextCycle; } } } /// @notice Counts cycles from which streams can be received. /// This function can be used to detect that there are /// too many cycles to analyze in a single transaction. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @return cycles The number of cycles which can be flushed function _receivableStreamsCycles(uint256 accountId, IERC20 erc20) internal view returns (uint32 cycles) { unchecked { (uint32 fromCycle, uint32 toCycle) = _receivableStreamsCyclesRange(accountId, erc20); return toCycle - fromCycle; } } /// @notice Calculates the cycles range from which streams can be received. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @return fromCycle The cycle from which funds can be received /// @return toCycle The cycle to which funds can be received function _receivableStreamsCyclesRange(uint256 accountId, IERC20 erc20) private view returns (uint32 fromCycle, uint32 toCycle) { fromCycle = _streamsStorage().states[erc20][accountId].nextReceivableCycle; toCycle = _cycleOf(_currTimestamp()); // slither-disable-next-line timestamp if (fromCycle == 0 || toCycle < fromCycle) { toCycle = fromCycle; } } /// @notice Receive streams from the currently running cycle from a single sender. /// It doesn't receive streams from the finished cycles, to do that use `_receiveStreams`. /// Squeezed funds won't be received in the next calls /// to `_squeezeStreams` or `_receiveStreams`. /// Only funds streamed before `block.timestamp` can be squeezed. /// @param accountId The ID of the account receiving streams to squeeze funds for. /// @param erc20 The used ERC-20 token. /// @param senderId The ID of the streaming account to squeeze funds from. /// @param historyHash The sender's history hash that was valid right before /// they set up the sequence of configurations described by `streamsHistory`. /// @param streamsHistory The sequence of the sender's streams configurations. /// It can start at an arbitrary past configuration, but must describe all the configurations /// which have been used since then including the current one, in the chronological order. /// Only streams described by `streamsHistory` will be squeezed. /// If `streamsHistory` entries have no receivers, they won't be squeezed. /// @return amt The squeezed amount. function _squeezeStreams( uint256 accountId, IERC20 erc20, uint256 senderId, bytes32 historyHash, StreamsHistory[] memory streamsHistory ) internal returns (uint128 amt) { unchecked { uint256 squeezedNum; uint256[] memory squeezedRevIdxs; bytes32[] memory historyHashes; uint256 currCycleConfigs; (amt, squeezedNum, squeezedRevIdxs, historyHashes, currCycleConfigs) = _squeezeStreamsResult(accountId, erc20, senderId, historyHash, streamsHistory); bytes32[] memory squeezedHistoryHashes = new bytes32[](squeezedNum); StreamsState storage state = _streamsStorage().states[erc20][accountId]; uint32[2 ** 32] storage nextSqueezed = state.nextSqueezed[senderId]; for (uint256 i = 0; i < squeezedNum; i++) { // `squeezedRevIdxs` are sorted from the newest configuration to the oldest, // but we need to consume them from the oldest to the newest. uint256 revIdx = squeezedRevIdxs[squeezedNum - i - 1]; squeezedHistoryHashes[i] = historyHashes[historyHashes.length - revIdx]; nextSqueezed[currCycleConfigs - revIdx] = _currTimestamp(); } uint32 cycleStart = _currCycleStart(); _addDeltaRange( state, cycleStart, cycleStart + 1, -int160(amt * _AMT_PER_SEC_MULTIPLIER) ); emit SqueezedStreams(accountId, erc20, senderId, amt, squeezedHistoryHashes); } } /// @notice Calculate effects of calling `_squeezeStreams` with the given parameters. /// See its documentation for more details. /// @param accountId The ID of the account receiving streams to squeeze funds for. /// @param erc20 The used ERC-20 token. /// @param senderId The ID of the streaming account to squeeze funds from. /// @param historyHash The sender's history hash that was valid right before `streamsHistory`. /// @param streamsHistory The sequence of the sender's streams configurations. /// @return amt The squeezed amount. /// @return squeezedNum The number of squeezed history entries. /// @return squeezedRevIdxs The indexes of the squeezed history entries. /// The indexes are reversed, meaning that to get the actual index in an array, /// they must counted from the end of arrays, as in `arrayLength - squeezedRevIdxs[i]`. /// These indexes can be safely used to access `streamsHistory`, `historyHashes` /// and `nextSqueezed` regardless of their lengths. /// `squeezeRevIdxs` is sorted ascending, from pointing at the most recent entry to the oldest. /// @return historyHashes The history hashes valid /// for squeezing each of `streamsHistory` entries. /// In other words history hashes which had been valid right before each streams /// configuration was set, matching `streamsHistoryHash` emitted in its `StreamsSet`. /// The first item is always equal to `historyHash`. /// @return currCycleConfigs The number of the sender's /// streams configurations which have been seen in the current cycle. /// This is also the number of used entries in each of the sender's `nextSqueezed` arrays. function _squeezeStreamsResult( uint256 accountId, IERC20 erc20, uint256 senderId, bytes32 historyHash, StreamsHistory[] memory streamsHistory ) internal view returns ( uint128 amt, uint256 squeezedNum, uint256[] memory squeezedRevIdxs, bytes32[] memory historyHashes, uint256 currCycleConfigs ) { { StreamsState storage sender = _streamsStorage().states[erc20][senderId]; historyHashes = _verifyStreamsHistory(historyHash, streamsHistory, sender.streamsHistoryHash); // If the last update was not in the current cycle, // there's only the single latest history entry to squeeze in the current cycle. currCycleConfigs = 1; // slither-disable-next-line timestamp if (sender.updateTime >= _currCycleStart()) currCycleConfigs = sender.currCycleConfigs; } squeezedRevIdxs = new uint256[](streamsHistory.length); uint32[2 ** 32] storage nextSqueezed = _streamsStorage().states[erc20][accountId].nextSqueezed[senderId]; uint32 squeezeEndCap = _currTimestamp(); unchecked { for (uint256 i = 1; i <= streamsHistory.length && i <= currCycleConfigs; i++) { StreamsHistory memory historyEntry = streamsHistory[streamsHistory.length - i]; if (historyEntry.receivers.length != 0) { uint32 squeezeStartCap = nextSqueezed[currCycleConfigs - i]; if (squeezeStartCap < _currCycleStart()) squeezeStartCap = _currCycleStart(); if (squeezeStartCap < historyEntry.updateTime) { squeezeStartCap = historyEntry.updateTime; } if (squeezeStartCap < squeezeEndCap) { squeezedRevIdxs[squeezedNum++] = i; amt += _squeezedAmt(accountId, historyEntry, squeezeStartCap, squeezeEndCap); } } squeezeEndCap = historyEntry.updateTime; } } } /// @notice Verify a streams history and revert if it's invalid. /// @param historyHash The account's history hash that was valid right before `streamsHistory`. /// @param streamsHistory The sequence of the account's streams configurations. /// @param finalHistoryHash The history hash at the end of `streamsHistory`. /// @return historyHashes The history hashes valid /// for squeezing each of `streamsHistory` entries. /// In other words history hashes which had been valid right before each streams /// configuration was set, matching `streamsHistoryHash`es emitted in `StreamsSet`. /// The first item is always equal to `historyHash` and `finalHistoryHash` is never included. function _verifyStreamsHistory( bytes32 historyHash, StreamsHistory[] memory streamsHistory, bytes32 finalHistoryHash ) private pure returns (bytes32[] memory historyHashes) { historyHashes = new bytes32[](streamsHistory.length); for (uint256 i = 0; i < streamsHistory.length; i++) { StreamsHistory memory historyEntry = streamsHistory[i]; bytes32 streamsHash = historyEntry.streamsHash; if (historyEntry.receivers.length != 0) { require(streamsHash == 0, "Entry with hash and receivers"); streamsHash = _hashStreams(historyEntry.receivers); } historyHashes[i] = historyHash; historyHash = _hashStreamsHistory( historyHash, streamsHash, historyEntry.updateTime, historyEntry.maxEnd ); } // slither-disable-next-line incorrect-equality,timestamp require(historyHash == finalHistoryHash, "Invalid streams history"); } /// @notice Calculate the amount squeezable by an account from a single streams history entry. /// @param accountId The ID of the account to squeeze streams for. /// @param historyEntry The squeezed history entry. /// @param squeezeStartCap The squeezed time range start. /// @param squeezeEndCap The squeezed time range end. /// @return squeezedAmt The squeezed amount. function _squeezedAmt( uint256 accountId, StreamsHistory memory historyEntry, uint32 squeezeStartCap, uint32 squeezeEndCap ) private view returns (uint128 squeezedAmt) { unchecked { StreamReceiver[] memory receivers = historyEntry.receivers; // Binary search for the `idx` of the first occurrence of `accountId` uint256 idx = 0; for (uint256 idxCap = receivers.length; idx < idxCap;) { uint256 idxMid = (idx + idxCap) / 2; if (receivers[idxMid].accountId < accountId) { idx = idxMid + 1; } else { idxCap = idxMid; } } uint32 updateTime = historyEntry.updateTime; uint32 maxEnd = historyEntry.maxEnd; uint256 amt = 0; for (; idx < receivers.length; idx++) { StreamReceiver memory receiver = receivers[idx]; if (receiver.accountId != accountId) break; (uint32 start, uint32 end) = _streamRange(receiver, updateTime, maxEnd, squeezeStartCap, squeezeEndCap); amt += _streamedAmt(receiver.config.amtPerSec(), start, end); } return uint128(amt); } } /// @notice Current account streams state. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @return streamsHash The current streams receivers list hash, see `_hashStreams` /// @return streamsHistoryHash The current streams history hash, see `_hashStreamsHistory`. /// @return updateTime The time when streams have been configured for the last time. /// @return balance The balance when streams have been configured for the last time. /// @return maxEnd The current maximum end time of streaming. function _streamsState(uint256 accountId, IERC20 erc20) internal view returns ( bytes32 streamsHash, bytes32 streamsHistoryHash, uint32 updateTime, uint128 balance, uint32 maxEnd ) { StreamsState storage state = _streamsStorage().states[erc20][accountId]; return ( state.streamsHash, state.streamsHistoryHash, state.updateTime, state.balance, state.maxEnd ); } /// @notice The account's streams balance at the given timestamp. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param currReceivers The current streams receivers list. /// It must be exactly the same as the last list set for the account with `_setStreams`. /// @param timestamp The timestamps for which balance should be calculated. /// It can't be lower than the timestamp of the last call to `_setStreams`. /// If it's bigger than `block.timestamp`, then it's a prediction assuming /// that `_setStreams` won't be called before `timestamp`. /// @return balance The account balance on `timestamp` function _balanceAt( uint256 accountId, IERC20 erc20, StreamReceiver[] memory currReceivers, uint32 timestamp ) internal view returns (uint128 balance) { StreamsState storage state = _streamsStorage().states[erc20][accountId]; require(timestamp >= state.updateTime, "Timestamp before the last update"); _verifyStreamsReceivers(currReceivers, state); return _calcBalance(state.balance, state.updateTime, state.maxEnd, currReceivers, timestamp); } /// @notice Calculates the streams balance at a given timestamp. /// @param lastBalance The balance when streaming started. /// @param lastUpdate The timestamp when streaming started. /// @param maxEnd The maximum end time of streaming. /// @param receivers The list of streams receivers. /// @param timestamp The timestamps for which balance should be calculated. /// It can't be lower than `lastUpdate`. /// If it's bigger than `block.timestamp`, then it's a prediction assuming /// that `_setStreams` won't be called before `timestamp`. /// @return balance The account balance on `timestamp` function _calcBalance( uint128 lastBalance, uint32 lastUpdate, uint32 maxEnd, StreamReceiver[] memory receivers, uint32 timestamp ) private view returns (uint128 balance) { unchecked { balance = lastBalance; for (uint256 i = 0; i < receivers.length; i++) { StreamReceiver memory receiver = receivers[i]; (uint32 start, uint32 end) = _streamRange({ receiver: receiver, updateTime: lastUpdate, maxEnd: maxEnd, startCap: lastUpdate, endCap: timestamp }); balance -= uint128(_streamedAmt(receiver.config.amtPerSec(), start, end)); } } } /// @notice Sets the account's streams configuration. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param currReceivers The current streams receivers list. /// It must be exactly the same as the last list set for the account with `_setStreams`. /// If this is the first update, pass an empty array. /// @param balanceDelta The streams balance change being applied. /// Positive when adding funds to the streams balance, negative to removing them. /// @param newReceivers The list of the streams receivers of the account to be set. /// Must be sorted, deduplicated and without 0 amtPerSecs. /// @param maxEndHint1 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The first hint for finding the maximum end time when all streams stop due to funds /// running out after the balance is updated and the new receivers list is applied. /// Hints have no effect on the results of calling this function, except potentially saving gas. /// Hints are Unix timestamps used as the starting points for binary search for the time /// when funds run out in the range of timestamps from the current block's to `2^32`. /// Hints lower than the current timestamp are ignored. /// You can provide zero, one or two hints. The order of hints doesn't matter. /// Hints are the most effective when one of them is lower than or equal to /// the last timestamp when funds are still streamed, and the other one is strictly larger /// than that timestamp,the smaller the difference between such hints, the higher gas savings. /// The savings are the highest possible when one of the hints is equal to /// the last timestamp when funds are still streamed, and the other one is larger by 1. /// It's worth noting that the exact timestamp of the block in which this function is executed /// may affect correctness of the hints, especially if they're precise. /// Hints don't provide any benefits when balance is not enough to cover /// a single second of streaming or is enough to cover all streams until timestamp `2^32`. /// Even inaccurate hints can be useful, and providing a single hint /// or two hints that don't enclose the time when funds run out can still save some gas. /// Providing poor hints that don't reduce the number of binary search steps /// may cause slightly higher gas usage than not providing any hints. /// @param maxEndHint2 An optional parameter allowing gas optimization, pass `0` to ignore it. /// The second hint for finding the maximum end time, see `maxEndHint1` docs for more details. /// @return realBalanceDelta The actually applied streams balance change. function _setStreams( uint256 accountId, IERC20 erc20, StreamReceiver[] memory currReceivers, int128 balanceDelta, StreamReceiver[] memory newReceivers, // slither-disable-next-line similar-names uint32 maxEndHint1, uint32 maxEndHint2 ) internal returns (int128 realBalanceDelta) { unchecked { StreamsState storage state = _streamsStorage().states[erc20][accountId]; _verifyStreamsReceivers(currReceivers, state); uint32 lastUpdate = state.updateTime; uint128 newBalance; uint32 newMaxEnd; { uint32 currMaxEnd = state.maxEnd; int128 currBalance = int128( _calcBalance( state.balance, lastUpdate, currMaxEnd, currReceivers, _currTimestamp() ) ); realBalanceDelta = balanceDelta; // Cap `realBalanceDelta` at withdrawal of the entire `currBalance` if (realBalanceDelta < -currBalance) { realBalanceDelta = -currBalance; } newBalance = uint128(currBalance + realBalanceDelta); newMaxEnd = _calcMaxEnd(newBalance, newReceivers, maxEndHint1, maxEndHint2); _updateReceiverStates( _streamsStorage().states[erc20], currReceivers, lastUpdate, currMaxEnd, newReceivers, newMaxEnd ); } state.updateTime = _currTimestamp(); state.maxEnd = newMaxEnd; state.balance = newBalance; bytes32 streamsHistory = state.streamsHistoryHash; // slither-disable-next-line timestamp if (streamsHistory != 0 && _cycleOf(lastUpdate) != _cycleOf(_currTimestamp())) { state.currCycleConfigs = 2; } else { state.currCycleConfigs++; } bytes32 newStreamsHash = _hashStreams(newReceivers); state.streamsHistoryHash = _hashStreamsHistory(streamsHistory, newStreamsHash, _currTimestamp(), newMaxEnd); emit StreamsSet(accountId, erc20, newStreamsHash, streamsHistory, newBalance, newMaxEnd); // slither-disable-next-line timestamp if (newStreamsHash != state.streamsHash) { state.streamsHash = newStreamsHash; for (uint256 i = 0; i < newReceivers.length; i++) { StreamReceiver memory receiver = newReceivers[i]; emit StreamReceiverSeen(newStreamsHash, receiver.accountId, receiver.config); } } } } /// @notice Verifies that the provided list of receivers is currently active for the account. /// @param currReceivers The verified list of receivers. /// @param state The account's state. function _verifyStreamsReceivers( StreamReceiver[] memory currReceivers, StreamsState storage state ) private view { require(_hashStreams(currReceivers) == state.streamsHash, "Invalid streams receivers list"); } /// @notice Calculates the maximum end time of all streams. /// @param balance The balance when streaming starts. /// @param receivers The list of streams receivers. /// Must be sorted, deduplicated and without 0 amtPerSecs. /// @param hint1 The first hint for finding the maximum end time. /// See `_setStreams` docs for `maxEndHint1` for more details. /// @param hint2 The second hint for finding the maximum end time. /// See `_setStreams` docs for `maxEndHint2` for more details. /// @return maxEnd The maximum end time of streaming. function _calcMaxEnd( uint128 balance, StreamReceiver[] memory receivers, uint32 hint1, uint32 hint2 ) private view returns (uint32 maxEnd) { (uint256[] memory configs, uint256 configsLen) = _buildConfigs(receivers); uint256 enoughEnd = _currTimestamp(); // slither-disable-start incorrect-equality,timestamp if (configsLen == 0 || balance == 0) { return uint32(enoughEnd); } uint256 notEnoughEnd = type(uint32).max; if (_isBalanceEnough(balance, configs, configsLen, notEnoughEnd)) { return uint32(notEnoughEnd); } if (hint1 > enoughEnd && hint1 < notEnoughEnd) { if (_isBalanceEnough(balance, configs, configsLen, hint1)) { enoughEnd = hint1; } else { notEnoughEnd = hint1; } } if (hint2 > enoughEnd && hint2 < notEnoughEnd) { if (_isBalanceEnough(balance, configs, configsLen, hint2)) { enoughEnd = hint2; } else { notEnoughEnd = hint2; } } while (true) { uint256 end; unchecked { end = (enoughEnd + notEnoughEnd) / 2; } if (end == enoughEnd) { return uint32(end); } if (_isBalanceEnough(balance, configs, configsLen, end)) { enoughEnd = end; } else { notEnoughEnd = end; } } // slither-disable-end incorrect-equality,timestamp } /// @notice Check if a given balance is enough to cover all streams with the given `maxEnd`. /// @param balance The balance when streaming starts. /// @param configs The list of streams configurations. /// @param configsLen The length of `configs`. /// @param maxEnd The maximum end time of streaming. /// @return isEnough `true` if the balance is enough, `false` otherwise. function _isBalanceEnough( uint256 balance, uint256[] memory configs, uint256 configsLen, uint256 maxEnd ) private view returns (bool isEnough) { unchecked { uint256 spent = 0; for (uint256 i = 0; i < configsLen; i++) { (uint256 amtPerSec, uint256 start, uint256 end) = _getConfig(configs, i); // slither-disable-next-line timestamp if (maxEnd <= start) { continue; } // slither-disable-next-line timestamp if (end > maxEnd) { end = maxEnd; } spent += _streamedAmt(amtPerSec, start, end); if (spent > balance) { return false; } } return true; } } /// @notice Build a preprocessed list of streams configurations from receivers. /// @param receivers The list of streams receivers. /// Must be sorted, deduplicated and without 0 amtPerSecs. /// @return configs The list of streams configurations /// @return configsLen The length of `configs` function _buildConfigs(StreamReceiver[] memory receivers) private view returns (uint256[] memory configs, uint256 configsLen) { unchecked { require(receivers.length <= _MAX_STREAMS_RECEIVERS, "Too many streams receivers"); configs = new uint256[](receivers.length); for (uint256 i = 0; i < receivers.length; i++) { StreamReceiver memory receiver = receivers[i]; if (i > 0) { require(_isOrdered(receivers[i - 1], receiver), "Streams receivers not sorted"); } configsLen = _addConfig(configs, configsLen, receiver); } } } /// @notice Preprocess and add a stream receiver to the list of configurations. /// @param configs The list of streams configurations /// @param configsLen The length of `configs` /// @param receiver The added stream receiver. /// @return newConfigsLen The new length of `configs` function _addConfig( uint256[] memory configs, uint256 configsLen, StreamReceiver memory receiver ) private view returns (uint256 newConfigsLen) { uint160 amtPerSec = receiver.config.amtPerSec(); require(amtPerSec >= _minAmtPerSec, "Stream receiver amtPerSec too low"); (uint32 start, uint32 end) = _streamRangeInFuture(receiver, _currTimestamp(), type(uint32).max); // slither-disable-next-line incorrect-equality,timestamp if (start == end) { return configsLen; } // By assignment we get `config` value: // `zeros (96 bits) | amtPerSec (160 bits)` uint256 config = amtPerSec; // By bit shifting we get `config` value: // `zeros (64 bits) | amtPerSec (160 bits) | zeros (32 bits)` // By bit masking we get `config` value: // `zeros (64 bits) | amtPerSec (160 bits) | start (32 bits)` config = (config << 32) | start; // By bit shifting we get `config` value: // `zeros (32 bits) | amtPerSec (160 bits) | start (32 bits) | zeros (32 bits)` // By bit masking we get `config` value: // `zeros (32 bits) | amtPerSec (160 bits) | start (32 bits) | end (32 bits)` config = (config << 32) | end; configs[configsLen] = config; unchecked { return configsLen + 1; } } /// @notice Load a streams configuration from the list. /// @param configs The list of streams configurations /// @param idx The loaded configuration index. It must be smaller than the `configs` length. /// @return amtPerSec The amount per second being streamed. /// @return start The timestamp when streaming starts. /// @return end The maximum timestamp when streaming ends. function _getConfig(uint256[] memory configs, uint256 idx) private pure returns (uint256 amtPerSec, uint256 start, uint256 end) { uint256 config; // `config` has value: // `zeros (32 bits) | amtPerSec (160 bits) | start (32 bits) | end (32 bits)` // slither-disable-next-line assembly assembly ("memory-safe") { config := mload(add(32, add(configs, shl(5, idx)))) } // By bit shifting we get value: // `zeros (96 bits) | amtPerSec (160 bits)` amtPerSec = config >> 64; // By bit shifting we get value: // `zeros (64 bits) | amtPerSec (160 bits) | start (32 bits)` // By casting down we get value: // `start (32 bits)` start = uint32(config >> 32); // By casting down we get value: // `end (32 bits)` end = uint32(config); } /// @notice Calculates the hash of the streams configuration. /// It's used to verify if streams configuration is the previously set one. /// @param receivers The list of the streams receivers. /// Must be sorted, deduplicated and without 0 amtPerSecs. /// If the streams have never been updated, pass an empty array. /// @return streamsHash The hash of the streams configuration function _hashStreams(StreamReceiver[] memory receivers) internal pure returns (bytes32 streamsHash) { if (receivers.length == 0) { return bytes32(0); } return keccak256(abi.encode(receivers)); } /// @notice Calculates the hash of the streams history /// after the streams configuration is updated. /// @param oldStreamsHistoryHash The history hash /// that was valid before the streams were updated. /// The `streamsHistoryHash` of an account before they set streams for the first time is `0`. /// @param streamsHash The hash of the streams receivers being set. /// @param updateTime The timestamp when the streams were updated. /// @param maxEnd The maximum end of the streams being set. /// @return streamsHistoryHash The hash of the updated streams history. function _hashStreamsHistory( bytes32 oldStreamsHistoryHash, bytes32 streamsHash, uint32 updateTime, uint32 maxEnd ) internal pure returns (bytes32 streamsHistoryHash) { return keccak256(abi.encode(oldStreamsHistoryHash, streamsHash, updateTime, maxEnd)); } /// @notice Applies the effects of the change of the streams on the receivers' streams state. /// @param states The streams states for the used ERC-20 token. /// @param currReceivers The list of the streams receivers /// set in the last streams update of the account. /// If this is the first update, pass an empty array. /// @param lastUpdate the last time the sender updated the streams. /// If this is the first update, pass zero. /// @param currMaxEnd The maximum end time of streaming according to the last streams update. /// @param newReceivers The list of the streams receivers of the account to be set. /// Must be sorted, deduplicated and without 0 amtPerSecs. /// @param newMaxEnd The maximum end time of streaming according to the new configuration. // slither-disable-next-line cyclomatic-complexity function _updateReceiverStates( mapping(uint256 accountId => StreamsState) storage states, StreamReceiver[] memory currReceivers, uint32 lastUpdate, uint32 currMaxEnd, StreamReceiver[] memory newReceivers, uint32 newMaxEnd ) private { uint256 currIdx = 0; uint256 newIdx = 0; while (true) { bool pickCurr = currIdx < currReceivers.length; // slither-disable-next-line uninitialized-local StreamReceiver memory currRecv; if (pickCurr) { currRecv = currReceivers[currIdx]; } bool pickNew = newIdx < newReceivers.length; // slither-disable-next-line uninitialized-local StreamReceiver memory newRecv; if (pickNew) { newRecv = newReceivers[newIdx]; } // Limit picking both curr and new to situations when they differ only by time if (pickCurr && pickNew) { if ( currRecv.accountId != newRecv.accountId || currRecv.config.amtPerSec() != newRecv.config.amtPerSec() ) { pickCurr = _isOrdered(currRecv, newRecv); pickNew = !pickCurr; } } if (pickCurr && pickNew) { // Shift the existing stream to fulfil the new configuration StreamsState storage state = states[currRecv.accountId]; (uint32 currStart, uint32 currEnd) = _streamRangeInFuture(currRecv, lastUpdate, currMaxEnd); (uint32 newStart, uint32 newEnd) = _streamRangeInFuture(newRecv, _currTimestamp(), newMaxEnd); int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec())); // Move the start and end times if updated. This has the same effects as calling // _addDeltaRange(state, currStart, currEnd, -amtPerSec); // _addDeltaRange(state, newStart, newEnd, amtPerSec); // but it allows skipping storage access if there's no change to the starts or ends. _addDeltaRange(state, currStart, newStart, -amtPerSec); _addDeltaRange(state, currEnd, newEnd, amtPerSec); // Ensure that the account receives the updated cycles uint32 currStartCycle = _cycleOf(currStart); uint32 newStartCycle = _cycleOf(newStart); // The `currStartCycle > newStartCycle` check is just an optimization. // If it's false, then `state.nextReceivableCycle > newStartCycle` must be // false too, there's no need to pay for the storage access to check it. // slither-disable-next-line timestamp if (currStartCycle > newStartCycle && state.nextReceivableCycle > newStartCycle) { state.nextReceivableCycle = newStartCycle; } } else if (pickCurr) { // Remove an existing stream // slither-disable-next-line similar-names StreamsState storage state = states[currRecv.accountId]; (uint32 start, uint32 end) = _streamRangeInFuture(currRecv, lastUpdate, currMaxEnd); // slither-disable-next-line similar-names int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec())); _addDeltaRange(state, start, end, -amtPerSec); } else if (pickNew) { // Create a new stream StreamsState storage state = states[newRecv.accountId]; // slither-disable-next-line uninitialized-local (uint32 start, uint32 end) = _streamRangeInFuture(newRecv, _currTimestamp(), newMaxEnd); int256 amtPerSec = int256(uint256(newRecv.config.amtPerSec())); _addDeltaRange(state, start, end, amtPerSec); // Ensure that the account receives the updated cycles uint32 startCycle = _cycleOf(start); // slither-disable-next-line timestamp uint32 nextReceivableCycle = state.nextReceivableCycle; if (nextReceivableCycle == 0 || nextReceivableCycle > startCycle) { state.nextReceivableCycle = startCycle; } } else { break; } unchecked { if (pickCurr) { currIdx++; } if (pickNew) { newIdx++; } } } } /// @notice Calculates the time range in the future in which a receiver will be streamed to. /// @param receiver The stream receiver. /// @param maxEnd The maximum end time of streaming. function _streamRangeInFuture(StreamReceiver memory receiver, uint32 updateTime, uint32 maxEnd) private view returns (uint32 start, uint32 end) { return _streamRange(receiver, updateTime, maxEnd, _currTimestamp(), type(uint32).max); } /// @notice Calculates the time range in which a receiver is to be streamed to. /// This range is capped to provide a view on the stream through a specific time window. /// @param receiver The stream receiver. /// @param updateTime The time when the stream is configured. /// @param maxEnd The maximum end time of streaming. /// @param startCap The timestamp the streaming range start should be capped to. /// @param endCap The timestamp the streaming range end should be capped to. function _streamRange( StreamReceiver memory receiver, uint32 updateTime, uint32 maxEnd, uint32 startCap, uint32 endCap ) private pure returns (uint32 start, uint32 end_) { start = receiver.config.start(); // slither-disable-start timestamp if (start == 0) { start = updateTime; } uint40 end; unchecked { end = uint40(start) + receiver.config.duration(); } // slither-disable-next-line incorrect-equality if (end == start || end > maxEnd) { end = maxEnd; } if (start < startCap) { start = startCap; } if (end > endCap) { end = endCap; } if (end < start) { end = start; } // slither-disable-end timestamp return (start, uint32(end)); } /// @notice Adds funds received by an account in a given time range /// @param state The account state /// @param start The timestamp from which the delta takes effect /// @param end The timestamp until which the delta takes effect /// @param amtPerSec The streaming rate function _addDeltaRange(StreamsState storage state, uint32 start, uint32 end, int256 amtPerSec) private { // slither-disable-next-line incorrect-equality,timestamp if (start == end) { return; } mapping(uint32 cycle => AmtDelta) storage amtDeltas = state.amtDeltas; _addDelta(amtDeltas, start, amtPerSec); _addDelta(amtDeltas, end, -amtPerSec); } /// @notice Adds delta of funds received by an account at a given time /// @param amtDeltas The account amount deltas /// @param timestamp The timestamp when the deltas need to be added /// @param amtPerSec The streaming rate function _addDelta( mapping(uint32 cycle => AmtDelta) storage amtDeltas, uint256 timestamp, int256 amtPerSec ) private { unchecked { // In order to set a delta on a specific timestamp it must be introduced in two cycles. // These formulas follow the logic from `_streamedAmt`, see it for more details. int256 amtPerSecMultiplier = int160(_AMT_PER_SEC_MULTIPLIER); int256 fullCycle = (int256(uint256(_cycleSecs)) * amtPerSec) / amtPerSecMultiplier; // slither-disable-next-line weak-prng int256 nextCycle = (int256(timestamp % _cycleSecs) * amtPerSec) / amtPerSecMultiplier; AmtDelta storage amtDelta = amtDeltas[_cycleOf(uint32(timestamp))]; // Any over- or under-flows are fine, they're guaranteed to be fixed by a matching // under- or over-flow from the other call to `_addDelta` made by `_addDeltaRange`. // This is because the total balance of `Streams` can never exceed `type(int128).max`, // so in the end no amtDelta can have delta higher than `type(int128).max`. amtDelta.thisCycle += int128(fullCycle - nextCycle); amtDelta.nextCycle += int128(nextCycle); } } /// @notice Checks if two receivers fulfil the sortedness requirement of the receivers list. /// @param prev The previous receiver /// @param next The next receiver function _isOrdered(StreamReceiver memory prev, StreamReceiver memory next) private pure returns (bool) { if (prev.accountId != next.accountId) { return prev.accountId < next.accountId; } return prev.config.lt(next.config); } /// @notice Calculates the amount streamed over a time range. /// The amount streamed in the `N`th second of each cycle is: /// `(N + 1) * amtPerSec / AMT_PER_SEC_MULTIPLIER - N * amtPerSec / AMT_PER_SEC_MULTIPLIER`. /// For a range of `N`s from `0` to `M` the sum of the streamed amounts is calculated as: /// `M * amtPerSec / AMT_PER_SEC_MULTIPLIER` assuming that `M <= cycleSecs`. /// For an arbitrary time range across multiple cycles the amount /// is calculated as the sum of the amount streamed in the start cycle, /// each of the full cycles in between and the end cycle. /// This algorithm has the following properties: /// - During every second full units are streamed, there are no partially streamed units. /// - Unstreamed fractions are streamed when they add up into full units. /// - Unstreamed fractions don't add up across cycle end boundaries. /// - Some seconds stream more units and some less. /// - Every `N`th second of each cycle streams the same amount. /// - Every full cycle streams the same amount. /// - The amount streamed in a given second is independent from the streaming start and end. /// - Streaming over time ranges `A:B` and then `B:C` is equivalent to streaming over `A:C`. /// - Different streams existing in the system don't interfere with each other. /// @param amtPerSec The streaming rate /// @param start The streaming start time /// @param end The streaming end time /// @return amt The streamed amount function _streamedAmt(uint256 amtPerSec, uint256 start, uint256 end) private view returns (uint256 amt) { // This function is written in Yul because it can be called thousands of times // per transaction and it needs to be optimized as much as possible. // As of Solidity 0.8.13, rewriting it in unchecked Solidity triples its gas cost. uint256 cycleSecs = _cycleSecs; // slither-disable-next-line assembly assembly { let endedCycles := sub(div(end, cycleSecs), div(start, cycleSecs)) // slither-disable-next-line divide-before-multiply let amtPerCycle := div(mul(cycleSecs, amtPerSec), _AMT_PER_SEC_MULTIPLIER) amt := mul(endedCycles, amtPerCycle) // slither-disable-next-line weak-prng let amtEnd := div(mul(mod(end, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER) amt := add(amt, amtEnd) // slither-disable-next-line weak-prng let amtStart := div(mul(mod(start, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER) amt := sub(amt, amtStart) } } /// @notice Calculates the cycle containing the given timestamp. /// @param timestamp The timestamp. /// @return cycle The cycle containing the timestamp. function _cycleOf(uint32 timestamp) private view returns (uint32 cycle) { unchecked { return timestamp / _cycleSecs + 1; } } /// @notice The current timestamp, casted to the contract's internal representation. /// @return timestamp The current timestamp function _currTimestamp() private view returns (uint32 timestamp) { return uint32(block.timestamp); } /// @notice The current cycle start timestamp, casted to the contract's internal representation. /// @return timestamp The current cycle start timestamp function _currCycleStart() private view returns (uint32 timestamp) { unchecked { uint32 currTimestamp = _currTimestamp(); // slither-disable-next-line weak-prng return currTimestamp - (currTimestamp % _cycleSecs); } } /// @notice Returns the Streams storage. /// @return streamsStorage The storage. function _streamsStorage() private view returns (StreamsStorage storage streamsStorage) { bytes32 slot = _streamsStorageSlot; // slither-disable-next-line assembly assembly { streamsStorage.slot := slot } } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.20; import {IERC20} from "openzeppelin-contracts/token/ERC20/IERC20.sol"; /// @notice A splits receiver struct SplitsReceiver { /// @notice The account ID. uint256 accountId; /// @notice The splits weight. Must never be zero. /// The account will be getting `weight / _TOTAL_SPLITS_WEIGHT` /// share of the funds collected by the splitting account. uint32 weight; } /// @notice Splits can keep track of at most `type(uint128).max` /// which is `2 ^ 128 - 1` units of each ERC-20 token. /// It's up to the caller to guarantee that this limit is never exceeded, /// failing to do so may result in a total protocol collapse. abstract contract Splits { /// @notice Maximum number of splits receivers of a single account. /// Limits the cost of splitting. uint256 internal constant _MAX_SPLITS_RECEIVERS = 200; /// @notice The total splits weight of an account. uint32 internal constant _TOTAL_SPLITS_WEIGHT = 1_000_000; /// @notice The amount the contract can keep track of each ERC-20 token. // slither-disable-next-line unused-state uint128 internal constant _MAX_SPLITS_BALANCE = type(uint128).max; /// @notice The storage slot holding a single `SplitsStorage` structure. bytes32 private immutable _splitsStorageSlot; /// @notice Emitted when an account collects funds /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param collected The collected amount event Collected(uint256 indexed accountId, IERC20 indexed erc20, uint128 collected); /// @notice Emitted when funds are split from an account to a receiver. /// This is caused by the account collecting received funds. /// @param accountId The account ID. /// @param receiver The splits receiver account ID /// @param erc20 The used ERC-20 token. /// @param amt The amount split to the receiver event Split( uint256 indexed accountId, uint256 indexed receiver, IERC20 indexed erc20, uint128 amt ); /// @notice Emitted when funds are made collectable after splitting. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param amt The amount made collectable for the account /// on top of what was collectable before. event Collectable(uint256 indexed accountId, IERC20 indexed erc20, uint128 amt); /// @notice Emitted when funds are given from the account to the receiver. /// @param accountId The account ID. /// @param receiver The receiver account ID. /// @param erc20 The used ERC-20 token. /// @param amt The given amount event Given( uint256 indexed accountId, uint256 indexed receiver, IERC20 indexed erc20, uint128 amt ); /// @notice Emitted when the account's splits are updated. /// @param accountId The account ID. /// @param receiversHash The splits receivers list hash event SplitsSet(uint256 indexed accountId, bytes32 indexed receiversHash); /// @notice Emitted when an account is seen in a splits receivers list. /// @param receiversHash The splits receivers list hash /// @param accountId The account ID. /// @param weight The splits weight. Must never be zero. /// The account will be getting `weight / _TOTAL_SPLITS_WEIGHT` /// share of the funds collected by the splitting account. event SplitsReceiverSeen( bytes32 indexed receiversHash, uint256 indexed accountId, uint32 weight ); struct SplitsStorage { /// @notice Account splits states. mapping(uint256 accountId => SplitsState) splitsStates; } struct SplitsState { /// @notice The account's splits configuration hash, see `hashSplits`. bytes32 splitsHash; /// @notice The account's splits balances. mapping(IERC20 erc20 => SplitsBalance) balances; } struct SplitsBalance { /// @notice The not yet split balance, must be split before collecting by the account. uint128 splittable; /// @notice The already split balance, ready to be collected by the account. uint128 collectable; } /// @param splitsStorageSlot The storage slot to holding a single `SplitsStorage` structure. constructor(bytes32 splitsStorageSlot) { _splitsStorageSlot = splitsStorageSlot; } function _addSplittable(uint256 accountId, IERC20 erc20, uint128 amt) internal { _splitsStorage().splitsStates[accountId].balances[erc20].splittable += amt; } /// @notice Returns account's received but not split yet funds. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @return amt The amount received but not split yet. function _splittable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) { return _splitsStorage().splitsStates[accountId].balances[erc20].splittable; } /// @notice Calculate the result of splitting an amount using the current splits configuration. /// @param accountId The account ID. /// @param currReceivers The list of the account's current splits receivers. /// It must be exactly the same as the last list set for the account with `_setSplits`. /// @param amount The amount being split. /// @return collectableAmt The amount made collectable for the account /// on top of what was collectable before. /// @return splitAmt The amount split to the account's splits receivers function _splitResult(uint256 accountId, SplitsReceiver[] memory currReceivers, uint128 amount) internal view returns (uint128 collectableAmt, uint128 splitAmt) { _assertCurrSplits(accountId, currReceivers); if (amount == 0) { return (0, 0); } unchecked { uint160 splitsWeight = 0; for (uint256 i = 0; i < currReceivers.length; i++) { splitsWeight += currReceivers[i].weight; } splitAmt = uint128(amount * splitsWeight / _TOTAL_SPLITS_WEIGHT); collectableAmt = amount - splitAmt; } } /// @notice Splits the account's splittable funds among receivers. /// The entire splittable balance of the given ERC-20 token is split. /// All split funds are split using the current splits configuration. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @param currReceivers The list of the account's current splits receivers. /// It must be exactly the same as the last list set for the account with `_setSplits`. /// @return collectableAmt The amount made collectable for the account /// on top of what was collectable before. /// @return splitAmt The amount split to the account's splits receivers function _split(uint256 accountId, IERC20 erc20, SplitsReceiver[] memory currReceivers) internal returns (uint128 collectableAmt, uint128 splitAmt) { _assertCurrSplits(accountId, currReceivers); SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20]; collectableAmt = balance.splittable; if (collectableAmt == 0) { return (0, 0); } balance.splittable = 0; unchecked { uint160 splitsWeight = 0; for (uint256 i = 0; i < currReceivers.length; i++) { splitsWeight += currReceivers[i].weight; uint128 currSplitAmt = uint128(collectableAmt * splitsWeight / _TOTAL_SPLITS_WEIGHT) - splitAmt; splitAmt += currSplitAmt; uint256 receiver = currReceivers[i].accountId; _addSplittable(receiver, erc20, currSplitAmt); emit Split(accountId, receiver, erc20, currSplitAmt); } collectableAmt -= splitAmt; balance.collectable += collectableAmt; } emit Collectable(accountId, erc20, collectableAmt); } /// @notice Returns account's received funds already split and ready to be collected. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @return amt The collectable amount. function _collectable(uint256 accountId, IERC20 erc20) internal view returns (uint128 amt) { return _splitsStorage().splitsStates[accountId].balances[erc20].collectable; } /// @notice Collects account's received already split funds. /// @param accountId The account ID. /// @param erc20 The used ERC-20 token. /// @return amt The collected amount function _collect(uint256 accountId, IERC20 erc20) internal returns (uint128 amt) { SplitsBalance storage balance = _splitsStorage().splitsStates[accountId].balances[erc20]; amt = balance.collectable; balance.collectable = 0; emit Collected(accountId, erc20, amt); } /// @notice Gives funds from the account to the receiver. /// The receiver can split and collect them immediately. /// @param accountId The account ID. /// @param receiver The receiver account ID. /// @param erc20 The used ERC-20 token. /// @param amt The given amount function _give(uint256 accountId, uint256 receiver, IERC20 erc20, uint128 amt) internal { _addSplittable(receiver, erc20, amt); emit Given(accountId, receiver, erc20, amt); } /// @notice Sets the account splits configuration. /// The configuration is common for all ERC-20 tokens. /// Nothing happens to the currently splittable funds, but when they are split /// after this function finishes, the new splits configuration will be used. /// @param accountId The account ID. /// @param receivers The list of the account's splits receivers to be set. /// Must be sorted by the splits receivers' addresses, deduplicated and without 0 weights. /// Each splits receiver will be getting `weight / _TOTAL_SPLITS_WEIGHT` /// share of the funds collected by the account. /// If the sum of weights of all receivers is less than `_TOTAL_SPLITS_WEIGHT`, /// some funds won't be split, but they will be left for the account to collect. /// It's valid to include the account's own `accountId` in the list of receivers, /// but funds split to themselves return to their splittable balance and are not collectable. /// This is usually unwanted, because if splitting is repeated, /// funds split to themselves will be again split using the current configuration. /// Splitting 100% to self effectively blocks splitting unless the configuration is updated. function _setSplits(uint256 accountId, SplitsReceiver[] memory receivers) internal { SplitsState storage state = _splitsStorage().splitsStates[accountId]; bytes32 newSplitsHash = _hashSplits(receivers); emit SplitsSet(accountId, newSplitsHash); if (newSplitsHash != state.splitsHash) { _assertSplitsValid(receivers, newSplitsHash); state.splitsHash = newSplitsHash; } } /// @notice Validates a list of splits receivers and emits events for them /// @param receivers The list of splits receivers /// @param receiversHash The hash of the list of splits receivers. /// Must be sorted by the splits receivers' addresses, deduplicated and without 0 weights. function _assertSplitsValid(SplitsReceiver[] memory receivers, bytes32 receiversHash) private { unchecked { require(receivers.length <= _MAX_SPLITS_RECEIVERS, "Too many splits receivers"); uint64 totalWeight = 0; // slither-disable-next-line uninitialized-local uint256 prevAccountId; for (uint256 i = 0; i < receivers.length; i++) { SplitsReceiver memory receiver = receivers[i]; uint32 weight = receiver.weight; require(weight != 0, "Splits receiver weight is zero"); totalWeight += weight; uint256 accountId = receiver.accountId; if (i > 0) require(prevAccountId < accountId, "Splits receivers not sorted"); prevAccountId = accountId; emit SplitsReceiverSeen(receiversHash, accountId, weight); } require(totalWeight <= _TOTAL_SPLITS_WEIGHT, "Splits weights sum too high"); } } /// @notice Asserts that the list of splits receivers is the account's currently used one. /// @param accountId The account ID. /// @param currReceivers The list of the account's current splits receivers. function _assertCurrSplits(uint256 accountId, SplitsReceiver[] memory currReceivers) internal view { require( _hashSplits(currReceivers) == _splitsHash(accountId), "Invalid current splits receivers" ); } /// @notice Current account's splits hash, see `hashSplits`. /// @param accountId The account ID. /// @return currSplitsHash The current account's splits hash function _splitsHash(uint256 accountId) internal view returns (bytes32 currSplitsHash) { return _splitsStorage().splitsStates[accountId].splitsHash; } /// @notice Calculates the hash of the list of splits receivers. /// @param receivers The list of the splits receivers. /// Must be sorted by the splits receivers' addresses, deduplicated and without 0 weights. /// @return receiversHash The hash of the list of splits receivers. function _hashSplits(SplitsReceiver[] memory receivers) internal pure returns (bytes32 receiversHash) { if (receivers.length == 0) { return bytes32(0); } return keccak256(abi.encode(receivers)); } /// @notice Returns the Splits storage. /// @return splitsStorage The storage. function _splitsStorage() private view returns (SplitsStorage storage splitsStorage) { bytes32 slot = _splitsStorageSlot; // slither-disable-next-line assembly assembly { splitsStorage.slot := slot } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol) pragma solidity ^0.8.0; import "../IERC20.sol"; import "../extensions/IERC20Permit.sol"; import "../../../utils/Address.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure (when the token * contract returns false). Tokens that return no value (and instead revert or * throw on failure) are also supported, non-reverting calls are assumed to be * successful. * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using Address for address; /** * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeTransfer(IERC20 token, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value)); } /** * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful. */ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal { _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value)); } /** * @dev Deprecated. This function has issues similar to the ones found in * {IERC20-approve}, and its usage is discouraged. * * Whenever possible, use {safeIncreaseAllowance} and * {safeDecreaseAllowance} instead. */ function safeApprove(IERC20 token, address spender, uint256 value) internal { // safeApprove should only be called when setting an initial allowance, // or when resetting it to zero. To increase and decrease it, use // 'safeIncreaseAllowance' and 'safeDecreaseAllowance' require( (value == 0) || (token.allowance(address(this), spender) == 0), "SafeERC20: approve from non-zero to non-zero allowance" ); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value)); } /** * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal { uint256 oldAllowance = token.allowance(address(this), spender); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value)); } /** * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. */ function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal { unchecked { uint256 oldAllowance = token.allowance(address(this), spender); require(oldAllowance >= value, "SafeERC20: decreased allowance below zero"); _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value)); } } /** * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value, * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to * 0 before setting it to a non-zero value. */ function forceApprove(IERC20 token, address spender, uint256 value) internal { bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value); if (!_callOptionalReturnBool(token, approvalCall)) { _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0)); _callOptionalReturn(token, approvalCall); } } /** * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`. * Revert on invalid signature. */ function safePermit( IERC20Permit token, address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { uint256 nonceBefore = token.nonces(owner); token.permit(owner, spender, value, deadline, v, r, s); uint256 nonceAfter = token.nonces(owner); require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). */ function _callOptionalReturn(IERC20 token, bytes memory data) private { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that // the target address contains contract code and also asserts for success in the low-level call. bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed"); require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed"); } /** * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement * on the return value: the return value is optional (but if data is returned, it must not be false). * @param token The token targeted by the call. * @param data The call data (encoded using abi.encode or one of its variants). * * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead. */ function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) { // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false // and not revert is the subcall reverts. (bool success, bytes memory returndata) = address(token).call(data); return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token)); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/utils/UUPSUpgradeable.sol) pragma solidity ^0.8.0; import "../../interfaces/draft-IERC1822.sol"; import "../ERC1967/ERC1967Upgrade.sol"; /** * @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an * {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy. * * A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is * reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing * `UUPSUpgradeable` with a custom implementation of upgrades. * * The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism. * * _Available since v4.1._ */ abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment address private immutable __self = address(this); /** * @dev Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case * for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a * function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to * fail. */ modifier onlyProxy() { require(address(this) != __self, "Function must be called through delegatecall"); require(_getImplementation() == __self, "Function must be called through active proxy"); _; } /** * @dev Check that the execution is not being performed through a delegate call. This allows a function to be * callable on the implementing contract but not through proxies. */ modifier notDelegated() { require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall"); _; } /** * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the * implementation. It is used to validate the implementation's compatibility when performing an upgrade. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier. */ function proxiableUUID() external view virtual override notDelegated returns (bytes32) { return _IMPLEMENTATION_SLOT; } /** * @dev Upgrade the implementation of the proxy to `newImplementation`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. * * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function upgradeTo(address newImplementation) public virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); } /** * @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call * encoded in `data`. * * Calls {_authorizeUpgrade}. * * Emits an {Upgraded} event. * * @custom:oz-upgrades-unsafe-allow-reachable delegatecall */ function upgradeToAndCall(address newImplementation, bytes memory data) public payable virtual onlyProxy { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data, true); } /** * @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by * {upgradeTo} and {upgradeToAndCall}. * * Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}. * * ```solidity * function _authorizeUpgrade(address) internal override onlyOwner {} * ``` */ function _authorizeUpgrade(address newImplementation) internal virtual; }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (proxy/ERC1967/ERC1967Proxy.sol) pragma solidity ^0.8.0; import "../Proxy.sol"; import "./ERC1967Upgrade.sol"; /** * @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an * implementation address that can be changed. This address is stored in storage in the location specified by * https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the * implementation behind the proxy. */ contract ERC1967Proxy is Proxy, ERC1967Upgrade { /** * @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`. * * If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded * function call, and allows initializing the storage of the proxy like a Solidity constructor. */ constructor(address _logic, bytes memory _data) payable { _upgradeToAndCall(_logic, _data, false); } /** * @dev Returns the current implementation address. */ function _implementation() internal view virtual override returns (address impl) { return ERC1967Upgrade._getImplementation(); } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol) // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js. pragma solidity ^0.8.0; /** * @dev Library for managing * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive * types. * * Sets have the following properties: * * - Elements are added, removed, and checked for existence in constant time * (O(1)). * - Elements are enumerated in O(n). No guarantees are made on the ordering. * * ```solidity * contract Example { * // Add the library methods * using EnumerableSet for EnumerableSet.AddressSet; * * // Declare a set state variable * EnumerableSet.AddressSet private mySet; * } * ``` * * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`) * and `uint256` (`UintSet`) are supported. * * [WARNING] * ==== * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure * unusable. * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info. * * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an * array of EnumerableSet. * ==== */ library EnumerableSet { // To implement this library for multiple types with as little code // repetition as possible, we write it in terms of a generic Set type with // bytes32 values. // The Set implementation uses private functions, and user-facing // implementations (such as AddressSet) are just wrappers around the // underlying Set. // This means that we can only create new EnumerableSets for types that fit // in bytes32. struct Set { // Storage of set values bytes32[] _values; // Position of the value in the `values` array, plus 1 because index 0 // means a value is not in the set. mapping(bytes32 => uint256) _indexes; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function _add(Set storage set, bytes32 value) private returns (bool) { if (!_contains(set, value)) { set._values.push(value); // The value is stored at length-1, but we add 1 to all indexes // and use 0 as a sentinel value set._indexes[value] = set._values.length; return true; } else { return false; } } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function _remove(Set storage set, bytes32 value) private returns (bool) { // We read and store the value's index to prevent multiple reads from the same storage slot uint256 valueIndex = set._indexes[value]; if (valueIndex != 0) { // Equivalent to contains(set, value) // To delete an element from the _values array in O(1), we swap the element to delete with the last one in // the array, and then remove the last element (sometimes called as 'swap and pop'). // This modifies the order of the array, as noted in {at}. uint256 toDeleteIndex = valueIndex - 1; uint256 lastIndex = set._values.length - 1; if (lastIndex != toDeleteIndex) { bytes32 lastValue = set._values[lastIndex]; // Move the last value to the index where the value to delete is set._values[toDeleteIndex] = lastValue; // Update the index for the moved value set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex } // Delete the slot where the moved value was stored set._values.pop(); // Delete the index for the deleted slot delete set._indexes[value]; return true; } else { return false; } } /** * @dev Returns true if the value is in the set. O(1). */ function _contains(Set storage set, bytes32 value) private view returns (bool) { return set._indexes[value] != 0; } /** * @dev Returns the number of values on the set. O(1). */ function _length(Set storage set) private view returns (uint256) { return set._values.length; } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function _at(Set storage set, uint256 index) private view returns (bytes32) { return set._values[index]; } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function _values(Set storage set) private view returns (bytes32[] memory) { return set._values; } // Bytes32Set struct Bytes32Set { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _add(set._inner, value); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) { return _remove(set._inner, value); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { return _contains(set._inner, value); } /** * @dev Returns the number of values in the set. O(1). */ function length(Bytes32Set storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) { return _at(set._inner, index); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(Bytes32Set storage set) internal view returns (bytes32[] memory) { bytes32[] memory store = _values(set._inner); bytes32[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // AddressSet struct AddressSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(AddressSet storage set, address value) internal returns (bool) { return _add(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(AddressSet storage set, address value) internal returns (bool) { return _remove(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(AddressSet storage set, address value) internal view returns (bool) { return _contains(set._inner, bytes32(uint256(uint160(value)))); } /** * @dev Returns the number of values in the set. O(1). */ function length(AddressSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(AddressSet storage set, uint256 index) internal view returns (address) { return address(uint160(uint256(_at(set._inner, index)))); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(AddressSet storage set) internal view returns (address[] memory) { bytes32[] memory store = _values(set._inner); address[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } // UintSet struct UintSet { Set _inner; } /** * @dev Add a value to a set. O(1). * * Returns true if the value was added to the set, that is if it was not * already present. */ function add(UintSet storage set, uint256 value) internal returns (bool) { return _add(set._inner, bytes32(value)); } /** * @dev Removes a value from a set. O(1). * * Returns true if the value was removed from the set, that is if it was * present. */ function remove(UintSet storage set, uint256 value) internal returns (bool) { return _remove(set._inner, bytes32(value)); } /** * @dev Returns true if the value is in the set. O(1). */ function contains(UintSet storage set, uint256 value) internal view returns (bool) { return _contains(set._inner, bytes32(value)); } /** * @dev Returns the number of values in the set. O(1). */ function length(UintSet storage set) internal view returns (uint256) { return _length(set._inner); } /** * @dev Returns the value stored at position `index` in the set. O(1). * * Note that there are no guarantees on the ordering of values inside the * array, and it may change when more values are added or removed. * * Requirements: * * - `index` must be strictly less than {length}. */ function at(UintSet storage set, uint256 index) internal view returns (uint256) { return uint256(_at(set._inner, index)); } /** * @dev Return the entire set in an array * * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that * this function has an unbounded cost, and using it as part of a state-changing function may render the function * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block. */ function values(UintSet storage set) internal view returns (uint256[] memory) { bytes32[] memory store = _values(set._inner); uint256[] memory result; /// @solidity memory-safe-assembly assembly { result := store } return result; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/StorageSlot.sol) // This file was procedurally generated from scripts/generate/templates/StorageSlot.js. pragma solidity ^0.8.0; /** * @dev Library for reading and writing primitive types to specific storage slots. * * Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts. * This library helps with reading and writing to such slots without the need for inline assembly. * * The functions in this library return Slot structs that contain a `value` member that can be used to read or write. * * Example usage to set ERC1967 implementation slot: * ```solidity * contract ERC1967 { * bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; * * function _getImplementation() internal view returns (address) { * return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; * } * * function _setImplementation(address newImplementation) internal { * require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); * StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; * } * } * ``` * * _Available since v4.1 for `address`, `bool`, `bytes32`, `uint256`._ * _Available since v4.9 for `string`, `bytes`._ */ library StorageSlot { struct AddressSlot { address value; } struct BooleanSlot { bool value; } struct Bytes32Slot { bytes32 value; } struct Uint256Slot { uint256 value; } struct StringSlot { string value; } struct BytesSlot { bytes value; } /** * @dev Returns an `AddressSlot` with member `value` located at `slot`. */ function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BooleanSlot` with member `value` located at `slot`. */ function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Bytes32Slot` with member `value` located at `slot`. */ function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `Uint256Slot` with member `value` located at `slot`. */ function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` with member `value` located at `slot`. */ function getStringSlot(bytes32 slot) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `StringSlot` representation of the string storage pointer `store`. */ function getStringSlot(string storage store) internal pure returns (StringSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } /** * @dev Returns an `BytesSlot` with member `value` located at `slot`. */ function getBytesSlot(bytes32 slot) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := slot } } /** * @dev Returns an `BytesSlot` representation of the bytes storage pointer `store`. */ function getBytesSlot(bytes storage store) internal pure returns (BytesSlot storage r) { /// @solidity memory-safe-assembly assembly { r.slot := store.slot } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.7.0) (metatx/ERC2771Context.sol) pragma solidity ^0.8.9; import "../utils/Context.sol"; /** * @dev Context variant with ERC2771 support. */ abstract contract ERC2771Context is Context { /// @custom:oz-upgrades-unsafe-allow state-variable-immutable address private immutable _trustedForwarder; /// @custom:oz-upgrades-unsafe-allow constructor constructor(address trustedForwarder) { _trustedForwarder = trustedForwarder; } function isTrustedForwarder(address forwarder) public view virtual returns (bool) { return forwarder == _trustedForwarder; } function _msgSender() internal view virtual override returns (address sender) { if (isTrustedForwarder(msg.sender)) { // The assembly code is more direct than the Solidity version using `abi.decode`. /// @solidity memory-safe-assembly assembly { sender := shr(96, calldataload(sub(calldatasize(), 20))) } } else { return super._msgSender(); } } function _msgData() internal view virtual override returns (bytes calldata) { if (isTrustedForwarder(msg.sender)) { return msg.data[:msg.data.length - 20]; } else { return super._msgData(); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 standard as defined in the EIP. */ interface IERC20 { /** * @dev Emitted when `value` tokens are moved from one account (`from`) to * another (`to`). * * Note that `value` may be zero. */ event Transfer(address indexed from, address indexed to, uint256 value); /** * @dev Emitted when the allowance of a `spender` for an `owner` is set by * a call to {approve}. `value` is the new allowance. */ event Approval(address indexed owner, address indexed spender, uint256 value); /** * @dev Returns the amount of tokens in existence. */ function totalSupply() external view returns (uint256); /** * @dev Returns the amount of tokens owned by `account`. */ function balanceOf(address account) external view returns (uint256); /** * @dev Moves `amount` tokens from the caller's account to `to`. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transfer(address to, uint256 amount) external returns (bool); /** * @dev Returns the remaining number of tokens that `spender` will be * allowed to spend on behalf of `owner` through {transferFrom}. This is * zero by default. * * This value changes when {approve} or {transferFrom} are called. */ function allowance(address owner, address spender) external view returns (uint256); /** * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. * * Returns a boolean value indicating whether the operation succeeded. * * IMPORTANT: Beware that changing an allowance with this method brings the risk * that someone may use both the old and the new allowance by unfortunate * transaction ordering. One possible solution to mitigate this race * condition is to first reduce the spender's allowance to 0 and set the * desired value afterwards: * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 * * Emits an {Approval} event. */ function approve(address spender, uint256 amount) external returns (bool); /** * @dev Moves `amount` tokens from `from` to `to` using the * allowance mechanism. `amount` is then deducted from the caller's * allowance. * * Returns a boolean value indicating whether the operation succeeded. * * Emits a {Transfer} event. */ function transferFrom(address from, address to, uint256 amount) external returns (bool); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol) pragma solidity ^0.8.0; /** * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612]. * * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't * need to send a transaction, and thus is not required to hold Ether at all. */ interface IERC20Permit { /** * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens, * given ``owner``'s signed approval. * * IMPORTANT: The same issues {IERC20-approve} has related to transaction * ordering also apply here. * * Emits an {Approval} event. * * Requirements: * * - `spender` cannot be the zero address. * - `deadline` must be a timestamp in the future. * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner` * over the EIP712-formatted function arguments. * - the signature must use ``owner``'s current nonce (see {nonces}). * * For more information on the signature format, see the * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP * section]. */ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) external; /** * @dev Returns the current nonce for `owner`. This value must be * included whenever a signature is generated for {permit}. * * Every successful call to {permit} increases ``owner``'s nonce by one. This * prevents a signature from being used multiple times. */ function nonces(address owner) external view returns (uint256); /** * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}. */ // solhint-disable-next-line func-name-mixedcase function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol) pragma solidity ^0.8.1; /** * @dev Collection of functions related to the address type */ library Address { /** * @dev Returns true if `account` is a contract. * * [IMPORTANT] * ==== * It is unsafe to assume that an address for which this function returns * false is an externally-owned account (EOA) and not a contract. * * Among others, `isContract` will return false for the following * types of addresses: * * - an externally-owned account * - a contract in construction * - an address where a contract will be created * - an address where a contract lived, but was destroyed * * Furthermore, `isContract` will also return true if the target contract within * the same transaction is already scheduled for destruction by `SELFDESTRUCT`, * which only has an effect at the end of a transaction. * ==== * * [IMPORTANT] * ==== * You shouldn't rely on `isContract` to protect against flash loan attacks! * * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract * constructor. * ==== */ function isContract(address account) internal view returns (bool) { // This method relies on extcodesize/address.code.length, which returns 0 // for contracts in construction, since the code is only stored at the end // of the constructor execution. return account.code.length > 0; } /** * @dev Replacement for Solidity's `transfer`: sends `amount` wei to * `recipient`, forwarding all available gas and reverting on errors. * * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost * of certain opcodes, possibly making contracts go over the 2300 gas limit * imposed by `transfer`, making them unable to receive funds via * `transfer`. {sendValue} removes this limitation. * * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more]. * * IMPORTANT: because control is transferred to `recipient`, care must be * taken to not create reentrancy vulnerabilities. Consider using * {ReentrancyGuard} or the * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern]. */ function sendValue(address payable recipient, uint256 amount) internal { require(address(this).balance >= amount, "Address: insufficient balance"); (bool success, ) = recipient.call{value: amount}(""); require(success, "Address: unable to send value, recipient may have reverted"); } /** * @dev Performs a Solidity function call using a low level `call`. A * plain `call` is an unsafe replacement for a function call: use this * function instead. * * If `target` reverts with a revert reason, it is bubbled up by this * function (like regular Solidity function calls). * * Returns the raw returned data. To convert to the expected return value, * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`]. * * Requirements: * * - `target` must be a contract. * - calling `target` with `data` must not revert. * * _Available since v3.1._ */ function functionCall(address target, bytes memory data) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, "Address: low-level call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with * `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { return functionCallWithValue(target, data, 0, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but also transferring `value` wei to `target`. * * Requirements: * * - the calling contract must have an ETH balance of at least `value`. * - the called Solidity function must be `payable`. * * _Available since v3.1._ */ function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) { return functionCallWithValue(target, data, value, "Address: low-level call with value failed"); } /** * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but * with `errorMessage` as a fallback revert reason when `target` reverts. * * _Available since v3.1._ */ function functionCallWithValue( address target, bytes memory data, uint256 value, string memory errorMessage ) internal returns (bytes memory) { require(address(this).balance >= value, "Address: insufficient balance for call"); (bool success, bytes memory returndata) = target.call{value: value}(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) { return functionStaticCall(target, data, "Address: low-level static call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a static call. * * _Available since v3.3._ */ function functionStaticCall( address target, bytes memory data, string memory errorMessage ) internal view returns (bytes memory) { (bool success, bytes memory returndata) = target.staticcall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) { return functionDelegateCall(target, data, "Address: low-level delegate call failed"); } /** * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`], * but performing a delegate call. * * _Available since v3.4._ */ function functionDelegateCall( address target, bytes memory data, string memory errorMessage ) internal returns (bytes memory) { (bool success, bytes memory returndata) = target.delegatecall(data); return verifyCallResultFromTarget(target, success, returndata, errorMessage); } /** * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract. * * _Available since v4.8._ */ function verifyCallResultFromTarget( address target, bool success, bytes memory returndata, string memory errorMessage ) internal view returns (bytes memory) { if (success) { if (returndata.length == 0) { // only check isContract if the call was successful and the return data is empty // otherwise we already know that it was a contract require(isContract(target), "Address: call to non-contract"); } return returndata; } else { _revert(returndata, errorMessage); } } /** * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the * revert reason or using the provided one. * * _Available since v4.3._ */ function verifyCallResult( bool success, bytes memory returndata, string memory errorMessage ) internal pure returns (bytes memory) { if (success) { return returndata; } else { _revert(returndata, errorMessage); } } function _revert(bytes memory returndata, string memory errorMessage) private pure { // Look for revert reason and bubble it up if present if (returndata.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly /// @solidity memory-safe-assembly assembly { let returndata_size := mload(returndata) revert(add(32, returndata), returndata_size) } } else { revert(errorMessage); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol) pragma solidity ^0.8.0; /** * @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified * proxy whose upgrades are fully controlled by the current implementation. */ interface IERC1822Proxiable { /** * @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation * address. * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this * function revert if invoked through a proxy. */ function proxiableUUID() external view returns (bytes32); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (proxy/ERC1967/ERC1967Upgrade.sol) pragma solidity ^0.8.2; import "../beacon/IBeacon.sol"; import "../../interfaces/IERC1967.sol"; import "../../interfaces/draft-IERC1822.sol"; import "../../utils/Address.sol"; import "../../utils/StorageSlot.sol"; /** * @dev This abstract contract provides getters and event emitting update functions for * https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots. * * _Available since v4.1._ */ abstract contract ERC1967Upgrade is IERC1967 { // This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1 bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143; /** * @dev Storage slot with the address of the current implementation. * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; /** * @dev Returns the current implementation address. */ function _getImplementation() internal view returns (address) { return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value; } /** * @dev Stores a new address in the EIP1967 implementation slot. */ function _setImplementation(address newImplementation) private { require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract"); StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation; } /** * @dev Perform implementation upgrade * * Emits an {Upgraded} event. */ function _upgradeTo(address newImplementation) internal { _setImplementation(newImplementation); emit Upgraded(newImplementation); } /** * @dev Perform implementation upgrade with additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCall(address newImplementation, bytes memory data, bool forceCall) internal { _upgradeTo(newImplementation); if (data.length > 0 || forceCall) { Address.functionDelegateCall(newImplementation, data); } } /** * @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call. * * Emits an {Upgraded} event. */ function _upgradeToAndCallUUPS(address newImplementation, bytes memory data, bool forceCall) internal { // Upgrades from old implementations will perform a rollback test. This test requires the new // implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing // this special case will break upgrade paths from old UUPS implementation to new ones. if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) { _setImplementation(newImplementation); } else { try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) { require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID"); } catch { revert("ERC1967Upgrade: new implementation is not UUPS"); } _upgradeToAndCall(newImplementation, data, forceCall); } } /** * @dev Storage slot with the admin of the contract. * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is * validated in the constructor. */ bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; /** * @dev Returns the current admin. */ function _getAdmin() internal view returns (address) { return StorageSlot.getAddressSlot(_ADMIN_SLOT).value; } /** * @dev Stores a new address in the EIP1967 admin slot. */ function _setAdmin(address newAdmin) private { require(newAdmin != address(0), "ERC1967: new admin is the zero address"); StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin; } /** * @dev Changes the admin of the proxy. * * Emits an {AdminChanged} event. */ function _changeAdmin(address newAdmin) internal { emit AdminChanged(_getAdmin(), newAdmin); _setAdmin(newAdmin); } /** * @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy. * This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor. */ bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50; /** * @dev Returns the current beacon. */ function _getBeacon() internal view returns (address) { return StorageSlot.getAddressSlot(_BEACON_SLOT).value; } /** * @dev Stores a new beacon in the EIP1967 beacon slot. */ function _setBeacon(address newBeacon) private { require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract"); require( Address.isContract(IBeacon(newBeacon).implementation()), "ERC1967: beacon implementation is not a contract" ); StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon; } /** * @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does * not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that). * * Emits a {BeaconUpgraded} event. */ function _upgradeBeaconToAndCall(address newBeacon, bytes memory data, bool forceCall) internal { _setBeacon(newBeacon); emit BeaconUpgraded(newBeacon); if (data.length > 0 || forceCall) { Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data); } } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.6.0) (proxy/Proxy.sol) pragma solidity ^0.8.0; /** * @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM * instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to * be specified by overriding the virtual {_implementation} function. * * Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a * different contract through the {_delegate} function. * * The success and return data of the delegated call will be returned back to the caller of the proxy. */ abstract contract Proxy { /** * @dev Delegates the current call to `implementation`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _delegate(address implementation) internal virtual { assembly { // Copy msg.data. We take full control of memory in this inline assembly // block because it will not return to Solidity code. We overwrite the // Solidity scratch pad at memory position 0. calldatacopy(0, 0, calldatasize()) // Call the implementation. // out and outsize are 0 because we don't know the size yet. let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) // Copy the returned data. returndatacopy(0, 0, returndatasize()) switch result // delegatecall returns 0 on error. case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } /** * @dev This is a virtual function that should be overridden so it returns the address to which the fallback function * and {_fallback} should delegate. */ function _implementation() internal view virtual returns (address); /** * @dev Delegates the current call to the address returned by `_implementation()`. * * This function does not return to its internal call site, it will return directly to the external caller. */ function _fallback() internal virtual { _beforeFallback(); _delegate(_implementation()); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other * function in the contract matches the call data. */ fallback() external payable virtual { _fallback(); } /** * @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data * is empty. */ receive() external payable virtual { _fallback(); } /** * @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback` * call, or as part of the Solidity `fallback` or `receive` functions. * * If overridden should call `super._beforeFallback()`. */ function _beforeFallback() internal virtual {} }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (utils/Context.sol) pragma solidity ^0.8.0; /** * @dev Provides information about the current execution context, including the * sender of the transaction and its data. While these are generally available * via msg.sender and msg.data, they should not be accessed in such a direct * manner, since when dealing with meta-transactions the account sending and * paying for execution may not be the actual sender (as far as an application * is concerned). * * This contract is only required for intermediate, library-like contracts. */ abstract contract Context { function _msgSender() internal view virtual returns (address) { return msg.sender; } function _msgData() internal view virtual returns (bytes calldata) { return msg.data; } }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol) pragma solidity ^0.8.0; /** * @dev This is the interface that {BeaconProxy} expects of its beacon. */ interface IBeacon { /** * @dev Must return an address that can be used as a delegate call target. * * {BeaconProxy} will check that this address is a contract. */ function implementation() external view returns (address); }
// SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v4.9.0) (interfaces/IERC1967.sol) pragma solidity ^0.8.0; /** * @dev ERC-1967: Proxy Storage Slots. This interface contains the events defined in the ERC. * * _Available since v4.8.3._ */ interface IERC1967 { /** * @dev Emitted when the implementation is upgraded. */ event Upgraded(address indexed implementation); /** * @dev Emitted when the admin account has changed. */ event AdminChanged(address previousAdmin, address newAdmin); /** * @dev Emitted when the beacon is changed. */ event BeaconUpgraded(address indexed beacon); }
{ "remappings": [ "chainlink/=lib/chainlink/contracts/src/v0.8/", "ds-test/=lib/forge-std/lib/ds-test/src/", "erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/", "forge-std/=lib/forge-std/src/", "openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/", "openzeppelin/=lib/openzeppelin-contracts/contracts/" ], "optimizer": { "enabled": true, "runs": 7700 }, "metadata": { "useLiteralContent": false, "bytecodeHash": "ipfs", "appendCBOR": true }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "evmVersion": "shanghai", "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"contract Drips","name":"drips_","type":"address"},{"internalType":"address","name":"forwarder","type":"address"},{"internalType":"uint32","name":"driverId_","type":"uint32"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"currentAdmin","type":"address"},{"indexed":true,"internalType":"address","name":"newAdmin","type":"address"}],"name":"NewAdminProposed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"PauserGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"},{"indexed":true,"internalType":"address","name":"admin","type":"address"}],"name":"PauserRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pauser","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"inputs":[],"name":"acceptAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"admin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"allPausers","outputs":[{"internalType":"address[]","name":"pausersList","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"calcAccountId","outputs":[{"internalType":"uint256","name":"accountId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"erc20","type":"address"},{"internalType":"address","name":"transferTo","type":"address"}],"name":"collect","outputs":[{"internalType":"uint128","name":"amt","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"drips","outputs":[{"internalType":"contract Drips","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"driverId","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes32","name":"key","type":"bytes32"},{"internalType":"bytes","name":"value","type":"bytes"}],"internalType":"struct AccountMetadata[]","name":"accountMetadata","type":"tuple[]"}],"name":"emitAccountMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"receiver","type":"uint256"},{"internalType":"contract IERC20","name":"erc20","type":"address"},{"internalType":"uint128","name":"amt","type":"uint128"}],"name":"give","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pauser","type":"address"}],"name":"grantPauser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"implementation","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pauser","type":"address"}],"name":"isPauser","outputs":[{"internalType":"bool","name":"isAddrPauser","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAdmin","type":"address"}],"name":"proposeNewAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"proposedAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"proxiableUUID","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceAdmin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pauser","type":"address"}],"name":"revokePauser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"accountId","type":"uint256"},{"internalType":"uint32","name":"weight","type":"uint32"}],"internalType":"struct SplitsReceiver[]","name":"receivers","type":"tuple[]"}],"name":"setSplits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"erc20","type":"address"},{"components":[{"internalType":"uint256","name":"accountId","type":"uint256"},{"internalType":"StreamConfig","name":"config","type":"uint256"}],"internalType":"struct StreamReceiver[]","name":"currReceivers","type":"tuple[]"},{"internalType":"int128","name":"balanceDelta","type":"int128"},{"components":[{"internalType":"uint256","name":"accountId","type":"uint256"},{"internalType":"StreamConfig","name":"config","type":"uint256"}],"internalType":"struct StreamReceiver[]","name":"newReceivers","type":"tuple[]"},{"internalType":"uint32","name":"maxEndHint1","type":"uint32"},{"internalType":"uint32","name":"maxEndHint2","type":"uint32"},{"internalType":"address","name":"transferTo","type":"address"}],"name":"setStreams","outputs":[{"internalType":"int128","name":"realBalanceDelta","type":"int128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"}],"name":"upgradeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newImplementation","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"upgradeToAndCall","outputs":[],"stateMutability":"payable","type":"function"}]
Deployed Bytecode
0x60806040526004361061018e575f3560e01c8063572b6c05116100dc578063a12ef25e11610087578063b187bd2611610062578063b187bd2614610507578063d30df0371461053d578063dde554c61461055c578063f851a4401461058e575f80fd5b8063a12ef25e14610460578063a1694a77146104a0578063a6376746146104e8575f80fd5b80637fe76df0116100b75780637fe76df0146104195780638456cb59146104385780638bad0c0a1461044c575f80fd5b8063572b6c05146103865780635c60da1b146103d25780637c435d29146103e6575f80fd5b806332f751ec1161013c57806346fbf68e1161011757806346fbf68e146103305780634f1ef2861461035f57806352d1902d14610372575f80fd5b806332f751ec146102aa5780633659cfe6146102fd5780633f4ba83a1461031c575f80fd5b80630e64589d1161016c5780630e64589d146101f157806321164d3f1461021057806328c2813c1461022f575f80fd5b806309c1d95f1461019257806309d48a94146101b35780630e18b681146101dd575b5f80fd5b34801561019d575f80fd5b506101b16101ac3660046122a3565b6105a2565b005b3480156101be575f80fd5b506101c76106b7565b6040516101d491906122be565b60405180910390f35b3480156101e8575f80fd5b506101b16106ea565b3480156101fc575f80fd5b506101b161020b36600461230a565b610780565b34801561021b575f80fd5b506101b161022a366004612396565b61087a565b34801561023a575f80fd5b5061029c6102493660046122a3565b6001600160a01b03167f000000000000000000000000000000000000000000000000000000000000000060e01b7fffffffff00000000000000000000000000000000000000000000000000000000161790565b6040519081526020016101d4565b3480156102b5575f80fd5b507fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa7600301546001600160a01b03165b6040516001600160a01b0390911681526020016101d4565b348015610308575f80fd5b506101b16103173660046122a3565b610904565b348015610327575f80fd5b506101b1610a77565b34801561033b575f80fd5b5061034f61034a3660046122a3565b610bc9565b60405190151581526020016101d4565b6101b161036d366004612402565b610bfd565b34801561037d575f80fd5b5061029c610d62565b348015610391575f80fd5b5061034f6103a03660046122a3565b7f00000000000000000000000060f25ac5f289dc7f640f948521d486c964a248e56001600160a01b0390811691161490565b3480156103dd575f80fd5b506102e5610e26565b3480156103f1575f80fd5b506102e57f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b481565b348015610424575f80fd5b506101b16104333660046122a3565b610e2f565b348015610443575f80fd5b506101b1610f3f565b348015610457575f80fd5b506101b1611095565b34801561046b575f80fd5b5061047f61047a3660046124c0565b6110fd565b6040516fffffffffffffffffffffffffffffffff90911681526020016101d4565b3480156104ab575f80fd5b506104d37f000000000000000000000000000000000000000000000000000000000000000081565b60405163ffffffff90911681526020016101d4565b3480156104f3575f80fd5b506101b16105023660046122a3565b61118f565b348015610512575f80fd5b507fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff1661034f565b348015610548575f80fd5b506101b161055736600461253f565b61127f565b348015610567575f80fd5b5061057b6105763660046125a4565b611348565b604051600f9190910b81526020016101d4565b348015610599575f80fd5b506102e56113e8565b336105ab6113e8565b6001600160a01b0316146106065760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206e6f74207468652061646d696e00000000000000000000000060448201526064015b60405180910390fd5b61063360017fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa701826113f1565b61067f5760405162461bcd60e51b815260206004820152601b60248201527f4164647265737320616c7265616479206973206120706175736572000000000060448201526064016105fd565b60405133906001600160a01b038316907fbb7fff487ca65a5841fe463ac801812d2aeb3c2059f6e44b2b3cdab8ee7c3db0905f90a350565b60606106e57fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa7600101611405565b905090565b3361071f60037fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa701546001600160a01b031690565b6001600160a01b0316146107755760405162461bcd60e51b815260206004820152601d60248201527f43616c6c6572206e6f74207468652070726f706f7365642061646d696e00000060448201526064016105fd565b61077e33611411565b565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff16156107f25760405162461bcd60e51b815260206004820152600f60248201527f436f6e747261637420706175736564000000000000000000000000000000000060448201526064016105fd565b7f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b46001600160a01b03166369610257610829611502565b84846040518463ffffffff1660e01b815260040161084993929190612668565b5f604051808303815f87803b158015610860575f80fd5b505af1158015610872573d5f803e3d5ffd5b505050505050565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff16156108ec5760405162461bcd60e51b815260206004820152600f60248201527f436f6e747261637420706175736564000000000000000000000000000000000060448201526064016105fd565b6108ff6108f7611502565b84848461150e565b505050565b6001600160a01b037f0000000000000000000000003ea1e774f98cc4c6359bbcb3238e3e60365fa5c91630036109a25760405162461bcd60e51b815260206004820152602c60248201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060448201527f64656c656761746563616c6c000000000000000000000000000000000000000060648201526084016105fd565b7f0000000000000000000000003ea1e774f98cc4c6359bbcb3238e3e60365fa5c96001600160a01b03166109d46115ee565b6001600160a01b031614610a505760405162461bcd60e51b815260206004820152602c60248201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060448201527f6163746976652070726f7879000000000000000000000000000000000000000060648201526084016105fd565b610a5981611620565b604080515f80825260208201909252610a749183919061167f565b50565b33610a806113e8565b6001600160a01b03161480610a995750610a9933610bc9565b610ae55760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206e6f74207468652061646d696e206f7220612070617573657260448201526064016105fd565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff16610b565760405162461bcd60e51b815260206004820152601360248201527f436f6e7472616374206e6f74207061757365640000000000000000000000000060448201526064016105fd565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016905560405133907f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa905f90a2565b5f610bf760017fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa7018361181f565b92915050565b6001600160a01b037f0000000000000000000000003ea1e774f98cc4c6359bbcb3238e3e60365fa5c9163003610c9b5760405162461bcd60e51b815260206004820152602c60248201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060448201527f64656c656761746563616c6c000000000000000000000000000000000000000060648201526084016105fd565b7f0000000000000000000000003ea1e774f98cc4c6359bbcb3238e3e60365fa5c96001600160a01b0316610ccd6115ee565b6001600160a01b031614610d495760405162461bcd60e51b815260206004820152602c60248201527f46756e6374696f6e206d7573742062652063616c6c6564207468726f7567682060448201527f6163746976652070726f7879000000000000000000000000000000000000000060648201526084016105fd565b610d5282611620565b610d5e8282600161167f565b5050565b5f306001600160a01b037f0000000000000000000000003ea1e774f98cc4c6359bbcb3238e3e60365fa5c91614610e015760405162461bcd60e51b815260206004820152603860248201527f555550535570677261646561626c653a206d757374206e6f742062652063616c60448201527f6c6564207468726f7567682064656c656761746563616c6c000000000000000060648201526084016105fd565b507f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc90565b5f6106e56115ee565b33610e386113e8565b6001600160a01b031614610e8e5760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206e6f74207468652061646d696e00000000000000000000000060448201526064016105fd565b610ebb60017fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa70182611840565b610f075760405162461bcd60e51b815260206004820152601760248201527f41646472657373206973206e6f7420612070617573657200000000000000000060448201526064016105fd565b60405133906001600160a01b038316907ffd55549bcbafb9531a61db3cfb88d1cca64e215b12def56e6ea913fe1ac91fd2905f90a350565b33610f486113e8565b6001600160a01b03161480610f615750610f6133610bc9565b610fad5760405162461bcd60e51b815260206004820181905260248201527f43616c6c6572206e6f74207468652061646d696e206f7220612070617573657260448201526064016105fd565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff161561101f5760405162461bcd60e51b815260206004820152600f60248201527f436f6e747261637420706175736564000000000000000000000000000000000060448201526064016105fd565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa780547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016600117905560405133907f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a258905f90a2565b3361109e6113e8565b6001600160a01b0316146110f45760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206e6f74207468652061646d696e00000000000000000000000060448201526064016105fd565b61077e5f611411565b5f6111297fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff1690565b156111765760405162461bcd60e51b815260206004820152600f60248201527f436f6e747261637420706175736564000000000000000000000000000000000060448201526064016105fd565b611188611181611502565b8484611854565b9392505050565b336111986113e8565b6001600160a01b0316146111ee5760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206e6f74207468652061646d696e00000000000000000000000060448201526064016105fd565b6040516001600160a01b0382169033907fed2d93e7985747cd1a4a093c2cc3bb73d0f177b81bdfd26020e8f20a97e8112e905f90a37fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa760030180547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b0392909216919091179055565b7fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff16156112f15760405162461bcd60e51b815260206004820152600f60248201527f436f6e747261637420706175736564000000000000000000000000000000000060448201526064016105fd565b7f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b46001600160a01b03166302cfc753611328611502565b84846040518463ffffffff1660e01b815260040161084993929190612797565b5f6113747fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa75460ff1690565b156113c15760405162461bcd60e51b815260206004820152600f60248201527f436f6e747261637420706175736564000000000000000000000000000000000060448201526064016105fd565b6113da6113cc611502565b8b8b8b8b8b8b8b8b8b6119d4565b9a9950505050505050505050565b5f6106e5611b80565b5f611188836001600160a01b038416611ba7565b60605f61118883611bf3565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f61143a6113e8565b604080516001600160a01b03928316815291841660208301520160405180910390a160037fe8b5d5c9b680c1fe7b4e140cb3994baa0dd775346136353dbe31ddb68b924fa70180547fffffffffffffffffffffffff0000000000000000000000000000000000000000169055807fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b80547fffffffffffffffffffffffff0000000000000000000000000000000000000000166001600160a01b039290921691909117905550565b5f6106e5610249611c4c565b6fffffffffffffffffffffffffffffffff811615611530576115308282611cad565b7f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b46040517fd9e0107000000000000000000000000000000000000000000000000000000000815260048101869052602481018590526001600160a01b0384811660448301526fffffffffffffffffffffffffffffffff84166064830152919091169063d9e01070906084015f604051808303815f87803b1580156115d2575f80fd5b505af11580156115e4573d5f803e3d5ffd5b5050505050505050565b5f7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc5b546001600160a01b0316919050565b336116296113e8565b6001600160a01b031614610a745760405162461bcd60e51b815260206004820152601460248201527f43616c6c6572206e6f74207468652061646d696e00000000000000000000000060448201526064016105fd565b7f4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd91435460ff16156116b2576108ff83611cf2565b826001600160a01b03166352d1902d6040518163ffffffff1660e01b8152600401602060405180830381865afa92505050801561170c575060408051601f3d908101601f19168201909252611709918101906127f3565b60015b61177e5760405162461bcd60e51b815260206004820152602e60248201527f45524331393637557067726164653a206e657720696d706c656d656e7461746960448201527f6f6e206973206e6f74205555505300000000000000000000000000000000000060648201526084016105fd565b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc81146118135760405162461bcd60e51b815260206004820152602960248201527f45524331393637557067726164653a20756e737570706f727465642070726f7860448201527f6961626c6555554944000000000000000000000000000000000000000000000060648201526084016105fd565b506108ff838383611d96565b6001600160a01b0381165f9081526001830160205260408120541515611188565b5f611188836001600160a01b038416611dc0565b5f7f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b46040517f8d3c100a000000000000000000000000000000000000000000000000000000008152600481018690526001600160a01b0385811660248301529190911690638d3c100a906044016020604051808303815f875af11580156118dd573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611901919061280a565b90506fffffffffffffffffffffffffffffffff811615611188577f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b46040517fd9caed120000000000000000000000000000000000000000000000000000000081526001600160a01b03858116600483015284811660248301526fffffffffffffffffffffffffffffffff84166044830152919091169063d9caed12906064015f604051808303815f87803b1580156119b7575f80fd5b505af11580156119c9573d5f803e3d5ffd5b505050509392505050565b5f8087600f0b13156119ea576119ea8a88611cad565b6040517fb3a3a5730000000000000000000000000000000000000000000000000000000081526001600160a01b037f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b4169063b3a3a57390611a5f908e908e908e908e908e908e908e908e908e90600401612864565b6020604051808303815f875af1158015611a7b573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611a9f91906128cf565b90505f81600f0b12156113da577f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b46001600160a01b031663d9caed128b84611ae685612917565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e086901b1681526001600160a01b0393841660048201529290911660248301526fffffffffffffffffffffffffffffffff1660448201526064015f604051808303815f87803b158015611b5c575f80fd5b505af1158015611b6e573d5f803e3d5ffd5b505050509a9950505050505050505050565b5f7fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103611611565b5f818152600183016020526040812054611bec57508154600181810184555f848152602080822090930184905584548482528286019093526040902091909155610bf7565b505f610bf7565b6060815f01805480602002602001604051908101604052809291908181526020018280548015611c4057602002820191905f5260205f20905b815481526020019060010190808311611c2c575b50505050509050919050565b5f7f00000000000000000000000060f25ac5f289dc7f640f948521d486c964a248e56001600160a01b03163303611ca857507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffec36013560601c90565b503390565b610d5e82611cb9611c4c565b7f000000000000000000000000d0dd053392db676d57317cd4fe96fc2ccf42d0b4846fffffffffffffffffffffffffffffffff16611ea3565b6001600160a01b0381163b611d6f5760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201527f6f74206120636f6e74726163740000000000000000000000000000000000000060648201526084016105fd565b807f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc6114c9565b611d9f83611f2b565b5f82511180611dab5750805b156108ff57611dba8383611f6a565b50505050565b5f8181526001830160205260408120548015611e9a575f611de2600183612953565b85549091505f90611df590600190612953565b9050818114611e54575f865f018281548110611e1357611e13612966565b905f5260205f200154905080875f018481548110611e3357611e33612966565b5f918252602080832090910192909255918252600188019052604090208390555b8554869080611e6557611e65612993565b600190038181905f5260205f20015f90559055856001015f8681526020019081526020015f205f905560019350505050610bf7565b5f915050610bf7565b604080516001600160a01b0385811660248301528416604482015260648082018490528251808303909101815260849091019091526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167f23b872dd00000000000000000000000000000000000000000000000000000000179052611dba908590611f8f565b611f3481611cf2565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a250565b60606111888383604051806060016040528060278152602001612a4f60279139612075565b5f611fe3826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166120e99092919063ffffffff16565b905080515f148061200357508080602001905181019061200391906129c0565b6108ff5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f7420737563636565640000000000000000000000000000000000000000000060648201526084016105fd565b60605f80856001600160a01b0316856040516120919190612a01565b5f60405180830381855af49150503d805f81146120c9576040519150601f19603f3d011682016040523d82523d5f602084013e6120ce565b606091505b50915091506120df868383876120ff565b9695505050505050565b60606120f784845f85612177565b949350505050565b6060831561216d5782515f03612166576001600160a01b0385163b6121665760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e747261637400000060448201526064016105fd565b50816120f7565b6120f78383612265565b6060824710156121ef5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c000000000000000000000000000000000000000000000000000060648201526084016105fd565b5f80866001600160a01b0316858760405161220a9190612a01565b5f6040518083038185875af1925050503d805f8114612244576040519150601f19603f3d011682016040523d82523d5f602084013e612249565b606091505b509150915061225a878383876120ff565b979650505050505050565b8151156122755781518083602001fd5b8060405162461bcd60e51b81526004016105fd9190612a1c565b6001600160a01b0381168114610a74575f80fd5b5f602082840312156122b3575f80fd5b81356111888161228f565b602080825282518282018190525f9190848201906040850190845b818110156122fe5783516001600160a01b0316835292840192918401916001016122d9565b50909695505050505050565b5f806020838503121561231b575f80fd5b823567ffffffffffffffff80821115612332575f80fd5b818501915085601f830112612345575f80fd5b813581811115612353575f80fd5b8660208260051b8501011115612367575f80fd5b60209290920196919550909350505050565b6fffffffffffffffffffffffffffffffff81168114610a74575f80fd5b5f805f606084860312156123a8575f80fd5b8335925060208401356123ba8161228f565b915060408401356123ca81612379565b809150509250925092565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f8060408385031215612413575f80fd5b823561241e8161228f565b9150602083013567ffffffffffffffff8082111561243a575f80fd5b818501915085601f83011261244d575f80fd5b81358181111561245f5761245f6123d5565b604051601f8201601f19908116603f01168101908382118183101715612487576124876123d5565b8160405282815288602084870101111561249f575f80fd5b826020860160208301375f6020848301015280955050505050509250929050565b5f80604083850312156124d1575f80fd5b82356124dc8161228f565b915060208301356124ec8161228f565b809150509250929050565b5f8083601f840112612507575f80fd5b50813567ffffffffffffffff81111561251e575f80fd5b6020830191508360208260061b8501011115612538575f80fd5b9250929050565b5f8060208385031215612550575f80fd5b823567ffffffffffffffff811115612566575f80fd5b612572858286016124f7565b90969095509350505050565b80600f0b8114610a74575f80fd5b803563ffffffff8116811461259f575f80fd5b919050565b5f805f805f805f805f60e08a8c0312156125bc575f80fd5b89356125c78161228f565b985060208a013567ffffffffffffffff808211156125e3575f80fd5b6125ef8d838e016124f7565b909a50985060408c013591506126048261257e565b90965060608b01359080821115612619575f80fd5b506126268c828d016124f7565b9096509450612639905060808b0161258c565b925061264760a08b0161258c565b915060c08a01356126578161228f565b809150509295985092959850929598565b5f60408083018684526020828186015281868352606092508286019050828760051b870101885f5b89811015612787577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa089840301845281357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18c36030181126126f0575f80fd5b8b018035845285810135368290037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe101811261272a575f80fd5b01858101903567ffffffffffffffff811115612744575f80fd5b803603821315612752575f80fd5b888786015280898601528082898701375f85820189015294860194601f01601f19169093018601925090840190600101612690565b50909a9950505050505050505050565b838152604060208083018290528282018490525f9190859060608501845b878110156127e6578335825263ffffffff6127d184860161258c565b168284015292840192908401906001016127b5565b5098975050505050505050565b5f60208284031215612803575f80fd5b5051919050565b5f6020828403121561281a575f80fd5b815161118881612379565b8183525f60208085019450825f5b858110156128595781358752828201358388015260409687019690910190600101612833565b509495945050505050565b8981526001600160a01b038916602082015260e060408201525f61288c60e08301898b612825565b87600f0b606084015282810360808401526128a8818789612825565b91505063ffffffff80851660a084015280841660c0840152509a9950505050505050505050565b5f602082840312156128df575f80fd5b81516111888161257e565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f81600f0b7fffffffffffffffffffffffffffffffff80000000000000000000000000000000810361294b5761294b6128ea565b5f0392915050565b81810381811115610bf757610bf76128ea565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603160045260245ffd5b5f602082840312156129d0575f80fd5b81518015158114611188575f80fd5b5f5b838110156129f95781810151838201526020016129e1565b50505f910152565b5f8251612a128184602087016129df565b9190910192915050565b602081525f8251806020840152612a3a8160408501602087016129df565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a264697066735822122070b2bc25645ba246c5634d5bd813cf244e28f7fc380c398e5e844d6ebb9a905b64736f6c63430008140033
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|
Loading...
Loading
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.