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: 157/283
Findings: 1
Award: $13.63
🌟 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
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); } /// @notice Burns the specified amount of tokens from the caller's address. /// @param amount The amount of tokens to be burned. function burn(uint256 amount) public virtual { _burn(msg.sender, amount); }
_burn(account, amount);
off-chain
If You know what data to hash, there is no need to consume more computational power to hash it using keccak256 , you’ll end up consuming 2x amount of gas.
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); /// @notice Role for spending tokens. bytes32 public constant SPENDER_ROLE = keccak256("SPENDER_ROLE"); /// @notice Role for staking tokens. bytes32 public constant STAKER_ROLE = keccak256("STAKER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); /// @notice Role for spending tokens. bytes32 public constant SPENDER_ROLE = keccak256("SPENDER_ROLE"); /// @notice Role for staking tokens. bytes32 public constant STAKER_ROLE = keccak256("STAKER_ROLE");
File: src/AiArenaHelper.sol 62: require(msg.sender == _ownerAddress); 69: require(msg.sender == _ownerAddress); 132: require(msg.sender == _ownerAddress); 145: require(msg.sender == _ownerAddress);
File: src/FighterFarm.sol 121: require(msg.sender == _ownerAddress); 130: require(msg.sender == _ownerAddress); 140: require(msg.sender == _ownerAddress); 148: require(msg.sender == _ownerAddress); 156: require(msg.sender == _ownerAddress); 164: require(msg.sender == _ownerAddress); 172: require(msg.sender == _ownerAddress);
File: src/GameItems.sol 109: require(msg.sender == _ownerAddress); 118: require(msg.sender == _ownerAddress); 127: require(msg.sender == _ownerAddress); 140: require(msg.sender == _ownerAddress);
File: src/MergingPool.sol 90: require(msg.sender == _ownerAddress); 99: require(msg.sender == _ownerAddress);
File: src/Neuron.sol 86: require(msg.sender == _ownerAddress); 94: require(msg.sender == _ownerAddress); 102: require(msg.sender == _ownerAddress); 110: require(msg.sender == _ownerAddress); 119: require(msg.sender == _ownerAddress);
File: src/RankedBattle.sol 168: require(msg.sender == _ownerAddress); 177: require(msg.sender == _ownerAddress); 185: require(msg.sender == _ownerAddress); 193: require(msg.sender == _ownerAddress); 202: require(msg.sender == _ownerAddress); 210: require(msg.sender == _ownerAddress);
If a value is only intended to be used once then it should not be cached. Caching the value will result in unnecessary stack manipulation.
uint32 lowerBound = numRoundsClaimed[msg.sender];
_ownerAddress = msg.sender;
10 ** 18
can be changed to 1e18
and save some gas10 ** 18
can be changed to 1e18
to avoid unnecessary arithmetic operation and save gas.
uint256 public rerollCost = 1000 * 10**18;
uint256 public constant INITIAL_TREASURY_MINT = 10**18 * 10**8 * 2; /// @notice Initial supply of NRN tokens to be minted and distributed to contributors. uint256 public constant INITIAL_CONTRIBUTOR_MINT = 10**18 * 10**8 * 5; /// @notice Maximum supply of NRN tokens. uint256 public constant MAX_SUPPLY = 10**18 * 10**9;
rankedNrnDistribution[0] = 5000 * 10**18;
rankedNrnDistribution[roundId] = newDistribution * 10**18;
(amountStaked[tokenId] + stakeAtRisk) / 10**18
Using > 0
uses slightly more gas than using != 0
. Use != 0
when comparing uint
variables to zero, which cannot hold values below zero
if (claimIndex > 0) {
Using private
for constant variables is cheaper in terms of gas usage. If the value of the constant variable is needed, it can be accessed via a getter function. In case of multiple constant variables, a single getter function could be used to return a tuple of the values of all currently-private constants.
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); /// @notice Role for spending tokens. bytes32 public constant SPENDER_ROLE = keccak256("SPENDER_ROLE"); /// @notice Role for staking tokens. bytes32 public constant STAKER_ROLE = keccak256("STAKER_ROLE"); /// @notice Initial supply of NRN tokens to be minted to the treasury. uint256 public constant INITIAL_TREASURY_MINT = 10**18 * 10**8 * 2; /// @notice Initial supply of NRN tokens to be minted and distributed to contributors. uint256 public constant INITIAL_CONTRIBUTOR_MINT = 10**18 * 10**8 * 5; /// @notice Maximum supply of NRN tokens. uint256 public constant MAX_SUPPLY = 10**18 * 10**9;
uint8 public constant VOLTAGE_COST = 10;
Caching a mapping’s value in a storage pointer when the value is accessed multiple times saves ~40 gas per access due to not having to perform the same offset calculation every time. Help the Optimizer by saving a storage variable’s reference instead of repeatedly fetching it.
To achieve this, declare a storage pointer for the variable and use it instead of repeatedly fetching the reference in a map or an array.
require(_neuronInstance.balanceOf(msg.sender) >= price, "Not enough NRN for purchase"); require( allGameItemAttributes[tokenId].finiteSupply == false || ( allGameItemAttributes[tokenId].finiteSupply == true && quantity <= allGameItemAttributes[tokenId].itemsRemaining ) );
_ownerAddress = msg.sender;
The EVM only operates on 32 bytes/ 256 bits at a time. This means that if you use uint8, EVM has to first convert it uint256 to work on it and the conversion costs extra gas! You may wonder, What were the devs thinking? Why did they create smaller variables then? The answer lies in packing. In solidity, you can pack multiple small variables into one slot, but if you are defining a lone variable and can’t pack it, it’s optimal to use a uint256 rather than uint8.
unchecked{}
You can also use unchecked{} for even more gas savings but this will not check to see if i overflows. For best gas savings, use inline assembly, however, this limits the functionality you can achieve.
All contest
Every call to an external contract costs a decent amount of gas. For optimization of gas usage, It’s better to call one function and have it return all the data you need rather than calling a separate function for every piece of data. This might go against the best coding practices for other languages, but solidity is special.
If you can fit your data in 32 bytes, then you should use bytes32 datatype rather than bytes or strings as it is much cheaper in solidity. Basically, Any fixed size variable in solidity is cheaper than variable size.
string name;
string public name = "AI Arena Game Items"; @audit /// @notice The symbol for this smart contract. string public symbol = "AGI";
if ( i == 0 && iconsType == 2 || // Custom icons head (beta helmet) i == 1 && iconsType > 0 || // Custom icons eyes (red diamond) i == 4 && iconsType == 3 // Custom icons hands (bowling ball) ) {
#0 - raymondfam
2024-02-25T21:22:12Z
17 generic G.
#1 - c4-pre-sort
2024-02-25T21:22:16Z
raymondfam marked the issue as sufficient quality report
#2 - c4-judge
2024-03-19T07:52:59Z
HickupHH3 marked the issue as grade-b