Platform: Code4rena
Start Date: 09/02/2024
Pot Size: $60,500 USDC
Total HM: 17
Participants: 283
Period: 12 days
Judge:
Id: 328
League: ETH
Rank: 131/283
Findings: 1
Award: $20.07
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: 0xSmartContract
Also found by: 0xAsen, 0xDetermination, 0xStriker, 0xblack_bird, 0xbrett8571, 0xepley, 0xweb3boy, 14si2o_Flint, Bauchibred, DarkTower, JayShreeRAM, JcFichtner, K42, McToady, Myd, PoeAudits, Rolezn, SAQ, ZanyBonzy, aariiif, albahaca, ansa-zanjbeel, cheatc0d3, clara, cudo, dimulski, fouzantanveer, foxb868, hassanshakeel13, hunter_w3b, kaveyjoe, klau5, peanuts, pipidu83, popeye, rspadi, scokaf, shaflow2, tala7985, wahedtalash77, yongskiws
20.0744 USDC - $20.07
Number of Contracts: The AI Arena codebase comprises a total of nine contracts, each serving a specific purpose within the platform's ecosystem. These contracts collectively govern various aspects of the platform, including NFT management, game mechanics, token economics, and external interactions. The modular design of the contracts enables clear separation of concerns and promotes code organization and maintainability.
Lines of Code (SLoC): The codebase consists of a total of 1271 lines of code, indicating a moderate-sized project with a considerable degree of complexity. The extensive codebase reflects the comprehensive set of features and functionalities offered by the platform, catering to diverse user needs and requirements.
External Imports: Within the contracts, five external imports are utilized to incorporate essential libraries and dependencies. Notably, the contracts rely on widely-used libraries such as OpenZeppelin, which provide standardized implementations of fundamental smart contract functionalities. Leveraging external imports enhances code reusability, accelerates development timelines, and ensures adherence to industry best practices.
Interfaces and Structs: The contracts define four distinct struct types, which play a crucial role in organizing and managing data structures within the platform. Structs facilitate the encapsulation of related data fields, promoting code clarity, and readability. Additionally, the presence of interfaces enables seamless interaction between different components of the system, enhancing modularity and extensibility.
Composition vs. Inheritance: Throughout the codebase, a preference for composition over inheritance is evident. This design choice emphasizes the construction of complex objects by composing simpler components, leading to more modular and flexible code. By favoring composition, the codebase achieves greater code reuse, scalability, and maintainability, thereby facilitating easier modifications and updates.
External Calls: The contracts do not involve any external calls, indicating a self-contained architecture that minimizes dependencies on external systems. This design approach enhances security, reliability, and determinism by eliminating potential vulnerabilities associated with external interactions. Moreover, avoiding external calls simplifies the auditing process and ensures the predictability of smart contract execution.
Test Coverage: The test suite exhibits a commendable line coverage of 90%, reflecting rigorous testing efforts aimed at ensuring the correctness and robustness of the platform. High test coverage is critical for identifying and addressing bugs, vulnerabilities, and edge cases effectively. A comprehensive testing suite instills confidence in the reliability and functionality of the platform, contributing to overall system stability and user satisfaction.
Upgrade of Existing System: The audit does not involve an upgrade of an existing system; instead, the focus is on reviewing newly developed contracts. This suggests a proactive approach to platform development, aimed at introducing new features and enhancing existing functionalities to improve user experience and platform performance continually.
Features: The contracts encompass a wide range of features, including ERC-20 token functionality, NFT management, voltage management, and various game-related mechanics. These features collectively contribute to the richness and diversity of the AI Arena platform, offering users an engaging and immersive gaming experience. Each feature is meticulously designed and implemented to enhance user engagement, foster community growth, and drive platform adoption.
Context and Required Understanding: To gain a comprehensive understanding of certain aspects of the codebase, familiarity with the mint pass redemption mechanism and the ranked battle points system is essential. These components play pivotal roles in shaping user interactions and platform dynamics. A deep understanding of these mechanisms enables auditors to assess the fairness, efficiency, and security of the platform effectively.
Oracle Usage: The platform leverages a game server as an oracle to provide reliable and transparent battle results on-chain. This approach ensures fairness and integrity in determining battle outcomes, enhancing user trust and confidence in the platform. By integrating a trusted external data source, the platform mitigates the risk of manipulation or tampering, thereby preserving the integrity of the gaming experience.
Novel Logic: One notable aspect of the codebase is the innovative points system used for distributing ERC-20 tokens at the end of each round. This system incorporates factors such as ELO score, staking factor, and merging pool allocation percentage, representing a novel approach to reward distribution. This innovative design underscores a thoughtful and comprehensive approach to incentivizing desirable behaviors, promoting user engagement, and fostering a vibrant ecosystem within the AI Arena platform.
1. Inadequate Access Control Mechanisms:
Problem: The contracts lack granular access control mechanisms, which could lead to unauthorized users executing sensitive functions or modifying critical parameters.
Example: In FighterFarm.sol
, the transferOwnership
function allows the contract owner to transfer ownership to any address without additional verification. This could be exploited by a malicious actor to take control of the contract.
function transferOwnership(address newOwnerAddress) external { require(msg.sender == _ownerAddress); _ownerAddress = newOwnerAddress; }
Solution: Implement role-based access control (RBAC) using OpenZeppelin's AccessControl
contract. Define distinct roles such as SuperAdmin
and Moderator
, each with specific access levels to functions within the contract. Utilize modifiers to restrict access to critical functions based on the caller's role.
2. Owner Privileges Vulnerabilities:
Problem: Granting excessive power to the owner address poses a risk of abuse, as a compromised or malicious owner could perform unauthorized actions such as transferring ownership or modifying contract parameters.
Example: In VoltageManager.sol
, the transferOwnership
function allows the current owner to transfer ownership to any address without additional authorization. This could be exploited by a compromised owner to seize control of the contract.
function transferOwnership(address newOwnerAddress) external { require(msg.sender == _ownerAddress); _ownerAddress = newOwnerAddress; }
Solution: Implement multi-signature authorization for critical operations by requiring approval from multiple authorized administrators before executing owner-specific functions. Utilize a threshold signature scheme or a multi-signature wallet to ensure that sensitive actions require consensus from multiple authorized parties.
3. Admin Access Control Complexity:
Problem: The presence of multiple admin roles with varying levels of access introduces complexity to the access control model, increasing the risk of misconfigurations or oversight.
Example: In AiArenaHelper.sol
, the adjustAdminAccess
function allows the owner to modify admin access levels without clear delineation of roles and permissions. This could lead to confusion or inconsistencies in admin privileges.
function adjustAdminAccess(address adminAddress, bool access) external { require(msg.sender == _ownerAddress); isAdmin[adminAddress] = access; }
Solution: Define clear roles and responsibilities for each admin role, specifying granular permissions and restrictions. Implement event logging to track administrative actions and changes to contract state, providing transparency and accountability. Utilize access control lists (ACLs) to manage admin roles and permissions dynamically.
4. Lack of External Audits and Reviews:
Problem: Without regular external audits and security reviews, potential vulnerabilities and weaknesses in the access control mechanisms may remain undetected, posing risks to the security and integrity of the platform.
Example: The absence of external audits and security reviews leaves the contracts susceptible to undiscovered vulnerabilities and exploits, increasing the likelihood of admin abuse.
Solution: Engage reputable blockchain security firms to conduct comprehensive audits and security reviews of the smart contracts. Implement rigorous testing methodologies, including code analysis, penetration testing, and risk assessment, to identify and remediate any vulnerabilities or weaknesses in the access control mechanisms. Regularly review and update the contracts based on audit findings and recommendations to maintain a robust security posture.
Smart Contract Vulnerabilities:
reclaimNRN
function in the StakeAtRisk
contract is susceptible to reentrancy if external calls are made after state changes. Here's a snippet of the vulnerable code:
function reclaimNRN(uint256 nrnToReclaim, uint256 fighterId, address fighterOwner) external { require(msg.sender == _rankedBattleAddress, "Call must be from RankedBattle contract"); require( stakeAtRisk[roundId][fighterId] >= nrnToReclaim, "Fighter does not have enough stake at risk" ); bool success = _neuronInstance.transfer(_rankedBattleAddress, nrnToReclaim); if (success) { // Vulnerable code: external call made after state changes stakeAtRisk[roundId][fighterId] -= nrnToReclaim; totalStakeAtRisk[roundId] -= nrnToReclaim; amountLost[fighterOwner] -= nrnToReclaim; emit ReclaimedStake(fighterId, nrnToReclaim); } }
Dependency Risks:
GameItems
, Neuron
, and FixedPointMathLib
contracts, as well as contracts from the OpenZeppelin library. Here's an example snippet:
import { GameItems } from "./GameItems.sol"; import { Neuron } from "./Neuron.sol"; import { FixedPointMathLib } from "./FixedPointMathLib.sol"; // Example of dependency import from external library (OpenZeppelin) import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
Oracle Dependency Risks:
/// @notice Gets the battle result from the game server oracle function getBattleResult(uint256 battleId) external returns (BattleResult memory) { // Logic to fetch battle result from game server }
Smart Contract Vulnerabilities:
reclaimNRN
function in StakeAtRisk.sol
lacks proper input validation, making it vulnerable to reentrancy attacks.StakeAtRisk.sol
:
function reclaimNRN(uint256 nrnToReclaim, uint256 fighterId, address fighterOwner) external { require(msg.sender == _rankedBattleAddress, "Call must be from RankedBattle contract"); require( stakeAtRisk[roundId][fighterId] >= nrnToReclaim, "Fighter does not have enough stake at risk" ); bool success = _neuronInstance.transfer(_rankedBattleAddress, nrnToReclaim); if (success) { stakeAtRisk[roundId][fighterId] -= nrnToReclaim; totalStakeAtRisk[roundId] -= nrnToReclaim; amountLost[fighterOwner] -= nrnToReclaim; emit ReclaimedStake(fighterId, nrnToReclaim); } }
Scalability Challenges:
useVoltageBattery
function in VoltageManager.sol
consumes significant gas due to the execution of multiple operations within a single transaction, potentially leading to higher costs and slower transaction processing.Code Quality and Maintainability:
// Example: Custom staking functionality in the Neuron (NRN) token contract function stake(uint256 amount) external { require(amount > 0, "Cannot stake zero tokens"); // Perform stake operation and update stake-related data // Emit event to track staking activity emit Staked(msg.sender, amount); }
// Example: Implementing missing ERC-20 standard functions in the Neuron (NRN) token contract function approve(address spender, uint256 amount) external returns (bool) { // Implement approve function logic } function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) { // Implement transferFrom function logic } function balanceOf(address account) external view returns (uint256) { // Implement balanceOf function logic }
// Example: Simplified staking functionality in the Neuron (NRN) token contract function stake(uint256 amount) external { require(amount > 0, "Cannot stake zero tokens"); // Perform stake operation and update stake-related data // Emit event to track staking activity emit Staked(msg.sender, amount); }
// Example: Implementing upgradeable smart contract pattern using Proxy contract NeuronProxy { address public implementation; function upgradeTo(address newImplementation) external { // Perform upgrade to new implementation contract } }
// Example of inheritance to promote code reuse contract Ownable { address public owner; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Ownable: caller is not the owner"); _; } } contract MyContract is Ownable { uint256 public value; function updateValue(uint256 _newValue) public onlyOwner { value = _newValue; } }
// Example of custom error messages and input validation function buyTokens(uint256 _amount) external payable { require(_amount > 0, "Invalid amount"); require(msg.value == _amount * tokenPrice, "Incorrect payment amount"); // Proceed with token purchase }
// Example of lazy evaluation and batch processing function batchTransfer(address[] calldata _recipients, uint256[] calldata _amounts) external { require(_recipients.length == _amounts.length, "Invalid input lengths"); for (uint256 i = 0; i < _recipients.length; i++) { // Perform batch transfer logic } }
// Example of inline comments contract MyContract { uint256 public value; // Updates the value to a new value function updateValue(uint256 _newValue) public { // Ensure the new value is not zero require(_newValue != 0, "New value cannot be zero"); // Update the value value = _newValue; } }
// Example of access control mechanism contract MyContract { address public owner; constructor() { owner = msg.sender; } // Modifier to restrict access to the owner modifier onlyOwner() { require(msg.sender == owner, "Only the owner can call this function"); _; } // Function accessible only to the owner function withdraw() external onlyOwner { // Withdraw funds } }
contract AiArenaHelper { function generateFighterAttributes() external returns (uint8 strength, uint8 agility, uint8 intelligence) { // Logic for generating attributes return (strength, agility, intelligence); } }
contract FighterFarm { // Core functionalities such as minting, ownership transfer, and merging pool management }
contract RankedBattle { // Battle logic including staking, tracking records, and reward calculation }
RankedBattle.sol
, this contract manages the staking of NRN tokens at risk during battles. It allows users to reclaim their stake if they win a battle while having NRNs at risk. Here's a high-level overview of the stake management logic:contract StakeAtRisk { // Logic for managing stake at risk and reclaiming stakes }
contract VoltageManager { // Logic for voltage replenishment }
contract GameItems { // Core functionality for managing in-game items }
generateFighterAttributes
function to ensure it generates valid attributes within the specified range.// AiArenaHelperTest.sol contract AiArenaHelperTest { AiArenaHelper aiArenaHelper; constructor(AiArenaHelper _aiArenaHelper) { aiArenaHelper = _aiArenaHelper; } function testGenerateFighterAttributes() public { (uint8 strength, uint8 agility, uint8 intelligence) = aiArenaHelper.generateFighterAttributes(); assert(strength >= 0 && strength <= 100); assert(agility >= 0 && agility <= 100); assert(intelligence >= 0 && intelligence <= 100); } }
// FighterFarmTest.sol contract FighterFarmTest { FighterFarm fighterFarm; constructor(FighterFarm _fighterFarm) { fighterFarm = _fighterFarm; } function testMintNewFighter() public { // Perform minting operation // Assert ownership is correctly assigned } function testTransferOwnership() public { // Perform ownership transfer operation // Assert ownership transfer successful } function testMergingPool() public { // Perform merging pool operation // Assert correct distribution of new NFTs } }
// RankedBattleTest.sol contract RankedBattleTest { RankedBattle rankedBattle; constructor(RankedBattle _rankedBattle) { rankedBattle = _rankedBattle; } function testStaking() public { // Perform staking operation // Assert correct staking amount and state } function testBattleOutcome() public { // Perform battle and calculate outcome // Assert correct reward distribution } }
// StakeAtRiskTest.sol contract StakeAtRiskTest { StakeAtRisk stakeAtRisk; constructor(StakeAtRisk _stakeAtRisk) { stakeAtRisk = _stakeAtRisk; } function testStakePlacement() public { // Perform stake placement operation // Assert correct stake amount and state } function testReclaimStake() public { // Perform stake reclaim operation // Assert correct stake reclaim amount } }
// VoltageManagerTest.sol contract VoltageManagerTest { VoltageManager voltageManager; constructor(VoltageManager _voltageManager) { voltageManager = _voltageManager; } function testVoltageReplenishment() public { // Perform voltage replenishment operation // Assert correct voltage level after replenishment } }
// GameItemsTest.sol contract GameItemsTest { GameItems gameItems; constructor(GameItems _gameItems) { gameItems = _gameItems; } function testItemCreation() public { // Perform item creation operation // Assert correct creation and ownership } function testItemTransfer() public { // Perform item transfer operation // Assert correct ownership transfer } }
In the setThresholds
function, there is a lack of proper authorization checks to ensure that only authorized parties can update the threshold values. The function allows the owner or an approved sender to modify the threshold values without adequate validation of their permissions. Below is the code snippet of the setThresholds
function:
function setThresholds( address[] calldata newErc20s, uint256[] calldata newAmounts ) public virtual onlyOwnerOrApproved(AccountId) { // Implementation logic }
Without proper access control mechanisms, malicious actors could exploit this vulnerability to manipulate threshold values, leading to unauthorized access to funds or accumulation of excessive token balances. For instance, an attacker could increase the threshold amount for a specific ERC20 token to divert funds or accumulate an unreasonable amount of tokens.
To mitigate this risk, the project should implement robust access control mechanisms, such as role-based permissions or multi-signature authorization, to restrict access to the setThresholds
function to authorized parties only. Below is an example of how role-based permissions can be implemented:
mapping(address => bool) public admins; modifier onlyAdmin() { require(admins[msg.sender], "Caller is not an admin"); _; } function setThresholds( address[] calldata newErc20s, uint256[] calldata newAmounts ) public virtual onlyAdmin { // Implementation logic }
12 hours
#0 - c4-pre-sort
2024-02-25T20:22:10Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2024-03-19T08:24:49Z
HickupHH3 marked the issue as grade-b