diff --git a/contracts/DrawCalculatorV3.sol b/contracts/DrawCalculatorV3.sol new file mode 100644 index 00000000..daf108f7 --- /dev/null +++ b/contracts/DrawCalculatorV3.sol @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.6; + +import "@pooltogether/owner-manager-contracts/contracts/Manageable.sol"; + +import "./interfaces/IDrawBuffer.sol"; +import "./interfaces/IDrawCalculatorV3.sol"; +import "./interfaces/IGaugeController.sol"; +import "./interfaces/IPrizeConfigHistory.sol"; + +import "./PrizeDistributor.sol"; +import "./PrizeConfigHistory.sol"; + +/** + * @title PoolTogether V4 DrawCalculatorV3 + * @author PoolTogether Inc Team + * @notice The DrawCalculator calculates a user's prize by matching a winning random number against + their picks. A users picks are generated deterministically based on their address and balance + of tickets held. Prize payouts are divided into multiple tiers: grand prize, second place, etc... + A user with a higher average weighted balance (during each draw period) will be given a large number of + picks to choose from, and thus a higher chance to match the winning numbers. +*/ +contract DrawCalculatorV3 is IDrawCalculatorV3, Manageable { + /// @dev The uint32[] type is extended with a binarySearch(uint32) function. + using BinarySearchLib for uint32[]; + + /* ============ Variables ============ */ + + /// @notice GaugeController address + IGaugeController public gaugeController; + + /// @notice DrawBuffer address + IDrawBuffer public immutable drawBuffer; + + /// @notice The tiers array length + uint8 public constant TIERS_LENGTH = 16; + + /* ============ Events ============ */ + + /** + * @notice Emitted when the contract is initialized + * @param gaugeController Address of the GaugeController + * @param drawBuffer Address of the DrawBuffer + */ + event Deployed( + IGaugeController indexed gaugeController, + IDrawBuffer indexed drawBuffer + ); + + /* ============ Constructor ============ */ + + /** + * @notice DrawCalculator constructor + * @param _gaugeController Address of the GaugeController + * @param _drawBuffer Address of the draw buffer to push draws to + * @param _owner Address of the owner + */ + constructor( + IGaugeController _gaugeController, + IDrawBuffer _drawBuffer, + address _owner + ) Ownable(_owner) { + require(address(_gaugeController) != address(0), "DrawCalc/GC-not-zero-address"); + require(address(_drawBuffer) != address(0), "DrawCalc/DB-not-zero-address"); + require(_owner != address(0), "DrawCalc/owner-not-zero-address"); + + gaugeController = _gaugeController; + drawBuffer = _drawBuffer; + + emit Deployed(_gaugeController, _drawBuffer); + } + + /* ============ External Functions ============ */ + + /// @inheritdoc IDrawCalculatorV3 + function calculate( + ITicket _ticket, + IPrizeConfigHistory _prizeConfigHistory, + address _user, + uint32[] calldata _drawIds, + bytes calldata _pickIndicesForDraws + ) external view override returns (uint256[] memory prizesAwardable, bytes memory prizeCounts) { + uint64[][] memory _pickIndices = abi.decode(_pickIndicesForDraws, (uint64 [][])); + require(_pickIndices.length == _drawIds.length, "DrawCalc/invalid-pick-indices"); + + // User address is hashed once. + bytes32 _userRandomNumber = keccak256(abi.encodePacked(_user)); + + return _calculatePrizesAwardable( + _ticket, + _prizeConfigHistory, + _user, + _userRandomNumber, + _drawIds, + _pickIndices + ); + } + + /// @inheritdoc IDrawCalculatorV3 + function calculateUserPicks( + ITicket _ticket, + IPrizeConfigHistory _prizeConfigHistory, + address _user, + uint32[] calldata _drawIds + ) external view override returns (uint64[] memory picks) { + IDrawBeacon.Draw[] memory _draws = drawBuffer.getDraws(_drawIds); + uint256 _drawsLength = _draws.length; + picks = new uint64[](_drawIds.length); + + for (uint32 _drawIndex = 0; _drawIndex < _drawsLength; _drawIndex++) { + IDrawBeacon.Draw memory _draw = _draws[_drawIndex]; + IPrizeConfigHistory.PrizeConfig memory _prizeConfig = _prizeConfigHistory.getPrizeConfig(_draw.drawId); + + _requireDrawUnexpired(_draw, _prizeConfig); + + picks[_drawIndex] = _calculateUserPicks( + _ticket, + _user, + _draw.timestamp - _draw.beaconPeriodSeconds, + _draw.timestamp - _prizeConfig.endTimestampOffset, + _prizeConfig.poolStakeCeiling, + _prizeConfig.bitRangeSize, + _prizeConfig.matchCardinality + ); + } + + return picks; + } + + /// @inheritdoc IDrawCalculatorV3 + function getDrawBuffer() external override view returns (IDrawBuffer) { + return drawBuffer; + } + + /// @inheritdoc IDrawCalculatorV3 + function getGaugeController() external override view returns (IGaugeController) { + return gaugeController; + } + + /// @inheritdoc IDrawCalculatorV3 + function getTotalPicks( + ITicket _ticket, + uint256 _startTime, + uint256 _endTime, + uint256 _poolStakeCeiling, + uint8 _bitRange, + uint8 _cardinality + ) external override view returns (uint256) { + return _getTotalPicks(_ticket, _startTime, _endTime, _poolStakeCeiling, _bitRange, _cardinality); + } + + /* ============ Internal Functions ============ */ + + /** + * @notice Ensure that the draw is not expired. + * @param _draw Draw + * @param _prizeConfig Prize tier + */ + function _requireDrawUnexpired( + IDrawBeacon.Draw memory _draw, + IPrizeConfigHistory.PrizeConfig memory _prizeConfig + ) internal view { + require(uint64(block.timestamp) < _draw.timestamp + _prizeConfig.expiryDuration, "DrawCalc/draw-expired"); + } + + /** + * @notice Calculates the prizes awardable for each DrawIds passed. + * @param _ticket Address of the ticket to calculate awardable prizes for + * @param _prizeConfigHistory Address of the prizeConfigHistory associated with the ticket + * @param _user Address of the user for which to calculate awardable prizes for + * @param _userRandomNumber Random number of the user to consider over draws + * @param _drawIds Array of DrawIds for which to calculate awardable prizes for + * @param _pickIndicesForDraws Pick indices for each Draw + */ + function _calculatePrizesAwardable( + ITicket _ticket, + IPrizeConfigHistory _prizeConfigHistory, + address _user, + bytes32 _userRandomNumber, + uint32[] memory _drawIds, + uint64[][] memory _pickIndicesForDraws + ) internal view returns (uint256[] memory prizesAwardable, bytes memory prizeCounts) { + // READ list of IDrawBeacon.Draw using the drawIds from drawBuffer + IDrawBeacon.Draw[] memory _draws = drawBuffer.getDraws(_drawIds); + + uint256[] memory _prizesAwardable = new uint256[](_drawIds.length); + uint256[][] memory _prizeCounts = new uint256[][](_drawIds.length); + + // Calculate prizes awardable for each Draw passed + for (uint32 _drawIndex = 0; _drawIndex < _draws.length; _drawIndex++) { + IDrawBeacon.Draw memory _draw = _draws[_drawIndex]; + IPrizeConfigHistory.PrizeConfig memory _prizeConfig = _prizeConfigHistory.getPrizeConfig(_draw.drawId); + + _requireDrawUnexpired(_draw, _prizeConfig); + + uint64 _totalUserPicks = _calculateUserPicks( + _ticket, + _user, + _draw.timestamp - _draw.beaconPeriodSeconds, + _draw.timestamp - _prizeConfig.endTimestampOffset, + _prizeConfig.poolStakeCeiling, + _prizeConfig.bitRangeSize, + _prizeConfig.matchCardinality + ); + + (_prizesAwardable[_drawIndex], _prizeCounts[_drawIndex]) = _calculate( + _draw.winningRandomNumber, + _totalUserPicks, + _userRandomNumber, + _pickIndicesForDraws[_drawIndex], + _prizeConfig + ); + } + + prizeCounts = abi.encode(_prizeCounts); + prizesAwardable = _prizesAwardable; + } + + /** + * @notice Calculates the number of picks a user gets for a Draw, considering the normalized user balance and the PrizeDistribution. + * @dev Divided by 1e18 since the normalized user balance is stored as a fixed point 18 number. + * @param _ticket Address of the ticket to get total picks for + * @param _startTimestamp Timestamp at which the prize starts + * @param _endTimestamp Timestamp at which the prize ends + * @param _poolStakeCeiling Globally configured pool stake ceiling + * @param _bitRange Number of bits allocated to each division + * @param _cardinality Number of sub-divisions of a random number + * @return Number of picks a user gets for a Draw + */ + function _calculateUserPicks( + ITicket _ticket, + address _user, + uint64 _startTimestamp, + uint64 _endTimestamp, + uint256 _poolStakeCeiling, + uint8 _bitRange, + uint8 _cardinality + ) internal view returns (uint64) { + uint256 _numberOfPicks = _getTotalPicks(_ticket, _startTimestamp, _endTimestamp, _poolStakeCeiling, _bitRange, _cardinality); + uint256 _normalizedBalance = _getNormalizedBalanceAt(_ticket, _user, _startTimestamp, _endTimestamp); + + return uint64((_normalizedBalance * _numberOfPicks) / 1 ether); + } + + /** + * @notice Calculates the normalized balance of a user against the total supply for a draw. + * @param _ticket Address of the ticket to get normalized balance for + * @param _user The user to consider + * @param _startTimestamp Timestamp at which the draw starts + * @param _endTimestamp Timestamp at which the draw ends + * @return User normalized balance for the draw + */ + function _getNormalizedBalanceAt( + ITicket _ticket, + address _user, + uint64 _startTimestamp, + uint64 _endTimestamp + ) internal view returns (uint256) { + uint64[] memory _timestampsWithStartCutoffTimes = new uint64[](1); + uint64[] memory _timestampsWithEndCutoffTimes = new uint64[](1); + + _timestampsWithStartCutoffTimes[0] = _startTimestamp; + _timestampsWithEndCutoffTimes[0] = _endTimestamp; + + uint256[] memory _balances = _ticket.getAverageBalancesBetween( + _user, + _timestampsWithStartCutoffTimes, + _timestampsWithEndCutoffTimes + ); + + uint256[] memory _totalSupplies = _ticket.getAverageTotalSuppliesBetween( + _timestampsWithStartCutoffTimes, + _timestampsWithEndCutoffTimes + ); + + uint256 _normalizedBalance; + + if (_totalSupplies[0] > 0) { + _normalizedBalance = (_balances[0] * 1 ether) / _totalSupplies[0]; + } + + return _normalizedBalance; + } + + /** + * @notice Returns the total number of picks for a prize pool. + * @param _ticket Address of the ticket to get total picks for + * @param _startTime Timestamp at which the prize starts + * @param _endTime Timestamp at which the prize ends + * @param _poolStakeCeiling Globally configured pool stake ceiling + * @param _bitRange Number of bits allocated to each division + * @param _cardinality Number of sub-divisions of a random number + * @return Total number of picks for a prize pool + */ + function _getTotalPicks( + ITicket _ticket, + uint256 _startTime, + uint256 _endTime, + uint256 _poolStakeCeiling, + uint8 _bitRange, + uint8 _cardinality + ) internal view returns (uint256) { + uint256 _totalChances = (2**_bitRange)**_cardinality; + uint256 _gaugeScaledAverage = gaugeController.getScaledAverageGaugeBetween(address(_ticket), _startTime, _endTime); + return (_gaugeScaledAverage * _totalChances) / _poolStakeCeiling; + } + + /** + * @notice Calculates the prize amount for a PrizeDistribution over given picks + * @param _winningRandomNumber Draw's winningRandomNumber + * @param _totalUserPicks number of picks the user gets for the Draw + * @param _userRandomNumber users randomNumber for that draw + * @param _picks users picks for that draw + * @param _prizeConfig PrizeConfig for that draw + * @return prize (if any), prizeCounts (if any) + */ + function _calculate( + uint256 _winningRandomNumber, + uint256 _totalUserPicks, + bytes32 _userRandomNumber, + uint64[] memory _picks, + IPrizeConfigHistory.PrizeConfig memory _prizeConfig + ) internal pure returns (uint256 prize, uint256[] memory prizeCounts) { + // Create bitmasks for the PrizeDistribution + uint256[] memory masks = _createBitMasks(_prizeConfig.matchCardinality, _prizeConfig.bitRangeSize); + uint32 picksLength = uint32(_picks.length); + uint256[] memory _prizeCounts = new uint256[](_prizeConfig.tiers.length); + + uint8 maxWinningTierIndex = 0; + + require( + picksLength <= _prizeConfig.maxPicksPerUser, + "DrawCalc/exceeds-max-user-picks" + ); + + // for each pick, find number of matching numbers and calculate prize distributions index + for (uint32 index = 0; index < picksLength; index++) { + require(_picks[index] < _totalUserPicks, "DrawCalc/insufficient-user-picks"); + + if (index > 0) { + require(_picks[index] > _picks[index - 1], "DrawCalc/picks-ascending"); + } + + // hash the user random number with the pick value + uint256 randomNumberThisPick = uint256( + keccak256(abi.encode(_userRandomNumber, _picks[index])) + ); + + uint8 tiersIndex = _calculateTierIndex( + randomNumberThisPick, + _winningRandomNumber, + masks + ); + + // there is prize for this tier index + if (tiersIndex < TIERS_LENGTH) { + if (tiersIndex > maxWinningTierIndex) { + maxWinningTierIndex = tiersIndex; + } + _prizeCounts[tiersIndex]++; + } + } + + // now calculate prizeFraction given prizeCounts + uint256 prizeFraction = 0; + uint256[] memory prizeTiersFractions = new uint256[]( + maxWinningTierIndex + 1 + ); + + for (uint8 i = 0; i <= maxWinningTierIndex; i++) { + prizeTiersFractions[i] = _calculatePrizeTierFraction( + _prizeConfig.tiers[i], + _prizeConfig.bitRangeSize, + i + ); + } + + // multiple the fractions by the prizeCounts and add them up + for ( + uint256 prizeCountIndex = 0; + prizeCountIndex <= maxWinningTierIndex; + prizeCountIndex++ + ) { + if (_prizeCounts[prizeCountIndex] > 0) { + prizeFraction += + prizeTiersFractions[prizeCountIndex] * + _prizeCounts[prizeCountIndex]; + } + } + + // return the absolute amount of prize awardable + // div by 1e9 as prize tiers are base 1e9 + prize = (prizeFraction * _prizeConfig.prize) / 1e9; + prizeCounts = _prizeCounts; + } + + /** + * @notice Calculates the tier index given the random numbers and masks + * @param _randomNumberThisPick User random number for this Pick + * @param _winningRandomNumber The winning number for this draw + * @param _masks The pre-calculated bitmasks for the prizeDistributions + * @return The position within the prize tier array (0 = top prize, 1 = runner-up prize, etc) + */ + function _calculateTierIndex( + uint256 _randomNumberThisPick, + uint256 _winningRandomNumber, + uint256[] memory _masks + ) internal pure returns (uint8) { + uint8 _numberOfMatches; + uint8 _masksLength = uint8(_masks.length); + + // main number matching loop + for (uint8 matchIndex = 0; matchIndex < _masksLength; matchIndex++) { + uint256 _mask = _masks[matchIndex]; + + if ((_randomNumberThisPick & _mask) != (_winningRandomNumber & _mask)) { + // there are no more sequential matches since this comparison is not a match + if (_masksLength == _numberOfMatches) { + return 0; + } else { + return _masksLength - _numberOfMatches; + } + } + + // else there was a match + _numberOfMatches++; + } + + return _masksLength - _numberOfMatches; + } + + /** + * @notice Creates an array of bitmasks equal to the PrizeDistribution.matchCardinality length + * @param _matchCardinality Match cardinality for Draw + * @param _bitRangeSize Bit range size for Draw + * @return Array of bitmasks + */ + function _createBitMasks(uint8 _matchCardinality, uint8 _bitRangeSize) + internal + pure + returns (uint256[] memory) + { + uint256[] memory _masks = new uint256[](_matchCardinality); + _masks[0] = (2**_bitRangeSize) - 1; + + for (uint8 _maskIndex = 1; _maskIndex < _matchCardinality; _maskIndex++) { + // shift mask bits to correct position and insert in result mask array + _masks[_maskIndex] = _masks[_maskIndex - 1] << _bitRangeSize; + } + + return _masks; + } + + /** + * @notice Calculates the expected prize fraction per PrizeDistributions and distributionIndex + * @param _prizeFraction Prize fraction for this PrizeDistribution + * @param _bitRangeSize Bit range size for Draw + * @param _prizeConfigIndex Index of the prize tiers array to calculate + * @return returns the fraction of the total prize (fixed point 9 number) + */ + function _calculatePrizeTierFraction( + uint256 _prizeFraction, + uint8 _bitRangeSize, + uint256 _prizeConfigIndex + ) internal pure returns (uint256) { + // calculate number of prizes for that index + uint256 numberOfPrizesForIndex = _numberOfPrizesForIndex( + _bitRangeSize, + _prizeConfigIndex + ); + + return _prizeFraction / numberOfPrizesForIndex; + } + + /** + * @notice Calculates the number of prizes for a given prizeDistributionIndex + * @param _bitRangeSize Bit range size for Draw + * @param _prizeConfigIndex Index of the prize config array to calculate + * @return returns the fraction of the total prize (base 1e18) + */ + function _numberOfPrizesForIndex(uint8 _bitRangeSize, uint256 _prizeConfigIndex) + internal + pure + returns (uint256) + { + if (_prizeConfigIndex > 0) { + return ( 1 << _bitRangeSize * _prizeConfigIndex ) - ( 1 << _bitRangeSize * (_prizeConfigIndex - 1) ); + } else { + return 1; + } + } +} diff --git a/contracts/PrizeConfigHistory.sol b/contracts/PrizeConfigHistory.sol new file mode 100644 index 00000000..e078d60f --- /dev/null +++ b/contracts/PrizeConfigHistory.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.6; + +import "@pooltogether/owner-manager-contracts/contracts/Manageable.sol"; +import "@pooltogether/v4-periphery/contracts/libraries/BinarySearchLib.sol"; + +import "./interfaces/IPrizeConfigHistory.sol"; + +/** + * @title PoolTogether V4 PrizeConfigHistory + * @author PoolTogether Inc Team + * @notice Contract to store prize configurations + */ +contract PrizeConfigHistory is IPrizeConfigHistory, Manageable { + /// @dev The uint32[] type is extended with a binarySearch(uint32) function. + using BinarySearchLib for uint32[]; + + /* ============ Variables ============ */ + + /** + * @notice Ordered array of Draw IDs. + * @dev The history, with sequentially ordered ids, can be searched using binary search. + The binary search will find index of a drawId (atOrBefore) using a specific drawId (at). + When a new Draw ID is added to the history, a corresponding mapping of the ID is + updated in the prizeConfigs mapping. + */ + uint32[] internal history; + + /** + * @notice Mapping of Draw ID to PrizeConfig struct. + * @dev drawId -> PrizeConfig + * @dev The prizeConfigs mapping is updated when a new Draw ID is added to the history. + */ + mapping(uint32 => PrizeConfig) internal prizeConfigs; + + /* ============ Events ============ */ + + /** + * @notice Emit when a new PrizeConfig is added to history + * @param drawId Draw ID at which the PrizeConfig was pushed and is since valid + * @param prizeConfig PrizeConfig struct + */ + event PrizeConfigPushed(uint32 indexed drawId, PrizeConfig prizeConfig); + + /** + * @notice Emit when existing PrizeConfig is updated in history + * @param drawId Draw ID at which the PrizeConfig was set and is since valid + * @param prizeConfig PrizeConfig struct + */ + event PrizeConfigSet(uint32 indexed drawId, PrizeConfig prizeConfig); + + /* ============ Constructor ============ */ + + /** + * @notice PrizeConfigHistory constructor + * @param _owner Address of the owner + */ + constructor(address _owner) Ownable(_owner) {} + + /* ============ External Functions ============ */ + + /// @inheritdoc IPrizeConfigHistory + function count() external view override returns (uint256) { + return history.length; + } + + /// @inheritdoc IPrizeConfigHistory + function getNewestDrawId() external view override returns (uint32) { + return history[history.length - 1]; + } + + /// @inheritdoc IPrizeConfigHistory + function getOldestDrawId() external view override returns (uint32) { + return history[0]; + } + + /// @inheritdoc IPrizeConfigHistory + function getPrizeConfig(uint32 _drawId) + external + view + override + returns (PrizeConfig memory prizeConfig) + { + require(_drawId > 0, "PrizeConfHistory/draw-id-gt-zero"); + return prizeConfigs[history.binarySearch(_drawId)]; + } + + /// @inheritdoc IPrizeConfigHistory + function getPrizeConfigAtIndex(uint256 _index) + external + view + override + returns (PrizeConfig memory prizeConfig) + { + return prizeConfigs[uint32(_index)]; + } + + // @inheritdoc IPrizeConfigHistory + function getPrizeConfigList(uint32[] calldata _drawIds) + external + view + override + returns (PrizeConfig[] memory prizeConfigList) + { + uint256 _length = _drawIds.length; + PrizeConfig[] memory _data = new PrizeConfig[](_length); + + for (uint256 index = 0; index < _length; index++) { + _data[index] = prizeConfigs[history.binarySearch(_drawIds[index])]; + } + + return _data; + } + + /// @inheritdoc IPrizeConfigHistory + function popAndPush(PrizeConfig calldata _newPrizeConfig) + external + override + onlyOwner + returns (uint32) + { + uint256 length = history.length; + + require(length > 0, "PrizeConfHistory/history-empty"); + require(history[length - 1] == _newPrizeConfig.drawId, "PrizeConfHistory/invalid-draw-id"); + + _replace(_newPrizeConfig); + + return _newPrizeConfig.drawId; + } + + /// @inheritdoc IPrizeConfigHistory + function push(PrizeConfig calldata _nextPrizeConfig) external override onlyManagerOrOwner { + _push(_nextPrizeConfig); + } + + /// @inheritdoc IPrizeConfigHistory + function replace(PrizeConfig calldata _newPrizeConfig) external override onlyOwner { + _replace(_newPrizeConfig); + } + + /* ============ Internal Functions ============ */ + + /** + * @notice Push PrizeConfigHistory struct onto history array. + * @param _prizeConfig New PrizeConfig struct to push onto history array + */ + function _push(PrizeConfig memory _prizeConfig) internal { + uint32 _historyLength = uint32(history.length); + + if (_historyLength > 0) { + // TODO: Check if cheaper in gas to only cast to uint32 below + uint32 _id = history[_historyLength - 1]; + + require(_prizeConfig.drawId > _id, "PrizeConfHistory/nonsequentialId"); + } + + history.push(_prizeConfig.drawId); + prizeConfigs[_historyLength] = _prizeConfig; + + emit PrizeConfigPushed(_prizeConfig.drawId, _prizeConfig); + } + + /** + * @notice Replace PrizeConfig struct from history array. + * @dev Performs a binary search to find which index in the history array contains the drawId to replace. + * @param _prizeConfig New PrizeConfig struct that will replace the previous PrizeConfig at the corresponding index. + */ + function _replace(PrizeConfig calldata _prizeConfig) internal { + require(history.length > 0, "PrizeConfHistory/no-prize-conf"); + + uint32 oldestDrawId = history[0]; + require(_prizeConfig.drawId >= oldestDrawId, "PrizeConfHistory/drawId-beyond"); + + uint32 index = history.binarySearch(_prizeConfig.drawId); + require(history[index] == _prizeConfig.drawId, "PrizeConfHistory/drawId-mismatch"); + + prizeConfigs[index] = _prizeConfig; + emit PrizeConfigSet(_prizeConfig.drawId, _prizeConfig); + } +} diff --git a/contracts/interfaces/IDrawCalculatorV3.sol b/contracts/interfaces/IDrawCalculatorV3.sol new file mode 100644 index 00000000..20a94df3 --- /dev/null +++ b/contracts/interfaces/IDrawCalculatorV3.sol @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.6; + +import "./ITicket.sol"; +import "./IDrawBuffer.sol"; +import "./IGaugeController.sol"; +import "./IPrizeConfigHistory.sol"; + +/** + * @title PoolTogether V4 IDrawCalculatorV3 + * @author PoolTogether Inc Team + * @notice The DrawCalculator interface. + */ +interface IDrawCalculatorV3 { + /** + * @notice Calculates the awardable prizes for a user for Multiple Draws. Typically called by a PrizeDistributor. + * @param ticket Address of the ticket to calculate awardable prizes for + * @param prizeConfigHistory Address of the prizeConfigHistory associated with the ticket + * @param user Address of the user for which to calculate awardable prizes for + * @param drawIds Array of DrawIds for which to calculate awardable prizes for + * @param data ABI encoded pick indices for all Draws. Expected to be winning picks. Pick indices must be less than the totalUserPicks. + * @return List of awardable prize amounts ordered by drawId. + * @return List of prize counts ordered by tiers. + */ + function calculate( + ITicket ticket, + IPrizeConfigHistory prizeConfigHistory, + address user, + uint32[] calldata drawIds, + bytes calldata data + ) external view returns (uint256[] memory, bytes memory); + + /** + * @notice Calculates picks for a user for Multiple Draws. + * @param ticket Address of the ticket to calculate picks for + * @param prizeConfigHistory Address of the prizeConfigHistory associated with the ticket + * @param user Address of the user for which to calculate picks for + * @param drawIds Array of DrawIds for which to calculate picks for + */ + function calculateUserPicks( + ITicket ticket, + IPrizeConfigHistory prizeConfigHistory, + address user, + uint32[] calldata drawIds + ) external view returns (uint64[] memory); + + /** + * @notice Returns DrawBuffer address. + * @return The DrawBuffer address + */ + function getDrawBuffer() external view returns (IDrawBuffer); + + /** + * @notice Returns GaugeController address. + * @return The GaugeController address + */ + function getGaugeController() external view returns (IGaugeController); + + /** + * @notice Returns the total number of picks for a prize pool / ticket. + * @param ticket Address of the ticket to get total picks for + * @param startTime Timestamp at which the draw starts + * @param endTime Timestamp at which the draw ends + * @param poolStakeCeiling Globally configured pool stake ceiling + * @param bitRange Number of bits allocated to each division + * @param cardinality Number of sub-divisions of a random number + * @return Total number of picks for this prize pool / ticket + */ + function getTotalPicks( + ITicket ticket, + uint256 startTime, + uint256 endTime, + uint256 poolStakeCeiling, + uint8 bitRange, + uint8 cardinality + ) external view returns (uint256); +} diff --git a/contracts/interfaces/IPrizeConfigHistory.sol b/contracts/interfaces/IPrizeConfigHistory.sol new file mode 100644 index 00000000..be49d72f --- /dev/null +++ b/contracts/interfaces/IPrizeConfigHistory.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.6; + +/** + * @title PoolTogether V4 IPrizeConfigHistory + * @author PoolTogether Inc Team + * @notice IPrizeConfigHistory is the base contract for PrizeConfigHistory + */ +interface IPrizeConfigHistory { + /** + * @notice PrizeConfig struct read every draw. + * @param bitRangeSize Number of bits in decimal allocated to each division + * @param matchCardinality Number of numbers to consider in the 256 bit random number. Must be > 1 and < 256/bitRangeSize. + * @param maxPicksPerUser Maximum number of picks a user can make in this draw + * @param drawId Draw ID at which the PrizeConfig was pushed and is since valid + * @param expiryDuration Length of time in seconds the PrizeDistribution is valid for. Relative to the Draw.timestamp. + * @param endTimestampOffset The end time offset in seconds from which Ticket balances are calculated. + * @param poolStakeCeiling Total globally configured POOL staking ceiling + * @param prize Total prize amount available for this draw + * @param tiers Array of prize tiers percentages, expressed in fraction form with base 1e9. Ordering: index0: grandPrize, index1: runnerUp, etc. + */ + struct PrizeConfig { + uint8 bitRangeSize; + uint8 matchCardinality; + uint16 maxPicksPerUser; + uint32 drawId; + uint32 expiryDuration; + uint32 endTimestampOffset; + uint128 poolStakeCeiling; + uint256 prize; + uint32[16] tiers; + } + + /** + * @notice Returns the number of PrizeConfig structs pushed + * @return The number of prize config that have been pushed + */ + function count() external view returns (uint256); + + /** + * @notice Returns last Draw ID recorded in the history. + * @return Draw ID of the last PrizeConfig record + */ + function getNewestDrawId() external view returns (uint32); + + /** + * @notice Returns first Draw ID used to initialize history. + * @return Draw ID of the first PrizeConfig record + */ + function getOldestDrawId() external view returns (uint32); + + /** + * @notice Returns PrizeConfig struct for the passed Draw ID. + * @param drawId Draw ID for which to return PrizeConfig struct + * @return The PrizeConfig struct for the passed Draw ID + */ + function getPrizeConfig(uint32 drawId) external view returns (PrizeConfig memory); + + /** + * @notice Returns the PrizeConfig struct at the given index. + * @param index Index at which the PrizeConfig struct is stored + * @return The PrizeConfig struct at the given index + */ + function getPrizeConfigAtIndex(uint256 index) external view returns (PrizeConfig memory); + + /** + * @notice Returns a list of PrizeConfig from the history array. + * @param drawIds List of Draw IDs for which to return PrizeConfig structs + * @return The list of PrizeConfig structs for the passed Draw IDs + */ + function getPrizeConfigList(uint32[] calldata drawIds) + external + view + returns (PrizeConfig[] memory); + + /** + * @notice Push PrizeConfigHistory struct onto history array. + * @dev Callable only by the owner. + * @param prizeConfig Updated PrizeConfigHistory struct + * @return Draw ID at which the PrizeConfig was pushed and is since valid + */ + function popAndPush(PrizeConfig calldata prizeConfig) external returns (uint32); + + /** + * @notice Push PrizeConfig struct onto history array. + * @dev Callable only by the owner or manager. + * @param prizeConfig New PrizeConfig struct to push onto the history array + */ + function push(PrizeConfig calldata prizeConfig) external; + + /** + * @notice Replace PrizeConfig struct from history array. + * @dev Callable only by the owner. + * @param prizeConfig New PrizeConfig struct that will replace the previous PrizeConfig at the corresponding index + */ + function replace(PrizeConfig calldata prizeConfig) external; +} diff --git a/package.json b/package.json index c1bbc8f5..07c84b8e 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "@pooltogether/fixed-point": "1.0.0", "@pooltogether/owner-manager-contracts": "1.1.0", "@pooltogether/pooltogether-rng-contracts": "1.4.0", + "@pooltogether/v4-periphery": "1.2.3", "@pooltogether/yield-source-interface": "1.3.0", "deploy-eip-1820": "1.0.0" }, diff --git a/yarn.lock b/yarn.lock index 6088e2c4..a89a0ea8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -704,6 +704,11 @@ resolved "https://registry.yarnpkg.com/@pooltogether/uniform-random-number/-/uniform-random-number-1.0.0-beta.2.tgz#fcc388269098a7303c83de6f07cdc5d46dede0e9" integrity sha512-xwEpcg+WXcoWtRdG9WPmE5B93YvkhBi4XCEVQ/kfkXS5tVsrhCjFHWQOuqvX2P7zMyCuI/Bq6Y84QQJnRywJhg== +"@pooltogether/v4-periphery@1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@pooltogether/v4-periphery/-/v4-periphery-1.2.3.tgz#59e64b12da30c8ce92351b3016a3f3709065b29f" + integrity sha512-l1Yh/TF0vys+/bsHk3oBMgdcS5gVnTlhPssVOaG6CSajf4XNed39AaInA5vDRcREeA8ya3/R9p4czAeeuMM55A== + "@pooltogether/yield-source-interface@1.3.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@pooltogether/yield-source-interface/-/yield-source-interface-1.3.0.tgz#93e9c12bb2d62e2215bb2f9c9d391a092e6eb6d5"