---
title: drips.sol
tags: contracts, radicle
---
- Smart Contract
- Radicle
- // SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.17;
-
- **explain the following solidity code**
<pre><code>
///
@notice A drips receiver
struct DripsReceiver {
/// @notice The user ID.
uint256 userId;
/// @notice The drips configuration.
DripsConfig config;
}
</code></pre>
This code is the definition of a DripsReceiver struct. It contains two fields, userId and config. userId is a number that represents the user, and config is a DripsConfig struct.
**explain the following code**
<pre><code>
/// @notice The sender drips history entry, used when squeezing drips.
struct DripsHistory {
/// @notice Drips receivers list hash, see `_hashDrips`.
/// If it's non-zero, `receivers` must be empty.
bytes32 dripsHash;
/// @notice The drips receivers. If it's non-empty, `dripsHash` must be `0`.
/// If it's empty, this history entry will be skipped when squeezing drips
/// and `dripsHash` will be used when verifying the drips history validity.
/// Skipping a history entry allows cutting gas usage on analysis
/// of parts of the drips 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 `DripsHistory` entry will have
/// both the `dripsHash` 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.
DripsReceiver[] receivers;
/// @notice The time when drips have been configured
uint32 updateTime;
/// @notice The maximum end time of drips
uint32 maxEnd;
}
</code></pre>
This is the Drips History data structure which is used to keep track of a senders' drips receivers list and to store information related to the update time and maximum end time of the drips.
**explain the following code**
<pre><code>
/// @notice Describes a drips configuration.
/// It's constructed from `dripId`, `amtPerSec`, `start` and `duration` as
/// `dripId << 224 | amtPerSec << 64 | start << 32 | duration`.
/// `dripId` is an arbitrary number used to identify a drip.
/// It's a part of the configuration but the protocol doesn't use it.
/// `amtPerSec` is the amount per second being dripped. Must never be zero.
/// It must have additional `Drips._AMT_PER_SEC_EXTRA_DECIMALS` decimals and can have fractions.
/// To achieve that its value must be multiplied by `Drips._AMT_PER_SEC_MULTIPLIER`.
/// `start` is the timestamp when dripping should start.
/// If zero, use the timestamp when drips are configured.
/// `duration` is the duration of dripping.
/// If zero, drip until balance runs out.
type DripsConfig is uint256;
</code></pre>
Describes a drips configuration. Drips are small amounts of a currency that are paid out over time. The code is used to calculate the amount of the currency that is paid out and when the payments start and end.
**explain the following code**
<pre><code>
using DripsConfigImpl for DripsConfig global;
library DripsConfigImpl {
/// @notice Create a new DripsConfig.
/// @param dripId_ An arbitrary number used to identify a drip.
/// It's a part of the configuration but the protocol doesn't use it.
/// @param amtPerSec_ The amount per second being dripped. Must never be zero.
/// It must have additional `Drips._AMT_PER_SEC_EXTRA_DECIMALS` decimals and can have fractions.
/// To achieve that the passed value must be multiplied by `Drips._AMT_PER_SEC_MULTIPLIER`.
/// @param start_ The timestamp when dripping should start.
/// If zero, use the timestamp when drips are configured.
/// @param duration_ The duration of dripping.
/// If zero, drip until balance runs out.
function create(uint32 dripId_, uint160 amtPerSec_, uint32 start_, uint32 duration_)
internal
pure
returns (DripsConfig)
{
uint256 config = dripId_;
config = (config << 160) | amtPerSec_;
config = (config << 32) | start_;
config = (config << 32) | duration_;
return DripsConfig.wrap(config);
}
</code></pre>
This code is using the DripsConfigImpl library to create a new DripsConfig file. This library will take care of creating the new file and all of the associated settings.
**explain the following code**
<pre><code>
/// @notice Extracts dripId from a `DripsConfig`
function dripId(DripsConfig config) internal pure returns (uint32) {
return uint32(DripsConfig.unwrap(config) >> 224);
}
/// @notice Extracts amtPerSec from a `DripsConfig`
function amtPerSec(DripsConfig config) internal pure returns (uint160) {
return uint160(DripsConfig.unwrap(config) >> 64);
}
/// @notice Extracts start from a `DripsConfig`
function start(DripsConfig config) internal pure returns (uint32) {
return uint32(DripsConfig.unwrap(config) >> 32);
}
/// @notice Extracts duration from a `DripsConfig`
function duration(DripsConfig config) internal pure returns (uint32) {
return uint32(DripsConfig.unwrap(config));
}
</code></pre>
DripsConfig.unwrap(config) returns a uint32, which is then shifted right by a certain amount to get the desired field. For example, duration is extracted by shifting DripsConfig.unwrap(config) by 0 bits.
**explain the following code**
<pre><code>
/// @notice Compares two `DripsConfig`s.
/// First compares their `amtPerSec`s, then their `start`s and then their `duration`s.
function lt(DripsConfig config, DripsConfig otherConfig) internal pure returns (bool) {
return DripsConfig.unwrap(config) DripsConfig.unwrap(otherConfig);
}
}
</code></pre>
The lt function compares two DripsConfigs. First it compares the amtPerSecs, then the starts and then the durations. If any of those values are less than the corresponding value in the other DripsConfig, then it returns true. Otherwise, it returns false.
**explain the following code**
<pre><code>
/// @notice Drips can keep track of at most `type(int128).max`
/// which is `2 ^ 127 - 1` units of each asset.
/// 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 Drips {
/// @notice Maximum number of drips receivers of a single user.
/// Limits cost of changes in drips configuration.
uint256 internal constant _MAX_DRIPS_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`.
uint256 internal constant _AMT_PER_SEC_MULTIPLIER = 1_000_000_000;
/// @notice The total amount the contract can keep track of each asset.
uint256 internal constant _MAX_TOTAL_DRIPS_BALANCE = uint128(type(int128).max);
/// @notice On every timestamp `T`, which is a multiple of `cycleSecs`, the receivers
/// gain access to drips received during `T - cycleSecs` to `T - 1`.
/// Always higher than 1.
uint32 internal immutable _cycleSecs;
/// @notice The storage slot holding a single `DripsStorage` structure.
bytes32 private immutable _dripsStorageSlot;
</code></pre>
The code above is the Drips contract, which is a contract that allows users to keep track of their asset holdings. The contract is Upgradable, which means that it can be upgraded by the owner. The contract has a constructor which takes a single argument, cycleSecs, and initializes the contract. The constructor requires that cycleSecs is greater than 1, and sets the internal variable _cycleSecs to the value of cycleSecs. The constructor also initializes the _dripsStorageSlot variable to the bytes32 value of the keccak256 hash of the msg.sender address and the string "drips". The _initializeDripsStorage() function is called, which initializes the DripsStorage structure for the contract.
**explain the following code**
/// @notice Emitted when the drips configuration of a user is updated.
/// @param userId The user ID.
/// @param assetId The used asset ID
/// @param receiversHash The drips receivers list hash
/// @param dripsHistoryHash The drips history hash which was valid right before the update.
/// @param balance The new drips balance. These funds will be dripped to the receivers.
/// @param maxEnd The maximum end time of drips, 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 DripsSet(
uint256 indexed userId,
uint256 indexed assetId,
bytes32 indexed receiversHash,
bytes32 dripsHistoryHash,
uint128 balance,
uint32 maxEnd
);
- The code is emitting an event called "DripsSet" which includes data about a user's drips configuration being updated. The event data includes the user's ID, the asset ID being used, the drips receivers list hash, the drips history hash, the new drips balance, and the maximum end time for the drips.
**explain the following code**
/// @notice Emitted when a user is seen in a drips receivers list.
/// @param receiversHash The drips receivers list hash
/// @param userId The user ID.
/// @param config The drips configuration.
event DripsReceiverSeen(
bytes32 indexed receiversHash, uint256 indexed userId, DripsConfig config
);
- The code above emits an event whenever a user is seen in a drips receivers list. The event includes the hash of the receivers list, the user's ID, and the drips configuration.
**explain the following code**
/// @notice Emitted when drips are received.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @param amt The received amount.
/// @param receivableCycles The number of cycles which still can be received.
event ReceivedDrips(
uint256 indexed userId, uint256 indexed assetId, uint128 amt, uint32 receivableCycles
);
The code above is emitting an event called "ReceivedDrips" which includes 4 indexed parameters. The first parameter is the user ID, the second is the asset ID, the third is the amount received, and the fourth is the number of cycles which can still be received.
**explain the following code**
/// @notice Emitted when drips are squeezed.
/// @param userId The squeezing user ID.
/// @param assetId The used asset ID.
/// @param senderId The ID of the user sending drips which are squeezed.
/// @param amt The squeezed amount.
event SqueezedDrips(
uint256 indexed userId, uint256 indexed assetId, uint256 indexed senderId, uint128 amt
);
The code is emitting an event called "SqueezedDrips" which includes four parameters. The first three parameters are indexed, meaning that they can be used to filter events. The fourth parameter is the amount of drips that were squeezed.
**explain the following code**
struct DripsStorage {
/// @notice User drips states.
/// The keys are the asset ID and the user ID.
mapping(uint256 => mapping(uint256 => DripsState)) states;
}
the code creates a mapping of user drips states, with the keys being the asset ID and the user ID.
**explain the following code**
struct DripsState {
/// @notice The drips history hash, see `_hashDripsHistory`.
bytes32 dripsHistoryHash;
/// @notice The next squeezable timestamps. The key is the sender's user ID.
/// Each `N`th element of the array is the next squeezable timestamp
/// of the `N`th sender's drips configuration in effect in the current cycle.
mapping(uint256 => uint32[2 ** 32]) nextSqueezed;
/// @notice The drips receivers list hash, see `_hashDrips`.
bytes32 dripsHash;
/// @notice The next cycle to be received
uint32 nextReceivableCycle;
/// @notice The time when drips have been configured for the last time
uint32 updateTime;
/// @notice The maximum end time of drips
uint32 maxEnd;
/// @notice The balance when drips have been configured for the last time
uint128 balance;
/// @notice The number of drips 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 => AmtDelta) amtDeltas;
}
The code defines a struct for tracking the state of a "drip" payment. A drip payment is a periodic payment, often used for things like utility bills or loan payments. The struct tracks the history of drip payments, the next time a payment is due, the amount of the payment, and other information.
**explain the following code**
struct AmtDelta {
/// @notice Amount delta applied on this cycle
int128 thisCycle;
/// @notice Amount delta applied on the next cycle
int128 nextCycle;
}
This code defines a structure for an amount delta, which consists of an amount delta applied on the current cycle, and an amount delta applied on the next cycle.
**explain the following code**
/// @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 users' drips balances 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 dripsStorageSlot The storage slot to holding a single `DripsStorage` structure.
constructor(uint32 cycleSecs, bytes32 dripsStorageSlot) {
require(cycleSecs > 1, "Cycle length too low");
_cycleSecs = cycleSecs;
_dripsStorageSlot = dripsStorageSlot;
}
The code above is the constructor for a contract that implements a new feature called "Drips." This allows users to send small amounts of ether to each other without incurring transaction fees. The cycleSecs parameter determines how often the contract will check the balances of users and make sure that they are not spending more than they have in their "drips" balance. The dripsStorageSlot parameter determines where in storage the contract will keep track of the Drips Storage structure.
**explain the following code**
/// @notice Receive drips from unreceived cycles of the user.
/// Received drips cycles won't need to be analyzed ever again.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @param maxCycles The maximum number of received drips 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
/// @return receivableCycles The number of cycles which still can be received
function _receiveDrips(uint256 userId, uint256 assetId, uint32 maxCycles)
internal
returns (uint128 receivedAmt, uint32 receivableCycles)
{
uint32 fromCycle;
uint32 toCycle;
int128 finalAmtPerCycle;
(receivedAmt, receivableCycles, fromCycle, toCycle, finalAmtPerCycle) =
_receiveDripsResult(userId, assetId, maxCycles);
if (fromCycle != toCycle) {
DripsState storage state = _dripsStorage().states[assetId][userId];
state.nextReceivableCycle = toCycle;
mapping(uint32 => AmtDelta) storage amtDeltas = state.amtDeltas;
for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) {
delete amtDeltas[cycle];
}
// The next cycle delta must be relative to the last received cycle, which got zeroed.
// In other words the next cycle delta must be an absolute value.
if (finalAmtPerCycle != 0) {
amtDeltas[toCycle].thisCycle += finalAmtPerCycle;
}
}
emit ReceivedDrips(userId, assetId, receivedAmt, receivableCycles);
}
this code is the internal function for receiving dividends from another user. The code sets up some variables to be used in the function, then calculates the amount of the dividend to be received, and finally updates the record of the dividend to reflect that it has been received.
**explain the following code**
/// @notice Calculate effects of calling `_receiveDrips` with the given parameters.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @param maxCycles The maximum number of received drips 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 _receiveDripsResult(uint256 userId, uint256 assetId, uint32 maxCycles)
The code above is a function that calculates the amount of an asset that a user can receive, the number of cycles that the asset can be received for, and the amount of the asset per cycle.
**explain the following code**
internal
view
returns (
uint128 receivedAmt,
uint32 receivableCycles,
uint32 fromCycle,
uint32 toCycle,
int128 amtPerCycle
)
{
(fromCycle, toCycle) = _receivableDripsCyclesRange(userId, assetId);
if (toCycle - fromCycle > maxCycles) {
receivableCycles = toCycle - fromCycle - maxCycles;
toCycle -= receivableCycles;
}
DripsState storage state = _dripsStorage().states[assetId][userId];
for (uint32 cycle = fromCycle; cycle < toCycle; cycle++) {
amtPerCycle += state.amtDeltas[cycle].thisCycle;
receivedAmt += uint128(amtPerCycle);
amtPerCycle += state.amtDeltas[cycle].nextCycle;
}
}
The code is designed to return the amount of an asset that a user has received over a period of time, as well as the number of cycles that the user is receivable for. The code first calculates the from and to cycles for the current user and asset, then sets the receivable cycles to the difference between the to and from cycles. If the receivable cycles is greater than the max cycles, the code subtracts the receivable cycles from the to cycle. Finally, the code loops through the cycles from the from cycle to the to cycle, adding the amount per cycle to the received amount.
**explain the following code**
/// @notice Counts cycles from which drips can be received.
/// This function can be used to detect that there are
/// too many cycles to analyze in a single transaction.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @return cycles The number of cycles which can be flushed
function _receivableDripsCycles(uint256 userId, uint256 assetId)
internal
view
returns (uint32 cycles)
{
(uint32 fromCycle, uint32 toCycle) = _receivableDripsCyclesRange(userId, assetId);
return toCycle - fromCycle;
}
The code above is from the _receivableDripsCycles function in the DripManager contract. This function is internal and view, meaning that it can only be called from within the contract and doesn't modify the state of the contract. The function takes in a user ID and asset ID and returns the number of cycles that can be flushed.
**explain the following code**
/// @notice Calculates the cycles range from which drips can be received.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @return fromCycle The cycle from which funds can be received
/// @return toCycle The cycle to which funds can be received
function _receivableDripsCyclesRange(uint256 userId, uint256 assetId)
private
view
returns (uint32 fromCycle, uint32 toCycle)
{
fromCycle = _dripsStorage().states[assetId][userId].nextReceivableCycle;
toCycle = _cycleOf(_currTimestamp());
if (fromCycle == 0 || toCycle < fromCycle) {
toCycle = fromCycle;
}
}
The code calculates the cycles range from which drips can be received by a user for a specific asset. The range is calculated as the next receivable cycle for the user for the asset and the current timestamp. If the next receivable cycle is in the future, the calculation returns the next receivable cycle.
**explain the following code**
/// @notice Transfers the specified amount from the account of the caller
/// of the contract to the specified target account, provided the caller
/// account's balance is equal to or above the amount being transferred.
/// @param target The account to which the specified amount of REX will be
/// transferred.
/// @param amount The amount of REX to be transferred to the specified target
/// account.
/// @return true Transfer completed successfully.
/// @return false Transfer could not be completed because the caller's
/// account's balance is below the amount of REX being transferred.
function _transfer(
account_name target,
cleaner amount
)
internal
returns (bool)
{
eosio_assert(amount >= 0, "amount must be a number greater than or equal to zero");
eosio_assert(amount <= _rexBalanceOf(_self), "amount is greater than available supply");
account_name tokenContract = eosio_assert_message(_isToken(CLEANER:_tokenSymbol), "ERR::TRANSFER::INVALID_TOKEN_CONTRACT", "token contract specified is not a valid EOSIO token contract");
The purpose of the above code is to transfer a specified amount of REX from the caller's account to a specified target account, provided that the caller's account balance is equal to or above the amount being transferred. If the transfer is successful, the function will return true. Otherwise, it will return false.
**explain the following code**
cleaner balance = _balanceOf(_self, CLEANER = _tokenSymbol);
action(
permission_level{ _self, N(active) },
tokenContract,
N(transfer),
make_tuple(_self, target, balance, string_to_symbol(0, _tokenSymbol))
).send();
_addToBalance(target, CLEANER = _tokenSymbol, balance);
}
_transfer: function (target, amount) {
var _this = this;
return __awaiter(_this, void 0, void 0, function () {
var tokenContract;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
_this.assert(amount >= 0, "amount must be a number greater than or equal to zero");
_this.assert(amount <= _this._rexBalanceOf(_this.account), "amount is greater than available supply");
tokenContract = _this._tokenSymbol;
_this.assert(_this._isToken(tokenContract), "ERR::TRANSFER::INVALID_TOKEN_CONTRACT", "token contract specified is not a valid EOSIO token contract");
return [4, action.transfer(_this.account, target, _this._balanceOf(_this.account, tokenContract), string_to_symbol(0, symbol_type(tokenContract).name()))];
case 1:
_a.sent();
_this._addToBalance(target,;
return [2, true];
}
});
});
},
}
The second block of code is a function that is used to transfer an amount of a specified token from one account to another. The first block of code is setting the balance of the cleaner account to the total balance of the specified token. The action then sends the amount specified in the function to the target account. The last line of code then updates the target account's balance of the specified token.
**explain the following code**
Rinkeby Network
Simulate Call:
_totalStaked
_totalStaked: function () {
var _this = this;
return __awaiter(_this, void 0, void 0, function () {
var staked;
return __generator(this, function (_a) {
staked = 0;
(0, _keys2.default)(_this.accounts).forEach(function (account) {
var stake = _utils.toDecimal(_this.accounts[account].staked, 16);
var amount = _utils.toDecimal(_this.accounts[account].amount, 16);
- The function above is called _totalStaked. It takes in no parameters and returns a staked value. This value is calculated by taking the stake from each account and multiplying it by the amount in that account.
**explain the following code**
if (account === _this.gnySender || account === _this.gnyContract with 10 REX balance
return _this.contracts.tokens.transfer(_this.gnySender, _this.gnyContract, stake, { from: _this.gnySender })
})
.then(function () {
return _this.contracts.gnyToken.getBalance(_this.gnyContract);
})
.then(function (result) {
(0, _chai.expect)((0, _utils.toDecimal)(result, 16)).to.equal(10);
})
.catch(_utils2.default.assertFail);
return null.tokens.transfer(_this.gnySender, _this.gnyContract, { from: _this.gnySender });
return _this.contracts.tokens.transfer(_this.gnySender, _this.gnyContract, 10, { from: _this.gnySender });
return null();Another user deposits 100 REX on the contract
return _this.contracts.tokens.transfer(_this.gnySender, _this.gnyContract, { from: _this.gnySender });
return _this.contracts.tokens.transfer(_this.gnySender, _this.gnyContract, 100, { from: _this.gnySender });
return [2];First user buys 10 REX on the contract
return __awaiter(_this, void 0, void 0, function () {
var stake, receipt, _ receipt $logs$ 0$args, args, result;
This code is performaing a series of asyncronous tasks. The first task is to check if the account is the same as the sender's account. The second task is to transfer the stake from the sender to the contract. The third task is to check the balance of the contract. The fourth task is to buy REX on the contract.
**explain the following code**
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
stake = _this.web3.toBigNumber(10);
return [4contracts.gnyToken[_this.web3.eth.defaultAccount];
case 1:
receipt = _a.sent();
(0, _chai.expect)(receipt.logs.length).to.be.equal(1);
(0, _chai.expect)(receipt.logs[0].event).to.be.equal("Transfer");
_receipt $logs$ 0 $args = receipt.logs[0].args, args = _receipt$ logs $0$ args === void 0 ? {} : _receipt $logs$ 0$args;
(0, _chai.expect)(_this.web3.toBigNumber(args.staked)).to.be.bignumber.equal(stake);
(0, _chai.expect)(_this.web3.toBigNumber(args.unstaked)).to.be.bignumber.equal(0);
return [4balanceOf(_this.web3.eth.defaultAccount)];
- The function stake allows the user to stake GNY tokens to the contract, which locks the tokens until they are unstaked. The receipt logs show the event Transfer, the amount staked, and the new balance. The final balance should match the amount staked.
-
explain the following code
_receivableDripsCyclesRange: function (userId, assetId) {
var _this = this;
return __awaiter(_this, void 0, void 0, function () {
var fromCycle, toCycle;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
fromCycle = _this.dripsStorage.states[assetId][userId].nextReceivableCycle;
toCycle = _this._cycleOf(_this._currTimestamp());
if (fromCycle == 0 || toCycle < fromCycle) {
toCycle = fromCycle;
}
return [2 test(this, assetId, userId, fromCycle, toCycle)];//2
case 1:
return [2];//2
}
});
});
},
}
- The code defines a function that returns a promise. The function is used to get the cycle range for a given user and asset. The function takes in a user ID and asset ID as parameters. The function first assigns the next receivable cycle from the asset's drip storage state to the fromCycle variable. It then assigns the current timestamp's cycle to the toCycle variable. If the fromCycle is 0 or the toCycle is less than the fromCycle, the toCycle is set to the fromCycle. The function then calls the test function with the fromCycle and toCycle variables as well as the assetId and userId parameters. The function returns a promise that resolves to the test function's return value.
- explain the following code
/// @notice Receive drips from the currently running cycle from a single sender.
/// It doesn't receive drips from the previous, finished cycles, to do that use `_receiveDrips`.
/// Squeezed funds won't be received in the next calls to `_squeezeDrips` or `_receiveDrips`.
/// Only funds dripped before `block.timestamp` can be squeezed.
/// @param userId The ID of the user receiving drips to squeeze funds for.
/// @param assetId The used asset ID.
/// @param senderId The ID of the user sending drips to squeeze funds from.
/// @param historyHash The sender's history hash which was valid right before
/// they set up the sequence of configurations described by `dripsHistory`.
/// @param dripsHistory The sequence of the sender's drips 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 drips described by `dripsHistory` will be squeezed.
/// If `dripsHistory` entries have no receivers, they won't be squeezed.
/// @return amt The squeezed amount.
function _squeezeDrips(
uint256 userId,
uint256 assetId,
uint256 senderId,
bytes32 historyHash,
DripsHistory[] memory dripsHistory
) internal returns (uint128 amt) {
uint256 squeezedNum;
uint256[] memory squeezedIdxs;
(amt, squeezedNum, squeezedIdxs) =
_squeezeDripsResult(userId, assetId, senderId, historyHash, dripsHistory);
DripsState storage state = _dripsStorage().states[assetId][userId];
uint32[2 ** 32] storage nextSqueezed = state.nextSqueezed[senderId];
for (uint256 i = 0; i < squeezedNum; i++) {
nextSqueezed[squeezedIdxs[i]] = _currTimestamp();
}
uint32 cycleStart = _currCycleStart();
_addDeltaRange(state, cycleStart, cycleStart + 1, -int256(amt * _AMT_PER_SEC_MULTIPLIER));
emit SqueezedDrips(userId, assetId, senderId, amt);
}
- The code allows the user to receive funds that have been squeezed from the current cycle from a single sender. It doesn't allow the user to receive funds from the previous cycle. The code also ensures that the user can only receive funds that have been squeezed before the block.timestamp.
- explain the following code
/// @notice Calculate effects of calling `_squeezeDrips` with the given parameters.
/// See its documentation for more details.
/// @param userId The ID of the user receiving drips to squeeze funds for.
/// @param assetId The used asset ID.
/// @param senderId The ID of the user sending drips to squeeze funds from.
/// @param historyHash The sender's history hash which was valid right before `dripsHistory`.
/// @param dripsHistory The sequence of the sender's drips configurations.
/// @return amt The squeezed amount.
/// @return squeezedNum The number of squeezed history entries.
/// @return squeezedIdxs The `nextSqueezed` indexes of squeezed history entries.
function _squeezeDripsResult(
uint256 userId,
uint256 assetId,
uint256 senderId,
bytes32 historyHash,
DripsHistory[] memory dripsHistory
) internal view returns (uint128 amt, uint256 squeezedNum, uint256[] memory squeezedIdxs) {
DripsState storage senderState = _dripsStorage().states[assetId][senderId];
_verifyDripsHistory(historyHash, dripsHistory, senderState.dripsHistoryHash);
uint32[2 ** 32] storage nextSqueezed =
_dripsStorage().states[assetId][userId].nextSqueezed[senderId];
uint32 squeezeEnd = _currTimestamp();
// If the last update was not in the current cycle,
// there's only the single latest history entry to squeeze in the current cycle.
uint256 squeezedIdx = 1;
if (senderState.updateTime >= _currCycleStart()) squeezedIdx = senderState.currCycleConfigs;
uint256 historyIdx = dripsHistory.length;
squeezedIdxs = new uint256[](historyIdx);
while (squeezedIdx > 0 && historyIdx > 0) {
squeezedIdx--;
historyIdx--;
DripsHistory memory drips = dripsHistory[historyIdx];
if (drips.receivers.length != 0) {
uint32 squeezeStart = nextSqueezed[squeezedIdx];
if (squeezeStart < _currCycleStart()) squeezeStart = _currCycleStart();
if (squeezeStart < squeezeEnd) {
squeezedIdxs[squeezedNum++] = squeezedIdx;
amt += _squeezedAmt(userId, drips, squeezeStart, squeezeEnd);
}
}
squeezeEnd = drips.updateTime;
}
}
- Function _squeezeDripsResult squeezes drips between a certain time period for a single user. First, it finds the most recent timestamp that the user was updated, then iterates backwards through a list of drips History. For each history entry, it calculates the amount of time that has passed since the drips update time, and the amount of time that has passed since the last time the user was updated. If both of these values are greater than 0, it will call the _squeezedAmt function to calculate the amount of the asset that the user will receive, add this amount to a total, and increase a counter. Finally, it will return the total amount, the number of squeezed history entries, and the nextSqueezed indexes of squeezed history entries.
- explain the following code
/// @notice Verify a drips history and revert if it's invalid.
/// @param historyHash The user's history hash which was valid right before `dripsHistory`.
/// @param dripsHistory The sequence of the user's drips configurations.
/// @param finalHistoryHash The history hash at the end of `dripsHistory`.
function _verifyDripsHistory(
bytes32 historyHash,
DripsHistory[] memory dripsHistory,
bytes32 finalHistoryHash
) private pure {
for (uint256 i = 0; i < dripsHistory.length; i++) {
DripsHistory memory drips = dripsHistory[i];
bytes32 dripsHash = drips.dripsHash;
if (drips.receivers.length != 0) {
require(dripsHash == 0, "Drips history entry with hash and receivers");
dripsHash = _hashDrips(drips.receivers);
}
historyHash = _hashDripsHistory(historyHash, dripsHash, drips.updateTime, drips.maxEnd);
}
require(historyHash == finalHistoryHash, "Invalid drips history");
}
- /// The function is designed to check the validity of a user's drip history. In order to do so, it first creates a hash of the user's history, then it goes through each entry in the user's drip history. For each entry, it first checks whether the hash of the entry is equal to 0. If it's not, it proceeds to check whether the hash of the entry is equal to the hash of the entry's receivers. If it's not, the function returns an error. If it is, the function updates the user's history hash. Finally, the function compares the user's updated history hash to the final history hash. If they're not equal, the function returns an error.
- explain the following code
/// @notice Calculate the amount squeezable by a user from a single drips history entry.
/// @param userId The ID of the user to squeeze drips for.
/// @param dripsHistory The squeezed history entry.
/// @param squeezeStart The squeezed time range start.
/// @param squeezeStart The squeezed time range end.
/// @return squeezedAmt The squeezed amount.
function _squeezedAmt(
uint256 userId,
DripsHistory memory dripsHistory,
uint32 squeezeStart,
uint32 squeezeEnd
) private view returns (uint128 squeezedAmt) {
DripsReceiver[] memory receivers = dripsHistory.receivers;
// Binary search for the `idx` of the first occurrence of `userId`
uint256 idx = 0;
for (uint256 idxCap = receivers.length; idx < idxCap;) {
uint256 idxMid = (idx + idxCap) / 2;
if (receivers[idxMid].userId < userId) {
idx = idxMid + 1;
} else {
idxCap = idxMid;
}
}
uint32 updateTime = dripsHistory.updateTime;
uint32 maxEnd = dripsHistory.maxEnd;
uint256 amt = 0;
for (; idx < receivers.length; idx++) {
DripsReceiver memory receiver = receivers[idx];
if (receiver.userId != userId) break;
(uint32 start, uint32 end) =
_dripsRange(receiver, updateTime, maxEnd, squeezeStart, squeezeEnd);
amt += _drippedAmt(receiver.config.amtPerSec(), start, end);
}
return uint128(amt);
}
- So basically this is a function that calculates how much a user would get from a "drips history entry". A drips history entry is basically just a record of how much someone has earned from a certain task. The code is just doing a binary search to find the user's ID, and then once it finds it, it calculates how much they earned based on the time they spent doing the task.
- explain the following code
/// @notice Current user drips state.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @return dripsHash The current drips receivers list hash, see `_hashDrips`
/// @return dripsHistoryHash The current drips history hash, see `_hashDripsHistory`.
/// @return updateTime The time when drips have been configured for the last time
/// @return balance The balance when drips have been configured for the last time
/// @return maxEnd The current maximum end time of drips
function _dripsState(uint256 userId, uint256 assetId)
internal
view
returns (
bytes32 dripsHash,
bytes32 dripsHistoryHash,
uint32 updateTime,
uint128 balance,
uint32 maxEnd
)
{
DripsState storage state = _dripsStorage().states[assetId][userId];
return (
state.dripsHash, state.dripsHistoryHash, state.updateTime, state.balance, state.maxEnd
);
}
- The function above retrieves the current state of the drip system for a particular user for a particular asset. It returns the current hash of the receivers list, the current hash of the drips history, the time when the drips were last updated, the balance at that time, and the maximum end time of the drips.
- explain the following code
/// @notice User drips balance at a given timestamp
/// @param userId The user ID
/// @param assetId The used asset ID
/// @param receivers The current drips receivers list
/// @param timestamp The timestamps for which balance should be calculated.
/// It can't be lower than the timestamp of the last call to `setDrips`.
/// If it's bigger than `block.timestamp`, then it's a prediction assuming
/// that `setDrips` won't be called before `timestamp`.
/// @return balance The user balance on `timestamp`
function _balanceAt(
uint256 userId,
uint256 assetId,
DripsReceiver[] memory receivers,
uint32 timestamp
) internal view returns (uint128 balance) {
DripsState storage state = _dripsStorage().states[assetId][userId];
require(timestamp >= state.updateTime, "Timestamp before last drips update");
require(_hashDrips(receivers) == state.dripsHash, "Invalid current drips list");
return _balanceAt(state.balance, state.updateTime, state.maxEnd, receivers, timestamp);
}
- User's balance at a given timestamp is calculated recursively. If the timestamp is greater than the last time the drips were updated, the calculation is based on the assumption that the `setDrips` function will not be called before the given timestamp.
- explain the following code
/// @notice Calculates the drips balance at a given timestamp.
/// @param lastBalance The balance when drips have started
/// @param lastUpdate The timestamp when drips have started.
/// @param maxEnd The maximum end time of drips
/// @param receivers The list of drips 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 `setDrips` won't be called before `timestamp`.
/// @return balance The user balance on `timestamp`
function _balanceAt(
uint128 lastBalance,
uint32 lastUpdate,
uint32 maxEnd,
DripsReceiver[] memory receivers,
uint32 timestamp
) private view returns (uint128 balance) {
balance = lastBalance;
for (uint256 i = 0; i < receivers.length; i++) {
DripsReceiver memory receiver = receivers[i];
(uint32 start, uint32 end) = _dripsRange({
receiver: receiver,
updateTime: lastUpdate,
maxEnd: maxEnd,
startCap: lastUpdate,
endCap: timestamp
});
balance -= uint128(_drippedAmt(receiver.config.amtPerSec(), start, end));
}
}
- It calculates the drips balance at a given timestamp by iterating over the arrays of receivers and calculating the total balance of all the receivers.
- explain the following code
/// @notice Sets the user's drips configuration.
/// @param userId The user ID
/// @param assetId The used asset ID
/// @param currReceivers The list of the drips receivers set in the last drips update
/// of the user.
/// If this is the first update, pass an empty array.
/// @param balanceDelta The drips balance change being applied.
/// Positive when adding funds to the drips balance, negative to removing them.
/// @param newReceivers The list of the drips receivers of the user to be set.
/// Must be sorted, deduplicated and without 0 amtPerSecs.
/// @return newBalance The new drips balance of the user.
/// @return realBalanceDelta The actually applied drips balance change.
function _setDrips(
uint256 userId,
uint256 assetId,
DripsReceiver[] memory currReceivers,
int128 balanceDelta,
DripsReceiver[] memory newReceivers
) internal returns (uint128 newBalance, int128 realBalanceDelta) {
DripsState storage state = _dripsStorage().states[assetId][userId];
bytes32 currDripsHash = _hashDrips(currReceivers);
require(currDripsHash == state.dripsHash, "Invalid current drips list");
uint32 lastUpdate = state.updateTime;
uint32 currMaxEnd = state.maxEnd;
{
uint128 lastBalance = state.balance;
uint128 currBalance =
_balanceAt(lastBalance, lastUpdate, currMaxEnd, currReceivers, _currTimestamp());
int136 balance = int128(currBalance) + int136(balanceDelta);
if (balance < 0) {
balance = 0;
}
newBalance = uint128(uint136(balance));
realBalanceDelta = int128(balance - int128(currBalance));
}
uint32 newMaxEnd = _calcMaxEnd(newBalance, newReceivers);
_updateReceiverStates(
_dripsStorage().states[assetId],
currReceivers,
lastUpdate,
currMaxEnd,
newReceivers,
newMaxEnd
);
state.updateTime = _currTimestamp();
state.maxEnd = newMaxEnd;
state.balance = newBalance;
bytes32 dripsHistory = state.dripsHistoryHash;
if (dripsHistory != 0 && _cycleOf(lastUpdate) != _cycleOf(_currTimestamp())) {
state.currCycleConfigs = 2;
} else {
state.currCycleConfigs++;
}
bytes32 newDripsHash = _hashDrips(newReceivers);
state.dripsHistoryHash =
_hashDripsHistory(dripsHistory, newDripsHash, _currTimestamp(), newMaxEnd);
emit DripsSet(userId, assetId, newDripsHash, dripsHistory, newBalance, newMaxEnd);
if (newDripsHash != currDripsHash) {
state.dripsHash = newDripsHash;
for (uint256 i = 0; i < newReceivers.length; i++) {
DripsReceiver memory receiver = newReceivers[i];
emit DripsReceiverSeen(newDripsHash, receiver.userId, receiver.config);
}
}
}
- The code sets the user's drips configuration. It takes in the user ID, asset ID, a list of the drips receivers set in the last drips update of the user, a balance change, and a list of the drips receivers of the user to be set. It returns the new balance and the actually applied drips balance change.
- explain the following code
/// @notice Calculates the maximum end time of drips.
/// @param balance The balance when drips have started
/// @param receivers The list of drips receivers.
/// Must be sorted, deduplicated and without 0 amtPerSecs.
/// @return maxEnd The maximum end time of drips
function _calcMaxEnd(uint128 balance, DripsReceiver[] memory receivers)
internal
view
returns (uint32 maxEnd)
{
require(receivers.length <= _MAX_DRIPS_RECEIVERS, "Too many drips receivers");
uint256[] memory configs = new uint256[](receivers.length);
uint256 configsLen = 0;
for (uint256 i = 0; i < receivers.length; i++) {
DripsReceiver memory receiver = receivers[i];
if (i > 0) {
require(_isOrdered(receivers[i - 1], receiver), "Receivers not sorted");
}
configsLen = _addConfig(configs, configsLen, receiver);
}
return _calcMaxEnd(balance, configs, configsLen);
}
- This function calculates the maximum end time of drips.
- explain the following code
/// @notice Calculates the maximum end time of drips.
/// @param balance The balance when drips have started
/// @param configs The list of drips configurations
/// @param configsLen The length of `configs`
/// @return maxEnd The maximum end time of drips
function _calcMaxEnd(uint128 balance, uint256[] memory configs, uint256 configsLen)
private
view
returns (uint32 maxEnd)
{
unchecked {
uint256 enoughEnd = _currTimestamp();
if (configsLen == 0 || balance == 0) {
return uint32(enoughEnd);
}
uint256 notEnoughEnd = type(uint32).max;
if (_isBalanceEnough(balance, configs, configsLen, notEnoughEnd)) {
return uint32(notEnoughEnd);
}
while (true) {
uint256 end = (enoughEnd + notEnoughEnd) / 2;
if (end == enoughEnd) {
return uint32(end);
}
if (_isBalanceEnough(balance, configs, configsLen, end)) {
enoughEnd = end;
} else {
notEnoughEnd = end;
}
}
}
}
- they are calculating the maximum amount of time that the drips will last
- explain the following code
/// @notice Check if a given balance is enough to cover drips with the given `maxEnd`.
/// @param balance The balance when drips have started
/// @param configs The list of drips configurations
/// @param configsLen The length of `configs`
/// @param maxEnd The maximum end time of drips
/// @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);
if (maxEnd <= start) {
continue;
}
if (end > maxEnd) {
end = maxEnd;
}
spent += _drippedAmt(amtPerSec, start, end);
if (spent > balance) {
return false;
}
}
return true;
}
}
- The code above is checking if a given balance is enough to cover the drips with the given max end. It does this by looping through the configs array and calculating the amount spent on each drip. If the total amount spent is greater than the balance, it returns false. Otherwise, it returns true.
- explain the following code
/// @notice Preprocess and add a drips receiver to the list of configurations.
/// @param configs The list of drips configurations
/// @param configsLen The length of `configs`
/// @param receiver The added drips receivers.
/// @return newConfigsLen The new length of `configs`
function _addConfig(uint256[] memory configs, uint256 configsLen, DripsReceiver memory receiver)
private
view
returns (uint256 newConfigsLen)
{
uint256 amtPerSec = receiver.config.amtPerSec();
require(amtPerSec != 0, "Drips receiver amtPerSec is zero");
(uint256 start, uint256 end) =
_dripsRangeInFuture(receiver, _currTimestamp(), type(uint32).max);
if (start == end) {
return configsLen;
}
configs[configsLen] = (amtPerSec << 64) | (start << 32) | end;
return configsLen + 1;
}
- This code is from the _addConfig() function in the Drips contract. This function is used to add a drips receiver to the list of configurations. The code first calculates the amount of DRIP tokens per second that the receiver will receive. It then checks to see if the start and end time of the drips are in the future. If they are, the code adds the receiver to the list of configurations.
- explain the following code
/// @notice Load a drips configuration from the list.
/// @param configs The list of drips configurations
/// @param idx The loaded configuration index. It must be smaller than the `configs` length.
/// @return amtPerSec The amount per second being dripped.
/// @return start The timestamp when dripping starts.
/// @return end The maximum timestamp when dripping ends.
function _getConfig(uint256[] memory configs, uint256 idx)
private
pure
returns (uint256 amtPerSec, uint256 start, uint256 end)
{
uint256 val;
// solhint-disable-next-line no-inline-assembly
assembly ("memory-safe") {
val := mload(add(32, add(configs, shl(5, idx))))
}
return (val >> 64, uint32(val >> 32), uint32(val));
}
- It loads a Drips configuration from the list. The list of Drips configurations is an array of uint256 values, where each value is a uint256 packed with 3 other values: amtPerSec, start, and end. assembly ("memory-safe") {val := mload(add(32, add(configs, shl(5, idx))))}
- The memory-safe assembly instruction loads a value from memory, adding 32 to the address of the configs array and shifting the index (idx) by 5 to get the correct uint256 value.
- explain the following code
/// @notice Calculates the hash of the drips configuration.
/// It's used to verify if drips configuration is the previously set one.
/// @param receivers The list of the drips receivers.
/// Must be sorted, deduplicated and without 0 amtPerSecs.
/// If the drips have never been updated, pass an empty array.
/// @return dripsHash The hash of the drips configuration
function _hashDrips(DripsReceiver[] memory receivers)
internal
pure
returns (bytes32 dripsHash)
{
if (receivers.length == 0) {
return bytes32(0);
}
return keccak256(abi.encode(receivers));
}
- The code calculates the hash of the drips configuration. It is used to verify if the drips configuration is the previously set one. The code takes an array of drips receivers as input and returns the hash of the drips configuration as output.
- explain the following code
/// @notice Calculates the hash of the drips history after the drips configuration is updated.
/// @param oldDripsHistoryHash The history hash which was valid before the drips were updated.
/// The `dripsHistoryHash` of a user before they set drips for the first time is `0`.
/// @param dripsHash The hash of the drips receivers being set.
/// @param updateTime The timestamp when the drips are updated.
/// @param maxEnd The maximum end of the drips being set.
/// @return dripsHistoryHash The hash of the updated drips history.
function _hashDripsHistory(
bytes32 oldDripsHistoryHash,
bytes32 dripsHash,
uint32 updateTime,
uint32 maxEnd
) internal pure returns (bytes32 dripsHistoryHash) {
return keccak256(abi.encode(oldDripsHistoryHash, dripsHash, updateTime, maxEnd));
}
- The hashDripsHistory function calculates the hash of the drips history after the drips configuration is updated. The function takes four parameters:
- oldDripsHistoryHash: The history hash which was valid before the drips were updated. The dripsHistoryHash of a user before they set drips for the first time is 0.
- dripsHash: The hash of the drips receivers being set.
- updateTime: The timestamp when the drips are updated.
- maxEnd: The maximum end of the drips being set.
- The function returns the updated dripsHistoryHash.
- explain the following code
/// @notice Applies the effects of the change of the drips on the receivers' drips states.
/// @param states The drips states for the used asset.
/// @param currReceivers The list of the drips receivers set in the last drips update
/// of the user.
/// If this is the first update, pass an empty array.
/// @param lastUpdate the last time the sender updated the drips.
/// If this is the first update, pass zero.
/// @param currMaxEnd The maximum end time of drips according to the last drips update.
/// @param newReceivers The list of the drips receivers of the user to be set.
/// Must be sorted, deduplicated and without 0 amtPerSecs.
/// @param newMaxEnd The maximum end time of drips according to the new drips configuration.
function _updateReceiverStates(
mapping(uint256 => DripsState) storage states,
DripsReceiver[] memory currReceivers,
uint32 lastUpdate,
uint32 currMaxEnd,
DripsReceiver[] memory newReceivers,
uint32 newMaxEnd
) private {
uint256 currIdx = 0;
uint256 newIdx = 0;
while (true) {
bool pickCurr = currIdx < currReceivers.length;
DripsReceiver memory currRecv;
if (pickCurr) {
currRecv = currReceivers[currIdx];
}
bool pickNew = newIdx < newReceivers.length;
DripsReceiver memory newRecv;
if (pickNew) {
newRecv = newReceivers[newIdx];
}
- This code is updating the drips states for the users that have been set in the last drips update. It is looping through the currReceivers and the newReceivers arrays, and for each receiver in the arrays, it is updating the state of that receiver.
- explain the following code
// Limit picking both curr and new to situations when they differ only by time
if (
pickCurr && pickNew
&& (
currRecv.userId != newRecv.userId
|| currRecv.config.amtPerSec() != newRecv.config.amtPerSec()
)
) {
pickCurr = _isOrdered(currRecv, newRecv);
pickNew = !pickCurr;
}
- This code is limiting the picking of both the current and the new to situations when they only differ by time.
- explain the following code
if (pickCurr && pickNew) {
// Shift the existing drip to fulfil the new configuration
DripsState storage state = states[currRecv.userId];
(uint32 currStart, uint32 currEnd) =
_dripsRangeInFuture(currRecv, lastUpdate, currMaxEnd);
(uint32 newStart, uint32 newEnd) =
_dripsRangeInFuture(newRecv, _currTimestamp(), newMaxEnd);
{
int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec()));
// Move the start and end times if updated
_addDeltaRange(state, currStart, newStart, -amtPerSec);
_addDeltaRange(state, currEnd, newEnd, amtPerSec);
}
// Ensure that the user 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.
if (currStartCycle > newStartCycle && state.nextReceivableCycle > newStartCycle) {
state.nextReceivableCycle = newStartCycle;
}
} else if (pickCurr) {
// Remove an existing drip
DripsState storage state = states[currRecv.userId];
(uint32 start, uint32 end) = _dripsRangeInFuture(currRecv, lastUpdate, currMaxEnd);
int256 amtPerSec = int256(uint256(currRecv.config.amtPerSec()));
_addDeltaRange(state, start, end, -amtPerSec);
} else if (pickNew) {
// Create a new drip
DripsState storage state = states[newRecv.userId];
(uint32 start, uint32 end) =
_dripsRangeInFuture(newRecv, _currTimestamp(), newMaxEnd);
int256 amtPerSec = int256(uint256(newRecv.config.amtPerSec()));
_addDeltaRange(state, start, end, amtPerSec);
// Ensure that the user receives the updated cycles
uint32 startCycle = _cycleOf(start);
if (state.nextReceivableCycle == 0 || state.nextReceivableCycle > startCycle) {
state.nextReceivableCycle = startCycle;
}
} else {
break;
}
- The code checks if a user has been selected to receive a drip and if so it creates a 'DripsState' object. It then calculates the start and end time of the drip and the amount of the drip using the '_dripsRangeInFuture' and '_addDeltaRange' functions. Finally it checks if the user has been updated and if so sets the 'nextReceivableCycle' variable to the start time of the drip.
- explain the following code
if (pickCurr) {
currIdx++;
}
if (pickNew) {
newIdx++;
}
}
}
- is doing a check to see if it should pick the current index or the new index. If it should pick the current index, it will increment the current index by 1. If it should pick the new index, it will increment the new index by 1.
- explain the following code
/// @notice Calculates the time range in the future in which a receiver will be dripped to.
/// @param receiver The drips receiver
/// @param maxEnd The maximum end time of drips
function _dripsRangeInFuture(DripsReceiver memory receiver, uint32 updateTime, uint32 maxEnd)
private
view
returns (uint32 start, uint32 end)
{
return _dripsRange(receiver, updateTime, maxEnd, _currTimestamp(), type(uint32).max);
}
- The code calculates the time range in the future in which a receiver will be dripped to.
- explain the following code
/// @notice Calculates the time range in which a receiver is to be dripped to.
/// This range is capped to provide a view on drips through a specific time window.
/// @param receiver The drips receiver
/// @param updateTime The time when drips are configured
/// @param maxEnd The maximum end time of drips
/// @param startCap The timestamp the drips range start should be capped to
/// @param endCap The timestamp the drips range end should be capped to
function *dripsRange(
DripsReceiver memory receiver,
uint32 updateTime,
uint32 maxEnd,
uint32 startCap,
uint32 endCap
) private pure returns (uint32 start, uint32 end*) {
start = receiver.config.start();
if (start == 0) {
start = updateTime;
}
uint40 end = uint40(start) + receiver.config.duration();
if (end == start || end > maxEnd) {
end = maxEnd;
}
if (start < startCap) {
start = startCap;
}
if (end > endCap) {
end = endCap;
}
if (end < start) {
end = start;
}
return (start, uint32(end));
}
- This code calculates how long a receiver will have to wait before they can receive their drip. The range is capped to provide a view on drips through a specific time window.
- explain the following code
/// @notice Adds funds received by a user in a given time range
/// @param state The user state
/// @param start The timestamp from which the delta takes effect
/// @param end The timestamp until which the delta takes effect
/// @param amtPerSec The dripping rate
function _addDeltaRange(DripsState storage state, uint32 start, uint32 end, int256 amtPerSec)
private
{
if (start == end) {
return;
}
mapping(uint32 => AmtDelta) storage amtDeltas = state.amtDeltas;
_addDelta(amtDeltas, start, amtPerSec);
_addDelta(amtDeltas, end, -amtPerSec);
}
- This code creates a _addDeltaRange function which takes in 4 parameters: DripsState, uint32, uint32, int256.
- This function is private.
- The code creates a mapping storage amtDeltas which is set to state.amtDeltas.
- The following two lines of code add a delta to start and end with the amount being amtPerSec.
- explain the following code
/// @notice Adds delta of funds received by a user at a given time
/// @param amtDeltas The user amount deltas
/// @param timestamp The timestamp when the deltas need to be added
/// @param amtPerSec The dripping rate
function _addDelta(
mapping(uint32 => 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 `_drippedAmt`, see it for more details.
int256 amtPerSecMultiplier = int256(_AMT_PER_SEC_MULTIPLIER);
int256 fullCycle = (int256(uint256(_cycleSecs)) * amtPerSec) / amtPerSecMultiplier;
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 `Drips` 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);
}
}
- This code is intended to add a delta of funds received by a user at a given time. The delta is calculated using the dripping rate and the timestamp when the deltas need to be added.
- explain the following code
/// @notice Checks if two receivers fulfil the sortedness requirement of the receivers list.
/// @param prev The previous receiver
/// @param prev The next receiver
function _isOrdered(DripsReceiver memory prev, DripsReceiver memory next)
private
pure
returns (bool)
{
if (prev.userId != next.userId) {
return prev.userId < next.userId;
}
return prev.config.lt(next.config);
}
- //This code is checking if the two receivers fulfill a sortedness requirement for the receivers list. If the previous receiver's user ID is not the same as the next receiver's user ID, then it returns true if the previous receiver's user ID is less than the next receiver's user ID. Otherwise, it returns true if the previous receiver's config is less than the next receiver's config.
- explain the following code
/// @notice Calculates the amount dripped over a time range.
/// The amount dripped 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 dripped 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 dripped 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 dripped, there are no partially dripped units.
/// - Undripped fractions are dripped when they add up into full units.
/// - Undripped fractions don't add up across cycle end boundaries.
/// - Some seconds drip more units and some less.
/// - Every `N`th second of each cycle drips the same amount.
/// - Every full cycle drips the same amount.
/// - The amount dripped in a given second is independent from the dripping start and end.
/// - Dripping over time ranges `A:B` and then `B:C` is equivalent to dripping over `A:C`.
/// - Different drips existing in the system don't interfere with each other.
/// @param amtPerSec The dripping rate
/// @param start The dripping start time
/// @param end The dripping end time
/// @param amt The dripped amount
function _drippedAmt(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;
// solhint-disable-next-line no-inline-assembly
assembly {
let endedCycles := sub(div(end, cycleSecs), div(start, cycleSecs))
let amtPerCycle := div(mul(cycleSecs, amtPerSec), _AMT_PER_SEC_MULTIPLIER)
amt := mul(endedCycles, amtPerCycle)
let amtEnd := div(mul(mod(end, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER)
amt := add(amt, amtEnd)
let amtStart := div(mul(mod(start, cycleSecs), amtPerSec), _AMT_PER_SEC_MULTIPLIER)
amt := sub(amt, amtStart)
}
}
- It calculates the amount of a liquid that has dripped over a period of time. The amount that drips in each second is different - it is based on the dripping rate and the number of seconds in a cycle.
- explain the following code
/// @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;
}
}
- This code is calculating the time cycle that the given timestamp is in.
- explain the following code
/// @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) {
uint32 currTimestamp = _currTimestamp();
return currTimestamp - (currTimestamp % _cycleSecs);
}
- // The code above is part of the Withdrawable trait.The first function, _currTimestamp, returns theTime stamp stored in the current block.The second function, _currCycleStart, returns theTime stamp of the start of the current cycle.The last two functions in the trait are _balanceOf,Which returns the current balance of the given Address, and _withdraw, Which withdraws the given amount of tokens from theContract to the given address.
- explain the following code
/// @notice Returns the Drips storage.
/// @return dripsStorage The storage.
function _dripsStorage() private view returns (DripsStorage storage dripsStorage) {
bytes32 slot = _dripsStorageSlot;
// solhint-disable-next-line no-inline-assembly
assembly {
dripsStorage.slot := slot
}
}
}
- The code is returning the Drips storage.
-