Prize Pools allow funds to be pooled together into a no-loss yield source, such as Compound, and have the yield safely exposed to a separate Prize Strategy. They are the primary way through which users interact with PoolTogether prize games.
Prize Pools provide controls to the owner so that participation can be made fair. See Fairness for more information.
There are two types of prize pools:
Yield Source Prize Pools: these prize pools utilize a yield source to generate prizes.
Stake Prize Pools: these prize pools simply hold deposits; prizes must be added manually.
All Prize Pools share the functionality below.
When a Prize Pool is created, the creator is set as the pool's "owner". The owner is able to:
Change the Prize Strategy
Set the credit rate and credit limit
The prize pool is not upgradeable and therefore the owner can never seize the funds deposited into the prize pool
When a Prize Pool is created it is initialized with some hard-coded limits to protect users. See Fairness for more details.
The maximum credit limit ensures that the credit limit cannot be set higher than this number. This prevents the owner of the Prize Pool from capturing *all* of a user's deposit at withdrawal time.
The maximum liquidity limit allows the PrizePool owner to set a cap on the amount of liquidity the pool can hold. This can be set by calling:
function setLiquidityCap(uint256 _liquidityCap) external override onlyOwner
A Prize Pool accepts a single type of ERC20 token for deposits. This token depends on the implementation: for a Compound Prize Pool bound to cDai it will be Dai, for a yEarn yUSDC vault it will be USDC. This is the underlying asset of the Prize Pool.
The asset that user deposit into the prize pool can be retrieved by calling:
function token() external view returns (address);
Prize Pools use Controlled Tokens for their internal accounting. These tokens are minted when depositing or awarding prizes. Controlled Tokens are burned when users withdraw. They are exchanged at a ratio of 1:1 to the asset.
The tokens associated with a PrizePool can be seen by calling:
function tokens() external override view returns (address memory)
A Controlled Token is a standard ERC20 that is bound to a Token Controller.
The Token Controller has the privileged ability to mint and burn tokens on user's behalf, and has a callback that listens for token transfers. Controlled Tokens are expected to trigger this callback on any mint, burns or transfers.
The Prize Pool must be the Token Controller for the controlled tokens that it is initialized with at construction.
A Controlled Token can added by the PrizePool owner by calling:
function addControlledToken(ControlledTokenInterface _controlledToken)external override onlyOwner
When a user deposits into a Prize Pool they must request what type of controlled token they receive in exchange. This token will be minted to them at an exchange rate of 1:1 for the asset.
When a user wishes to withdraw from a Prize Pool they must burn controlled tokens.
Users can deposit into the Prize Pool using the depositTo function. A user is instantly minted tokens upon deposit.
function depositTo(address to,uint256 amount,address controlledToken,address referrer) external;
The address to whom the controlled tokens should be minted
The amount of the underlying asset the user wishes to deposit. The Prize Pool contract should have been pre-approved by the caller to transfer the underlying ERC20 tokens.
The address of the token that they wish to mint. For our default Prize Strategy this will either be the Ticket address or the Sponsorship address. Those addresses can be looked up on the Prize Strategy.
The address that should receive referral awards, if any.
Depositing fires the event:
event Deposited(address indexed operator,address indexed to,address indexed token,uint256 amount);
The caller that made the deposit
The address that received the minted tokens
The address of the controlled token that was minted
The amount of both the underlying asset that was transferred and the tokens that were minted.
When a user withdraws they may need to contribute to the prize according to the fairness rules. If a user would like their tickets right away, they may pay an early exit fee to the prize. The early exit fee is determined by the Prize Strategy.
The instant withdrawal function returns the amount of the withdrawal that was retained as payment. This means you can call this function in a constant way to check to see what the exit fee will be. When it comes time to run the tx, that exit fee can be passed as the
maximumExitFee to ensure it doesn't exceed the expected limit.
function withdrawInstantlyFrom(address from,uint256 amount,address controlledToken,uint256 maximumExitFee)externalreturns (uint256 exitFee);
The address to withdraw from. This means you can withdraw on another user's behalf if you have an allowance for the controlled token.
The amount to withdraw
The controlled token to withdraw from
The maximum early exit fee the caller is willing to pay. This prevents the Prize Strategy from changing the fee on-the-fly.
This early exit fee can also be calculated by calling:
function calculateEarlyExitFee(address from, address controlledToken, uint256 amount)external override returns (uint256 exitFee, uint256 burnedCredit)
The address to withdraw from
The controlled token to withdraw from
The amount to withdraw
"calculateEarlyExitFee" returns the
exitFee that would be paid along with the credit that would be burned (
Similarly it is also possible to calculate how long a user must keep their funds in the pool:
function estimateCreditAccrualTime(address _controlledToken,uint256 _principal,uint256 _interest)external override view returns (uint256 durationSeconds)
The type of controlled token.
The principal amount on which interest is accruing.
The amount of interest that must accrue.
Only the Prize Strategy can call the award functions. These functions allow prizes to be disbursed to users.
Yield that accrues in the Prize Pool can be awarded by the Prize Strategy. The yield must first be captured and then it can be awarded.
To capture the yield the prize strategy can call the
function captureAwardBalance() external onlyPrizeStrategy returns (uint256);
This function will:
add the current yield balance to the available award balance
capture a portion for the reserve
return the total available award balance.
To award the captured yield to an address, the Prize strategy uses the
award function. The yield must be awarded as one of the controlled tokens configured in the Prize Pool.
function award(address to,uint256 amount,address controlledToken) external onlyPrizeStrategy;
The address to receive the newly minted tokens
The amount of tokens to mint
The type of token to mint
The Prize Strategy can award ERC20 tokens that are held by the Prize Pool.
function awardExternalERC20(address to,address externalToken,uint256 amount) external onlyPrizeStrategy;
However, some tokens are be blacklisted if they need to be held to generate yield (i.e. Compound cTokens).
The address to receive the transfer
The ERC20 to transfer
The amount of tokens to transfer
The Prize Strategy can award ERC721 tokens that are held by the Prize Pool.
function awardExternalERC721(address to,address externalToken,uint256 calldata tokenIds)externalonlyPrizeStrategy;
The address to receive the NFTs
The ERC721 contract address
The NFT token ids to transfer.
Credit accrues differently for each of the Prize Pool's controlled tokens, so each token will have its own credit rate and credit limit.
To get a users credit balance for a controlled token:
function balanceOfCredit(address user,address controlledToken) external returns (uint256);
The user whose credit balance should be returned
The token for which the credit balance should be pulled
The credit rate for a controlled token can be checked like so:
function creditRateOf(address controlledToken) external view returns (uint128 creditLimitMantissa,uint128 creditRateMantissa);
The controlled token whose credit limit and rate should be returned.
Note that the returned values are "mantissas": i.e. fixed point numbers with 18 decimal places.
The credit plan associated with a
controlledToken can be found by calling:
function creditPlanOf(address controlledToken) external override view returns (uint128 creditLimitMantissa, uint128 creditRateMantissa)
To calculate the total interest that can be given away you can statically call this function:
function captureAwardBalance() external returns (uint256);
The total of all controlled tokens (including timelocked) can be obtained by calling:
function accountedBalance() external override view returns (uint256)
The total underlying balance of all assets (including both principal and interest) can be obtained by calling:
function balance() external returns (uint256)
The owner can add "external" ERC20 tokens as prizes. The strategy will award the entire balance held by the Prize Pool to the winner.
function addExternalErc20Award(address _externalErc20) external onlyOwner;
The owner can add "external" ERC721 tokens as prizes. These tokens will be transferred to the winner.
function addExternalErc721Award(address _externalErc721,uint256 calldata _tokenIds) external onlyOwner
Checks with the Prize Pool if a specific token type (
_externalToken) may be awarded as an external prize:
function canAwardExternal(address _externalToken) external view returns (bool)
To retrieve when the current prize started:
function prizePeriodStartedAt() external view returns (uint256)
To retrieve when the prize will end:
function prizePeriodEndAt() external view returns (uint256)
Calculates the reserve portion of the given
amount of funds. If there is no reserve address, the Reserve fee portion will be zero.
function calculateReserveFee(uint256 amount) public view returns (uint256)
The associated Prize Strategy can be set by calling:
function setPrizeStrategy(TokenListenerInterface _prizeStrategy) external override onlyOwner
Only the Prize Pool owner can call this function.