Maximus Team
Smart Contract Audit Report
Audit Summary
Maximus is creating a new staking platform which includes multiple new tokens.
For this audit, we reviewed Maximus' PerpetualPool, Team, StakeRewardDistribution, MAXIEscrow, and MysteryBox contracts at commit ca4514258a99bb9f850847e0b5a161fa6c3b6d52 on the team's private GitHub repository.
We previously reviewed the project team's token contract here and staking contract here.
Audit Findings
All findings have been resolved.
Date: August 31st, 2022.
Updated: September 1st, 2022.Finding #1 - PerpetualPool - High (Resolved)
Description: The HEX_REDEMPTION_RATE is not correctly updated in the endStakeHex() function. The rate calculation uses the contract's HEX balance before a portion is transferred to the Team contract, which will result in a higher share price than intended:
Risk/Impact: There will not be enough HEX tokens in the contract for all users to redeem their pool tokens.uint256 hex_balance = hex_contract.balanceOf(address(this)); uint256 bpb_bonus_sharing_amount = get_bonus_sharing_amount(CURRENT_STAKE_PRINCIPAL, hex_balance,STAKE_LENGTH); hex_token.transfer(TEAM_CONTRACT_ADDRESS, bpb_bonus_sharing_amount); hedron_token.transfer(TEAM_CONTRACT_ADDRESS,hedron_contract.balanceOf(address(this))); uint256 total_supply = IERC20(address(this)).totalSupply(); HEX_REDEMPTION_RATE = calculate_redemption_rate(hex_balance, total_supply);
Recommendation: The line updating the HEX_REDEMPTION_RATE variable should be changed to the following:
Resolution: The team has implemented the above recommendation.HEX_REDEMPTION_RATE = calculate_redemption_rate(hex_contract.balanceOf(address(this)), total_supply);
Finding #2 - MAXIEscrow - High (Resolved)
Description: The releaseMAXI() function is intended to slowly distribute MAXI back to the Team contract over time, but has no limit on the number of times it can be called once the first distribution period is reached.
Risk/Impact: Once period 5 is reached, anyone can call the releaseMAXI() function 6 times in order to send the contract's entire MAXI balance back to the Team contract to be distributed as rewards instead of the intended 1/6th.
Recommendation: The period should be marked as distributed once releaseMAXI() is called and should not be permitted to be called again until the next distribution period.
Resolution: The releaseMAXI() function will now transfer 0 MAXI back to the Team contract on any additional calls within a distribution period.
Finding #3 - PerpetualPool - Medium (Resolved)
Description: The get_bonus_sharing_amount() function contains inconsistent calculations that are not intended. When calculating the scaled LPB multiplier, the result jumps from a value of 200,000,000 for a stake length of 3,650 days to a value of 2 for stake lengths greater than 3,650 days.
Risk/Impact: An incorrect scaled LPB multiplier will result in an unintended amount of HEX to be transferred as rewards to the Team contract.if (stake_length>lpb_threshold) { scaled_lpb_multiplier = 2; } else { scaled_lpb_multiplier = 2*((10**8)*(stake_length))/lpb_threshold; }
Recommendation: The team should update this function so the scaled LPB multiplier is set to 200,000,000 for stake lengths greater than 3,650 days.
Resolution: The team has implemented the above recommendation.
Contracts Overview
PerpetualPool Contract:
- As the contracts are implemented with Solidity v0.8.x, they are safe from any possible overflows/underflows.
- ReentrancyGuard is utilized in applicable functions, protecting them from any potential reentrancy attacks.
- The project team should exercise caution and ensure that the appropriate contracts are excluded from any fee-on-transfer mechanisms.
Team Contract:
- This contract is used as a pool for HEX staking.
- The contract consists of two periods: a minting/reload period and a staking period.
- Upon deployment, a pool token name and symbol, initial mint period length, staking period duration, and reload period duration are provided.
- The initial minting period begins once the contract is deployed. During this time, users can provide HEX to this contract in order to have pool tokens minted to them.
- Pool tokens are minted based on the HEX amount deposited divided by the HEX redemption rate, which begins at 1 pool token per HEX.
- Users can also redeem their pool tokens in exchange for HEX at the redemption rate during this period. The pool tokens are burned in the process.
- Any user can end the minting period and activate the staking period once the current day has exceeded the end of the minting period.
- Once activated, the HEX contract's stake is initiated for the duration of the staking period using this contract's collected HEX tokens.
- Any user can end the staking period once the current day has exceeded the end day of the staking period.
- A portion of the contract's total HEX after staking is transferred to the Team contract based on the principal stake amount, stake length, and earned HEX staking rewards.
- This contract's entire hedron balance is also transferred to the Team contract.
- A new HEX redemption rate is then calculated based on the number HEX tokens in the contract per pool token in existence.
- A new minting/reload period will then restart where users can again provide and redeem HEX.
- The duration of each minting/reload period after the initial minting period is equal to the specified reload period duration.
- This contract's period number is incremented each time a new period starts.
- Hedron tokens can be minted through this contract by any user at any time.
- As the HEX and Hedron contracts were not included in the scope of this audit, we are unable to provide an assessment with regards to security or functionality.
- Days are calculated according to the HEX contract's current day.
StakeRewardDistribution Contract:
- This contract allows users to deposit MAXI in return for Team tokens. TEAM can then be staked to earn rewards in various tokens.
- Upon deployment, PerpetualPool contracts with the following token names and stake periods are created:
- A "Maximus Base" pool with a stake period of 1 day.
- A "Maximus Trio" pool with a stake period of 3 days.
- A "Maximus Lucky" pool with a stake period of 7 days.
- A "Maximus Decimus" pool with a stake period of 10 days.
- Each of these pools are created with an initial mint period length of 21 days and a reload period of 7 days.
- A StakeRewardDistribution contract is also deployed.
- During this contract's minting phase, users are permitted to deposit Maxi tokens in exchange for minted TEAM at a 1:1 ratio.
- Users can then "stake" their TEAM at any time which will burn them and create or update their "Stake Record" for the next staking period.
- The Stake Record's stake ID and expiry period are set to the next available staking period.
- After two days have passed, any user can finalize the minting phase.
- This will mark the minting phase as completed and deploy a new MAXIEscrow contract and a new MysteryBox contract.
- Of the contract's total collected Maxi tokens, 20% are burned, 30% are transferred to the MAXIEscrow contract, and 50% are transferred to the MysteryBox contract.
- Users are still permitted to stake at any time.
- Periods are determined by querying the Maximus Base PerpetualPool's contract's period information.
- Any user can prepare a claim for any or all reward tokens once a staking period has ended.
- When a claim is prepared, the contract's balance of the specified token is transferred to the Stake Reward Distribution contract.
- This balance is divided by the total amount of TEAM staked during the previous staking period to calculate the specified token rewards.
- Users are entitled to a share of these rewards based on their Staked amount during the previous staking period.
- The team should ensure that no fee-on-transfer tokens are used as reward tokens unless the proper exemptions are made.
- Once a user's stake expiry period has passed, they can end it and receive their originally deposited TEAM.
- A user can end a portion or all of their Team Stake before its staking period is over, but must pay a 3.69% penalty on the amount withdrawn.
- The amount ended minus this penalty is then minted back to the user.
- A user can also choose to restart an expired Stake instead of ending it. The balance of the expired stake is moved into a new Stake with an expiration set to the end of the next staking period.
MAXIEscrow Contract:
- Users can use this contract in order to claim rewards earned from the Team contract.
- Users can claim at any time after rewards for the specified token have been transferred for a given staking period.
- When claiming, the user must specify the reward token ticker, Stake ID, and staking period in which the user is claiming rewards for.
MysteryBox Contract:
- This contract is used to lock MAXI tokens received by the Team contract and release them back into the Team contract over time.
- Once the minting period is finalized in the Team contract, 30% of its received MAXI is transferred to a new instance of this contract.
- 1/6th of these funds can be released on Period 5, 1/3rd can be released on Period 11, and the remaining half can be released on Period 17.
- Periods are determined using the Maximus Base Perpetual Pool contract's determined period number.
- Once the minting period is finalized in the Team contract, 50% of its received MAXI is transferred to a new instance of this contract.
- This contract allows any user to "flush" the contract's TEAM or MAXI by providing an equal amount of their own MAXI.
- When flushing, the user can provide any amount less than 1 million MAXI.
- This MAXI is transferred from the user to the "Mystery Box Hot Address".
- An equal amount of either MAXI or TEAM is then transferred from this contract to the Mystery Box Hot Address.
- As the Mystery Box Hot Address was not included in the scope of this audit, we are unable to provide an assessment with regards to security or functionality.
Audit Results
Vulnerability Category | Notes | Result |
---|---|---|
Arbitrary Jump/Storage Write | N/A | PASS |
Centralization of Control | N/A | PASS |
Compiler Issues | N/A | PASS |
Delegate Call to Untrusted Contract | N/A | PASS |
Dependence on Predictable Variables | N/A | PASS |
Ether/Token Theft | N/A | PASS |
Flash Loans | N/A | PASS |
Front Running | N/A | PASS |
Improper Events | N/A | PASS |
Improper Authorization Scheme | N/A | PASS |
Integer Over/Underflow | N/A | PASS |
Logical Issues | N/A | PASS |
Oracle Issues | N/A | PASS |
Outdated Compiler Version | N/A | PASS |
Race Conditions | N/A | PASS |
Reentrancy | N/A | PASS |
Signature Issues | N/A | PASS |
Unbounded Loops | N/A | PASS |
Unused Code | N/A | PASS |
Overall Contract Safety | PASS |
PerpetualPool Contract
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IERC20
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] transfer #
- [Ext] allowance
- [Ext] approve #
- [Ext] transferFrom #
+ [Int] IERC20Metadata (IERC20)
- [Ext] name
- [Ext] symbol
- [Ext] decimals
+ Context
- [Int] _msgSender
- [Int] _msgData
+ ERC20 (Context, IERC20, IERC20Metadata)
- [Pub] #
- [Pub] name
- [Pub] symbol
- [Pub] decimals
- [Pub] totalSupply
- [Pub] balanceOf
- [Pub] transfer #
- [Pub] allowance
- [Pub] approve #
- [Pub] transferFrom #
- [Pub] increaseAllowance #
- [Pub] decreaseAllowance #
- [Int] _transfer #
- [Int] _mint #
- [Int] _burn #
- [Int] _approve #
- [Int] _spendAllowance #
- [Int] _beforeTokenTransfer #
- [Int] _afterTokenTransfer #
+ [Int] IERC20Permit
- [Ext] permit #
- [Ext] nonces
- [Ext] DOMAIN_SEPARATOR
+ [Lib] Address
- [Int] isContract
- [Int] sendValue #
- [Int] functionCall #
- [Int] functionCall #
- [Int] functionCallWithValue #
- [Int] functionCallWithValue #
- [Int] functionStaticCall
- [Int] functionStaticCall
- [Int] functionDelegateCall #
- [Int] functionDelegateCall #
- [Int] verifyCallResult
+ [Lib] SafeERC20
- [Int] safeTransfer #
- [Int] safeTransferFrom #
- [Int] safeApprove #
- [Int] safeIncreaseAllowance #
- [Int] safeDecreaseAllowance #
- [Int] safePermit #
- [Prv] _callOptionalReturn #
+ ERC20Burnable (Context, ERC20)
- [Pub] burn #
- [Pub] burnFrom #
+ [Lib] SafeMath
- [Int] tryAdd
- [Int] trySub
- [Int] tryMul
- [Int] tryDiv
- [Int] tryMod
- [Int] add
- [Int] sub
- [Int] mul
- [Int] div
- [Int] mod
- [Int] sub
- [Int] div
- [Int] mod
+ ReentrancyGuard
- [Pub] #
+ HedronToken
- [Ext] approve #
- [Ext] transfer #
- [Ext] mintNative #
- [Ext] claimNative #
- [Ext] currentDay
+ HEXToken
- [Ext] currentDay
- [Ext] stakeStart #
- [Ext] approve #
- [Pub] transfer #
- [Pub] stakeEnd #
- [Ext] stakeCount
+ PerpetualPool (ERC20, ERC20Burnable, ReentrancyGuard)
- [Pub] #
- modifiers: ERC20,ReentrancyGuard
- [Pub] decimals
- [Ext] getCurrentPeriod
- [Ext] getHexDay
- [Ext] getEndStaker
- [Prv] mint #
- [Ext] pledgeHEX #
- modifiers: nonReentrant
- [Ext] redeemHEX #
- modifiers: nonReentrant
- [Ext] stakeHEX #
- modifiers: nonReentrant
- [Prv] _stakeHEX #
- [Prv] _endStakeHEX #
- [Ext] endStakeHEX #
- modifiers: nonReentrant
- [Prv] get_bonus_sharing_amount
- [Prv] calculate_redemption_rate
- [Ext] mintHedron #
- [Prv] _mintHedron #
Team, StakeRewardDistribution, MAXIEscrow, & MysteryBox Contracts
($) = payable function
# = non-constant function
Int = Internal
Ext = External
Pub = Public
+ [Int] IERC20
- [Ext] totalSupply
- [Ext] balanceOf
- [Ext] transfer #
- [Ext] allowance
- [Ext] approve #
- [Ext] transferFrom #
+ [Int] IERC20Metadata (IERC20)
- [Ext] name
- [Ext] symbol
- [Ext] decimals
+ Context
- [Int] _msgSender
- [Int] _msgData
+ ERC20 (Context, IERC20, IERC20Metadata)
- [Pub] #
- [Pub] name
- [Pub] symbol
- [Pub] decimals
- [Pub] totalSupply
- [Pub] balanceOf
- [Pub] transfer #
- [Pub] allowance
- [Pub] approve #
- [Pub] transferFrom #
- [Pub] increaseAllowance #
- [Pub] decreaseAllowance #
- [Int] _transfer #
- [Int] _mint #
- [Int] _burn #
- [Int] _approve #
- [Int] _spendAllowance #
- [Int] _beforeTokenTransfer #
- [Int] _afterTokenTransfer #
+ [Int] IERC20Permit
- [Ext] permit #
- [Ext] nonces
- [Ext] DOMAIN_SEPARATOR
+ [Lib] Address
- [Int] isContract
- [Int] sendValue #
- [Int] functionCall #
- [Int] functionCall #
- [Int] functionCallWithValue #
- [Int] functionCallWithValue #
- [Int] functionStaticCall
- [Int] functionStaticCall
- [Int] functionDelegateCall #
- [Int] functionDelegateCall #
- [Int] verifyCallResult
+ [Lib] SafeERC20
- [Int] safeTransfer #
- [Int] safeTransferFrom #
- [Int] safeApprove #
- [Int] safeIncreaseAllowance #
- [Int] safeDecreaseAllowance #
- [Int] safePermit #
- [Prv] _callOptionalReturn #
+ ERC20Burnable (Context, ERC20)
- [Pub] burn #
- [Pub] burnFrom #
+ [Lib] SafeMath
- [Int] tryAdd
- [Int] trySub
- [Int] tryMul
- [Int] tryDiv
- [Int] tryMod
- [Int] add
- [Int] sub
- [Int] mul
- [Int] div
- [Int] mod
- [Int] sub
- [Int] div
- [Int] mod
+ ReentrancyGuard
- [Pub] #
+ HedronToken
- [Ext] approve #
- [Ext] transfer #
- [Ext] mintNative #
- [Ext] claimNative #
- [Ext] currentDay
+ HEXToken
- [Ext] currentDay
- [Ext] stakeStart #
- [Ext] approve #
- [Pub] transfer #
- [Pub] stakeEnd #
- [Ext] stakeCount
+ PerpetualPool (ERC20, ERC20Burnable, ReentrancyGuard)
- [Pub] #
- modifiers: ERC20,ReentrancyGuard
- [Pub] decimals
- [Ext] getCurrentPeriod
- [Ext] getHexDay
- [Ext] getEndStaker
- [Prv] mint #
- [Ext] pledgeHEX #
- modifiers: nonReentrant
- [Ext] redeemHEX #
- modifiers: nonReentrant
- [Ext] stakeHEX #
- modifiers: nonReentrant
- [Prv] _stakeHEX #
- [Prv] _endStakeHEX #
- [Ext] endStakeHEX #
- modifiers: nonReentrant
- [Prv] get_bonus_sharing_amount
- [Prv] calculate_redemption_rate
- [Ext] mintHedron #
- [Prv] _mintHedron #
+ Team (ERC20, ERC20Burnable, ReentrancyGuard)
- [Pub] #
- modifiers: ERC20,ReentrancyGuard
- [Prv] deployPools #
- [Prv] deployPool #
- [Prv] declareSupportedTokens #
- [Pub] getSupportedTokens
- [Prv] deployStakeRewardDistributionContract #
- [Ext] mintTEAM #
- modifiers: nonReentrant
- [Ext] finalizeMinting #
- modifiers: nonReentrant
- [Prv] deployMAXIEscrow #
- [Prv] deployMysteryBox #
- [Pub] stakeTeam #
- modifiers: nonReentrant
- [Prv] newStakeRecord #
- [Pub] earlyEndStakeTeam #
- modifiers: nonReentrant
- [Prv] earlyEndStakeRecord #
- [Pub] endCompletedStake #
- [Prv] endExpiredStake #
- [Pub] extendStake #
- modifiers: nonReentrant
- [Prv] stakeExtension #
- [Pub] restakeExpiredStake #
- modifiers: nonReentrant
- [Pub] prepareClaim #
- [Pub] prepareClaims #
- [Pub] getAddressPeriodEndTotal
- [Pub] getPeriodRedemptionRates
- [Pub] getPoolAddresses
- [Pub] getClaimableAmount
- [Pub] getCurrentPeriod
- [Pub] isStakingPeriod
- [Prv] getNextStakingPeriod
- [Pub] decimals
- [Prv] mint #
+ StakeRewardDistribution (ReentrancyGuard)
- [Pub] #
- modifiers: ReentrancyGuard
- [Ext] claimRewards #
- modifiers: nonReentrant
- [Prv] collectSupportedTokenAddress #
- [Pub] prepareSupportedTokens #
- modifiers: nonReentrant
+ MAXIEscrow (ReentrancyGuard)
- [Pub] #
- modifiers: ReentrancyGuard
- [Pub] scheduleRebates #
- [Ext] releaseMAXI #
+ MysteryBox (ReentrancyGuard)
- [Pub] #
- modifiers: ReentrancyGuard
- [Pub] flushTEAM #
- modifiers: nonReentrant
- [Pub] flushMAXI #
- modifiers: nonReentrant
+ MAXIToken
- [Ext] approve #
- [Pub] transfer #
- [Pub] burn #
+ TEAMToken
- [Pub] getCurrentPeriod
- [Pub] getAddressPeriodEndTotal
- [Pub] getPeriodRedemptionRates
- [Pub] getPoolAddresses
- [Pub] getSupportedTokens
- [Pub] stakeDidClaim #
- [Pub] didStakeClaim
- [Pub] getClaimableAmount
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 1300+ 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 across 1500 projects!.
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.