Piston Race

Smart Contract Audit Report

Audit Summary

Piston Race Audit Report Piston Race is a new staking contract with a distinctive referral program.

For this audit, we reviewed the PistonRace contract provided to us by the project team.

We previously reviewed the project team's PistonToken and PistonTokenController contracts here.

Audit Findings

Please ensure trust in the team prior to investing as they have substantial control in the ecosystem.
Date: April 1st, 2022.
Updated: April 2nd, 2022 to support changes made by the project team.

Finding #1 - PistonRace - Medium (Resolved)

Description: If the STORE_BUSD_VALUE flag is disabled, the stakeBoost() function will not provide any functionality, but will still accept users' tokens.
Risk/Impact: If the STORE_BUSD_VALUE flag is disabled, users will have their tokens transferred to the contract, but they will not count towards their referral level and will be unable to be withdrawn.
Recommendation: The team should disallow users to call the stakeBoost() function if STORE_BUSD_VALUE is set to false.
Resolution: Users are now prevented from calling stakeBoost() if the STORE_BUSD_VALUE flag is enabled.

Finding #2 - PistonRace - Informational (Partially Resolved)

Description: The ClaimTax variable and calculateClaimTax() function are never used.
Recommendation: We recommend removing these to save on deployment costs if they are not needed for future functionality.
Resolution: The team has removed the calculateClaimTax() function, but the ClaimTax variable has not been removed.

Finding #4 - PistonRace - Informational (Resolved)

Description: There are multiple instances of multiplication occurring on the result of a division.
361: uint256 current_amount_BUSD = pistonPrice.mul(usersBoosts[_addr].stakedBoost_PSTN.div(1 ether));
653: uint256 current_amount_BUSD = pistonPrice.mul(user.userDepositsForEject[i].amount_PSTN.div(1 ether));
661: pistonPrice.mul(user.userDepositsForEject[i].amount_PSTN.div(1 ether))
686: uint256 ejectTaxAmount = amountAvailableForEject.div(100).mul(EjectTax);
Risk/Impact: In Solidity, division can lead to integer truncation, so dividing and subsequently multiplying can cause the user's calculated unstake amount and unstaking tax amount to lose precision, thus becoming less accurate.
Recommendation: The project team should rearrange the operations so that multiplication is performed before division in these instances.
Resolution: Multiplication is now properly performed before division in the above instances.

Finding #3 - PistonRace - Informational (Resolved)

Description: The following functions are declared public, but are never called internally:
PistonRace.updateTaxes(), PistonRace.updatePayoutRate(), PistonRace.updateRefDepth(), PistonRace.updateRefBonus(), PistonRace.updateInitialDeposit(), PistonRace.updateMinimumAmount(), PistonRace.updateCompoundTax(), PistonRace.updateExitTax(), 
PistonRace.updateDepositBracketSize(), PistonRace.updateMaxPayoutCap(), PistonRace.updateMinimumStakedBoostAmount(), PistonRace.SET_AIRDROP_MIN_AMOUNT(), PistonRace.UPDATE_EJECT_DAYS(), PistonRace.updateHoldRequirements(), PistonRace.roll()
Recommendation: These functions can be declared external for additional gas savings on each call.
Resolution: The team has declared the above functions external.

Contract Overview

  • This contract allows users to stake Piston tokens in order to earn rewards over time.
  • On a user's first deposit, they must deposit more than a minimum initial deposit amount and minimum deposit amount.
  • On any deposit, users must deposit more than a minimum deposit amount.
  • When depositing, a percentage "deposit tax" is taken from the user's deposit amount.
  • If a user's rewards are greater than 1% of their deposited amount, their rewards will be claimed and reinvested after a "compound tax" is deducted.
  • On a user's first deposit, they must also specify a referrer who will be the bottom of the user's "referral chain". The referral chain is made up of the referrer, the referrer's referrer, etc, up to the length of the contract's specified referral depth.
  • A user cannot list themselves as a referral. A user could circumvent this by using multiple personal wallets.
  • The specified referrer address must also have tokens staked in the contract unless the referrer is the owner.
  • When claiming rewards, a user can elect to either reinvest their rewards or withdraw them.
  • A user's reward amount is based on their total deposited amount, payout rate, and their amount of time staked.
  • If the user is withdrawing rewards, a "sustainability fee", "compound tax", and "exit tax" are taken from the user's reward amount.
  • If the user is reinvesting rewards, only the sutainability fee and compound tax are taken.
  • Users are only permitted to unstake their tokens if the withdraw cutoff duration has not passed since their first deposit.
  • If the BUSD value of the user's deposited tokens has increased, the unstake amount will be set to either their deposited token amount or their original BUSD equivalent divided by the current Piston token price, whichever is smaller.
  • If the BUSD value of the user's deposited tokens has decreased, their unstake amount will be set to their deposited token amount.
  • As the PriceFeed contract used to calculate this BUSD amount is outside the scope of this audit, we are unable to provide an assessment regarding security or accuracy.
  • Of this amount, an unstaking tax of 10% is taken before being sent to the user.
  • Any withdrawn rewards will also be subtracted from this amount; if the user's withdrawn rewards has exceeded their post-tax unstake amount, they will not be permitted to unstake.

  • Each time the user earns rewards or makes a deposit, a referral percentage will be added to one of the referrers' deposit amounts in the referral chain as rewards. The referrer who receives the reward will be rotated the next time referral rewards are given.
  • To qualify to receive referral rewards, a referrer must also have reached the required amount in "Stake Boosts", based on how many levels up the chain they are from the user.
  • The sum of the referrer's reinvestments, deposit amount, and sent airdrops must also exceed their total withdrawn amount from rewards.
  • To perform a Stake Boost, users must use the stakeBoost() function to deposit at least a "minimum Stake Boost" amount in Piston tokens.
  • Stake Boosts will only add to a user's Stake Boost amount if the Stake Boost flag is enabled.
  • Rewards will be added to the referrer's deposited amount used to calculate their payout.
  • Users are not permitted to withdraw their Stake Boost amount until they have reached their maximum allowed payout.
  • If the BUSD value of the user's Stake Boost amount has increased, they will be transferred either their Stake Boost amount or their original Stake Boost BUSD equivalent divided by the current Piston token price, whichever is smaller.
  • If the BUSD value of the user's Stake Boost amount has decreased, they will be transferred their Stake Boost amount.
  • This contract also allows users to send airdrops to other users that have deposited tokens to the contract.
  • Users must airdrop an amount greater than or equal to the minimum airdrop amount.
  • Tokens will be transferred from the sender to this contract; the recipient will then have their deposit amount increased by the specified amount.
  • Users cannot make any further deposits, Stake Boosts, airdrops, or earn referral rewards after they have unstaked.
  • If this contract does not hold a sufficient Piston token balance for a user's unstake, claim, or boost withdraw, the difference will be minted to this contract from a TokenMint contract before being transferred to the user.
  • As the TokenMint contract was not included in the scope of this audit, we are unable to provide an assessment regarding security or functionality.
  • The maximum total withdrawable reward amount for a user is limited to either 3.65x a user's deposited amount or 3.65x the maximum payout cap, whichever is smaller.
  • A user cannot reinvest their rewards if their total deposit amount (including rewards earned) has exceeded 5x the amount they have contributed solely from their own token deposits.
  • If a user claims more than their current payout cap, the excess will be forfeited; if their payout cap is increased, the excess will still be counted towards it.
  • The team should ensure that the proper exemptions are made if a fee-on-transfer token is used as the Piston token.

  • The owner can update the deposit, exit, and compound taxes to any amounts at any time.
  • The owner can update the sustainability tax to up to 50% at any time.
  • The owner can update the Price Feed contract at any time.
  • The owner can toggle the Stake Boost flag at any time.
  • The owner can update the referral bonus, maximum referral depth, minimum initial deposit, minimum deposit, maximum payout, minimum stake boost amount, minimum airdrop amount, and withdraw cutoff duration at any time.
  • The owner can update the boost amount required for any level at any time.
  • As the contract is implemented with Solidity 0.8.x, it is protected from overflows/underflows.

Audit Results

Vulnerability CategoryNotesResult
Arbitrary Jump/Storage WriteN/APASS
Centralization of Control
  • The owner has the permissions described above.
  • The owner can disable the functionality of Stake Boosts while still accepting tokens.
  • The owner can update the deposit, exit, and compound taxes to any percentages at any time.
  • The owner can update the sustainability tax to up to 50%.
  • The owner can update the reward rate at any time.
    Compiler IssuesN/APASS
    Delegate Call to Untrusted ContractN/APASS
    Dependence on Predictable VariablesN/APASS
    Ether/Token TheftN/APASS
    Flash LoansN/APASS
    Front RunningN/APASS
    Improper EventsN/APASS
    Improper Authorization SchemeN/APASS
    Integer Over/UnderflowN/APASS
    Logical IssuesN/APASS
    Oracle IssuesN/APASS
    Outdated Compiler VersionN/APASS
    Race ConditionsN/APASS
    Signature IssuesN/APASS
    Unbounded LoopsN/APASS
    Unused CodeN/APASS
    Overall Contract Safety PASS

    Inheritance Chart

    Smart Contract Audit - Inheritance

    Function Graph

    Smart Contract Audit - Graph

    Functions Overview

     ($) = payable function
     # = non-constant function
      + [Lib] AddressUpgradeable 
        - [Int] isContract
        - [Int] sendValue #
        - [Int] functionCall #
        - [Int] functionCall #
        - [Int] functionCallWithValue #
        - [Int] functionCallWithValue #
        - [Int] functionStaticCall
        - [Int] functionStaticCall
        - [Int] verifyCallResult
     +  Initializable 
        - [Prv] _isConstructor
     +  ContextUpgradeable (Initializable)
        - [Int] __Context_init #
           - modifiers: onlyInitializing
        - [Int] __Context_init_unchained #
           - modifiers: onlyInitializing
        - [Int] _msgSender
        - [Int] _msgData
     +  OwnableUpgradeable (Initializable, ContextUpgradeable)
        - [Int] __Ownable_init #
           - modifiers: onlyInitializing
        - [Int] __Ownable_init_unchained #
           - modifiers: onlyInitializing
        - [Pub] owner
        - [Pub] renounceOwnership #
           - modifiers: onlyOwner
        - [Pub] transferOwnership #
           - modifiers: onlyOwner
        - [Int] _transferOwnership #
     +  PistonRace (OwnableUpgradeable)
        - [Ext] initialize #
           - modifiers: initializer
        - [Pub] updateTaxes #
           - modifiers: onlyOwner
        - [Pub] updatePistonTokenPriceFeed #
           - modifiers: onlyOwner
        - [Pub] updatePayoutRate #
           - modifiers: onlyOwner
        - [Pub] updateRefDepth #
           - modifiers: onlyOwner
        - [Pub] updateRefBonus #
           - modifiers: onlyOwner
        - [Pub] updateInitialDeposit #
           - modifiers: onlyOwner
        - [Pub] updateMinimumAmount #
           - modifiers: onlyOwner
        - [Pub] updateCompoundTax #
           - modifiers: onlyOwner
        - [Pub] updateExitTax #
           - modifiers: onlyOwner
        - [Pub] updateDepositBracketSize #
           - modifiers: onlyOwner
        - [Pub] updateMaxPayoutCap #
           - modifiers: onlyOwner
        - [Pub] updateMinimumStakedBoostAmount #
           - modifiers: onlyOwner
        - [Pub] SET_AIRDROP_MIN_AMOUNT #
           - modifiers: onlyOwner
        - [Pub] UPDATE_EJECT_DAYS #
           - modifiers: onlyOwner
        - [Pub] updateHoldRequirements #
           - modifiers: onlyOwner
        - [Ext] deposit #
           - modifiers: onlyOwner
        - [Ext] deposit #
        - [Ext] stakeBoost #
        - [Ext] unstakeBoost #
        - [Ext] claim #
        - [Pub] roll #
        - [Int] _setUpline #
        - [Int] _deposit #
        - [Int] _refPayout #
        - [Int] _roll #
        - [Pub] rollAmountOf
        - [Pub] maxRollOf
        - [Int] _claim_out #
        - [Int] _claim #
        - [Pub] calculateDepositTax
        - [Pub] calculateClaimTax
        - [Ext] eject #
        - [Pub] isNetPositive
        - [Pub] creditsAndDebits
        - [Pub] HasUsedEject
        - [Pub] isBalanceCovered
        - [Pub] balanceLevel
        - [Pub] claimsAvailable
        - [Pub] maxPayoutOf
        - [Pub] sustainabilityFeeV2
        - [Pub] payoutOf
        - [Ext] userInfo
        - [Ext] userInfoTotals
        - [Ext] userInfoRealDeposits
        - [Pub] getVaultBalance
        - [Ext] contractInfo
        - [Ext] airdrop #
     + [Lib] SafeMath 
        - [Int] mul
        - [Int] div
        - [Int] sub
        - [Int] safeSub
        - [Int] add
        - [Int] max
        - [Int] min
     + [Int] IToken 
        - [Ext] transferFrom #
        - [Ext] transfer #
        - [Ext] balanceOf
        - [Ext] allowance
        - [Ext] approve #
     + [Int] ITokenMint 
        - [Ext] mint #
     + [Int] ITokenPriceFeed 
        - [Ext] getPrice

    About SourceHat

    SourceHat has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1800+ solidity smart contract audits covering all major project types and protocols, securing a total of over $50 billion U.S. dollars in on-chain value!
    Our firm is well-reputed in the community and is trusted as a top smart contract auditing company for the review of solidity code, no matter how complex. Our team of experienced solidity smart contract auditors performs audits for tokens, NFTs, crowdsales, marketplaces, gambling games, financial protocols, and more!

    Contact us today to get a free quote for a smart contract audit of your project!

    What is a SourceHat Audit?

    Typically, a smart contract audit is a comprehensive review process designed to discover logical errors, security vulnerabilities, and optimization opportunities within code. A SourceHat Audit takes this a step further by verifying economic logic to ensure the stability of smart contracts and highlighting privileged functionality to create a report that is easy to understand for developers and community members alike.

    How Do I Interpret the Findings?

    Each of our Findings will be labeled with a Severity level. We always recommend the team resolve High, Medium, and Low severity findings prior to deploying the code to the mainnet. Here is a breakdown on what each Severity level means for the project:

    • High severity indicates that the issue puts a large number of users' funds at risk and has a high probability of exploitation, or the smart contract contains serious logical issues which can prevent the code from operating as intended.
    • Medium severity issues are those which place at least some users' funds at risk and has a medium to high probability of exploitation.
    • Low severity issues have a relatively minor risk association; these issues have a low probability of occurring or may have a minimal impact.
    • Informational issues pose no immediate risk, but inform the project team of opportunities for gas optimizations and following smart contract security best practices.