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: 33/283
Findings: 2
Award: $179.80
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xDetermination
Also found by: 0x11singh99, 0xAnah, 0xRiO, JcFichtner, K42, MatricksDeCoder, McToady, PetarTolev, Raihan, SAQ, SM3_SS, SY_S, Timenov, ahmedaghadi, al88nsk, dharma09, donkicha, emrekocak, favelanky, hunter_w3b, judeabara, kiqo, lrivo, lsaudit, merlinboii, mikesans, offside0011, oualidpro, peter, rekxor, shamsulhaq123, unique, yashgoel72, yovchev_yoan, ziyou-
13.6293 USDC - $13.63
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L131-L139
File: src/AiArenaHelper.sol 131 function addAttributeProbabilities(uint256 generation, uint8[][] memory probabilities) public { 132 require(msg.sender == _ownerAddress); 133 require(probabilities.length == 6, "Invalid number of attribute arrays"); 134 135 uint256 attributesLength = attributes.length; 136 for (uint8 i = 0; i < attributesLength; i++) { 137 attributeProbabilities[generation][attributes[i]] = probabilities[i]; 138 } 139 }
Avoid reading from storage if we might revert early. The first check involves making a state read, _ownerAddress
The second check, the require statement, checks if a function parameter probabilities
is equal to 6
and reverts if it happens not. As it is, if probabilities
is not 6, we would consume gas in the require statement to make the state read(2100 Gas for the state read
) and then revert on the second check we can save some gas in the case of such a revert by moving the cheap check to the top and only reading state variable after validating the function parameter.
Also in this function
File: src/FighterFarm.sol 191 function claimFighters( 192 uint8[2] calldata numToMint, 193 bytes calldata signature, 194 string[] calldata modelHashes, 195 string[] calldata modelTypes 196 ) 197 external 198 { 199 bytes32 msgHash = bytes32(keccak256(abi.encode( 200 msg.sender, 201 numToMint[0], 202 numToMint[1], 203 nftsClaimed[msg.sender][0], 204 nftsClaimed[msg.sender][1] 205 ))); 206 require(Verification.verify(msgHash, signature, _delegatedAddress)); 207 uint16 totalToMint = uint16(numToMint[0] + numToMint[1]); 208 require(modelHashes.length == totalToMint && modelTypes.length == totalToMint); // @audit first check this one
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L191-L210
File: src/Neuron.sol 127 function setupAirdrop(address[] calldata recipients, uint256[] calldata amounts) external { 128 require(isAdmin[msg.sender]); 129 require(recipients.length == amounts.length); 130 uint256 recipientsLength = recipients.length; 131 for (uint32 i = 0; i < recipientsLength; i++) { 132 _approve(treasuryAddress, recipients[i], amounts[i]); 133 } 134 }
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L127-L134
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L201
File: src/Neuron.sol 201 uint256 decreasedAllowance = allowance(account, msg.sender) - amount;
This will not undferflow becasue of this check
File: src/Neuron.sol 197 require( 198 allowance(account, msg.sender) >= amount, 199 "ERC20: burn amount exceeds allowance" 200 );
File: src/Neuron.sol 28 bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); 31 bytes32 public constant SPENDER_ROLE = keccak256("SPENDER_ROLE"); 34 bytes32 public constant STAKER_ROLE = keccak256("STAKER_ROLE");
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L28-L34
File: src/AiArenaHelper.sol attributeProbabilityIndex = i + 1;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L181
missed from bot
File: src/GameItems.sol 231 emit Locked(_itemCount);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L231
When you have a mapping, accessing its values through accessor functions involves an additional layer of indirection, which can incur some gas cost. This is because accessing a value from a mapping typically involves two steps: first, locating the key in the mapping, and second, retrieving the corresponding value.
File: src/FighterFarm.sol 402 function tokenURI(uint256 tokenId) public view override(ERC721) returns (string memory) { 403 return _tokenURIs[tokenId]; 404 }
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L402-L404
File: src/RankedBattle.sol 379 function getNrnDistribution(uint256 roundId_) public view returns(uint256) { 380 return rankedNrnDistribution[roundId_];
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L379
File: src/StakeAtRisk.sol 132 function getStakeAtRisk(uint256 fighterId) external view returns(uint256) { 133 return stakeAtRisk[roundId][fighterId];
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/StakeAtRisk.sol#L132-L134
Nested mappings and multi-dimensional arrays in Solidity operate through a process of double hashing, wherein the original storage slot and the first key are concatenated and hashed, and then this hash is again concatenated with the second key and hashed. This process can be quite gas expensive due to the double-hashing operation and subsequent storage operation (sstore).
A possible optimization involves manually concatenating the keys followed by a single hash operation and an sstore. However, this technique introduces the risk of storage collision, especially when there are other nested hash maps in the contract that use the same key types. Because Solidity is unaware of the number and structure of nested hash maps in a contract, it follows a conservative approach in computing the storage slot to avoid possible collisions.
OpenZeppelin's EnumerableSet provides a potential solution to this problem. It creates a data structure that combines the benefits of set operations with the ability to enumerate stored elements, which is not natively available in Solidity. EnumerableSet handles the element uniqueness internally and can therefore provide a more gas-efficient and collision-resistant alternative to nested mappings or multi-dimensional arrays in certain scenarios.
File: src/AiArenaHelper.sol 30 mapping(uint256 => mapping(string => uint8[])) public attributeProbabilities;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L30
File: src/FighterFarm.sol 88 mapping(address => mapping(uint8 => uint8)) public nftsClaimed;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L88
File: src/GameItems.sol 74 mapping(address => mapping(uint256 => uint256)) public allowanceRemaining; 77 mapping(address => mapping(uint256 => uint256)) public dailyAllowanceReplenishTime;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L74-L78
File: src/RankedBattle.sol 113 mapping(address => mapping(uint256 => uint256)) public accumulatedPointsPerAddress; 116 mapping(uint256 => mapping(uint256 => uint256)) public accumulatedPointsPerFighter; 125 mapping(uint256 => mapping(uint256 => bool)) public hasUnstaked; 134 mapping(uint256 => mapping(uint256 => bool)) _calculatedStakingFactor;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L113
File: src/StakeAtRisk.sol 46 mapping(uint256 => mapping(uint256 => uint256)) public stakeAtRisk;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/StakeAtRisk.sol#L46
When fetching data from a storage location, assigning the data to a memory variable causes all fields of the struct/array to be read from storage, which incurs a Gcoldsload (2100 gas) for each field of the struct/array.
If the fields are read from the new memory variable, they incur an additional MLOAD rather than a cheap stack read.
Instead of declearing the variable with the memory keyword, declaring the variable with the storage keyword and caching any fields that need to be re-read in stack variables, will be much cheaper, only incuring the Gcoldsload for the fields actually read.
The only time it makes sense to read the whole struct/array into a memory variable, is if the full struct/array is being returned by the function, is being passed to a function that requires memory, or if the array/struct is being read from another memory array/struct
File: src/AiArenaHelper.sol 96 uint256[] memory finalAttributeProbabilityIndexes = new uint[](attributes.length); 174 uint8[] memory attrProbabilities = getAttributeProbabilities(generation, attribute);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L96
File: src/FighterFarm.sol 510 FighterOps.FighterPhysicalAttributes memory attrs = _aiArenaHelperInstance.createPhysicalAttributes(
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L510
File: src/MergingPool.sol 123 address[] memory currentWinnerAddresses = new address[](winnersLength); 206 uint256[] memory points = new uint256[](1);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L123
If data can fit into 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is cheaper in solidity.
File: src/GameItems.sol 48 /// @notice The name of this smart contract. 49 string public name = "AI Arena Game Items"; 51 /// @notice The symbol for this smart contract. 52 string public symbol = "AGI";
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L49-L53
When a function with a memory array is called externally, the abi.decode () step has to use a for-loop to copy each index of the calldata to the memory index. Each iteration of this for-loop costs at least 60 gas (i.e. 60 * <mem_array>.length). Using calldata directly, obliviates the need for such a loop in the contract code and runtime execution.
File: src/AiArenaHelper.sol 131 function addAttributeProbabilities(uint256 generation, uint8[][] memory probabilities) public { 157 function getAttributeProbabilities(uint256 generation, string memory attribute) 169 function dnaToIndex(uint256 generation, uint256 rarityRank, string memory attribute)
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L169
File: src/GameItems.sol 194 function setTokenURI(uint256 tokenId, string memory _tokenURI) public { 208 function createGameItem( 209 string memory name_, 210 string memory tokenURI, 291 function safeTransferFrom( 292 address from, 293 address to, 294 uint256 tokenId, 295 uint256 amount, 296 bytes memory data 297 )
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L194
File: src/FighterFarm.sol 48 address public treasuryAddress; 51 address _ownerAddress; 54 address _delegatedAddress; 57 address _mergingPoolAddress;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L48-L57
File: src/RankedBattle.sol 69 address _stakeAtRiskAddress; 72 address _ownerAddress; 75 address _gameServerAddress;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L69-L75
File: src/GameItems.sol 58 address public treasuryAddress; 61 address _ownerAddress;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L57-L62
File: src/Neuron.sol 46 address public treasuryAddress; 49 address _ownerAddress;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/Neuron.sol#L45-L49
File: src/StakeAtRisk.sol 30 address public treasuryAddress; 33 address _rankedBattleAddress;
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/StakeAtRisk.sol#L33
File: src/FighterFarm.sol 250 require(msg.sender == _mintpassInstance.ownerOf(mintpassIdsToBurn[i])); 251 _mintpassInstance.burn(mintpassIdsToBurn[i]); 373 require(_neuronInstance.balanceOf(msg.sender) >= rerollCost, "Not enough NRN for reroll"); 375 _neuronInstance.approveSpender(msg.sender, rerollCost); 376 bool success = _neuronInstance.transferFrom(msg.sender, treasuryAddress, rerollCost);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L250-L251
File: src/GameItems.sol 163 _neuronInstance.approveSpender(msg.sender, price); 164 bool success = _neuronInstance.transferFrom(msg.sender, treasuryAddress, price);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L163-L164
File: src/RankedBattle.sol 246 require(_fighterFarmInstance.ownerOf(tokenId) == msg.sender, "Caller does not own fighter"); 254 _fighterFarmInstance.updateFighterStaking(tokenId, true); 247 require(_neuronInstance.balanceOf(msg.sender) >= amount, "Stake amount exceeds balance"); 250 _neuronInstance.approveStaker(msg.sender, address(this), amount); 251 bool success = _neuronInstance.transferFrom(msg.sender, address(this), amount); 336 _voltageManagerInstance.ownerVoltageReplenishTime(fighterOwner) <= block.timestamp || 337 _voltageManagerInstance.ownerVoltage(fighterOwner) >= VOLTAGE_COST
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L246
File: src/VoltageManager.sol 95 require(_gameItemsContractInstance.balanceOf(msg.sender, 0) > 0); 96 _gameItemsContractInstance.burn(msg.sender, 0, 1);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/VoltageManager.sol#L95
ASSEMBLY can be used to shorten the array by changing the length slot, so that the entries don't have to be copied to a new, shorter array
File: src/AiArenaHelper.sol 96 uint256[] memory finalAttributeProbabilityIndexes = new uint[](attributes.length); 149 attributeProbabilities[generation][attributes[i]] = new uint8[](0);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/AiArenaHelper.sol#L96
File: src/MergingPool.sol 123 address[] memory currentWinnerAddresses = new address[](winnersLength); 206 uint256[] memory points = new uint256[](1);
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L123
File: src/RankedBattle.sol 253 if (amountStaked[tokenId] == 0) { 285 if (amountStaked[tokenId] == 0) { 440 if (battleResult == 0) { 444 if (stakeAtRisk == 0) { 506 if (battleResult == 0) { 530 if (stakingFactor_ == 0) {
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L253
File: src/MergingPool.sol 149 for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { 150 numRoundsClaimed[msg.sender] += 1; 151 winnersLength = winnerAddresses[currentRound].length; 152 for (uint32 j = 0; j < winnersLength; j++) { 153 if (msg.sender == winnerAddresses[currentRound][j]) { 154 _fighterFarmInstance.mintFromMergingPool( 155 msg.sender, 156 modelURIs[claimIndex], 157 modelTypes[claimIndex], 158 customAttributes[claimIndex] 159 ); 160 claimIndex += 1; 161 } 162 } 163 }
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/MergingPool.sol#L149-L163
File: src/RankedBattle.sol 299 for (uint32 currentRound = lowerBound; currentRound < roundId; currentRound++) { 300 nrnDistribution = getNrnDistribution(currentRound); 301 claimableNRN += ( 302 accumulatedPointsPerAddress[msg.sender][currentRound] * nrnDistribution 303 ) / totalAccumulatedPoints[currentRound]; 304 numRoundsClaimed[msg.sender] += 1; 305 }
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/RankedBattle.sol#L299-L305
#0 - raymondfam
2024-02-25T20:49:29Z
G1 - G4: bot 10 generic G
#1 - c4-pre-sort
2024-02-25T20:49:35Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2024-03-19T07:57:59Z
HickupHH3 marked the issue as grade-b
🌟 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
166.1676 USDC - $166.17
AI Arena is a PvP platform fighting game where the fighters are AIs that were trained by humans. In the web3 version of the game, these fighters are tokenized via the FighterFarm.sol smart contract.
Key Features and Concepts:
Mission and Goals:
Gameplay:
Unique Features:
Overall, AI Arena aims to foster AI literacy and intuition through a fun and competitive gaming experience, while also showcasing the potential of Web3 for talent monetization and AI democratization.
FighterFarm.sol: This contract manages the creation, ownership, and redemption of AI Arena Fighter NFTs, allowing for minting new NFTs from a merging pool or through the redemption of mint passes which represent AI-controlled fighters in the AI Arena game. with functionalities such as updating fighter attributes, and managing staking statuses.
Key Features:
Additional Functionality:
RankedBattle.sol: This contract manages staking NRN tokens on fighters, tracks battle records, calculates and distributes rewards based on battle outcomes and staked amounts, and enables claiming of accumulated rewards.
Key Features:
AiArenaHelper.sol: The AiArenaHelper
contract generates and manages AI Arena fighters' physical attributes based on DNA, generation, and whether they are dendroids or not.
Key Features:
DNA to attribute conversion: The contract provides a function to convert a fighter's DNA and rarity rank into an attribute probability index. This index is used to determine the fighter's physical attributes.
Custom icons: The contract supports custom icons for certain attributes, such as the head, eyes, and hands. This allows for more customization and variety in fighter appearances.
Dendroid support: The contract can generate physical attributes for dendroids, which are a special type of fighter in the AI Arena game. Dendroids have unique physical attributes that are different from regular fighters.
FighterOps.sol: The FighterOps library provides functions for creating and managing Fighter NFTs in the AI Arena game, including emitting events, extracting fighter attributes, and viewing fighter information.
GameItems.sol:This contract represents a collection of game items used in the AI Arena game, allowing users to purchase, burn, and view information about these items.
MergingPool.sol: The contract, MergingPool
, is designed for a game where players can earn new fighter NFTs by participating in a merging pool. It allows users to accumulate points by battling with their fighters in a separate ranked battle contract. At the end of each round, a specified number of winners are selected from the top-scoring fighters, and these winners can claim new fighter NFTs.
Neuron.sol: This contract, "Neuron," is an ERC20 token contract representing Neuron (NRN) tokens for AI Arena's in-game, featuring roles such as minters, spenders, and stakers, allowing for token minting, burning, and allowances setup, with an initial supply distribution to the treasury and contributors.
StakeAtRisk.sol: This contract manages the NRNs (Neuron tokens) that are at risk of being lost during a round of competition in a RankedBattle
.
VoltageManager.sol: Manages the voltage system for AI Arena, allowing users to replenish and spend voltage.
FighterFarm.sol:
Owner Role:
Staker Role:
Delegated Address: The address responsible for setting token URIs and signing fighter claim messages.
RankedBattle.sol:
Owner: The address that deploys the contract and has special privileges, including:
Admin: Addresses designated by the owner with admin access, capable of:
Game Server: The address responsible for updating battle records. This role is crucial for maintaining the integrity and accuracy of battle records.
Fighter Owner: Addresses that own fighters and can stake or unstake NRN tokens for battles. They have control over their own fighters and stakes.
Initiator: Refers to the fighter owner who initiates a battle and expends voltage for the match. They are responsible for spending the required voltage.
AiArenaHelper.sol:
Owner: The owner address has the following roles:
GameItems.sol:
Owner: The owner address has the following roles:
Admin: Admins have the following roles:
MergingPool.sol:
Owner: The address that has owner privileges and can transfer ownership, adjust admin access and configure parameters such as the number of winners per period..
Admins: Addresses that have been granted admin access by the owner.
Ranked Battle Contract: The address of the contract that manages ranked battles and awards merging pool points.
Neuron.sol:
Owner: The address that has owner privileges (initially the contract deployer).
Minter: Addresses that have been granted the minter role by the owner.
Spender: Addresses that have been granted the spender role by the owner.
Staker: Addresses that have been granted the staker role by the owner.
VoltageManager.sol:
AI Arena is built on the Ethereum blockchain currently deployed on Arbitrum.
FighterFarm.sol:
Maximum Number of Fighters per Address:
balanceOf(to) < MAX_FIGHTERS_ALLOWED
MAX_FIGHTERS_ALLOWED
).Ownership Verification for Token Transfer:
_isApprovedOrOwner(msg.sender, tokenId)
Fighter Staking Status Check:
!fighterStaked[tokenId]
Maximum Rerolls Allowed Check:
numRerolls[tokenId] < maxRerollsAllowed[fighterType]
Neuron Balance Verification for Reroll:
_neuronInstance.balanceOf(msg.sender) >= rerollCost
NRN
) to cover the cost of rerolling a fighter.Ownership Verification for Mint Pass Redemption:
msg.sender == _mintpassInstance.ownerOf(mintpassIdsToBurn[i])
Approval for Mint Pass Transfer:
_mintpassInstance.approveSpender(msg.sender, rerollCost)
RankedBattle.sol:
Staked Amount Check:
amountStaked[tokenId] >= 0
Total Staked Amount Calculation:
globalStakedAmount == Sum(amountStaked[tokenId])
Staking Factor Calculation:
stakingFactor[tokenId] == sqrt((amountStaked[tokenId] + stakeAtRisk) / 10**18) || stakingFactor[tokenId] == 1
Admin Access Control:
isAdmin[ownerAddress] == true
Battle Result Update:
fighterBattleRecord[tokenId].wins + fighterBattleRecord[tokenId].ties + fighterBattleRecord[tokenId].loses == totalBattles
Claimable NRN Calculation:
amountClaimed[claimer] == Sum(accumulatedPointsPerAddress[claimer][roundId] * rankedNrnDistribution[roundId] / totalAccumulatedPoints[roundId])
Ownership Verification:
msg.sender == _ownerAddress || isAdmin[msg.sender] == true
AiArenaHelper.sol:
Owner Privileges Verification:
msg.sender == _ownerAddress
Ownership Transfer Authorization:
msg.sender == _ownerAddress
(in the transferOwnership
function)Attribute Divisor Addition Authorization:
msg.sender == _ownerAddress
(in the addAttributeDivisor
function)Physical Attribute Generation:
createPhysicalAttributes
function should correctly calculate the final attribute probability indexes based on the input DNA, generation, icons type, and dendroid status.Attribute Probabilities Update:
msg.sender == _ownerAddress
(in the addAttributeProbabilities
and deleteAttributeProbabilities
functions)Attribute Probability Retrieval:
getAttributeProbabilities(generation, attribute)
should return the correct attribute probabilities for a given generation and attribute.DNA to Index Conversion:
dnaToIndex
function should correctly convert DNA and rarity rank into an attribute probability index for a given attribute and generation.dnaToIndex
function operates as intended, providing accurate attribute probability indexes.GameItems.sol:
Game Item Minting Authorization:
_itemCount
) && (_neuronInstance.balanceOf(msg.sender) >= price
) && ((allGameItemAttributes[tokenId].finiteSupply == false) || ((allGameItemAttributes[tokenId].finiteSupply == true) && (quantity <= allGameItemAttributes[tokenId].itemsRemaining))) && ((dailyAllowanceReplenishTime[msg.sender][tokenId] <= block.timestamp) || (quantity <= allowanceRemaining[msg.sender][tokenId]))Transferability Check for Safe Transfer Function:
allGameItemAttributes[tokenId].transferable
Daily Allowance Replenishment:
URI Retrieval Override:
MergingPool.sol:
Winners per Period Update Authorization:
isAdmin[msg.sender]
Winner Selection Authorization:
isAdmin[msg.sender]
Reward Claiming Limit Check:
Unclaimed Rewards Retrieval Authorization:
Total Points Calculation:
totalPoints
correctly reflects the sum of points added to all fighters.totalPoints
variable accurately tracks the total points added to all fighters in the merging pool.Fighter Points Retrieval:
getFighterPoints
function correctly retrieves the points for multiple fighters up to the specified maximum token ID.getFighterPoints
function returns an array of points corresponding to the fighters' token IDs within the specified range.Accordingly, I analyzed and audited the subject in the following steps;
Core Protocol Contract Overview:
I focused on thoroughly understanding the codebase and providing recommendations to improve its functionality. The main goal was to take a close look at the important contracts and how they work together in the AI Arena Protocol.
I start with the following contracts, which play crucial roles in the AI Arena protocol:
Main Contracts I Looked At
I start with the following contracts, which play crucial roles in the AI Arena protocol:
FighterFarm.sol RankedBattle.sol AiArenaHelper.sol FighterOps.sol GameItems.sol MergingPool.sol Neuron.sol StakeAtRisk.sol VoltageManager.sol
I started my analysis by examining the intricate structure and functionalities of the AI Arena
protocol, focusing on key contracts that play critical roles in the game ecosystem:- FighterFarm.sol
this contract oversees the creation, ownership, and redemption of AI Arena Fighter NFTs. It facilitates minting new NFTs from a merging pool or via mint passes, representing AI-controlled fighters. Additionally, it manages fighter attribute updates and staking statuses.RankedBattle.sol
Central to the competitive aspect of the game, this contract manages NRN token staking on fighters, battle record tracking, reward calculation, and distribution based on battle outcomes and staked amounts. It enables reward claiming and plays a crucial role in competitive gameplay.AiArenaHelper.sol
: Responsible for generating and managing the physical attributes of AI Arena fighters based on DNA, generation, and dendroid status. It ensures each fighter's unique visual appearance and characteristics.
FighterOps.sol
This library offers functions for creating and managing Fighter NFTs, including event emission, attribute extraction, and information viewing, serving as a helper contract for fighter-related operations.GameItems.sol
this contract represents a collection of in-game items, allowing users to purchase, burn, and view information about these items, enhancing the gameplay experience.MergingPool.sol
designed for players to earn new fighter NFTs by participating in a merging pool, allowing point accumulation through battles in a ranked battle contract. Winners claim new fighter NFTs at the round's end.Neuron.sol
as the ERC20 token contract for Neuron (NRN) tokens, the in-game currency, it facilitates token minting, burning, and allowance setup, featuring roles such as minters, spenders, and stakers.StakeAtRisk.sol
managing NRNs at risk of loss during a RankedBattle
round, it ensures player engagement by adding risk and reward elements to the competitive aspect.
Documentation Review:
Then went to Review This Docs for a more detailed and technical explanation of AI Arena protocol.
Compiling code and running provided tests:
Manuel Code Review In this phase, I initially conducted a line-by-line analysis, following that, I engaged in a comparison mode.
Line by Line Analysis: Pay close attention to the contract's intended functionality and compare it with its actual behavior on a line-by-line basis.
Comparison Mode: Compare the implementation of each function with established standards or existing implementations, focusing on the function names to identify any deviations.
Overall, I consider the quality of the AI Arena protocol codebase to be Good. The code appears to be mature and well-developed. We have noticed the implementation of various standards adhere to appropriately. Details are explained below:
Codebase Quality Categories | Comments |
---|---|
Code Maintainability and Reliability | The AI Arena Protocol contracts demonstrates good maintainability through modular structure, consistent naming, and efficient use of libraries. It also prioritizes reliability by handling errors, conducting security checks. However, for enhanced reliability, consider professional security audits and documentation improvements. Regular updates are crucial in the evolving space. |
Code Comments | During the audit of the AI Arena contracts codebase, I found that some areas lacked sufficient internal documentation to enable independent comprehension. While comments provided high-level context for certain constructs, lower-level logic flows and variables were not fully explained within the code itself. Following best practices like NatSpec could strengthen self-documentation. As an auditor without additional context, it was challenging to analyze code sections without external reference docs. To further understand implementation intent for those complex parts, referencing supplemental documentation was necessary. |
Documentation | The documentation of the AI Arena project is quite comprehensive and detailed, providing a solid overview of how AI Arena protocol is structured and how its various aspects function. However, we have noticed that there is room for additional details, such as diagrams , to gain a deeper understanding of how different contracts interact and the functions they implement. With considerable enthusiasm. We are confident that these diagrams will bring significant value to the protocol as they can be seamlessly integrated into the existing documentation, enriching it and providing a more comprehensive and detailed understanding for users, developers and auditors. |
Testing | The audit scope of the contracts to be audited is 90% this is Good but aim to 100%. |
Code Structure and Formatting | The codebase contracts are well-structured and formatted. but some order of functions does not follow the Solidity Style Guide According to the Solidity Style Guide, functions should be grouped according to their visibility and ordered: constructor, receive, fallback, external, public, internal, private. Within a grouping, place the view and pure functions last. |
Error | Use custom errors, custom errors are available from solidity version 0.8.4. Custom errors are more easily processed in try-catch blocks, and are easier to re-use and maintain. |
Imports | In AI Arena protocol the contract's interface should be imported first, followed by each of the interfaces it uses, followed by all other files. |
The analysis provided highlights several significant systemic and centralization risks present in the AI Arena protocol. These risks encompass concentration risk in ArbitrageSearch, Proposals.sol risk and more, third-party dependency risk, and centralization risks arising.
Here's an analysis of potential systemic and centralization risks in the contracts:
FighterFarm.sol
_ownerAddress
) for critical operations such as transferring ownership, adding stakers, and setting contract parameters. This centralization poses a risk if the owner account is compromised or the owner becomes unresponsive.RankedBattle.sol
AiArenaHelper.sol
GameItems.sol
Neuron
contract (_neuronInstance.balanceOf(msg.sender)
). If the price oracle mechanism in the Neuron
contract is compromised or inaccurate, it could lead to incorrect pricing of game items, affecting user transactions and potentially loss.MergingPool.sol:
pickWinner
function does not have a gas limit on iterating over the winners
array. If winners
array becomes excessively large, it could exceed the block gas limit, causing the function to fail, thereby denying legitimate users from claiming rewards.Neuron.sol:
VoltageManager.sol:
Potential Loss of User Funds: Users could lose their voltage if the useVoltageBattery
function is called mistakenly or maliciously without the intended game item balance check, causing a permanent reduction in their voltage without proper validation.
Unrestricted Access to Voltage Adjustment: The contract allows the owner to adjust admin access and modify the list of allowed voltage spenders without any restrictions or checks, potentially leading to abuse of power or unauthorized changes that could harm the system's integrity.
FighterFarm.sol
Single Point of Control: The _ownerAddress_
has excessive control over the contract, including the ability to: Transfer ownership,Add and remove stakers,Set contract parameters (e.g., fighter generation, reroll costs)
Potential Abuse: The owner could potentially abuse their privileges to manipulate the contract or benefit themselves at the expense of other participants.
Reduced Accountability: With a single owner, there is less accountability for decision-making and contract management.
RankedBattle.sol
Admin access: The owner address has the ability to grant and revoke admin access to other addresses. If the owner address is compromised or malicious, they could grant admin access to unauthorized individuals who could then manipulate the contract's functionality.
Game server address: The owner address can set the game server address, which is responsible for updating battle records. If the game server address is compromised or malicious, it could provide inaccurate battle records or manipulate results to favor certain players.
Contract instantiation: The owner address is responsible for instantiating the neuron contract, merging pool contract, and stake at risk contract. If the owner address fails to instantiate these contracts correctly or uses malicious contracts, it could lead to system failures or vulnerabilities.
AiArenaHelper.sol
Owner privileges: The owner address has the ability to transfer ownership, add attribute divisors, and add or delete attribute probabilities. This gives the owner a significant amount of control over the contract and the fighter attributes that are generated.
Single point of failure: If the owner address is compromised or lost, it could lead to the contract becoming unusable or controlled by a malicious actor.
GameItems.sol
Control over game items: The owner has the ability to modify game item attributes, including transferability and supply, which could impact the value and utility of these items for users.
Potential for abuse: The owner could use their privileges to grant themselves or their associates unfair advantages, such as creating new game items with exclusive benefits or adjusting the daily allowance for specific users.
Token URI Management: The contract allows only administrators to set token URIs (setTokenURI
). This centralization of control over token metadata could lead to censorship or manipulation of game item information, undermining the principles of decentralization and transparency.
MergingPool.sol:
Owner privileges: The contract owner has the ability to transfer ownership and adjust admin access. This gives the owner significant control over the contract and could lead to centralization of power.
Admin privileges: Admins have the ability to select winners, adjust admin access, and change the number of winners per period. This gives admins a high level of influence over the contract's operation and could lead to centralization of decision-making.
Neuron.sol:
Owner privileges: The contract owner has the ability to transfer ownership and adjust admin access. This gives the owner significant control over the contract and could lead to centralization of power.
Admin privileges: Admins have the ability to add and remove minters, stakers, and spenders, and adjust admin access. This gives admins a high level of influence over the contract's operation and could lead to centralization of decision-making.
StakeAtRisk.sol:
RankedBattle contract privileges: The RankedBattle contract has significant control over the StakeAtRisk contract, including the ability to set new rounds, reclaim NRNs, and update at-risk records. This centralization of power could lead to abuse or misuse of the contract.
Treasury address: The treasury address is hardcoded in the contract, which means that it cannot be changed without deploying a new contract. This centralization of control over the treasury could lead to funds being misappropriated or used for unauthorized purposes.
VoltageManager.sol:
Owner privileges: The owner has the ability to transfer ownership, adjust admin access, and adjust allowed voltage spenders. This centralization of power could lead to a single individual having too much control over the contract.
Admin privileges: Admins have the ability to adjust allowed voltage spenders, which could lead to a small group of individuals having too much control over who is allowed to spend voltage.
Hardcoded game items contract address: The address of the game items contract is hardcoded in the constructor, which means that it cannot be changed without deploying a new contract. This centralization of control over the game items contract could lead to problems if the game items contract is compromised or if the game items contract team makes changes that are not compatible with the voltage manager contract.
Properly managing these risks and implementing best practices in security and decentralization will contribute to the sustainability and long-term success of the AI Arena protocol.
AI-controlled fighters: AI Arena is unique in that it features AI-controlled fighters that battle each other in a competitive environment. This introduces an element of unpredictability and skill to the gameplay, as players must adapt their strategies to counter the AI's behavior.
Merging pool: The merging pool mechanic allows players to earn new fighter NFTs by participating in a merging pool and accumulating points through battles. This provides an additional way for players to acquire new fighters and enhance their collection.
Voltage system: The voltage system adds a layer of resource management to the gameplay. Players must carefully manage their voltage to ensure they have enough to replenish their fighters' energy and perform special abilities.
Fighter NFTs with Dynamic Attributes: One unique aspect is the creation and management of AI Arena Fighter NFTs with dynamic attributes such as physical appearance, element, weight, and machine learning model information. This adds depth to the gameplay by allowing for a wide variety of unique fighters with distinct characteristics.
Competitive Staking Mechanism: The RankedBattle contract introduces a competitive staking mechanism where players can stake NRN tokens on fighters to earn rewards based on battle outcomes and staked amounts. This incentivizes strategic gameplay and enhances the competitive aspect of the game.
Player-created content: Allowing players to create and share their own custom fighters and game items could foster a sense of community and ownership within the AI Arena ecosystem.
Cross-chain compatibility: Integrating with other blockchain platforms could expand the reach of AI Arena and attract a wider player base.
Tournaments and competitions: Hosting regular tournaments and competitions could add an extra layer of excitement and challenge to the gameplay, providing players with opportunities to showcase their skills and win rewards.
In general, The AI Arena protocol is a well-designed and innovative blockchain-based gaming platform that introduces several unique features to the NFT gaming space. The protocol's focus on AI-controlled fighters, merging pool mechanics, and voltage system provides a compelling and engaging gameplay experience.The AI Arena protocol exhibits strengths comprehensive testing, and detailed documentation, we believe the team has done a good job regarding the code.the AI Arena protocol has the potential to become a leading player in the NFT gaming industry. Its unique gameplay mechanics, coupled with its robust security measures, make it an attractive platform for both players and developers.It is highly recommended that the team continues to invest in security measures such as mitigation reviews, audits, and bug bounty programs to maintain the security and reliability of the project.
35 hours
#0 - c4-pre-sort
2024-02-25T20:31:26Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2024-03-18T04:39:17Z
HickupHH3 marked the issue as grade-a