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: 121/283
Findings: 5
Award: $22.56
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: CodeWasp
Also found by: 0x13, 0xAlix2, 0xAsen, 0xCiphky, 0xE1, 0xLogos, 0xaghas, 0xlemon, 0xlyov, 0xvj, ADM, Aamir, BARW, Bauchibred, DMoore, DanielArmstrong, Draiakoo, Fulum, GhK3Ndf, Josh4324, Kalogerone, KmanOfficial, Krace, KupiaSec, Limbooo, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Tendency, _eperezok, adam-idarrha, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, c0pp3rscr3w3r, cartlex_, cats, d3e4, deadrxsezzz, denzi_, devblixt, dimulski, erosjohn, evmboi32, fnanni, grearlake, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, juancito, klau5, korok, ktg, ladboy233, matejdb, merlinboii, novamanbg, nuthan2x, oualidpro, peanuts, petro_1912, pkqs90, pynschon, radev_sw, rouhsamad, sashik_eth, shaka, sobieski, soliditywala, stackachu, tallo, thank_you, ubl4nk, vnavascues, web3pwn, xchen1130, zhaojohnson
0.1044 USDC - $0.10
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L338-L348
Users can transfer fighters even if the fighter is staked which is a violation of core functionality.
As you can see, transfer functions are overridden to disable fighters from being transferred when the _ableToTransfer
function returns false:
function transferFrom( address from, address to, uint256 tokenId ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _transfer(from, to, tokenId); } /// @notice Safely transfers an NFT from one address to another. /// @dev Add a custom check for an ability to transfer the fighter. /// @param from Address of the current owner. /// @param to Address of the new owner. /// @param tokenId ID of the fighter being transferred. function safeTransferFrom( address from, address to, uint256 tokenId ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _safeTransfer(from, to, tokenId, ""); }
Here is the _ableToTransfer
function:
function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); }
Which makes sure that a fighter is not staked, and the recipient has less than MAX_FIGHTERS_ALLOWED
.
However, this can be circumvented because there is another transfer function that is inherited from the ERC721 contract and is not overridden:
function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data ) public virtual override { require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner nor approved"); _safeTransfer(from, to, tokenId, data); }
Here is a simple POC that you can add to FighterFarm.t.sol:
function testTransferringFighterWhileStakedSucceeds() public { _mintFromMergingPool(_ownerAddress); _fighterFarmContract.addStaker(_ownerAddress); //stake the fighter _fighterFarmContract.updateFighterStaking(0, true); //assert that owner is _ownerAddress assertEq(_fighterFarmContract.ownerOf(0), _ownerAddress); //assert that it is staked assertEq(_fighterFarmContract.fighterStaked(0), true); //transfer the fighter _fighterFarmContract.safeTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, 0, ""); //assert that owner is _DELEGATED_ADDRESS assertEq(_fighterFarmContract.ownerOf(0), _DELEGATED_ADDRESS); }
Manual review, Foundry
Override the 4-argument safeTransferFrom
function and add this require like in the other functions:
require(_ableToTransfer(tokenId, to));
ERC721
#0 - c4-pre-sort
2024-02-23T05:10:26Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T05:10:39Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:44:46Z
HickupHH3 marked the issue as satisfactory
🌟 Selected for report: Aamir
Also found by: 0rpse, 0x11singh99, 0x13, 0xAlix2, 0xAsen, 0xBinChook, 0xCiphky, 0xE1, 0xKowalski, 0xLogos, 0xWallSecurity, 0xaghas, 0xbranded, 0xlemon, 0xlyov, 0xpoor4ever, 0xprinc, 0xvj, ADM, Aymen0909, BARW, Bauchibred, Breeje, CodeWasp, DMoore, DeFiHackLabs, Draiakoo, Fulum, GhK3Ndf, Greed, Jorgect, Josh4324, Kalogerone, KmanOfficial, Krace, Limbooo, McToady, MidgarAudits, MrPotatoMagic, PedroZurdo, Pelz, Ryonen, SovaSlava, SpicyMeatball, Tendency, Timenov, ZanyBonzy, _eperezok, al88nsk, alexxander, alexzoid, aslanbek, blutorque, btk, cartlex_, cats, csanuragjain, deadrxsezzz, denzi_, devblixt, dimulski, djxploit, erosjohn, evmboi32, fnanni, grearlake, haxatron, hulkvision, immeas, israeladelaja, jaydhales, jesjupyter, jnforja, josephdara, juancito, kiqo, klau5, korok, krikolkk, ktg, kutugu, ladboy233, lil_eth, m4ttm, matejdb, merlinboii, n0kto, ni8mare, novamanbg, nuthan2x, oualidpro, pa6kuda, peter, petro_1912, pkqs90, pynschon, sandy, sashik_eth, shaflow2, shaka, sobieski, soliditywala, solmaxis69, stackachu, tallo, thank_you, tpiliposian, ubl4nk, visualbits, vnavascues, web3pwn, xchen1130, zhaojohnson
0.0037 USDC - $0.00
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/GameItems.sol#L291-L303
Users can transfer game items even if transfers are disabled.
In GameItems.sol there is a function adjustTransferability
that lets the owner of the contract change the transferability of a game item - i.e make it transferrable or not-transferable.
Also, the safeTransferFrom
function is overridden so it disables transfers of items that are marked as non-transferable:
function safeTransferFrom( address from, address to, uint256 tokenId, uint256 amount, bytes memory data ) public override(ERC1155) { require(allGameItemAttributes[tokenId].transferable); <-- super.safeTransferFrom(from, to, tokenId, amount, data); }
However, the issue stems from the fact that in the inherited ERC1155 contract there is another transfer function that is not rewritten and therefore this require is not checked:
require(allGameItemAttributes[tokenId].transferable);
Here's the safeBatchTransferFrom
from ERC1155.sol:
function safeBatchTransferFrom( address from, address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data ) public virtual override { require( from == _msgSender() || isApprovedForAll(from, _msgSender()), "ERC1155: caller is not token owner nor approved" ); _safeBatchTransferFrom(from, to, ids, amounts, data); }
This function can be called directly by a user and the item can be transferred doesn't matter its transferability status.
Here is a POC that is a modified version of the already existing testSafeTransferFrom
test in GameItems.t.sol:
function testSafeTransferFromBug() public { _fundUserWith4kNeuronByTreasury(_ownerAddress); //mint token with id 0 to the _ownerAddress _gameItemsContract.mint(0, 1); //act as owner vm.prank(address(this)); //set transferability as false _gameItemsContract.adjustTransferability(0, false); // SafeBatch - create arrays as the function needs arrays uint256[] memory ids = new uint256[](1); ids[0] = 0; uint256[] memory amounts = new uint256[](1); amounts[0] = 1; //attempt to transfer - this should fail because transferability is set to false. However, it succeeds _gameItemsContract.safeBatchTransferFrom(_ownerAddress, _DELEGATED_ADDRESS, ids, amounts, ""); //assert that the item is transferred assertEq(_gameItemsContract.balanceOf(_DELEGATED_ADDRESS, 0), 1); assertEq(_gameItemsContract.balanceOf(_ownerAddress, 0), 0); }
Manual review, Foundry
Override the safeBatchTransferFrom
function as well and add this require:
require(allGameItemAttributes[tokenId].transferable);
Error
#0 - c4-pre-sort
2024-02-22T04:02:26Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T04:02:34Z
raymondfam marked the issue as duplicate of #18
#2 - c4-pre-sort
2024-02-26T00:28:20Z
raymondfam marked the issue as duplicate of #575
#3 - c4-judge
2024-03-05T04:47:38Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-05T04:54:27Z
HickupHH3 marked the issue as satisfactory
🌟 Selected for report: Abdessamed
Also found by: 0rpse, 0xAlix2, 0xAsen, 0xCiphky, 0xlemon, 0xmystery, 0xvj, ADM, Aamir, Archime, BARW, DarkTower, Draiakoo, FloatingPragma, JCN, McToady, MrPotatoMagic, OMEN, PetarTolev, Ryonen, SpicyMeatball, Tendency, VAD37, Velislav4o, VrONTg, Zac, adamn000, ahmedaghadi, alexxander, alexzoid, bhilare_, btk, cats, d3e4, denzi_, devblixt, dimulski, evmboi32, fnanni, givn, haxatron, immeas, jesjupyter, juancito, ke1caM, klau5, korok, krikolkk, matejdb, n0kto, niser93, peter, pkqs90, radin100, shaka, sl1, soliditywala, stackachu, stakog, t0x1c, vnavascues, yotov721, zhaojohnson
1.2667 USDC - $1.27
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L233-L262
There are 2 types of mint passes - for regular Champions and Dendroid fighters, where the Dendroid fighters are rarer and should be more expensive.
However, a user can mint a Dendroid with just a regular mint pass by simply specifying the type of fighter he wants to mint.
Let's take a look at the redeemMintPass
function in the FighterFarm contract:
function redeemMintPass( uint256[] calldata mintpassIdsToBurn, uint8[] calldata fighterTypes, uint8[] calldata iconsTypes, string[] calldata mintPassDnas, string[] calldata modelHashes, string[] calldata modelTypes ) external { require( mintpassIdsToBurn.length == mintPassDnas.length && mintPassDnas.length == fighterTypes.length && fighterTypes.length == modelHashes.length && modelHashes.length == modelTypes.length ); for (uint16 i = 0; i < mintpassIdsToBurn.length; i++) { require(msg.sender == _mintpassInstance.ownerOf(mintpassIdsToBurn[i])); _mintpassInstance.burn(mintpassIdsToBurn[i]); _createNewFighter( msg.sender, uint256(keccak256(abi.encode(mintPassDnas[i]))), modelHashes[i], modelTypes[i], fighterTypes[i], iconsTypes[i], [uint256(100), uint256(100)] ); } }
As you can see, nothing is stopping a user from just claiming Dendroids for all of the mint passes he owns. A malicious user would just buy mint passes for normal fighters and claim only Dendroids.
Manual review
Implement a sufficient validation that the user is claiming the right type of fighters according to his mint passes.
Invalid Validation
#0 - c4-pre-sort
2024-02-22T07:56:40Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T07:56:50Z
raymondfam marked the issue as duplicate of #33
#2 - c4-pre-sort
2024-02-26T00:53:42Z
raymondfam marked the issue as duplicate of #1626
#3 - c4-judge
2024-03-06T03:34:47Z
HickupHH3 marked the issue as satisfactory
🌟 Selected for report: klau5
Also found by: 0xAleko, 0xAlix2, 0xAsen, 0xCiphky, 0xKowalski, 0xlemon, 0xvj, 14si2o_Flint, Aamir, AlexCzm, Aymen0909, BARW, Blank_Space, DanielArmstrong, Davide, Draiakoo, Giorgio, McToady, MrPotatoMagic, PoeAudits, Ryonen, Silvermist, SpicyMeatball, Tychai0s, VAD37, Varun_05, alexxander, alexzoid, aslanbek, blutorque, btk, cats, d3e4, denzi_, evmboi32, fnanni, givn, grearlake, haxatron, jesjupyter, juancito, ke1caM, ktg, lanrebayode77, linmiaomiao, matejdb, merlinboii, n0kto, novamanbg, nuthan2x, petro_1912, pynschon, radin100, sashik_eth, shaka, sl1, soliditywala, solmaxis69, t0x1c, ubl4nk, vnavascues, xchen1130, yotov721, zhaojohnson
1.1225 USDC - $1.12
https://github.com/code-423n4/2024-02-ai-arena/blob/main/src/FighterFarm.sol#L370-L391
A user can reroll a normal champion as Dendroid for fighterType
as there is no validation of this input parameter in the function.
This shouldn't happen and affects the attributes of the fighter.
Let's take a look at the reroll function:
function reRoll(uint8 tokenId, uint8 fighterType) public { require(msg.sender == ownerOf(tokenId)); require(numRerolls[tokenId] < maxRerollsAllowed[fighterType]); require(_neuronInstance.balanceOf(msg.sender) >= rerollCost, "Not enough NRN for reroll"); _neuronInstance.approveSpender(msg.sender, rerollCost); bool success = _neuronInstance.transferFrom(msg.sender, treasuryAddress, rerollCost); if (success) { numRerolls[tokenId] += 1; uint256 dna = uint256(keccak256(abi.encode(msg.sender, tokenId, numRerolls[tokenId]))); (uint256 element, uint256 weight, uint256 newDna) = _createFighterBase(dna, fighterType); fighters[tokenId].element = element; fighters[tokenId].weight = weight; fighters[tokenId].physicalAttributes = _aiArenaHelperInstance.createPhysicalAttributes( newDna, generation[fighterType], fighters[tokenId].iconsType, fighters[tokenId].dendroidBool ); _tokenURIs[tokenId] = ""; } }
As you can see, there is no check that the fighterType a user specifies is the same type he originally owned.
This messes up the newDna that the fighter receives and makes it extremely likely to get a fighter with a very low rarityRank(which means it is very rare):
function _createFighterBase( uint256 dna, uint8 fighterType ) private view returns (uint256, uint256, uint256) { uint256 element = dna % numElements[generation[fighterType]]; uint256 weight = dna % 31 + 65; uint256 newDna = fighterType == 0 ? dna : uint256(fighterType); <--- return (element, weight, newDna); }
Here, newDna will take the user-inputed value as this is the fighterType i.e newDna = 1.
function createPhysicalAttributes( uint256 dna, uint8 generation, uint8 iconsType, bool dendroidBool ) external view returns (FighterOps.FighterPhysicalAttributes memory) { //unrelated functionality uint256 rarityRank = (dna / attributeToDnaDivisor[attributes[i]]) % 100; }
In the createPhysicalAttributes
function, dna will be 1 and as a result rarityRank
will be very low, possibly 0, which is not expected and it is not the way rarity rank is supposed to be calculated in the system.
Note: the user can't pass anything else other than 0 or 1 as fighterType
because otherwise maxRerollsAllowed[fighterType]
will have an array-out-of-bounds error.
Manual review
Add a check that makes sure the user is passing the right fighter type:
if(fighters[tokenId].dendroidBool == false) require(fighterType == 0); else require(fighterType == 1);
Invalid Validation
#0 - c4-pre-sort
2024-02-22T02:05:10Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T02:05:20Z
raymondfam marked the issue as duplicate of #306
#2 - c4-judge
2024-03-05T04:34:25Z
HickupHH3 marked the issue as satisfactory
#3 - c4-judge
2024-03-19T09:05:07Z
HickupHH3 changed the severity to 3 (High Risk)
🌟 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
AI Arena is a blockchain-based PvP fighting game where players train AI fighters to compete in battles.
These fighters are represented as NFTs, minted through the FighterFarm.sol smart contract, with unique physical attributes, generations, weight, elements, and types (regular Champion or Dendroid), alongside model data.
The game's economy revolves around the native ERC20 token, $NRN, used for staking in ranked battles to earn rewards or face potential stakes loss based on performance.
The RankedBattle.sol contract, having MINTER and STAKER roles, manages the reward system, updates battle results, and leverages a stakingFactor based on the square root of staked amounts for fair play.
Additionally, players manage a "voltage" resource, necessary for engaging in battles, which replenishes daily or can be instantly refilled by purchasing batteries via the GameItems.sol contract.
The core game mechanics, including battle outcomes and rewards distribution, operate off-chain, with the game server acting as the oracle.
AI Arena blends NFT technology with traditional gaming to create a dynamic, strategy-based platform where players can earn cryptocurrency rewards through skilled play and strategic management of digital assets.
Here is an analysis of all of the contracts and each of their functions.
The FighterFarm
contract for the AI Arena game manages the creation, ownership, and redemption of AI Arena Fighter NFTs.
Below is a summary of each function within this contract:
transferOwnership
: Transfers the ownership of the contract to a new address. Only the current owner can call this function.incrementGeneration
: Increases the generation count of a specified fighter type, which can only be called by the owner. It also increases the max rerolls allowed for that fighter type.addStaker
: Adds a new address that is permitted to stake fighters, an action restricted to the owner.instantiateAIArenaHelperContract
: Initializes the AI Arena Helper contract, a task that only the owner can perform.instantiateMintpassContract
: Sets up the mint pass contract, again only callable by the owner.instantiateNeuronContract
: Initializes the Neuron (ERC20 token) contract, a function reserved for the owner.setMergingPoolAddress
: Assigns the address for the merging pool contract, exclusively callable by the owner.setTokenURI
: Sets the token URI for a specific tokenId, a task that the delegated address can perform.claimFighters
: Allows users to claim a predetermined number of fighters by verifying a signature from the delegated address.redeemMintPass
: Burns mint passes to create fighter NFTs, ensuring all input arrays match in length and correspond to the same index across arrays.updateFighterStaking
: Updates the staking status of a fighter, a function restricted to addresses with the staker role.updateModel
: Updates the machine learning model for a fighter, callable only by the fighter's owner.doesTokenExist
: Checks if a token ID exists within the contract.mintFromMergingPool
: Mints a new fighter from the merging pool, restricted to being called by the merging pool address.transferFrom
and safeTransferFrom
: Transfer a fighter NFT from one address to another, with added checks for transfer eligibility.reRoll
: Rerolls a fighter to randomly change its traits, restricted to the fighter's owner who must have sufficient $NRN and has not exceeded the reroll limit.contractURI
: Provides the URI for the contract's metadata.tokenURI
: Returns the metadata URI for a specific fighter token.supportsInterface
: Indicates whether a given interface ID is supported by this contract, implementing ERC721 and ERC721Enumerable standards.getAllFighterInfo
: Retrieves all relevant information about a specific fighter token._beforeTokenTransfer
: Hook that is called before any token transfer, implementing checks from ERC721 and ERC721Enumerable._createFighterBase
: Internally creates the base attributes for a new fighter based on its DNA and type._createNewFighter
: Internally creates a new fighter and mints the corresponding NFT to the designated address, utilizing custom or base attributes._ableToTransfer
: Checks whether a specific token is eligible for transfer, considering the max fighter limit per address and staking status.This contract combines ERC1155 functionality for managing an array of game items with additional features like daily minting allowances, finite supplies, and integrated payment and pricing in $NRN tokens.
Below is a summary of each function in the GameItems
contract:
transferOwnership
: Transfers the ownership of the contract to a new address, requiring the caller to be the current owner.adjustAdminAccess
: Grants or revokes administrative privileges to a specific address, with the operation restricted to the contract owner.adjustTransferability
: Modifies the transferability of a specific game item, enabling or disabling the ability to trade it. This is owner-restricted and emits corresponding Locked
or Unlocked
events.instantiateNeuronContract
: Initializes the Neuron
contract instance, setting its address for future interactions. Only the owner can call this.mint
: Allows users to mint specified quantities of game items, charging them in $NRN tokens, with checks for item existence, sufficient balance, supply constraints, and daily minting allowances.setAllowedBurningAddresses
: Authorizes specific addresses to burn game items, an action limited to administrators.setTokenURI
: Assigns a unique URI to a game item token, enabling custom metadata to be set by administrators.createGameItem
: Creates a new game item with specified attributes such as name, URI, supply information, and pricing. Only admins can call this function.burn
: Enables authorized addresses to destroy specified amounts of game items, reducing their total supply.contractURI
: Returns the URI for the contract metadata, a static function providing off-chain contract details.uri
: Overrides the ERC1155 uri
function to return custom URIs for each token, falling back to the default URI if a custom one isn't set.getAllowanceRemaining
: Provides the remaining daily minting allowance for a specific game item for a user, reflecting how many more of the item the user can mint that day.remainingSupply
: Reports the remaining supply of a specific game item, applicable to items with finite supplies.uniqueTokensOutstanding
: Counts the total number of unique game item tokens managed by the contract.safeTransferFrom
: Safely transfers game items from one address to another, with an added check to ensure the item is transferable._replenishDailyAllowance
: A private function that resets a user's daily minting allowance for a game item after a 24-hour interval.This contract interacts with FighterFarm
for minting new fighters as rewards and uses internal mappings to manage points, claims, and administrative roles effectively.
transferOwnership
: Changes the owner of the contract to a new address, ensuring only the current owner can execute this action.adjustAdminAccess
: Modifies administrative access for a given address, allowing only the contract owner to grant or revoke admin status.updateWinnersPerPeriod
: Updates the number of winners allowed per competition period, restricted to administrators.pickWinner
: Selects winners for the current round, ensuring the operation adheres to the expected winners per period and that selection has not been completed for the round. It resets winner points to zero and advances the round.claimRewards
: Enables users to claim their rewards for multiple rounds, minting new fighters based on provided model URIs, types, and attributes. It updates the number of rounds claimed by the user.getUnclaimedRewards
: Returns the number of unclaimed rewards for a specific user, calculating based on rounds not yet claimed by the user.addPoints
: Adds points to a fighter's score in the merging pool, callable only by the ranked battle contract to ensure integrity in point allocation.getFighterPoints
: Retrieves points for fighters up to a specified token ID, allowing for a bulk view of points allocated in the merging pool.This contract implements an ERC20 token with extended functionalities for an in-game economy, such as roles for minting, spending, and staking, alongside mechanisms for token distribution and burning.
Here's a summary of each function in the Neuron
contract:
constructor
: Initializes the contract, sets up initial roles, and mints the initial supply of NRN tokens to the treasury and contributors.transferOwnership
: Transfers the contract's ownership to a new address, restricted to the current owner.addMinter
: Grants the minter role to a new address, allowing it to mint new tokens, limited to the contract owner.addStaker
: Assigns the staker role to a new address, enabling it to stake tokens, an action restricted to the owner.addSpender
: Adds a new address to the spender role, permitting it to spend tokens on behalf of others, again limited to the owner's discretion.adjustAdminAccess
: Modifies admin privileges for a specified address, controlled by the owner.setupAirdrop
: Prepares an airdrop by setting allowances from the treasury to recipients, an action that can only be executed by admins.claim
: Allows users to claim tokens from the treasury up to their approved allowance, emitting a TokensClaimed
event upon success.mint
: Mints new NRN tokens to a specified address, ensuring the action doesn't exceed the maximum supply and is performed by an address with the minter role.burn
: Destroys a specified amount of tokens from the caller's balance, reducing the total supply.approveSpender
: Approves a specified amount of the caller's tokens for spending by another account, requiring the caller to have the spender role.approveStaker
: Similar to approveSpender
, but for approving tokens for staking purposes, necessitating the caller to possess the staker role.burnFrom
: Burns tokens from a specified account, dependent on having enough allowance from the token owner to the caller, effectively reducing the owner's balance and the caller's allowance.The RankedBattle
contract is designed for managing the staking, battling, and rewards distribution within the game ecosystem. Here's a summary of each function:
transferOwnership
: Changes the contract's ownership to a new address, enforceable by the current owner only.adjustAdminAccess
: Grants or revokes admin privileges to an address, with this capability restricted to the contract's owner.setGameServerAddress
: Assigns the game server's address for updating battle records, an action limited to the owner.setStakeAtRiskAddress
: Sets the address of the StakeAtRisk
contract and initializes its instance. This action is also restricted to the owner.instantiateNeuronContract
: Initializes the Neuron
(ERC20 token) contract instance, a function callable only by the owner.instantiateMergingPoolContract
: Sets up the MergingPool
contract instance, with the operation being restricted to the contract owner.setRankedNrnDistribution
: Adjusts the NRN token distribution amount for the ranked battles of the current round, modifiable only by admins.setBpsLostPerLoss
: Sets the basis points that players lose from their stake when they lose a battle, a parameter that only admins can adjust.setNewRound
: Advances the game to a new round, enabling new claims and resetting battle and staking records, a function reserved for admin use.stakeNRN
: Allows players to stake NRN tokens on their fighters, increasing their potential rewards from participating in battles.unstakeNRN
: Permits players to withdraw their staked NRN tokens from their fighters, with certain restrictions to prevent abuse during ongoing rounds.claimNRN
: Enables players to claim their earned NRN tokens based on their participation and success in ranked battles up to the current round.updateBattleRecord
: Updates the battle record for a fighter based on the outcome of a battle, including win, tie, or loss. This function can only be called by the designated game server address.getBattleRecord
: Retrieves the battle record (wins, ties, and losses) for a specific fighter token.getCurrentStakingData
: Provides an overview of the current staking situation, including the round ID, the amount of NRN tokens to be distributed, and the total accumulated points for the round.getNrnDistribution
: Returns the amount of NRN tokens distributed for winning in a particular round.getUnclaimedNRN
: Calculates the amount of unclaimed NRN tokens for a specific player, based on their participation and success in previous rounds._addResultPoints
(Private): Internally calculates and assigns points to fighters based on battle outcomes, adjusting staking rewards and potentially transferring stakes to the risk pool based on the battle results._updateRecord
(Private): Internally updates a fighter's battle record after each match._getStakingFactor
(Private): Computes the staking factor for a fighter based on the square root of their staked amount, influencing the calculation of rewards.The StakeAtRisk
contract manages the NRNs (Neuron tokens) that players have at risk of losing during ranked battles in the AI Arena game. Here’s a breakdown of its functionality:
Constructor
: Initializes the contract by setting the treasury, Neuron (NRN), and RankedBattle contract addresses. It also instantiates the Neuron contract.setNewRound
: Called by the RankedBattle contract to initiate a new round of competition. It sweeps all NRNs held in this contract to the treasury and updates the round ID. This function ensures that only the RankedBattle contract can call it.reclaimNRN
: Allows the RankedBattle contract to reclaim NRN tokens from the stake at risk for a fighter who wins a match. It ensures the caller is the RankedBattle contract and that the fighter has sufficient stake at risk before transferring the reclaimed NRNs.updateAtRiskRecords
: Called by the RankedBattle contract to place more NRN tokens at risk for a specific fighter, typically after a loss that results in a deficit of points. This function updates the stake at risk for the fighter and the total stake at risk for the round.getStakeAtRisk
: Provides the amount of NRN tokens at risk for a specific fighter in the current round. This view function allows querying the stake at risk without altering the contract state._sweepLostStake
(Private): Transfers the lost stake (NRNs at risk that were not reclaimed by players) to the treasury at the end of a round. This internal function is part of the mechanism to reset the stakes at risk for a new round.The VoltageManager
contract oversees the voltage system in the AI Arena, regulating energy usage by players for initiating matches. Here's a summary of its functionality:
Constructor
: Initializes the contract, setting the owner and linking to the Game Items contract for managing in-game items like voltage batteries.transferOwnership
: Allows the current owner to transfer ownership of the contract to a new address.adjustAdminAccess
: Grants or revokes admin privileges to a specified address, ensuring only the owner can perform this action.adjustAllowedVoltageSpenders
: Admins can authorize or deauthorize addresses to spend voltage on behalf of users, controlling who can initiate actions that consume voltage.useVoltageBattery
: Players can replenish their voltage to the maximum limit by using a voltage battery, an in-game item. This action burns a battery from the player's inventory.spendVoltage
: Deducts a specified amount of voltage from a player's total. This function can be called directly by the player or by an approved spender, facilitating various gameplay mechanics that require energy expenditure._replenishVoltage
(Private): Automatically replenishes a player's voltage to full capacity every 24 hours, ensuring players have the energy needed to participate in daily activities.The AiArenaHelper
contract assists in generating and managing physical attributes for AI Arena fighters. Here’s a breakdown of its functionality:
Constructor
: Initializes the contract with predefined attribute probabilities for the initial generation of fighters. It sets the owner and maps each attribute to a divisor for determining their rarity.transferOwnership
: Allows the current owner to transfer ownership of the contract to a new address.addAttributeDivisor
: Adds divisors for attributes, which are used to calculate the rarity of each attribute of a fighter. This function is restricted to the owner.createPhysicalAttributes
: Generates physical attributes for a fighter based on its DNA, generation, and special icons type. This function considers whether the fighter is a dendroid and uses custom logic for determining attributes based on the fighter's DNA and predefined attribute probabilities.addAttributeProbabilities
: Allows the owner to add attribute probabilities for a new generation of fighters, enabling customization of fighter attributes as the game evolves.deleteAttributeProbabilities
: Permits the owner to delete attribute probabilities for a given generation, providing flexibility in managing the game's content and fighter generation logic.getAttributeProbabilities
: Publicly accessible function that returns the attribute probabilities for a specified generation and attribute, facilitating transparency and enabling external calculations related to fighter attributes.dnaToIndex
: Converts a fighter’s DNA and rarity rank into an attribute probability index, determining the rarity and type of attributes a fighter possesses based on its genetic code.FighterOps
, is a Solidity library designed for managing Fighter NFTs within the AI Arena game. It provides a structured way to define, emit events for, and retrieve details about fighters.
Here’s a breakdown of its functionality:
fighterCreatedEmitter
: Emits the FighterCreated
event, signaling the creation of a new fighter with its core attributes.getFighterAttributes
: Extracts and returns the physical attributes of a fighter as an array, making it easier to access these details for game mechanics or display purposes.viewFighterInfo
: Compiles and returns comprehensive information about a fighter, including the owner
address and a variety of attributes and identifiers. This function is essential for external calls to fetch detailed data about a fighter for gameplay or display.Fighter NFTs: Managed by the FighterFarm.sol contract, each fighter NFT encapsulates the fighter's physical attributes, generation, weight, element, fighter type (regular Champion or Dendroid), and model data (model type and model hash).
Ranked Battles: The RankedBattle.sol contract allows players to stake $NRN tokens on their fighters and participate in ranked battles. Winners earn rewards based on battle outcomes and the amount staked. The contract uses a staking factor calculated from the square root of the staked amount to determine points after each match.
Voltage System: Managed by the VoltageManager.sol contract, voltage acts as an energy system for fighters. Players must manage their fighters' voltage to participate in battles, with options to wait for natural replenishment or purchase batteries for instant recharge.
Reward Mechanism: Rewards are distributed in $NRN tokens, the platform's native ERC20 token. The Neuron.sol contract, along with StakeAtRisk.sol for managing stakes at risk, and MergingPool.sol for potential earnings of new fighter NFTs, supports the reward system.
Game Items: The GameItems.sol contract will represent various in-game items, for now, only batteries for voltage replenishment are available.
Fighter NFT Acquisition: Players can acquire fighter NFTs through minting in the FighterFarm.sol contract or by trading on the marketplace.
Preparation for Battle: Players stake NRN tokens on their fighters, manage their voltage, and may equip them with in-game items for enhanced performance.
Participation in Ranked Battles: Players enter their staked fighters into battles, with outcomes determined off-chain by the game server, acting as an oracle.
Reward Distribution: Post-match, rewards in NRN tokens are calculated and distributed based on the battle outcomes, staked amounts, and points system. Players can also claim their stakes or any earnings from the MergingPool.sol.
Here is a non-exhaustive diagram of the interactions between the contracts that I created and used for reference throughout the contest:
The reliance on off-chain servers to determine the outcomes of matches and interactions within the game introduces a level of trust and centralization.
The server acts as an oracle, and if it is compromised or malfunctions, it could affect the integrity of the game's outcomes and rewards.
Day 1-3: Initial Familiarization and High-Level Overview
Day 4-7: In-Depth Contract Review
Day 8-10: Test Case Development and Execution
Day 11-12: Final Review and Issue Compilation
The AI Arena protocol introduces a unique blend of AI-driven gameplay and blockchain technology, offering an engaging PvP experience through NFTs and token staking.
It capitalizes on the growing interest in decentralized gaming, allowing players to train, battle, and earn within a secure ecosystem.
By tokenizing fighters and implementing a reward system based on competitive outcomes, it creates a dynamic economy that rewards skill, strategy, and participation.
50 hours
#0 - c4-pre-sort
2024-02-25T20:34:23Z
raymondfam marked the issue as sufficient quality report
#1 - c4-judge
2024-03-19T08:12:27Z
HickupHH3 marked the issue as grade-b