Skip to content

Commit

Permalink
Merge dd46d62 into 0bf90de
Browse files Browse the repository at this point in the history
  • Loading branch information
kamescg committed Mar 21, 2022
2 parents 0bf90de + dd46d62 commit 6abcf57
Show file tree
Hide file tree
Showing 5 changed files with 466 additions and 168 deletions.
227 changes: 90 additions & 137 deletions contracts/PrizeTierHistory.sol
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);
}
}
70 changes: 70 additions & 0 deletions contracts/abstract/BinarySearchLib.sol
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;
}
}
27 changes: 27 additions & 0 deletions contracts/test/BinarySearchLibHarness.sol
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;
}
}

0 comments on commit 6abcf57

Please sign in to comment.