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: 177/283
Findings: 2
Award: $7.39
π 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
Staked tokens are not meant to be transferred, but they can be transferred to another address by simply using another function for transferring that was not overridden.
FighterFarm.sol, locks staked tokens by having _ableToTransfer return false when token is staked.
function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); }
This check is done in safeTransferFrom and transferFrom
function safeTransferFrom( address from, address to, uint256 tokenId ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _safeTransfer(from, to, tokenId, ""); } . . . function transferFrom( address from, address to, uint256 tokenId ) public override(ERC721, IERC721) { require(_ableToTransfer(tokenId, to)); _transfer(from, to, tokenId); } . . .
However there's another public safeTransferFrom with a different parameter list that will be public and available for user use but not have _ableToTransfer check
In ERC721.sol
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); }
This function with this parameter list is not overridden and hence appears for user to use without the _ableToTransfer require statement.
Can be further be verified by using console prints on the safeTransfer functions.
One on the
function safeTransferFrom( address from, address to, uint256 tokenId, bytes memory data )
in ERC721.sol
and another in FighterFarm.sol
manual analysis
override all forms of transfer in ERC721.sol
Access Control
#0 - c4-pre-sort
2024-02-23T06:00:39Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T06:00:48Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:57:50Z
HickupHH3 marked the issue as satisfactory
π Selected for report: nuthan2x
Also found by: 0xE1, 0xblackskull, 0xgrbr, 0xvj, Greed, McToady, MidgarAudits, PetarTolev, Sabit, SovaSlava, SpicyMeatball, Timenov, Tychai0s, _eperezok, alexxander, btk, c0pp3rscr3w3r, favelanky, jesjupyter, josephdara, juancito, klau5, kutugu, lil_eth, merlinboii, pynschon, sandy, shaflow2, zaevlad
7.2869 USDC - $7.29
Address with hasStakerRole in Fighter farm can lock any token id he/she wants without any sort of permission from user
function updateFighterStaking(uint256 tokenId, bool stakingStatus) external { require(hasStakerRole[msg.sender]); fighterStaked[tokenId] = stakingStatus; if (stakingStatus) { emit Locked(tokenId); } else { emit Unlocked(tokenId); } }
The staker address can behave maliciously and lock any token id they want. The fact that once an address that has been added to hasStakerRole, can't be removed makes this worse as the bad actor cannot be removed from this abusive form of authority.
Staking affects an user's ability to transfer
function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); }
person with hasStakerRole, cannot be revoked from his authority
function addStaker(address newStaker) external { require(msg.sender == _ownerAddress); hasStakerRole[newStaker] = true; }
there are no other functions that modify staker. https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L139
manual analysis
Have both of these mitigations.
DoS
#0 - c4-pre-sort
2024-02-24T06:25:25Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T06:25:32Z
raymondfam marked the issue as duplicate of #20
#2 - HickupHH3
2024-03-05T10:06:05Z
Ignoring the privileged access with the hasStakerRole
, this touches a little on the inability to remove stakers
#3 - c4-judge
2024-03-05T10:06:09Z
HickupHH3 marked the issue as partial-25