Skip to content

Commit

Permalink
Merge a19029d into e259f46
Browse files Browse the repository at this point in the history
  • Loading branch information
PierrickGT committed Jun 2, 2022
2 parents e259f46 + a19029d commit 39424ad
Show file tree
Hide file tree
Showing 5 changed files with 299 additions and 111 deletions.
28 changes: 17 additions & 11 deletions contracts/DrawCalculatorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,20 +87,22 @@ contract DrawCalculatorV3 is IDrawCalculatorV3, Manageable {
ITicket _ticket,
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");
uint64 [][] calldata _drawPickIndices
) external view override returns (
uint256[] memory prizesAwardable,
bytes memory prizeCounts
) {
require(_drawPickIndices.length == _drawIds.length, "DrawCalc/invalid-pick-indices");

// User address is hashed once.
bytes32 _userRandomNumber = keccak256(abi.encodePacked(_user));

return _calculatePrizesAwardable(
(prizesAwardable, prizeCounts) = _calculatePrizesAwardable(
_ticket,
_user,
_userRandomNumber,
_drawIds,
_pickIndices
_drawPickIndices
);
}

Expand Down Expand Up @@ -181,23 +183,27 @@ contract DrawCalculatorV3 is IDrawCalculatorV3, Manageable {
* @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
* @param _drawPickIndices Pick indices for each Draw
*/
function _calculatePrizesAwardable(
ITicket _ticket,
address _user,
bytes32 _userRandomNumber,
uint32[] memory _drawIds,
uint64[][] memory _pickIndicesForDraws
) internal view returns (uint256[] memory prizesAwardable, bytes memory prizeCounts) {
uint64[][] memory _drawPickIndices
) 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 _drawsLength = _draws.length;

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++) {
for (uint32 _drawIndex = 0; _drawIndex < _drawsLength; _drawIndex++) {
IDrawBeacon.Draw memory _draw = _draws[_drawIndex];
IPrizeConfigHistory.PrizeConfig memory _prizeConfig = prizeConfigHistory.getPrizeConfig(_draw.drawId);

Expand All @@ -217,7 +223,7 @@ contract DrawCalculatorV3 is IDrawCalculatorV3, Manageable {
_draw.winningRandomNumber,
_totalUserPicks,
_userRandomNumber,
_pickIndicesForDraws[_drawIndex],
_drawPickIndices[_drawIndex],
_prizeConfig
);
}
Expand Down
181 changes: 141 additions & 40 deletions contracts/PrizeDistributorV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ pragma solidity 0.8.6;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@pooltogether/owner-manager-contracts/contracts/Manageable.sol";

import "./interfaces/IDrawCalculatorV3.sol";

/**
* @title PoolTogether V4 PrizeDistributorV2
* @author PoolTogether Inc Team
* @notice The PrizeDistributorV2 contract holds Tickets (captured interest) and distributes tickets to users with winning draw claims.
PrizeDistributorV2 uses an external IDrawCalculatorV3 to validate a users draw claim, before awarding payouts. To prevent users
PrizeDistributorV2 uses an external IDrawCalculatorV3 to validate a users draw claim, before awarding payouts. To prevent users
from reclaiming prizes, a payout history for each draw claim is mapped to user accounts. Reclaiming a draw can occur
if an "optimal" prize was not included in previous claim pick indices and the new claims updated payout is greater then
the previous prize distributor claim payout.
Expand All @@ -20,30 +21,44 @@ contract PrizeDistributorV2 is Manageable {
using SafeERC20 for IERC20;

/**
* @notice Emit when user has claimed token from the PrizeDistributorV2.
* @notice Emitted when user has claimed token from the PrizeDistributorV2.
* @param user User address receiving draw claim payouts
* @param drawId Draw id that was paid out
* @param payout Payout for draw
* @param pickIndices Pick indices for draw
*/
event ClaimedDraw(address indexed user, uint32 indexed drawId, uint256 payout);
event ClaimedDraw(
address indexed user,
uint32 indexed drawId,
uint256 payout,
uint64[] pickIndices
);

/**
* @notice Emit when IDrawCalculatorV3 is set.
* @notice Emitted when IDrawCalculatorV3 is set.
* @param caller Address who has set the new DrawCalculator
* @param calculator IDrawCalculatorV3 address
*/
event DrawCalculatorSet(IDrawCalculatorV3 indexed calculator);
event DrawCalculatorSet(address indexed caller, IDrawCalculatorV3 indexed calculator);

/**
* @notice Emit when Token is set.
* @notice Emitted when Token is set.
* @param token Token address
*/
event TokenSet(IERC20 indexed token);

/**
* @notice Emit when ERC20 tokens are withdrawn.
* @param token ERC20 token transferred.
* @param to Address that received funds.
* @param amount Amount of tokens transferred.
* @notice Emitted when tokenVault is set.
* @param caller Address who has set the new tokenVault
* @param tokenVault Address of the tokenVault that was set
*/
event TokenVaultSet(address indexed caller, address indexed tokenVault);

/**
* @notice Emitted when ERC20 tokens are withdrawn.
* @param token ERC20 token transferred
* @param to Address that received funds
* @param amount Amount of tokens transferred
*/
event ERC20Withdrawn(IERC20 indexed token, address indexed to, uint256 amount);

Expand All @@ -55,55 +70,79 @@ contract PrizeDistributorV2 is Manageable {
/// @notice Token address
IERC20 internal immutable token;

/// @notice The tokenVault that stores the prize tokens
address internal tokenVault;

/// @notice Maps users => drawId => paid out balance
mapping(address => mapping(uint256 => uint256)) internal userDrawPayouts;

/// @notice The vault that stores the prize tokens
address public vault;

/* ============ Initialize ============ */
/* ============ Constructor ============ */

/**
* @notice Initialize PrizeDistributorV2 smart contract.
* @param _owner Owner address
* @param _token Token address
* @param _drawCalculator IDrawCalculatorV3 address
* @notice Constructs PrizeDistributorV2 smart contract.
* @param _owner Contract owner address
* @param _token Address of the token being used to pay out prizes
* @param _drawCalculator Address of the DrawCalculatorV3 contract which computes draw payouts
* @param _tokenVault Address of the TokenVault contract that holds the `token` being used to pay out prizes
*/
constructor(
address _owner,
IERC20 _token,
IDrawCalculatorV3 _drawCalculator,
address _vault
address _tokenVault
) Ownable(_owner) {
require(_owner != address(0), "PDistV2/owner-not-zero-address");
require(address(_token) != address(0), "PDistV2/token-not-zero-address");

_setDrawCalculator(_drawCalculator);
require(address(_token) != address(0), "PrizeDistributorV2/token-not-zero-address");
_setTokenVault(_tokenVault);

token = _token;
vault = _vault;

emit TokenSet(_token);
}

/* ============ External Functions ============ */

/**
* @notice Claim prize payout(s) by submitting valid drawId(s) and winning pick indice(s). The user address
is used as the "seed" phrase to generate random numbers.
* @dev The claim function is public and any wallet may execute claim on behalf of another user.
Prizes are always paid out to the designated user account and not the caller (msg.sender).
Claiming prizes is not limited to a single transaction. Reclaiming can be executed
subsequentially if an "optimal" prize was not included in previous claim pick indices. The
payout difference for the new claim is calculated during the award process and transfered to user.
* @param _ticket Address of the Ticket to claim prizes for
* @param _user Address of the user to claim rewards for. Does NOT need to be msg.sender
* @param _drawIds Draw IDs from global DrawBuffer reference
* @param _drawPickIndices Pick indices for each drawId
* @return Total claim payout. May include calculations from multiple draws.
*/
function claim(
ITicket _ticket,
address _user,
uint32[] calldata _drawIds,
bytes calldata _data
uint64[][] calldata _drawPickIndices
) external returns (uint256) {

uint256 totalPayout;

(uint256[] memory drawPayouts, ) = drawCalculator.calculate(_ticket, _user, _drawIds, _data); // neglect the prizeCounts since we are not interested in them here

(uint256[] memory drawPayouts, ) = drawCalculator.calculate(
_ticket,
_user,
_drawIds,
_drawPickIndices
);

uint256 drawPayoutsLength = drawPayouts.length;

for (uint256 payoutIndex = 0; payoutIndex < drawPayoutsLength; payoutIndex++) {
uint32 drawId = _drawIds[payoutIndex];
uint256 payout = drawPayouts[payoutIndex];
uint256 oldPayout = _getDrawPayoutBalanceOf(_user, drawId);
uint256 payoutDiff = 0;

// helpfully short-circuit, in case the user screwed something up.
require(payout > oldPayout, "PrizeDistributorV2/zero-payout");
require(payout > oldPayout, "PDistV2/zero-payout");

unchecked {
payoutDiff = payout - oldPayout;
Expand All @@ -113,21 +152,29 @@ contract PrizeDistributorV2 is Manageable {

totalPayout += payoutDiff;

emit ClaimedDraw(_user, drawId, payoutDiff);
emit ClaimedDraw(_user, drawId, payoutDiff, _drawPickIndices[payoutIndex]);
}

_awardPayout(_user, totalPayout);

return totalPayout;
}

/**
* @notice Transfer ERC20 tokens out of contract to recipient address.
* @dev Only callable by contract owner or manager.
* @param _erc20Token Address of the ERC20 token to transfer
* @param _to Address of the recipient of the tokens
* @param _amount Amount of tokens to transfer
* @return true if operation is successful.
*/
function withdrawERC20(
IERC20 _erc20Token,
address _to,
uint256 _amount
) external onlyManagerOrOwner returns (bool) {
require(_to != address(0), "PrizeDistributorV2/recipient-not-zero-address");
require(address(_erc20Token) != address(0), "PrizeDistributorV2/ERC20-not-zero-address");
require(_to != address(0), "PDistV2/to-not-zero-address");
require(address(_erc20Token) != address(0), "PDistV2/ERC20-not-zero-address");

_erc20Token.safeTransfer(_to, _amount);

Expand All @@ -136,22 +183,44 @@ contract PrizeDistributorV2 is Manageable {
return true;
}

/**
* @notice Read global DrawCalculator address.
* @return IDrawCalculatorV3
*/
function getDrawCalculator() external view returns (IDrawCalculatorV3) {
return drawCalculator;
}

function getDrawPayoutBalanceOf(address _user, uint32 _drawId)
external
view
returns (uint256)
{
/**
* @notice Get the amount that a user has already been paid out for a draw
* @param _user User address
* @param _drawId Draw ID
*/
function getDrawPayoutBalanceOf(address _user, uint32 _drawId) external view returns (uint256) {
return _getDrawPayoutBalanceOf(_user, _drawId);
}

/**
* @notice Read global Token address.
* @return IERC20
*/
function getToken() external view returns (IERC20) {
return token;
}

/**
* @notice Read global tokenVault address.
* @return Address of the tokenVault
*/
function getTokenVault() external view returns (address) {
return tokenVault;
}

/**
* @notice Sets DrawCalculator reference contract.
* @param _newCalculator DrawCalculator address
* @return New DrawCalculator address
*/
function setDrawCalculator(IDrawCalculatorV3 _newCalculator)
external
onlyManagerOrOwner
Expand All @@ -161,8 +230,24 @@ contract PrizeDistributorV2 is Manageable {
return _newCalculator;
}

/**
* @notice Sets TokenVault address.
* @param _tokenVault Address of the new TokenVault
* @return New TokenVault address
*/
function setTokenVault(address _tokenVault) external onlyManagerOrOwner returns (address) {
_setTokenVault(_tokenVault);
return _tokenVault;
}

/* ============ Internal Functions ============ */

/**
* @notice Get payout balance of a user for a draw ID.
* @param _user Address of the user to get payout balance for
* @param _drawId Draw ID to get payout balance for
* @return Draw ID payout balance
*/
function _getDrawPayoutBalanceOf(address _user, uint32 _drawId)
internal
view
Expand All @@ -171,6 +256,12 @@ contract PrizeDistributorV2 is Manageable {
return userDrawPayouts[_user][_drawId];
}

/**
* @notice Set payout balance for a user and draw ID.
* @param _user Address of the user to set payout balance for
* @param _drawId Draw ID to set payout balance for
* @param _payout Payout amount to set
*/
function _setDrawPayoutBalanceOf(
address _user,
uint32 _drawId,
Expand All @@ -184,19 +275,29 @@ contract PrizeDistributorV2 is Manageable {
* @param _newCalculator IDrawCalculatorV3 address
*/
function _setDrawCalculator(IDrawCalculatorV3 _newCalculator) internal {
require(address(_newCalculator) != address(0), "PrizeDistributorV2/calc-not-zero");
require(address(_newCalculator) != address(0), "PDistV2/calc-not-zero-address");
drawCalculator = _newCalculator;

emit DrawCalculatorSet(_newCalculator);
emit DrawCalculatorSet(msg.sender, _newCalculator);
}

/**
* @notice Sets TokenVault address.
* @param _tokenVault Address of the new TokenVault
*/
function _setTokenVault(address _tokenVault) internal {
require(_tokenVault != address(0), "PDistV2/vault-not-zero-address");
tokenVault = _tokenVault;

emit TokenVaultSet(msg.sender, _tokenVault);
}

/**
* @notice Transfer claimed draw(s) total payout to user.
* @param _to User address
* @param _amount Transfer amount
* @param _to Address of the user to award payout to
* @param _amount Amount of `token` to transfer
*/
function _awardPayout(address _to, uint256 _amount) internal {
token.safeTransferFrom(vault, _to, _amount);
token.safeTransferFrom(tokenVault, _to, _amount);
}

}
Loading

0 comments on commit 39424ad

Please sign in to comment.