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: 173/283
Findings: 1
Award: $8.81
π Selected for report: 0
π Solo Findings: 0
π Selected for report: givn
Also found by: 0x11singh99, 0xAkira, 0xBinChook, 0xDetermination, 0xMosh, 0xStriker, 0xmystery, 14si2o_Flint, 7ashraf, Aamir, AlexCzm, BARW, Bauchibred, BenasVol, BigVeezus, Blank_Space, Bube, DarkTower, DeFiHackLabs, EagleSecurity, KmanOfficial, Krace, McToady, MrPotatoMagic, PetarTolev, Rolezn, SHA_256, SpicyMeatball, Tekken, Timenov, ZanyBonzy, agadzhalov, alexzoid, boredpukar, btk, cartlex_, dimulski, forkforkdog, haxatron, immeas, jesjupyter, juancito, kartik_giri_47538, klau5, lsaudit, merlinboii, nuthan2x, offside0011, oualidpro, peter, radev_sw, rekxor, rspadi, shaflow2, shaka, swizz, vnavascues, yotov721, yovchev_yoan
8.8123 USDC - $8.81
Number | Severity | Issue Title |
---|---|---|
[1] | Low | Missing two-step procedure for ownership transfer |
[2] | Low | Missing check for token existence in RankedBattle::getBattleRecord function |
[3] | Low | Missing check for token existence in RankedBattle::getNrnDistribution function |
[4] | Low | Incorrect require statement in ``Neuron::mint` function. |
[5] | Low | Some important events are missing in Neuron contract |
[6] | Low | Unsafe transferFrom in Neuron contract |
[7] | Low | The attributeProbabilities mapping is initialized twice in the AiArenaHelper contract |
Missing two-step procedure for ownership transfer
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/AiArenaHelper.sol#L61-L64 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L120-L123 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/GameItems.sol#L108-L111 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/MergingPool.sol#L89-L92 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/Neuron.sol#L85-L88 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/RankedBattle.sol#L167-L170 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/VoltageManager.sol#L64-L67
The transferOwnership
function in smart contracts AiArenaHelper
, FighterFarm
, GameItems
, MergingPool
, Neuron
, RankedBattle
, VoltageManager
doesn't check if the newOwnerAddress
is a non-zero address. If the newOwnerAddress
is a zero address or invalid (wrong) address, the owner
functionality will be lost forever.
The owner
is the authorized user in the solidity contracts: AiArenaHelper
, FighterFarm
, GameItems
, MergingPool
, Neuron
, RankedBattle
, VoltageManager
. An owner
can be updated with transferOwnership
function in these contracts. However, the process is only completed with single transaction. If the address is updated incorrectly, the owner
functionality will be lost forever.
The function transferOwnership
has the same implementation in the following smart contracts: AiArenaHelper
, FighterFarm
, GameItems
, MergingPool
, Neuron
, RankedBattle
, VoltageManager
.
function transferOwnership(address newOwnerAddress) external { require(msg.sender == _ownerAddress); _ownerAddress = newOwnerAddress; }
Add a two-step procedure for the transferOwnership
functionality. Also, it is a good practice to emit an event when important functions like transferOwnership
are executed. Therefore, add an event for every successfull transfer of the ownership.
Missing check for token existence in RankedBattle::getBattleRecord
function
The function RankedBattle::getBattleRecord
gets the battle record for a token and returns record which is comprised of wins, ties, and losses for the tokenId
. But if the tokenId
is invalid, the function will return the default values: 0,0,0
. That leads to a confusion, the user will not understand that the tokenId
is wrong, he/she will thing that these are the battle records for the tokenId
.
function getBattleRecord(uint256 tokenId) external view returns(uint32, uint32, uint32) { return ( fighterBattleRecord[tokenId].wins, fighterBattleRecord[tokenId].ties, fighterBattleRecord[tokenId].loses ); }
Add a check for existing/valid tokenId
in the RankedBattle::getBattleRecord
function.
Missing check for token existence in RankedBattle::getNrnDistribution
function
The function RankedBattle::getNrnDistribution
retrieves the nrn distribution amount for the given roundId
. The function is public
and anyone can call it. If a user call the function with invalid roundId_
, the function will return 0
, because Solidity returns the default value from the type of mapping when the searched item is not existing. So the caller will not understand that the roundId
is wrong.
function getNrnDistribution(uint256 roundId_) public view returns(uint256) { return rankedNrnDistribution[roundId_]; }
Add a check for existing/valid roundId
in the RankedBattle::getNrnDistribution
function.
Incorrect require
statement in ``Neuron::mint` function.
The Neuron::mint
function mints the specified amount of tokens to the given address. The function requires the sum of the totalSupply
and the amount
to not be more than the MAX_SUPPLY
. But in the requirement
statement is used only the sign for lower than <
. That means if the totalSupply() + amount
is equal to MAX_SUPPLY
the function will revert and the minter will lose the possibility to mint one more token.
function mint(address to, uint256 amount) public virtual { @> require(totalSupply() + amount < MAX_SUPPLY, "Trying to mint more than the max supply"); require(hasRole(MINTER_ROLE, msg.sender), "ERC20: must have minter role to mint"); _mint(to, amount); }
But in the error message is said: "Trying to mint more than the max supply"
which means that if the totalSupply() + amount
is equal to MAX_SUPPLY
the function should continue with the minting process.
Add equals sign to the require
statement:
function mint(address to, uint256 amount) public virtual { + require(totalSupply() + amount <= MAX_SUPPLY, "Trying to mint more than the max supply"); - require(totalSupply() + amount < MAX_SUPPLY, "Trying to mint more than the max supply"); require(hasRole(MINTER_ROLE, msg.sender), "ERC20: must have minter role to mint"); _mint(to, amount); }
Some important events are missing in Neuron
contract
The Neuron
contract includes functions addMinter
, addStaker
, and addSpender
to assign respective roles. However, no events are emitted when these functions are successfully called.
function addMinter(address newMinterAddress) external { require(msg.sender == _ownerAddress); //only Owner _setupRole(MINTER_ROLE, newMinterAddress); //add a minter role to a new address } function addStaker(address newStakerAddress) external { require(msg.sender == _ownerAddress); _setupRole(STAKER_ROLE, newStakerAddress); // add a staker role } function addSpender(address newSpenderAddress) external { require(msg.sender == _ownerAddress); _setupRole(SPENDER_ROLE, newSpenderAddress); // add a spender role }
Emit an event in the functions addMinter
, addStaker
, and addSpender
when the new role is successfully set.
Unsafe transferFrom
in Neuron
contract
Unsafe ERC20::transferFrom
function is used in Neuron::claim
function. The ERC20::transferFrom
reverts by failure. There is no check if the transfer is successfully executed.
function claim(uint256 amount) external { require( allowance(treasuryAddress, msg.sender) >= amount, "ERC20: claim amount exceeds allowance" ); @> transferFrom(treasuryAddress, msg.sender, amount); emit TokensClaimed(msg.sender, amount); }
Check if the transferFrom
is successfully executed or use safeTransferFrom
function instead.
The attributeProbabilities
mapping is initialized twice in the AiArenaHelper
contract
The attributeProbabilities
mapping is updated twice in the constructor of the AiArenaHelper
contract. The constructor takes an array of probabilities as an argument and calls the addAttributeProbabilities
function to initialize the probabilities for generation 0
. After calling addAttributeProbabilities
, the constructor then enters a loop where it directly sets the attributeProbabilities
for generation 0
again, along with setting the attributeToDnaDivisor
for each attribute.
constructor(uint8[][] memory probabilities) { _ownerAddress = msg.sender; // Initialize the probabilities for each attribute @> addAttributeProbabilities(0, probabilities); uint256 attributesLength = attributes.length; for (uint8 i = 0; i < attributesLength; i++) { @> attributeProbabilities[0][attributes[i]] = probabilities[i]; attributeToDnaDivisor[attributes[i]] = defaultAttributeDivisor[i]; } }
This redundancy is unnecessary and could lead to wasted gas during contract deployment. The direct assignment in the loop is not needed since the addAttributeProbabilities
function already performs the same action. It would be more efficient to remove the direct assignment or the function call to addAttributeProbabilities
from the constructor to avoid duplicating the state changes.
Remove the function call addAttributeProbabilities
from the constructor to avoid duplicating the state changes.
constructor(uint8[][] memory probabilities) { _ownerAddress = msg.sender; // Initialize the probabilities for each attribute - addAttributeProbabilities(0, probabilities); uint256 attributesLength = attributes.length; for (uint8 i = 0; i < attributesLength; i++) { attributeProbabilities[0][attributes[i]] = probabilities[i]; attributeToDnaDivisor[attributes[i]] = defaultAttributeDivisor[i]; } }
#0 - raymondfam
2024-02-26T05:34:35Z
Adequate amount of L and NC entailed.
#1 - c4-pre-sort
2024-02-26T05:34:41Z
raymondfam marked the issue as sufficient quality report
#2 - HickupHH3
2024-03-18T00:56:47Z
6Rs
#3 - c4-judge
2024-03-18T00:56:52Z
HickupHH3 marked the issue as grade-b