// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.7.0; import "@openzeppelin/contracts-upgradeable/math/SafeMathUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/utils/SafeCastUpgradeable.sol"; import "../utils/ExtendedSafeCast.sol"; import "@pooltogether/fixed-point/contracts/FixedPoint.sol"; /// @title Calculates a users share of a token faucet. /// @notice The tokens are dripped at a "drip rate per second". This is the number of tokens that /// are dripped each second to the entire supply of a "measure" token. A user's share of ownership /// of the measure token corresponds to the share of the drip tokens per second. library BalanceDrip { using SafeMathUpgradeable for uint256; using SafeCastUpgradeable for uint256; using ExtendedSafeCast for uint256; struct UserState { uint128 lastExchangeRateMantissa; } struct State { uint256 dripRatePerSecond; uint112 exchangeRateMantissa; uint112 totalDripped; uint32 timestamp; mapping(address => UserState) userStates; } /// @notice Captures new tokens for a user /// @dev This must be called before changes to the user's balance (i.e. before mint, transfer or burns) /// @param self The balance drip state /// @param user The user to capture tokens for /// @param userMeasureBalance The current balance of the user's measure tokens /// @return The number of new tokens function captureNewTokensForUser( State storage self, address user, uint256 userMeasureBalance ) internal returns (uint128) { return _captureNewTokensForUser( self, user, userMeasureBalance ); } function resetTotalDripped(State storage self) internal { self.totalDripped = 0; } /// @notice Drips new tokens. /// @dev Should be called immediately before a change to the measure token's total supply /// @param self The balance drip state /// @param measureTotalSupply The measure token's last total supply (prior to any change) /// @param timestamp The current time /// @param maxNewTokens Maximum new tokens that can be dripped /// @return The number of new tokens dripped. function drip( State storage self, uint256 measureTotalSupply, uint256 timestamp, uint256 maxNewTokens ) internal returns (uint256) { // this should only run once per block. if (self.timestamp == uint32(timestamp)) { return 0; } uint256 lastTime = self.timestamp == 0 ? timestamp : self.timestamp; uint256 newSeconds = timestamp.sub(lastTime); uint112 exchangeRateMantissa = self.exchangeRateMantissa == 0 ? FixedPoint.SCALE.toUint112() : self.exchangeRateMantissa; uint256 newTokens; if (newSeconds > 0 && self.dripRatePerSecond > 0) { newTokens = newSeconds.mul(self.dripRatePerSecond); if (newTokens > maxNewTokens) { newTokens = maxNewTokens; } uint256 indexDeltaMantissa = measureTotalSupply > 0 ? FixedPoint.calculateMantissa(newTokens, measureTotalSupply) : 0; exchangeRateMantissa = uint256(exchangeRateMantissa).add(indexDeltaMantissa).toUint112(); } self.exchangeRateMantissa = exchangeRateMantissa; self.totalDripped = uint256(self.totalDripped).add(newTokens).toUint112(); self.timestamp = timestamp.toUint32(); return newTokens; } function _captureNewTokensForUser( State storage self, address user, uint256 userMeasureBalance ) private returns (uint128) { UserState storage userState = self.userStates[user]; uint256 lastExchangeRateMantissa = userState.lastExchangeRateMantissa; if (lastExchangeRateMantissa == 0) { // if the index is not intialized lastExchangeRateMantissa = FixedPoint.SCALE.toUint112(); } uint256 deltaExchangeRateMantissa = uint256(self.exchangeRateMantissa).sub(lastExchangeRateMantissa); uint128 newTokens = FixedPoint.multiplyUintByMantissa(userMeasureBalance, deltaExchangeRateMantissa).toUint128(); self.userStates[user] = UserState({ lastExchangeRateMantissa: self.exchangeRateMantissa }); return newTokens; } }