Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(PrizeDistV2): add missing events #274

Merged
merged 6 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions contracts/DrawCalculatorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -88,14 +88,20 @@ contract DrawCalculatorV3 is IDrawCalculatorV3, Manageable {
address _user,
uint32[] calldata _drawIds,
bytes calldata _pickIndicesForDraws
) external view override returns (uint256[] memory prizesAwardable, bytes memory prizeCounts) {
) external view override returns (
uint256[] memory prizesAwardable,
bytes memory prizeCounts,
uint64[][] memory drawPickIndices
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha this is funny!

The _pickIndicesForDraws are being passed in, decoded, then returned. Nothing else. This is like an elaborate ABI decoder.

The PrizeDistributor has no knowledge of the "picks". Originally that's because we wanted the DC to be swappable.

Returning the picks to the prize distributor means it's now aware of "picks", which completely negates the whole abstraction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in: 0f242fb

) {
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(
drawPickIndices = _pickIndices;

(prizesAwardable, prizeCounts) = _calculatePrizesAwardable(
_ticket,
_user,
_userRandomNumber,
Expand Down Expand Up @@ -189,15 +195,19 @@ contract DrawCalculatorV3 is IDrawCalculatorV3, Manageable {
bytes32 _userRandomNumber,
uint32[] memory _drawIds,
uint64[][] memory _pickIndicesForDraws
) internal view returns (uint256[] memory prizesAwardable, bytes memory prizeCounts) {
) 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 Down
129 changes: 95 additions & 34 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 @@ -24,14 +25,21 @@ contract PrizeDistributorV2 is Manageable {
* @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.
* @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.
Expand All @@ -41,9 +49,9 @@ contract PrizeDistributorV2 is Manageable {

/**
* @notice Emit when ERC20 tokens are withdrawn.
* @param token ERC20 token transferred.
* @param to Address that received funds.
* @param amount Amount of tokens transferred.
* @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 @@ -58,52 +66,72 @@ contract PrizeDistributorV2 is Manageable {
/// @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;
/// @notice The tokenVault that stores the prize tokens
address public tokenVault;

/* ============ 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");

token = _token;
vault = _vault;
tokenVault = _tokenVault;

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 _data Draws pick indices
* @return Total claim payout. May include calculations from multiple draws.
*/
function claim(
ITicket _ticket,
address _user,
uint32[] calldata _drawIds,
bytes calldata _data
) 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, , uint64[][] memory drawPickIndices) = drawCalculator
.calculate(_ticket, _user, _drawIds, _data);

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 +141,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 +172,36 @@ 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 Sets DrawCalculator reference contract.
* @param _newCalculator DrawCalculator address
* @return New DrawCalculator address
*/
function setDrawCalculator(IDrawCalculatorV3 _newCalculator)
external
onlyManagerOrOwner
Expand All @@ -163,6 +213,12 @@ contract PrizeDistributorV2 is Manageable {

/* ============ 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 +227,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 +246,18 @@ 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 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);
}

}
10 changes: 9 additions & 1 deletion contracts/interfaces/IDrawCalculatorV3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,21 @@ interface IDrawCalculatorV3 {
* @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.
* @return Pick indices for each drawId.
*/
function calculate(
ITicket ticket,
address user,
uint32[] calldata drawIds,
bytes calldata data
) external view returns (uint256[] memory, bytes memory);
)
external
view
returns (
uint256[] memory,
bytes memory,
uint64[][] memory
);

/**
* @notice Calculates picks for a user for Multiple Draws.
Expand Down
Loading