Pix - Smart Contract Audit Report
Pix is building a platform where users can earn tokens by playing games and staking LP tokens.
For this audit we reviewed the project team's code at commit 2be13f041a0acab4719771d7640911ecd1ae6ced on the team's GitHub repository.
Notes on the Contracts:
- This contract is made to interact with the project's NFT contract through drawing NFTs from different boxes.
- Users can spend platform tokens in exchange for minted key tokens, which can then be used to draw NFTs from boxes. Users can either draw 1 or an owner-defined draw number of NFTs at once. The owner can update the draw number at any time.
- Of these platform tokens spent, 90% are sent to a prize pool address, 5% are sent to a lable address, and 5% are sent to a user specified inviter address.
- The owner can change the cost of these key tokens at any time.
- Each NFT has a unique token ID, which is used to identify it's grade and box series.
- The owner has the ability to create new boxes at any time, and can also change a box's reward and odds to draw certain graded NFTs at any time.
- Once the user has aquired 5 NFTs of the same box series and grade, exchange them for a new NFT of the same series with a random grade assigned.
- Note that the the randomness functionality used to determine the grade of the new NFT can be potentially manipulated to produce the same grade for every call while within a block.
- Users can cash in their NFTs for a reward if they have the required number of NFTs of each grade for a given box series.
- The reward for a certain box series can be multiple different tokens with varying amounts of each.
- The owner of this contract can define and update both a 'Box Pro' and 'handling' address at any time. These addresses, in addition to the intended Flip, Lock, and BlindBox contracts, are given authorized access to the NFT contract functions.
- Addresses with authorized access can mint any NFTs from any series with any grade to any address at any time.
- Addresses with authorized access can burn any NFTs from any address at any time.
- The owner of this contract can also authorize any address to send transfer any token in the PrizePool contract to any address at any time.
- This contract is a governance contract which allows users to propose and vote on changes to the Pix ecosystem.
- Any user can deposit platform tokens in order to create a new poll to be voted on at any time. Once the poll is complete, the user will receive this deposit back.
- Users can stake tokens on the contract, enabling them to be used to vote on different polls. Note that a user can only vote one time on a poll, regardless of the amount.
- The tokens used to vote will be locked from withdraw until the voting period ends.
- In order for a poll to pass, it must reach an owner defined quorum and pass ratio. The voting period must also be over in order to end a poll.
- Once a poll has passed, it can be executed by anyone after a delay period has passed.
- Note that the owner has the ability to change the platform token, owner, quorum, voting period, deposit, delay, and expiration at any time.
- Flip is a simple betting game where the user can bet 10, 100, or 1000 platform tokens at a time.
- The user will pass in their wager, and bet on either 1 or 0. A 1 or 0 will then be generated by the function.
- If the user guessed the correct number, they will receive 1.9x their wager.
- If a user is incorrect, they will receive some compensation based on their bet amount.
- With a loss on a 10 platform token bet, the user will receive a D grade NFT from a random box series.
- With a loss on a 100 or 1000 platform token bet, the user will receive 1 or 10 key tokens (used to draw NFTs through BlindBox), respectively.
- Warning: The number generated by betAndFlip is not truly random. While within the same block, a consistent wager will cause the function to always generate a consistent 1 or 0. This could enable a user to keep winning until the contract's funds are drained.
- Users can stake the platform token on the Lock contract in order to earn rewards over time.
- For every 1000 platform tokens staked, the user will instantly be rewarded a D grade NFT of a random box series (limited to 20 per transaction).
- Users will be able to withdraw earned rewards, however will not be able to withdraw their staked tokens until the lockperiod during which they staked their tokens is over.
Audit Findings Summary
- This contract is Pix's staking platform, where users can bond different staking tokens in exchange for rewards.
- The owner of the contract can register new tokens available to stake at any time.
- In this contract, a user is able to withdraw their staked tokens at any time.
- Note that when a user withdraws their tokens, they will only receive 50% of their original deposit, while the rest will be deposited into the most current Lock reward pool.
- This contract should not be used with deflationary tokens or ERC-777 tokens. If a deflationary token is added as a staking asset, then the contract must be exempt from transfer fees.
- Ensure trust in the team as the owner and authorized users have substantial control of the ecosystem. Authorized users can mint/burn NFTs to/from any address at any time. The project team intends the Gov contract to have ownership of the other contracts, however this cannot be confirmed until deployment.
- The project's randomness functionality does not employ the use of chainlink and could potentially be exploited as a result.
- Date: October 15th, 2021.
- Updated: October 31st with implementation changes and addition of Box Pro.
External Threat Results
|Arbitrary Storage Write||N/A||PASS|
|Delegate Call to Untrusted Contract||N/A||PASS|
|Dependence on Predictable Variables||"Random" numbers generated inside Flip.sol and BlindBox.sol are not truly random and leave the contracts vulnerable to exploitation.||WARNING|
|State Change External Calls||N/A||PASS|
|User Supplied Assertion||N/A||PASS|
|Critical Solidity Compiler||N/A||PASS|
|Overall Contract Safety||WARNING|
Details: BlindBox Contract
($) = payable function # = non-constant function Int = Internal Ext = External Pub = Public + BlindBox (ERC20PermitUpgradeable) - [Pub]
# - [Ext] init # - modifiers: onlyOwner - [Ext] addBoxPro # - modifiers: onlyOwner - [Pub] MintBox # - modifiers: onlyOwner,checkBox - [Ext] Draw # - modifiers: onlynumberofDraw - [Ext] ResetRatio # - modifiers: onlyOwner - [Ext] mintKey # - modifiers: onlyFlip - [Ext] burnKey # - modifiers: onlyBoxPro - [Ext] DrawOut # - modifiers: onlyBox,onlynumberofDrawOut - [Ext] MixTrue # - modifiers: onlyBox,onlyGrade,checkTokenIdLens - [Ext] Convert # - modifiers: onlyBox - [Ext] ($) - [Int] _sendReward # - [Ext] ResetOwner # - modifiers: onlyOwner - [Ext] ResetDraw # - modifiers: onlyOwner,onlyBox - [Int] than - [Ext] ResetMix # - modifiers: onlyOwner,onlyBox - [Pub] ResetReward # - modifiers: onlyOwner,onlyBox - [Ext] ResetDrawNumber # - modifiers: onlyOwner - [Ext] QueryBox - [Ext] QueryConfig - [Ext] QuerySeriesIds - [Pub] QuerySeriesIdsNotNull - [Ext] QueryRatio - [Ext] QueryDraws - [Pub] QueryLevels - [Ext] QueryImage - [Pub] QueryMix - [Ext] QueryBoxs - [Int] _mint # - [Int] _createToken #
Details: Gov Contract
($) = payable function # = non-constant function Int = Internal Ext = External Pub = Public + Gov - [Prv] assertTitle - [Prv] assertDesc - [Prv] assertLink - [Pub]
# - modifiers: assertPercent,assertPercent - [Ext] UpdateConfig # - modifiers: assertPercent,assertPercent - [Ext] Init # - [Ext] CreatePoll # - [Ext] StakeVotingTokens # - [Ext] WithdrawVotingTokens # - [Ext] CastVote # - [Ext] EndPoll # - [Ext] ExcutePoll # - [Ext] ExpirePoll # - [Int] _locked_balance # - [Ext] QueryConfig - [Ext] QueryState - [Ext] QueryStaker - [Ext] QueryPoll - [Ext] QueryPolls - [Ext] QueryVoters - [Int] _update_token_manager # - [Int] _passCommand # - [Int] _polls_itmap_insert_or_update # - [Int] _polls_itmap_contains - [Int] _polls_itmap_keyindex - [Int] _polls_itmap_delete - [Int] _polls_itmap_iterate_valid - [Int] _polls_itmap_iterate_next - [Int] _polls_itmap_iterate_prev - [Int] _polls_itmap_iterate_get - [Int] _polls_itmap_value_get - [Int] _banks_itmap_insert_or_update # - [Int] _banks_itmap_contains - [Int] _banks_itmap_keyindex - [Int] _banks_itmap_value_get - [Int] _voters_itmap_insert_or_update # - [Int] _voters_itmap_remove # - [Int] _voters_itmap_contains - [Int] _voters_itmap_keyindex - [Int] _voters_itmap_value_get
Details: Flip Contract
($) = payable function # = non-constant function Int = Internal Ext = External Pub = Public + flip - [Pub]
# - [Ext] init # - [Ext] betAndFlip # - [Ext] getLastBlockNumberUsed - [Ext] getLastBlockHashUsed - [Ext] getResultOfLastFlip
Details: Staking Contract
($) = payable function # = non-constant function Int = Internal Ext = External Pub = Public + Staking - [Pub]
# - [Ext] QueryDistribute - [Ext] Distributer # - [Ext] UpdateConfig # - [Ext] registerPlatformAsset # - [Ext] Bond # - [Int] depositReward # - [Ext] Unbond # - [Ext] Withdraw # - [Ext] QueryConfig - [Ext] QueryPoolInfo - [Ext] QueryRewardInfo - [Ext] QueryBondReward - [Int] _increase_bond_amount # - [Int] _before_share_change # - [Int] _decrease_bond_amount # - [Int] _itmap_insert # - [Int] _itmap_remove #