generated from pooltogether/pooltogether-contracts-template
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
466 additions
and
168 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,182 +1,135 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.6; | ||
|
||
import "@pooltogether/owner-manager-contracts/contracts/Manageable.sol"; | ||
import "./interfaces/IPrizeTierHistory.sol"; | ||
import "./abstract/BinarySearchLib.sol"; | ||
|
||
/** | ||
* @title PoolTogether V4 IPrizeTierHistory | ||
* @title PoolTogether V4 PrizeTierHistory | ||
* @author PoolTogether Inc Team | ||
* @notice IPrizeTierHistory is the base contract for PrizeTierHistory | ||
* @notice The PrizeTierHistory smart contract stores a history of PrizeTier structs linked to | ||
a range of valid Draw IDs. | ||
* @dev If the history param has single PrizeTier struct with a "drawId" of 1 all subsequent | ||
Draws will use that PrizeTier struct for PrizeDitribution calculations. The BinarySearchLib | ||
will find a PrizeTier using a "atOrBefore" range search when supplied drawId input parameter. | ||
*/ | ||
contract PrizeTierHistory is IPrizeTierHistory, Manageable { | ||
/* ============ Global Variables ============ */ | ||
/** | ||
* @notice History of PrizeTier updates | ||
*/ | ||
PrizeTier[] internal history; | ||
|
||
/* ============ Constructor ============ */ | ||
constructor(address _owner) Ownable(_owner) {} | ||
// @dev The uint32[] type is extended with a binarySearch(uint32) function. | ||
using BinarySearchLib for uint32[]; | ||
|
||
/* ============ External Functions ============ */ | ||
/** | ||
* @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 prizeTiers mapping. | ||
*/ | ||
uint32[] internal history; | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function push(PrizeTier calldata _nextPrizeTier) external override onlyManagerOrOwner { | ||
PrizeTier[] memory _history = history; | ||
/** | ||
* @notice Mapping a Draw ID to a PrizeTier struct. | ||
* @dev The prizeTiers mapping links a Draw ID to a PrizeTier struct. | ||
The prizeTiers mapping is updated when a new Draw ID is added to the history. | ||
*/ | ||
mapping(uint32 => PrizeTier) internal prizeTiers; | ||
|
||
constructor(address owner, PrizeTier[] memory _history) Ownable(owner) { | ||
if (_history.length > 0) { | ||
// READ the newest PrizeTier struct | ||
PrizeTier memory _newestPrizeTier = history[history.length - 1]; | ||
// New PrizeTier ID must only be 1 greater than the last PrizeTier ID. | ||
require( | ||
_nextPrizeTier.drawId > _newestPrizeTier.drawId, | ||
"PrizeTierHistory/non-sequential-prize-tier" | ||
); | ||
inject(_history); | ||
} | ||
|
||
history.push(_nextPrizeTier); | ||
|
||
emit PrizeTierPushed(_nextPrizeTier.drawId, _nextPrizeTier); | ||
} | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function replace(PrizeTier calldata _prizeTier) external override onlyOwner { | ||
uint256 cardinality = history.length; | ||
require(cardinality > 0, "PrizeTierHistory/no-prize-tiers"); | ||
|
||
uint256 leftSide = 0; | ||
uint256 rightSide = cardinality - 1; | ||
uint32 oldestDrawId = history[leftSide].drawId; | ||
|
||
require(_prizeTier.drawId >= oldestDrawId, "PrizeTierHistory/draw-id-out-of-range"); | ||
|
||
uint256 index = _binarySearchIndex(_prizeTier.drawId, leftSide, rightSide, history); | ||
|
||
require(history[index].drawId == _prizeTier.drawId, "PrizeTierHistory/draw-id-must-match"); | ||
|
||
history[index] = _prizeTier; | ||
|
||
emit PrizeTierSet(_prizeTier.drawId, _prizeTier); | ||
} | ||
|
||
/* ============ Setter Functions ============ */ | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function popAndPush(PrizeTier calldata _prizeTier) | ||
external | ||
override | ||
onlyOwner | ||
returns (uint32) | ||
{ | ||
require(history.length > 0, "PrizeTierHistory/history-empty"); | ||
PrizeTier memory _newestPrizeTier = history[history.length - 1]; | ||
require(_prizeTier.drawId >= _newestPrizeTier.drawId, "PrizeTierHistory/invalid-draw-id"); | ||
history[history.length - 1] = _prizeTier; | ||
emit PrizeTierSet(_prizeTier.drawId, _prizeTier); | ||
|
||
return _prizeTier.drawId; | ||
function count() external view override returns (uint256) { | ||
return history.length; | ||
} | ||
|
||
/* ============ Getter Functions ============ */ | ||
|
||
|
||
// @inheritdoc IPrizeTierHistory | ||
function getPrizeTier(uint32 _drawId) external view override returns (PrizeTier memory) { | ||
require(_drawId > 0, "PrizeTierHistory/draw-id-not-zero"); | ||
return _getPrizeTier(_drawId); | ||
function getOldestDrawId() external view override returns (uint32) { | ||
return history[0]; | ||
} | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function getOldestDrawId() external view override returns (uint32) { | ||
return history[0].drawId; | ||
function getNewestDrawId() external view override returns (uint32) { | ||
return uint32(history[history.length - 1]); | ||
} | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function getNewestDrawId() external view override returns (uint32) { | ||
return history[history.length - 1].drawId; | ||
function getPrizeTier(uint32 drawId) override external view returns (PrizeTier memory) { | ||
require(drawId > 0, "PrizeTierHistory/draw-id-not-zero"); | ||
return prizeTiers[history.binarySearch(drawId)]; | ||
} | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function getPrizeTierList(uint32[] calldata _drawIds) | ||
external | ||
view | ||
override | ||
returns (PrizeTier[] memory) | ||
{ | ||
PrizeTier[] memory _data = new PrizeTier[](_drawIds.length); | ||
for (uint256 index = 0; index < _drawIds.length; index++) { | ||
_data[index] = _getPrizeTier(_drawIds[index]); // SLOAD each struct instead of the whole array before the FOR loop. | ||
function getPrizeTierList(uint32[] calldata _drawIds) override external view returns (PrizeTier[] memory) { | ||
uint256 _length = _drawIds.length; | ||
PrizeTier[] memory _data = new PrizeTier[](_length); | ||
for (uint256 index = 0; index < _length; index++) { | ||
_data[index] = prizeTiers[history.binarySearch(_drawIds[index])]; | ||
} | ||
return _data; | ||
} | ||
|
||
function _getPrizeTier(uint32 _drawId) internal view returns (PrizeTier memory) { | ||
uint256 cardinality = history.length; | ||
require(cardinality > 0, "PrizeTierHistory/no-prize-tiers"); | ||
|
||
uint256 leftSide = 0; | ||
uint256 rightSide = cardinality - 1; | ||
uint32 oldestDrawId = history[leftSide].drawId; | ||
uint32 newestDrawId = history[rightSide].drawId; | ||
|
||
require(_drawId >= oldestDrawId, "PrizeTierHistory/draw-id-out-of-range"); | ||
if (_drawId >= newestDrawId) return history[rightSide]; | ||
if (_drawId == oldestDrawId) return history[leftSide]; | ||
|
||
return _binarySearch(_drawId, leftSide, rightSide, history); | ||
// @inheritdoc IPrizeTierHistory | ||
function getPrizeTierAtIndex(uint256 index) external view override returns (PrizeTier memory) { | ||
return prizeTiers[history[index]]; | ||
} | ||
|
||
function _binarySearch( | ||
uint32 _drawId, | ||
uint256 leftSide, | ||
uint256 rightSide, | ||
PrizeTier[] storage _history | ||
) internal view returns (PrizeTier memory) { | ||
return _history[_binarySearchIndex(_drawId, leftSide, rightSide, _history)]; | ||
// @inheritdoc IPrizeTierHistory | ||
function push(PrizeTier calldata nextPrizeTier) override external onlyManagerOrOwner { | ||
_push(nextPrizeTier); | ||
} | ||
|
||
// @inheritdoc IPrizeTierHistory | ||
function popAndPush(PrizeTier calldata newPrizeTier) override external onlyOwner returns (uint32) { | ||
uint length = history.length; | ||
require(length > 0, "PrizeTierHistory/history-empty"); | ||
require(history[length - 1] == newPrizeTier.drawId, "PrizeTierHistory/invalid-draw-id"); | ||
_replace(newPrizeTier); | ||
return newPrizeTier.drawId; | ||
} | ||
|
||
function _binarySearchIndex( | ||
uint32 _drawId, | ||
uint256 _leftSide, | ||
uint256 _rightSide, | ||
PrizeTier[] storage _history | ||
) internal view returns (uint256) { | ||
uint256 index; | ||
uint256 leftSide = _leftSide; | ||
uint256 rightSide = _rightSide; | ||
while (true) { | ||
uint256 center = leftSide + (rightSide - leftSide) / 2; | ||
uint32 centerPrizeTierID = _history[center].drawId; | ||
|
||
if (centerPrizeTierID == _drawId) { | ||
index = center; | ||
break; | ||
} | ||
|
||
if (centerPrizeTierID < _drawId) { | ||
leftSide = center + 1; | ||
} else if (centerPrizeTierID > _drawId) { | ||
rightSide = center - 1; | ||
} | ||
// @inheritdoc IPrizeTierHistory | ||
function replace(PrizeTier calldata newPrizeTier) override external onlyOwner { | ||
_replace(newPrizeTier); | ||
} | ||
|
||
if (leftSide == rightSide) { | ||
if (centerPrizeTierID >= _drawId) { | ||
index = center - 1; | ||
break; | ||
} else { | ||
index = center; | ||
break; | ||
} | ||
} | ||
/** | ||
* @notice Inject a PrizeTier timeline into the history array. | ||
* @dev A PrizeTierHistory timeline can only be injected once. The history | ||
cannot be injected if a PrizeTier history previously exists. | ||
* @param timeline PrizeTier[] - Array of PrizeTier structs | ||
*/ | ||
function inject(PrizeTier[] memory timeline) public onlyOwner { | ||
require(history.length == 0, "PrizeTierHistory/history-not-empty"); | ||
require(timeline.length > 0, "PrizeTierHistory/timeline-empty"); | ||
for (uint256 i = 0; i < timeline.length; i++) { | ||
_push(timeline[i]); | ||
} | ||
return index; | ||
} | ||
|
||
function getPrizeTierAtIndex(uint256 index) external view override returns (PrizeTier memory) { | ||
return history[index]; | ||
function _push(PrizeTier memory _prizeTier) internal { | ||
if (history.length > 0) { | ||
uint32 _id = history[history.length - 1]; | ||
require( | ||
_prizeTier.drawId > _id, | ||
"PrizeTierHistory/non-sequential-dpr" | ||
); | ||
} | ||
history.push(_prizeTier.drawId); | ||
prizeTiers[_prizeTier.drawId] = _prizeTier; | ||
emit PrizeTierPushed(_prizeTier.drawId, _prizeTier); | ||
} | ||
|
||
function count() external view override returns (uint256) { | ||
return history.length; | ||
function _replace(PrizeTier calldata _prizeTier) internal { | ||
uint256 cardinality = history.length; | ||
require(cardinality > 0, "PrizeTierHistory/no-prize-tiers"); | ||
uint32 oldestDrawId = history[0]; | ||
require(_prizeTier.drawId >= oldestDrawId, "PrizeTierHistory/draw-id-out-of-range"); | ||
uint32 index = history.binarySearch(_prizeTier.drawId); | ||
require(history[index] == _prizeTier.drawId, "PrizeTierHistory/draw-id-must-match"); | ||
prizeTiers[_prizeTier.drawId] = _prizeTier; | ||
emit PrizeTierSet(_prizeTier.drawId, _prizeTier); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.6; | ||
import "hardhat/console.sol"; | ||
/** | ||
* @title PoolTogether V4 BinarySearchLib | ||
* @author PoolTogether Inc Team | ||
* @notice BinarySearchLib uses binary search to find a parent contract struct with the drawId parameter | ||
* @dev The implementing contract must provider access to a struct (i.e. PrizeTier) list with is both | ||
* sorted and indexed by the drawId field for binary search to work. | ||
*/ | ||
library BinarySearchLib { | ||
|
||
/** | ||
* @notice Find ID in array of ordered IDs using Binary Search. | ||
* @param _history uin32[] - Array of IDsq | ||
* @param _drawId uint32 - Draw ID to search for | ||
* @return uint32 - Index of ID in array | ||
*/ | ||
function binarySearch(uint32[] storage _history, uint32 _drawId) external view returns (uint32) { | ||
uint32 index; | ||
uint32 leftSide = 0; | ||
uint32 rightSide = uint32(_history.length - 1); | ||
|
||
uint32 oldestDrawId = _history[0]; | ||
uint32 newestDrawId = _history[rightSide]; | ||
|
||
require(_drawId >= oldestDrawId, "BinarySearchLib/draw-id-out-of-range"); | ||
if (_drawId >= newestDrawId) return rightSide; | ||
if (_drawId == oldestDrawId) return leftSide; | ||
|
||
while (true) { | ||
|
||
uint32 length = rightSide - leftSide; | ||
uint32 center = leftSide + (length) / 2; | ||
uint32 centerID = _history[center]; | ||
|
||
if (centerID == _drawId) { | ||
index = center; | ||
break; | ||
} | ||
|
||
if (length == 1) { | ||
if(_history[rightSide] <= _drawId) { | ||
index = rightSide; | ||
} else { | ||
index = leftSide; | ||
} | ||
break; | ||
} | ||
|
||
if (centerID < _drawId) { | ||
leftSide = center; | ||
} else if (centerID > _drawId) { | ||
rightSide = center - 1; | ||
} | ||
|
||
if (leftSide == rightSide) { | ||
if (centerID >= _drawId) { | ||
index = center - 1; | ||
break; | ||
} else { | ||
index = center; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return index; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
// SPDX-License-Identifier: GPL-3.0 | ||
pragma solidity 0.8.6; | ||
import "../abstract/BinarySearchLib.sol"; | ||
|
||
contract BinarySearchLibHarness { | ||
using BinarySearchLib for uint32[]; | ||
uint32[] internal history; | ||
|
||
function getIndex(uint32 id) external view returns (uint32) | ||
{ | ||
return history.binarySearch(id); | ||
} | ||
|
||
function getIndexes(uint32[] calldata ids) external view returns (uint32[] memory) | ||
{ | ||
uint32[] memory data = new uint32[](ids.length); | ||
for (uint256 i = 0; i < ids.length; i++) { | ||
data[i] = history.binarySearch(ids[i]); | ||
} | ||
return data; | ||
} | ||
|
||
function set(uint32[] calldata _history) external | ||
{ | ||
history = _history; | ||
} | ||
} |
Oops, something went wrong.