Hocus Pocus Aether
Smart Contract Audit Report
Hocus Pocus is building a new platform where users can stake tokens to receive Cauldron NFTs, as well as purchase Booster NFTs.
For this audit, we reviewed the project team's Aether, Booster, Cauldron, and Dime contracts at 0xd22E78C22D7E77229d60cc9fC57b0E294F54488E on the PulseChain Mainnet.
All findings have been resolved.
Date: June 9th, 2023.
Updated: June 14th, 2023 to reflect updates made to the contracts by the project team from commit 25166ac534dc4070de239ef263bbb035 to commit b8b54c2d40959f0f08fada0a6000695d488d33b3.
Date: July 20th, 2023 to reflect the project's Mainnet address.
Finding #1 - Booster - Medium (Resolved)Description: The summon() function recalculates the price per NFT using the getAmountsOut WPLS return value of the "reference" token which can be manipulated through the use of a flash loan.
Risk/Impact: Malicious users can leverage a flash loan to manipulate the reference token and WPLS reserves and subsequently mint NFTs at a lower cost. Additionally, users can leverage a flash loan to manipulate the reserves and increase the price for the next subsequent buyer.
IUniswapV2Router02 dexRouter = IUniswapV2Router02(ROUTER_ADDRESS); address memory path = new address(2); path = BOOSTER_REFERENCE_TOKEN_ADDRESS; path = dexRouter.WPLS(); uint256 memory amounts = dexRouter.getAmountsOut( BOOSTER_REFERENCE_PRICE, path ); price = amounts; }
Recommendation: The team should implement a Time Weighted Average Price Oracle (TWAP) or use Chainlink for pricing to mitigate the potential price manipulation. Additionally, the team could use a fixed price that can be updated by an authorized address.
Resolution: The team has implemented a custom weighted moving average price oracle.
Finding #2 - Aether - Medium (Resolved)Description: The buyAndBurn() function is susceptible to front-running attacks.
Risk/Impact: Malicious users can perform a front-running attack by purchasing Dime tokens prior to a buyAndBurn() transaction, intentionally causing the token price to increase. Once the buyAndBurn() transaction takes place at the inflated price, the attacker can sell their tokens, profiting from the artificially inflated value.
Recommendation: The team should include a limit to the amount of PLS that can be swapped per transaction to mitigate the front-running impact.
Resolution: The team has implemented a 100 million PLS limit that can be swapped per transaction.
Finding #3 - Booster - Low (Resolved)Description: Although the team intends to use the mintSpells() function to initially mint NFTs to users, any user may call this function and mint NFTs.
Risk/Impact: Any user can mint any number of NFTs to any address incurring no cost. Additionally, users can prematurely declare the minting process as finished.
Recommendation: The team should modify the mintSpells() function to restrict its access to only be called by an authorized user.
Resolution: All Spells have been successfully minted by the team.
Finding #4 - Cauldron - Informational (Resolved)Description: In the getMinimumPeriod() function, the first condition in the following require statement contains a tautology as it will always be true:
Recommendation: The above require statement could be modified as follows for additional gas savings on each call:
require( boosterBonus >= 0 && boosterBonus <= 100, "Bonus must be in range from 0 to 100" );
Resolution: The team has implemented the above recommendation.
require( boosterBonus <= 100, "Bonus must be in range from 0 to 100" );
- As the contracts are implemented with Solidity v0.8.0, they are safe from any possible overflows/underflows.
- Any user can use this contract to stake Dime tokens to earn rewards.
- A new Dime contract, Booster contract, and Cauldron contract are created upon deployment.
- Users can specify an amount of Dime tokens to claim during the claim period which lasts for three months after the contract is deployed.
- The contract uses an off-chain generated Merkle tree provided by the owner to store addresses that are verified to claim and their allocated token amounts.
- The number of Dime tokens allocated to the user is transferred from the contract to their wallet address. The team must ensure the contract has a sufficient Dime token balance to support claims.
- Once the three-month claim period has ended, any user can initiate a burn of the remaining Dime tokens in the contract.
- Any user can initiate a "brewing" process by specifying an amount of Dime tokens that will be burned and a brewing period up to 3,690 days.
- Users may elect to provide any of their owned Booster NFT IDs. If a valid Booster NFT ID is provided, its booster bonus value will be used during the brewing process to increase rewards for the user.
- The specified number of period days must exceed the minimum period value based on the booster bonus. If the transaction's booster bonus is zero, the minimum number of period days is 30.
- A Cauldron NFT is minted to the user representing the user's stake in the brewing process.
- If a Booster NFT is provided, the Booster NFT is locked for the duration of the brewing process.
- Users may elect to provide the maximum uint256 value as the Booster token ID to initiate a brewing process without using a valid Booster NFT.
- Any user can finish a brewing process by specifying any of their owned Cauldron NFT IDs.
- A number of Dime tokens are calculated and minted to the caller based on how much time has passed since the brewing process started and any additional booster bonuses. A "long bonus" is applied if the full brewing duration has been completed.
- The Brewing process is declared finished and the user's Cauldron NFT is burned.
- Users should exercise caution when finishing the brewing process, as doing so too early may result in the loss of a percentage of their original stake.
- Any user can initiate a "Buy and Burn" transaction which will transfer the Booster contract's full PLS balance to this contract.
- This contract must hold at least 1 PLS in order for the transaction to successfully occur.
- 3.69% of the contract's PLS balance is transferred to the caller as a reward.
- 25% of the contract's remaining PLS balance is swapped for Dime tokens and is burned from this contract.
- The contract's remaining PLS balance is transferred to the team's Treasury wallet.
- The Aether contract can mint an NFT by specifying a list of NFT attributes, a sender address, and a booster bonus value.
- The NFT is minted to the sender and the attributes are stored on behalf of the token ID.
- The Aether contract can specify an NFT to burn at any time. The stored attributes are removed on behalf of the token during the process.
- The Aether contract can use this contract to finish the brewing process at any time. The brewing process's rewards are calculated based on the stake duration and any additional bonuses.
- The total supply of the token is set to 555.5 billion $HOC [555,500,000,000] and is minted to the deployer.
- A percentage of the tokens specified by the deployer are transferred to the pool address.
- Any user can burn their own tokens to reduce the total supply.
- Any user can burn tokens on another user's behalf if an allowance has been granted.
- The Aether contract can use this contract to mint tokens to a specified address.
- The Aether contract can use this contract to burn tokens from a specified address.
- The contract complies with the ERC-20 token standard.
- Any user can specify a number of Spell NFTs to mint which include an owner address and a bonus value.
- The specified NFTs are minted to the owner address and the bonus values are stored on their behalf.
- If a user specifies that minting is "finished" during this process, this functionality can no longer be performed.
- Once the minting of Spell NFTs has been declared finished, any user can specify a number of Booster NFTs to mint along with a gender value.
- Each Booster NFT is initially assigned a 10% bonus.
- The user must supply a sufficient amount of PLS to cover the current cost of the transaction. Any excess PLS supplied by the user is returned to their wallet address.
- The price per NFT is calculated based on the current average market value of the "reference" token set by the team.
- Any user can specify two of their owned NFTs with the same gender to forge together at any time.
- The NFTs must currently be unlocked and must have been fully brewed in a previous stake.
- The Booster bonus value of the first NFT is increased by the booster bonus value of the second NFT. If the total bonus exceeds 100, the excess value is assigned to a second Booster NFT.
- Both original Booster NFTs are burned, and a new one with the merged attributes is minted and assigned to the recipient.
- If an excess bonus was prevalent, a second new Booster NFT is minted using that bonus and the brewing statistics reset to zero.
- Users can update the name associated with any of their owned NFTs at any time.
- The Aether contract can use this contract to lock and unlock NFTs for the brewing process. Tokens that are currently being used for brewing may not be transferred.
- The Aether contract can withdraw all of the PLS from this contract at any time.
|Arbitrary Jump/Storage Write||N/A||PASS|
|Centralization of Control||N/A||PASS|
|Delegate Call to Untrusted Contract||N/A||PASS|
|Dependence on Predictable Variables||N/A||PASS|
|Improper Authorization Scheme||N/A||PASS|
|Outdated Compiler Version||N/A||PASS|
|Overall Contract Safety||PASS|
($) = 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] _update # - [Int] _mint # - [Int] _burn # - [Int] _approve # - [Int] _spendAllowance # + ERC20Burnable (Context, ERC20) - [Pub] burn # - [Pub] burnFrom # + [Int] IERC165 - [Ext] supportsInterface + [Int] IERC721 (IERC165) - [Ext] balanceOf - [Ext] ownerOf - [Ext] safeTransferFrom # - [Ext] safeTransferFrom # - [Ext] transferFrom # - [Ext] approve # - [Ext] setApprovalForAll # - [Ext] getApproved - [Ext] isApprovedForAll + [Int] IERC721Receiver - [Ext] onERC721Received # + [Int] IERC721Metadata (IERC721) - [Ext] name - [Ext] symbol - [Ext] tokenURI + [Lib] Math - [Int] tryAdd - [Int] trySub - [Int] tryMul - [Int] tryDiv - [Int] tryMod - [Int] max - [Int] min - [Int] average - [Int] ceilDiv - [Int] mulDiv - [Int] mulDiv - [Int] sqrt - [Int] sqrt - [Int] log2 - [Int] log2 - [Int] log10 - [Int] log10 - [Int] log256 - [Int] log256 + [Lib] SignedMath - [Int] max - [Int] min - [Int] average - [Int] abs + [Lib] Strings - [Int] toString - [Int] toString - [Int] toHexString - [Int] toHexString - [Int] toHexString - [Int] equal + ERC165 (IERC165) - [Pub] supportsInterface + ERC721 (Context, ERC165, IERC721, IERC721Metadata) - [Pub] # - [Pub] supportsInterface - [Pub] balanceOf - [Pub] ownerOf - [Pub] name - [Pub] symbol - [Pub] tokenURI - [Int] _baseURI - [Pub] approve # - [Pub] getApproved - [Pub] setApprovalForAll # - [Pub] isApprovedForAll - [Pub] transferFrom # - [Pub] safeTransferFrom # - [Pub] safeTransferFrom # - [Int] _safeTransfer # - [Int] _ownerOf - [Int] _exists - [Int] _isApprovedOrOwner - [Int] _safeMint # - [Int] _safeMint # - [Int] _mint # - [Int] _burn # - [Int] _transfer # - [Int] _approve # - [Int] _setApprovalForAll # - [Int] _requireMinted - [Prv] _checkOnERC721Received # - [Int] _beforeTokenTransfer # - [Int] _afterTokenTransfer # - [Int] __unsafe_increaseBalance # + [Int] IERC721Enumerable (IERC721) - [Ext] totalSupply - [Ext] tokenOfOwnerByIndex - [Ext] tokenByIndex + ERC721Enumerable (ERC721, IERC721Enumerable) - [Pub] supportsInterface - [Pub] tokenOfOwnerByIndex - [Pub] totalSupply - [Pub] tokenByIndex - [Int] _beforeTokenTransfer # - [Prv] _addTokenToOwnerEnumeration # - [Prv] _addTokenToAllTokensEnumeration # - [Prv] _removeTokenFromOwnerEnumeration # - [Prv] _removeTokenFromAllTokensEnumeration # + ERC721Burnable (Context, ERC721) - [Pub] burn # + [Lib] MerkleProof - [Int] verify - [Int] verifyCalldata - [Int] processProof - [Int] processProofCalldata - [Int] multiProofVerify - [Int] multiProofVerifyCalldata - [Int] processMultiProof - [Int] processMultiProofCalldata - [Prv] _hashPair - [Prv] _efficientHash + [Int] IUniswapV2Factory - [Ext] createPair # + [Int] IUniswapV2Router02 - [Ext] factory - [Ext] WPLS - [Ext] swapExactETHForTokens ($) - [Ext] addLiquidityETH ($) - [Ext] getAmountsOut + Constants + BaseContract - [Pub] # + NFTBaseContract (ERC721, ERC721Burnable, BaseContract, Constants) - [Pub] # - modifiers: ERC721 - [Int] _mintNew # - [Int] _burn # - [Pub] getTokenIdsForOwner - [Int] _beforeTokenTransfer # + Booster (NFTBaseContract) - [Pub] # - modifiers: NFTBaseContract,BaseContract - [Int] _baseURI - [Pub] getSummonCost - [Prv] _mintNewWithBonus # - [Pub] summon ($) - [Int] _burn # - [Pub] forge # - [Pub] setName # - [Pub] getBoosterBonus - [Pub] _lockForBrewing # - modifiers: magicOnlyComesFromAether - [Pub] _unlockForBrewing # - modifiers: magicOnlyComesFromAether - [Int] _beforeTokenTransfer # - [Pub] mintSpells # - [Pub] _withdraw # - modifiers: magicOnlyComesFromAether + Dime (ERC20, ERC20Burnable, BaseContract, Constants) - [Pub] # - modifiers: ERC20,BaseContract - [Pub] _ignite # - modifiers: magicOnlyComesFromAether - [Pub] _conjure # - modifiers: magicOnlyComesFromAether + Cauldron (NFTBaseContract) - [Pub] # - modifiers: NFTBaseContract,BaseContract - [Int] _baseURI - [Pub] _conjure # - modifiers: magicOnlyComesFromAether - [Pub] _ignite # - modifiers: magicOnlyComesFromAether - [Prv] _getProgress - [Pub] getProgress - [Pub] getMinimumPeriod - [Pub] _finish - modifiers: magicOnlyComesFromAether + Aether (IERC721Receiver, Constants) - [Ext] ($) - [Pub] # - [Pub] wizClaimDimes # - [Prv] _wizClaimDimes # - [Pub] burnUnclaimedDimes # - [Pub] startBrewing # - [Pub] finishBrewing # - [Pub] buyAndBurn # - [Ext] onERC721Received
SourceHat (formerly Solidity Finance - founded in 2020) has quickly grown to have one of the most experienced and well-equipped smart contract auditing teams in the industry. Our team has conducted 1700+ 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.