We express our gratitude to the Common Wealth team for the collaborative engagement that enabled the execution of this Smart Contract Security Assessment.
Common Wealth is an investment protocol that allows users to invest their tokens and get rewards.
Document
Review Scope
The system users should acknowledge all the risks summed up in the risks section of the report
Functional requirements are provided.
Technical description is provided.
The code mostly follows best practices.
The development environment is configured.
Code coverage of the project is 98.14%.
Deployment and basic user interactions are covered with tests.
Common Wealth is an investment protocol that allows users to invest their tokens and get rewards by staking or handling NFTs with the following contracts:
UniswapWlthPriceOracle.sol - The contract is designed to provide token prices calculated based on the Oracle library for specific Uniswap pools.
UniswapSwapper.sol - The wrapper for swaps on Uniswap DEX.
WlthFund.sol - The contract that keeps hashed data of proposals driven by discourse and also executes transfers to investees from secondary sales wallet, triggered by CW team or backend along with proper allowance.
ProfitProvider.sol - The contract that handles the profit sent by the project.
OwnablePausable.sol - The contract that handles pause/unpause.
BuybackAndBurn.sol - The contract that automatically buys WLTH tokens from DEX using USDC then burns acquired WLTH.
StateMachine.sol - The contract that restricts unacceptable states.
PerpetualNFT.sol - The NFT contract that reflects perpetual fund investment.
PerpetualFund.sol - The contract that handles perpetual fund logic, raising funds and distributing fees and payouts for investors
WlthBonusStaking.sol - The contract that allows users to stake WLTH for extra WLTH reward. Already deployed and running on production
Marketplace.sol - The contract that handles listing (with edit and cancel option) and selling NFTs functionalities.
The owner of the UniswapWlthPriceOracle contract can set an observation time and pool addresses.
The owner of the UniswapSwapper contract can set the swap router contract address.
The owner of the ProfitProvider contract can set a minimum profit.
The owner of the BuybackAndBurn contract can set a minimum buyback.
The owner of the PerpetualNFT can add/remove minters, set a perpetual fund, set a metadata name, metadata description, metadata image, all metadata, metadata external URL, minimum value, and marketplace.
The owner of the PerpetualFund contract can set a staking wlth amount, a minimum investment amount, a profit provider, a buyback and burn address, and close funds.
The owner of the WlthBonusStaking contract can set staking schedules.
The owner of the Marketplace contract can add/remove the allowed contracts.
Additionally, the owner of any contracts can pause/unpause the contracts.
Centralized Control of Minting Process: The token contract’s design allows for centralized control over the minting process, posing a risk of unauthorized token issuance, potentially diluting the token value and undermining trust in the project's economic governance.
Owner's Unrestricted State Modification: The absence of restrictions on state variable modifications by the owner leads to arbitrary changes, affecting contract integrity and user trust, especially during critical operations like minting phases.
Lack of Monitoring on Key Functions: The smart contracts currently lack monitoring for its key functions. Without proper monitoring, it is challenging to detect and respond to unauthorized activities, irregular behaviors, or potential hacking attempts in real-time. This absence of surveillance increases the risk of undetected exploits or issues that could compromise the contract's security and integrity.
Code ― | Title | Status | Severity | |
---|---|---|---|---|
F-2024-6230 | Missing Active Status Check in _cancelListing Function Can Lead to Listing Count Inaccuracy and NFT Locking | Fixed | Critical | |
F-2025-8463 | Incorrect Profit Withdrawal Index Increment Allows Mult iple Withdrawals | Fixed | High | |
F-2024-6234 | Potential Price Manipulation in updateListingPrice Function Can Lead to Frontrunning and Overpayment | Fixed | Medium | |
F-2024-6240 | Missing Slippage Protection in the BuybackAndBurn Contract | Fixed | Medium | |
F-2024-6241 | Insufficient Reward Allocation in WlthBonusStaking Contract Leading to Stake Damages | Fixed | Low | |
F-2024-6229 | Risk of Ownership Control Loss in Owner-Dependent Contracts | Mitigated | Low | |
F-2025-8462 | Unbounded Array Growth Leading to DoS in PerpetualFunds | Fixed | Low | |
F-2024-6242 | Redundant listingId Field in Listing Struct Leads to Unnecessary Gas Usage | Fixed | Observation | |
F-2024-6239 | Redundant Pausable Inheritance | Mitigated | Observation | |
F-2024-6233 | Floating Pragma | Fixed | Observation |
When auditing smart contracts, Hacken is using a risk-based approach that considers Likelihood, Impact, Exploitability and Complexity metrics to evaluate findings and score severities.
Reference on how risk scoring is done is available through the repository in our Github organization:
Severity
Description
Severity
Description
Severity
Description
Severity
Description
The "Potential Risks" section identifies issues that are not direct security vulnerabilities but could still affect the project’s performance, reliability, or user trust. These risks arise from design choices, architectural decisions, or operational practices that, while not immediately exploitable, may lead to problems under certain conditions. Additionally, potential risks can impact the quality of the audit itself, as they may involve external factors or components beyond the scope of the audit, leading to incomplete assessments or oversight of key areas. This section aims to provide a broader perspective on factors that could affect the project's long-term security, functionality, and the comprehensiveness of the audit findings.
The scope of the project includes the following smart contracts from the provided repository:
Scope Details
contracts/UniswapWlthPriceOracle.sol
contracts/UniswapSwapper.sol
contracts/WlthFund.sol
contracts/ProfitProvider.sol
contracts/OwnablePausable.sol
contracts/BuybackAndBurn.sol
contracts/StateMachine.sol
contracts/PerpetualNFT.sol
contracts/PerpetualFund.sol
contracts/WlthBonusStaking.sol
contracts/Marketplace.sol
contracts/libraries/Constants.sol
contracts/libraries/LibFund.sol
contracts/libraries/Utils.sol
contracts/Wlth.sol
contracts/interfaces/IProfitProvider.sol
contracts/interfaces/IWlthBonusStaking.sol
contracts/interfaces/IUniswapWlthPrice.sol
contracts/interfaces/IInvestmentNFT.sol
During the audit of Common Wealth, Hacken followed its methodology by performing fuzz-testing on the project's main functions. Echidna →, a tool used for fuzz-testing, was employed to check how the protocol behaves under various inputs. Due to the complex and dynamic interactions within the protocol, unexpected edge cases might arise. Therefore, it was important to use fuzz-testing to ensure that several system invariants hold true in all situations.
Fuzz-testing allows the input of many random data points into the system, helping to identify issues that regular testing might miss. A specific Echidna fuzzing suite was prepared for this task, and throughout the assessment, 17 invariants were tested over 80,000 runs. This thorough testing ensured that the system works correctly even with unexpected or unusual inputs.
Invariant
Marketplace::listNFT
should always return Marketplace__ERC721AddressNotAllowed
if the newToken
is not added using addAllowedContract
. The listNFT
function should only allow tokens from contracts that are explicitly added to the allowed contract list.Test Result
Run Count
Invariant
Marketplace::listNFT
should always return Marketplace__NFTNotOwnedByMsgSender
if the tokenOwner and msg.sender are not the same. The listNFT
function should allow only the owner of the token to list it.Test Result
Run Count
Invariant
Marketplace::listNFT
should always return Marketplace__NFTNotApprovedForMarketplaceContract
if the token is not approved by approve
for the marketplace contract. The listNFT
function should only list tokens if they are approved for the marketplace contract.Test Result
Run Count
Invariant
Marketplace::listNFT
should always return Marketplace__NftAlreadyListed
if the token is already listed. A token cannot be listed again if it's already listed in the marketplace.Test Result
Run Count
Invariant
Marketplace::listNFT
should always return Marketplace__ZeroPrice
if the price of the listed token is zero. The listNFT
function should not allow a price of zero.Test Result
Run Count
Invariant
Marketplace::listNFT
should always handle listing of multiple tokens (tokensAmount
) for a valid tokenOwner
. The marketplace should handle different amounts of token listings (e.g., from 1 to 1000 tokens).Test Result
Run Count
Invariant
Marketplace::updateListingPrice
should always return Marketplace__ZeroPrice
if the updated price is zero. The updateListingPrice
function should revert if the new price is set to zero.Test Result
Run Count
Invariant
Marketplace::buyNFT
should always return Marketplace__ListingNotActive
if the token is not listed. The buyNFT
function should only succeed if the token is actively listed.Test Result
Run Count
Invariant
Marketplace::buyNFT
should always return Marketplace__NotEnoughWlthApproved
if the buyer’s allowance is less than the price of the listed token. The buyNFT
function should revert if the buyer has insufficient allowance.Test Result
Run Count
Invariant
Marketplace::buyNFT
The balanceOf
function should return allowance - price
after a successful purchase for the buyer’s address. After a successful purchase, the buyer's balance should decrease by the token price.Test Result
Run Count
Invariant
WlthBonusStaking::stake
should always subtract a 1% fee from the staked amount and record the remaining 99% of the tokens under the user’s staked balance. The test should assert that the staked balance for the staker
matches the expected amount.Test Result
Run Count
Invariant
WlthBonusStaking::claimReward
should calculate and apply the correct penalty based on the time passed, and the staker should receive the remaining reward after the penalty deduction.Test Result
Run Count
Invariant
WlthBonusStaking::claimReward
should always distribute the reward that remains after the penalty has been deducted. The staked amount should be set to zero after claiming, and the staker’s balance should reflect the correct reward after applying any applicable penalty.Test Result
Run Count
Invariant
WlthBonusStaking::unstake
should always deduct a 1% fee from the staked amount and transfer the remaining 99% to the staker. After unstaking, the staker’s balance should reflect the correct amount after the fee, and the contract should record zero staked tokens for the staker.Test Result
Run Count
Invariant
WlthBonusStaking::unstake
should ensure that the community fund receives both the initial 1% staking fee and the 1% unstaking fee.Test Result
Run Count
Invariant
PerpetualNFT::mint()
should only allow minters who have been registered via addMinter()
to mint new tokens.Test Result
Run Count
Invariant
PerpetualNFT::split()
function should revert if the sum of splitValues[]
does not equal the original token's value, ensuring value consistency during splits. If the split is successful, new tokens must be created with values exactly matching the specified splitValues[]
.Test Result
Run Count