// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.7.0; pragma experimental ABIEncoderV2; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../utils/UInt256Array.sol"; import "./ComptrollerStorage.sol"; import "../token/TokenListener.sol"; /// @title The Comptroller disburses rewards to pool users /// @dev DEPRECATED. This contract is too imcomplete for production use. /* solium-disable security/no-block-members */ contract Comptroller is ComptrollerStorage, TokenListener { using SafeMathUpgradeable for uint256; using SafeCastUpgradeable for uint256; using UInt256Array for uint256[]; using ExtendedSafeCast for uint256; using BalanceDrip for BalanceDrip.State; using VolumeDrip for VolumeDrip.State; using BalanceDripManager for BalanceDripManager.State; using VolumeDripManager for VolumeDripManager.State; using MappedSinglyLinkedList for MappedSinglyLinkedList.Mapping; /// @notice Emitted when a balance drip is actived event BalanceDripActivated( address indexed source, address indexed measure, address indexed dripToken, uint256 dripRatePerSecond ); /// @notice Emitted when a balance drip is deactivated event BalanceDripDeactivated( address indexed source, address indexed measure, address indexed dripToken ); /// @notice Emitted when a balance drip rate is updated event BalanceDripRateSet( address indexed source, address indexed measure, address indexed dripToken, uint256 dripRatePerSecond ); /// @notice Emitted when a balance drip drips tokens event BalanceDripDripped( address indexed source, address indexed measure, address indexed dripToken, address user, uint256 amount ); event DripTokenDripped( address indexed dripToken, address indexed user, uint256 amount ); /// @notice Emitted when a volue drip drips tokens event VolumeDripDripped( address indexed source, address indexed measure, address indexed dripToken, bool isReferral, address user, uint256 amount ); /// @notice Emitted when a user claims drip tokens event DripTokenClaimed( address indexed operator, address indexed dripToken, address indexed user, uint256 amount ); /// @notice Emitted when a volume drip is activated event VolumeDripActivated( address indexed source, address indexed measure, address indexed dripToken, bool isReferral, uint256 periodSeconds, uint256 dripAmount ); event TransferredOut( address indexed token, address indexed to, uint256 amount ); /// @notice Emitted when a new volume drip period has started event VolumeDripPeriodStarted( address indexed source, address indexed measure, address indexed dripToken, bool isReferral, uint32 period, uint256 dripAmount, uint256 endTime ); /// @notice Emitted when a volume drip period has ended event VolumeDripPeriodEnded( address indexed source, address indexed measure, address indexed dripToken, bool isReferral, uint32 period, uint256 totalSupply, uint256 drippedTokens ); /// @notice Emitted when a volume drip is updated event VolumeDripSet( address indexed source, address indexed measure, address indexed dripToken, bool isReferral, uint256 periodSeconds, uint256 dripAmount ); /// @notice Emitted when a volume drip is deactivated. event VolumeDripDeactivated( address indexed source, address indexed measure, address indexed dripToken, bool isReferral ); /// @notice Convenience struct used when updating drips struct UpdatePair { address source; address measure; } /// @notice Convenience struct used to retrieve balances after updating drips struct DripTokenBalance { address dripToken; uint256 balance; } /// @notice Initializes a new Comptroller. constructor () public { __Ownable_init(); } function transferOut(address token, address to, uint256 amount) external onlyOwner { IERC20Upgradeable(token).transfer(to, amount); emit TransferredOut(token, to, amount); } /// @notice Activates a balance drip. Only callable by the owner. /// @param source The balance drip "source"; i.e. a Prize Pool address. /// @param measure The ERC20 token whose balances determines user's share of the drip rate. /// @param dripToken The token that is dripped to users. /// @param dripRatePerSecond The amount of drip tokens that are awarded each second to the total supply of measure. function activateBalanceDrip(address source, address measure, address dripToken, uint256 dripRatePerSecond) external onlyOwner { balanceDrips[source].activateDrip(measure, dripToken, dripRatePerSecond); emit BalanceDripActivated( source, measure, dripToken, dripRatePerSecond ); } /// @notice Deactivates a balance drip. Only callable by the owner. /// @param source The balance drip "source"; i.e. a Prize Pool address. /// @param measure The ERC20 token whose balances determines user's share of the drip rate. /// @param dripToken The token that is dripped to users. /// @param prevDripToken The previous drip token in the balance drip list. If the dripToken is the first address, /// then the previous address is the SENTINEL address: 0x0000000000000000000000000000000000000001 function deactivateBalanceDrip(address source, address measure, address dripToken, address prevDripToken) external onlyOwner { _deactivateBalanceDrip(source, measure, dripToken, prevDripToken); } /// @notice Deactivates a balance drip. Only callable by the owner. /// @param source The balance drip "source"; i.e. a Prize Pool address. /// @param measure The ERC20 token whose balances determines user's share of the drip rate. /// @param dripToken The token that is dripped to users. /// @param prevDripToken The previous drip token in the balance drip list. If the dripToken is the first address, /// then the previous address is the SENTINEL address: 0x0000000000000000000000000000000000000001 function _deactivateBalanceDrip(address source, address measure, address dripToken, address prevDripToken) internal { balanceDrips[source].deactivateDrip(measure, dripToken, prevDripToken, _currentTime().toUint32(), _availableDripTokenBalance(dripToken)); emit BalanceDripDeactivated(source, measure, dripToken); } /// @notice Gets a list of active balance drip tokens /// @param source The balance drip "source"; i.e. a Prize Pool address. /// @param measure The ERC20 token whose balances determines user's share of the drip rate. /// @return An array of active Balance Drip token addresses function getActiveBalanceDripTokens(address source, address measure) external view returns (address[] memory) { return balanceDrips[source].getActiveBalanceDrips(measure); } /// @notice Returns the state of a balance drip. /// @param source The balance drip "source"; i.e. Prize Pool /// @param measure The token that measure's a users share of the drip /// @param dripToken The token that is being dripped to users /// @return dripRatePerSecond The current drip rate of the balance drip. /// @return exchangeRateMantissa The current exchange rate from measure to dripTokens /// @return timestamp The timestamp at which the balance drip was last updated. function getBalanceDrip( address source, address measure, address dripToken ) external view returns ( uint256 dripRatePerSecond, uint128 exchangeRateMantissa, uint32 timestamp ) { BalanceDrip.State storage balanceDrip = balanceDrips[source].getDrip(measure, dripToken); dripRatePerSecond = balanceDrip.dripRatePerSecond; exchangeRateMantissa = balanceDrip.exchangeRateMantissa; timestamp = balanceDrip.timestamp; } /// @notice Sets the drip rate for a balance drip. The drip rate is the number of drip tokens given to the /// entire supply of measure tokens. Only callable by the owner. /// @param source The balance drip "source"; i.e. Prize Pool /// @param measure The token to use to measure a user's share of the drip rate /// @param dripToken The token that is dripped to the user /// @param dripRatePerSecond The new drip rate per second function setBalanceDripRate(address source, address measure, address dripToken, uint256 dripRatePerSecond) external onlyOwner { balanceDrips[source].setDripRate(measure, dripToken, dripRatePerSecond, _currentTime().toUint32(), _availableDripTokenBalance(dripToken)); emit BalanceDripRateSet( source, measure, dripToken, dripRatePerSecond ); } /// @notice Activates a volume drip. Volume drips distribute tokens to users based on their share of the activity within a period. /// @param source The Prize Pool for which to bind to /// @param measure The Prize Pool controlled token whose volume should be measured /// @param dripToken The token that is being disbursed /// @param isReferral Whether this volume drip is for referrals /// @param periodSeconds The period of the volume drip, in seconds /// @param dripAmount The amount of dripTokens disbursed each period. /// @param endTime The time at which the first period ends. function activateVolumeDrip( address source, address measure, address dripToken, bool isReferral, uint32 periodSeconds, uint112 dripAmount, uint32 endTime ) external onlyOwner { uint32 period; if (isReferral) { period = referralVolumeDrips[source].activate(measure, dripToken, periodSeconds, dripAmount, endTime); } else { period = volumeDrips[source].activate(measure, dripToken, periodSeconds, dripAmount, endTime); } emit VolumeDripActivated( source, measure, dripToken, isReferral, periodSeconds, dripAmount ); emit VolumeDripPeriodStarted( source, measure, dripToken, isReferral, period, dripAmount, endTime ); } /// @notice Deactivates a volume drip. Volume drips distribute tokens to users based on their share of the activity within a period. /// @param source The Prize Pool for which to bind to /// @param measure The Prize Pool controlled token whose volume should be measured /// @param dripToken The token that is being disbursed /// @param isReferral Whether this volume drip is for referrals /// @param prevDripToken The previous drip token in the volume drip list. Is different for referrals vs non-referral volume drips. function deactivateVolumeDrip( address source, address measure, address dripToken, bool isReferral, address prevDripToken ) external onlyOwner { _deactivateVolumeDrip(source, measure, dripToken, isReferral, prevDripToken); } /// @notice Deactivates a volume drip. Volume drips distribute tokens to users based on their share of the activity within a period. /// @param source The Prize Pool for which to bind to /// @param measure The Prize Pool controlled token whose volume should be measured /// @param dripToken The token that is being disbursed /// @param isReferral Whether this volume drip is for referrals /// @param prevDripToken The previous drip token in the volume drip list. Is different for referrals vs non-referral volume drips. function _deactivateVolumeDrip( address source, address measure, address dripToken, bool isReferral, address prevDripToken ) internal { if (isReferral) { referralVolumeDrips[source].deactivate(measure, dripToken, prevDripToken); } else { volumeDrips[source].deactivate(measure, dripToken, prevDripToken); } emit VolumeDripDeactivated( source, measure, dripToken, isReferral ); } /// @notice Sets the parameters for the *next* volume drip period. The source, measure, dripToken and isReferral combined /// are used to uniquely identify a volume drip. Only callable by the owner. /// @param source The Prize Pool of the volume drip /// @param measure The token whose volume is being measured /// @param dripToken The token that is being disbursed /// @param isReferral Whether this volume drip is a referral /// @param periodSeconds The length to use for the next period /// @param dripAmount The amount of tokens to drip for the next period function setVolumeDrip( address source, address measure, address dripToken, bool isReferral, uint32 periodSeconds, uint112 dripAmount ) external onlyOwner { if (isReferral) { referralVolumeDrips[source].set(measure, dripToken, periodSeconds, dripAmount); } else { volumeDrips[source].set(measure, dripToken, periodSeconds, dripAmount); } emit VolumeDripSet( source, measure, dripToken, isReferral, periodSeconds, dripAmount ); } function getVolumeDrip( address source, address measure, address dripToken, bool isReferral ) external view returns ( uint256 periodSeconds, uint256 dripAmount, uint256 periodCount ) { VolumeDrip.State memory drip; if (isReferral) { drip = referralVolumeDrips[source].volumeDrips[measure][dripToken]; } else { drip = volumeDrips[source].volumeDrips[measure][dripToken]; } return ( drip.nextPeriodSeconds, drip.nextDripAmount, drip.periodCount ); } /// @notice Gets a list of active volume drip tokens /// @param source The volume drip "source"; i.e. a Prize Pool address. /// @param measure The ERC20 token whose volume determines user's share of the drip rate. /// @param isReferral Whether this volume drip is a referral /// @return An array of active Volume Drip token addresses function getActiveVolumeDripTokens(address source, address measure, bool isReferral) external view returns (address[] memory) { if (isReferral) { return referralVolumeDrips[source].getActiveVolumeDrips(measure); } else { return volumeDrips[source].getActiveVolumeDrips(measure); } } function isVolumeDripActive( address source, address measure, address dripToken, bool isReferral ) external view returns (bool) { if (isReferral) { return referralVolumeDrips[source].isActive(measure, dripToken); } else { return volumeDrips[source].isActive(measure, dripToken); } } function getVolumeDripPeriod( address source, address measure, address dripToken, bool isReferral, uint16 period ) external view returns ( uint112 totalSupply, uint112 dripAmount, uint32 endTime ) { VolumeDrip.Period memory periodState; if (isReferral) { periodState = referralVolumeDrips[source].volumeDrips[measure][dripToken].periods[period]; } else { periodState = volumeDrips[source].volumeDrips[measure][dripToken].periods[period]; } return ( periodState.totalSupply, periodState.dripAmount, periodState.endTime ); } /// @notice Returns a users claimable balance of drip tokens. This is the combination of all balance and volume drips. /// @param dripToken The token that is being disbursed /// @param user The user whose balance should be checked. /// @return The claimable balance of the dripToken by the user. function balanceOfDrip(address user, address dripToken) external view returns (uint256) { return dripTokenBalances[dripToken][user]; } /// @notice Claims a drip token on behalf of a user. If the passed amount is less than or equal to the users drip balance, then /// they will be transferred that amount. Otherwise, it fails. /// @param user The user for whom to claim the drip tokens /// @param dripToken The drip token to claim /// @param amount The amount of drip token to claim function claimDrip(address user, address dripToken, uint256 amount) public { address sender = _msgSender(); dripTokenTotalSupply[dripToken] = dripTokenTotalSupply[dripToken].sub(amount); dripTokenBalances[dripToken][user] = dripTokenBalances[dripToken][user].sub(amount); require(IERC20Upgradeable(dripToken).transfer(user, amount), "Comptroller/claim-transfer-failed"); emit DripTokenClaimed(sender, dripToken, user, amount); } function claimDrips(address user, address[] memory dripTokens) public { for (uint i = 0; i < dripTokens.length; i++) { claimDrip(user, dripTokens[i], dripTokenBalances[dripTokens[i]][user]); } } function updateActiveBalanceDripsForPairs( UpdatePair[] memory pairs ) public { uint256 currentTime = _currentTime(); uint256 i; for (i = 0; i < pairs.length; i++) { UpdatePair memory pair = pairs[i]; _updateActiveBalanceDrips( balanceDrips[pair.source], pair.source, pair.measure, IERC20Upgradeable(pair.measure).totalSupply(), currentTime ); } } function updateActiveVolumeDripsForPairs( UpdatePair[] memory pairs ) public { uint256 i; for (i = 0; i < pairs.length; i++) { UpdatePair memory pair = pairs[i]; _updateActiveVolumeDrips( volumeDrips[pair.source], pair.source, pair.measure, false ); _updateActiveVolumeDrips( referralVolumeDrips[pair.source], pair.source, pair.measure, true ); } } function mintAndCaptureVolumeDripsForPairs( UpdatePair[] memory pairs, address user, uint256 amount, address[] memory dripTokens ) public { uint256 i; for (i = 0; i < pairs.length; i++) { UpdatePair memory pair = pairs[i]; _mintAndCaptureForVolumeDrips(pair.source, pair.measure, user, amount, dripTokens); _mintAndCaptureReferralVolumeDrips(pair.source, pair.measure, user, amount, dripTokens); } } function _mintAndCaptureForVolumeDrips( address source, address measure, address user, uint256 amount, address[] memory dripTokens ) internal { uint i; for (i = 0; i < dripTokens.length; i++) { address dripToken = dripTokens[i]; VolumeDrip.State storage state = volumeDrips[source].volumeDrips[measure][dripToken]; _captureClaimForVolumeDrip(state, source, measure, dripToken, false, user, amount); } } function _mintAndCaptureReferralVolumeDrips( address source, address measure, address user, uint256 amount, address[] memory dripTokens ) internal { uint i; for (i = 0; i < dripTokens.length; i++) { address dripToken = dripTokens[i]; VolumeDrip.State storage referralState = referralVolumeDrips[source].volumeDrips[measure][dripToken]; _captureClaimForVolumeDrip(referralState, source, measure, dripToken, true, user, amount); } } function _captureClaimForVolumeDrip( VolumeDrip.State storage dripState, address source, address measure, address dripToken, bool isReferral, address user, uint256 amount ) internal { uint256 newUserTokens = dripState.mint( user, amount ); if (newUserTokens > 0) { _addDripBalance(dripToken, user, newUserTokens); emit VolumeDripDripped(source, measure, dripToken, isReferral, user, newUserTokens); } } /// @param pairs The (source, measure) pairs to update. For each pair all of the balance drips, volume drips, and referral volume drips will be updated. /// @param user The user whose drips and balances will be updated. /// @param dripTokens The drip tokens to retrieve claim balances for. function captureClaimsForBalanceDripsForPairs( UpdatePair[] memory pairs, address user, address[] memory dripTokens ) public { uint256 i; for (i = 0; i < pairs.length; i++) { UpdatePair memory pair = pairs[i]; uint256 measureBalance = IERC20Upgradeable(pair.measure).balanceOf(user); _captureClaimsForBalanceDrips(pair.source, pair.measure, user, measureBalance, dripTokens); } } function _captureClaimsForBalanceDrips( address source, address measure, address user, uint256 userMeasureBalance, address[] memory dripTokens ) internal { uint i; for (i = 0; i < dripTokens.length; i++) { address dripToken = dripTokens[i]; BalanceDrip.State storage state = balanceDrips[source].balanceDrips[measure][dripToken]; if (state.exchangeRateMantissa > 0) { _captureClaimForBalanceDrip(state, source, measure, dripToken, user, userMeasureBalance); } } } function _captureClaimForBalanceDrip( BalanceDrip.State storage dripState, address source, address measure, address dripToken, address user, uint256 measureBalance ) internal { uint256 newUserTokens = dripState.captureNewTokensForUser( user, measureBalance ); if (newUserTokens > 0) { _addDripBalance(dripToken, user, newUserTokens); emit BalanceDripDripped(source, measure, dripToken, user, newUserTokens); } } function balanceOfClaims( address user, address[] memory dripTokens ) public view returns (DripTokenBalance[] memory) { DripTokenBalance[] memory balances = new DripTokenBalance[](dripTokens.length); uint256 i; for (i = 0; i < dripTokens.length; i++) { balances[i] = DripTokenBalance({ dripToken: dripTokens[i], balance: dripTokenBalances[dripTokens[i]][user] }); } return balances; } /// @notice Updates the given drips for a user and then claims the given drip tokens. This call will /// poke all of the drips and update the claim balances for the given user. /// @dev This function will be useful to check the *current* claim balances for a user. /// Just need to run this as a constant function to see the latest balances. /// in order to claim the values, this function needs to be run alongside a claimDrip function. /// @param pairs The (source, measure) pairs of drips to update for the given user /// @param user The user for whom to update and claim tokens /// @param dripTokens The drip tokens whose entire balance will be claimed after the update. /// @return The claimable balance of each of the passed drip tokens for the user. These are the post-update balances, and therefore the most accurate. function updateDrips( UpdatePair[] memory pairs, address user, address[] memory dripTokens ) public returns (DripTokenBalance[] memory) { updateActiveBalanceDripsForPairs(pairs); captureClaimsForBalanceDripsForPairs(pairs, user, dripTokens); updateActiveVolumeDripsForPairs(pairs); mintAndCaptureVolumeDripsForPairs(pairs, user, 0, dripTokens); DripTokenBalance[] memory balances = balanceOfClaims(user, dripTokens); return balances; } /// @notice Updates the given drips for a user and then claims the given drip tokens. This call will /// poke all of the drips and update the claim balances for the given user. /// @dev This function will be useful to check the *current* claim balances for a user. /// Just need to run this as a constant function to see the latest balances. /// in order to claim the values, this function needs to be run alongside a claimDrip function. /// @param pairs The (source, measure) pairs of drips to update for the given user /// @param user The user for whom to update and claim tokens /// @param dripTokens The drip tokens whose entire balance will be claimed after the update. /// @return The claimable balance of each of the passed drip tokens for the user. These are the post-update balances, and therefore the most accurate. function updateAndClaimDrips( UpdatePair[] calldata pairs, address user, address[] calldata dripTokens ) external returns (DripTokenBalance[] memory) { DripTokenBalance[] memory balances = updateDrips(pairs, user, dripTokens); claimDrips(user, dripTokens); return balances; } function _activeBalanceDripTokens(address source, address measure) internal view returns (address[] memory) { return balanceDrips[source].activeBalanceDrips[measure].addressArray(); } function _activeVolumeDripTokens(address source, address measure) internal view returns (address[] memory) { return volumeDrips[source].activeVolumeDrips[measure].addressArray(); } function _activeReferralVolumeDripTokens(address source, address measure) internal view returns (address[] memory) { return referralVolumeDrips[source].activeVolumeDrips[measure].addressArray(); } /// @notice Updates the balance drips /// @param source The Prize Pool of the balance drip /// @param manager The BalanceDripManager whose drips should be updated /// @param measure The measure token whose balance is changing /// @param measureTotalSupply The last total supply of the measure tokens /// @param currentTime The current function _updateActiveBalanceDrips( BalanceDripManager.State storage manager, address source, address measure, uint256 measureTotalSupply, uint256 currentTime ) internal { address prevDripToken = manager.activeBalanceDrips[measure].end(); address currentDripToken = manager.activeBalanceDrips[measure].start(); while (currentDripToken != address(0) && currentDripToken != manager.activeBalanceDrips[measure].end()) { BalanceDrip.State storage dripState = manager.balanceDrips[measure][currentDripToken]; uint256 limit = _availableDripTokenBalance(currentDripToken); uint256 newTokens = dripState.drip( measureTotalSupply, currentTime, limit ); // if we've hit the limit, then kill it. bool isDripComplete = newTokens == limit; if (isDripComplete) { _deactivateBalanceDrip(source, measure, currentDripToken, prevDripToken); } prevDripToken = currentDripToken; currentDripToken = manager.activeBalanceDrips[measure].next(currentDripToken); } } /// @notice Records a deposit for a volume drip /// @param source The Prize Pool of the volume drip /// @param manager The VolumeDripManager containing the drips that need to be iterated through. /// @param isReferral Whether the passed manager contains referral volume drip /// @param measure The token that was deposited function _updateActiveVolumeDrips( VolumeDripManager.State storage manager, address source, address measure, bool isReferral ) internal { address prevDripToken = manager.activeVolumeDrips[measure].end(); uint256 currentTime = _currentTime(); address currentDripToken = manager.activeVolumeDrips[measure].start(); while (currentDripToken != address(0) && currentDripToken != manager.activeVolumeDrips[measure].end()) { VolumeDrip.State storage dripState = manager.volumeDrips[measure][currentDripToken]; uint256 limit = _availableDripTokenBalance(currentDripToken); uint32 lastPeriod = dripState.periodCount; uint256 newTokens = dripState.drip( currentTime, limit ); if (lastPeriod != dripState.periodCount) { emit VolumeDripPeriodEnded( source, measure, currentDripToken, isReferral, lastPeriod, dripState.periods[lastPeriod].totalSupply, newTokens ); emit VolumeDripPeriodStarted( source, measure, currentDripToken, isReferral, dripState.periodCount, dripState.periods[dripState.periodCount].dripAmount, dripState.periods[dripState.periodCount].endTime ); } // if we've hit the limit, then kill it. bool isDripComplete = newTokens == limit; if (isDripComplete) { _deactivateVolumeDrip(source, measure, currentDripToken, isReferral, prevDripToken); } prevDripToken = currentDripToken; currentDripToken = manager.activeVolumeDrips[measure].next(currentDripToken); } } function _addDripBalance(address dripToken, address user, uint256 amount) internal returns (uint256) { uint256 amountAvailable = _availableDripTokenBalance(dripToken); uint256 actualAmount = (amount > amountAvailable) ? amountAvailable : amount; dripTokenTotalSupply[dripToken] = dripTokenTotalSupply[dripToken].add(actualAmount); dripTokenBalances[dripToken][user] = dripTokenBalances[dripToken][user].add(actualAmount); emit DripTokenDripped(dripToken, user, actualAmount); return actualAmount; } function _availableDripTokenBalance(address dripToken) internal view returns (uint256) { uint256 comptrollerBalance = IERC20Upgradeable(dripToken).balanceOf(address(this)); uint256 totalClaimable = dripTokenTotalSupply[dripToken]; return (totalClaimable < comptrollerBalance) ? comptrollerBalance.sub(totalClaimable) : 0; } /// @notice Called by a "source" (i.e. Prize Pool) when a user mints new "measure" tokens. /// @param to The user who is minting the tokens /// @param amount The amount of tokens they are minting /// @param measure The measure token they are minting /// @param referrer The user who referred the minting. function beforeTokenMint( address to, uint256 amount, address measure, address referrer ) external override { address source = _msgSender(); uint256 balance = IERC20Upgradeable(measure).balanceOf(to); uint256 totalSupply = IERC20Upgradeable(measure).totalSupply(); address[] memory balanceDripTokens = _activeBalanceDripTokens(source, measure); _updateActiveBalanceDrips( balanceDrips[source], source, measure, totalSupply, _currentTime() ); _captureClaimsForBalanceDrips(source, measure, to, balance, balanceDripTokens); address[] memory volumeDripTokens = _activeVolumeDripTokens(source, measure); _updateActiveVolumeDrips( volumeDrips[source], source, measure, false ); _mintAndCaptureForVolumeDrips(source, measure, to, amount, volumeDripTokens); if (referrer != address(0)) { address[] memory referralVolumeDripTokens = _activeReferralVolumeDripTokens(source, measure); _updateActiveVolumeDrips( referralVolumeDrips[source], source, measure, true ); _mintAndCaptureReferralVolumeDrips(source, measure, referrer, amount, referralVolumeDripTokens); } } /// @notice Called by a "source" (i.e. Prize Pool) when tokens change hands or are burned /// @param from The user who is sending the tokens /// @param to The user who is receiving the tokens /// @param measure The measure token they are burning function beforeTokenTransfer( address from, address to, uint256, address measure ) external override { if (from == address(0)) { // ignore minting return; } address source = _msgSender(); uint256 totalSupply = IERC20Upgradeable(measure).totalSupply(); uint256 fromBalance = IERC20Upgradeable(measure).balanceOf(from); address[] memory balanceDripTokens = _activeBalanceDripTokens(source, measure); _updateActiveBalanceDrips( balanceDrips[source], source, measure, totalSupply, _currentTime() ); _captureClaimsForBalanceDrips(source, measure, from, fromBalance, balanceDripTokens); if (to != address(0)) { uint256 toBalance = IERC20Upgradeable(measure).balanceOf(to); _captureClaimsForBalanceDrips(source, measure, to, toBalance, balanceDripTokens); } } /// @notice returns the current time. Allows for override in testing. /// @return The current time (block.timestamp) function _currentTime() internal virtual view returns (uint256) { return block.timestamp; } }