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: 192/283
Findings: 2
Award: $3.71
π Selected for report: 1
π Solo Findings: 0
π 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.6467 USDC - $1.65
The function redeemMintPass allows burning multiple mint passes in exchange for fighters' NFTs. It is mentioned by the sponsor that the player should not have a choice of customizing the fighters' properties and their type. However, nothing prevents a player from:
uint8[] fighterTypes
of values 1
to mint fighters of types Dendroid.dna
provided led to minting fighters with rare physical attributes, copying those Dnas and passing them to the redeemMinPass to mint fighters with low rarity attributes. That is because creating physical attributes is deterministic, so providing the same inputs leads to generating a fighter with the same attributes.This issue has two major impacts:
For someone having valid mint passes, he calls the function redeemMintPass providing fighterTypes
array of values 1. For each mint pass, the inner function _createNewFighter will be called passing the value 1 as fighterType
argument which corresponds to Dendroid, a new fighter of type dendroid will be minted for the caller.
function test_redeeming_dendroid_fighters_easily() public { uint8[2] memory numToMint = [1, 0]; bytes memory signature = abi.encodePacked( hex"20d5c3e5c6b1457ee95bb5ba0cbf35d70789bad27d94902c67ec738d18f665d84e316edf9b23c154054c7824bba508230449ee98970d7c8b25cc07f3918369481c" ); string[] memory _tokenURIs = new string[](1); _tokenURIs[0] = "ipfs://bafybeiaatcgqvzvz3wrjiqmz2ivcu2c5sqxgipv5w2hzy4pdlw7hfox42m"; // first i have to mint an nft from the mintpass contract assertEq(_mintPassContract.mintingPaused(), false); _mintPassContract.claimMintPass(numToMint, signature, _tokenURIs); assertEq(_mintPassContract.balanceOf(_ownerAddress), 1); assertEq(_mintPassContract.ownerOf(1), _ownerAddress); // once owning one i can then redeem it for a fighter uint256[] memory _mintpassIdsToBurn = new uint256[](1); string[] memory _mintPassDNAs = new string[](1); uint8[] memory _fighterTypes = new uint8[](1); uint8[] memory _iconsTypes = new uint8[](1); string[] memory _neuralNetHashes = new string[](1); string[] memory _modelTypes = new string[](1); _mintpassIdsToBurn[0] = 1; _mintPassDNAs[0] = "dna"; _fighterTypes[0] = 1; // @audit Notice that I can provide value 1 which corresponds to Dendroid type _neuralNetHashes[0] = "neuralnethash"; _modelTypes[0] = "original"; _iconsTypes[0] = 1; // approve the fighterfarm contract to burn the mintpass _mintPassContract.approve(address(_fighterFarmContract), 1); _fighterFarmContract.redeemMintPass( _mintpassIdsToBurn, _fighterTypes, _iconsTypes, _mintPassDNAs, _neuralNetHashes, _modelTypes ); // check balance to see if we successfully redeemed the mintpass for a fighter assertEq(_fighterFarmContract.balanceOf(_ownerAddress), 1); }
Ran 1 test for test/FighterFarm.t.sol:FighterFarmTest [PASS] test_redeeming_dendroid_fighters_easily() (gas: 578678) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.56ms Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests)
The player can also inspect previous transactions that minted a fighter with rare attributes, copy the provided mintPassDnas
and provide them as argument in the redeemMintPass
. The _createNewFighter
function calls AiArenaHelper
to create the physical attributes for the fighter. The probability of attributes is deterministic and since the player provided dna
that already led to a fighter with rare attributes, his fighter will also have rare attributes.
Manual Review
The main issue is that the mint pass token is not tied to the fighter properties that the player should claim and the player has complete freedom of the inputs. Consider implementing a signature mechanism that prevents the player from changing the fighter's properties like implemented in claimFighters
Invalid Validation
#0 - c4-pre-sort
2024-02-22T07:31:31Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T07:31:38Z
raymondfam marked the issue as duplicate of #33
#2 - c4-pre-sort
2024-02-26T00:53:02Z
raymondfam marked the issue as duplicate of #1626
#3 - c4-judge
2024-03-05T10:55:51Z
HickupHH3 marked the issue as satisfactory
#4 - c4-judge
2024-03-06T03:29:01Z
HickupHH3 marked the issue as selected for report
#5 - SonnyCastro
2024-03-18T17:56:34Z
Mitigated here
#6 - c4-sponsor
2024-03-25T14:19:14Z
brandinho (sponsor) confirmed
π Selected for report: t0x1c
Also found by: 0rpse, 0xAadi, 0xBinChook, 0xCiphky, 0xDetermination, 14si2o_Flint, AC000123, Aamir, Abdessamed, Blank_Space, CodeWasp, DanielArmstrong, DarkTower, Draiakoo, Honour, Kalogerone, Krace, McToady, Merulez99, MidgarAudits, MrPotatoMagic, PedroZurdo, Silvermist, Tychai0s, VAD37, Velislav4o, VrONTg, WoolCentaur, YouCrossTheLineAlfie, ZanyBonzy, alexxander, aslanbek, btk, csanuragjain, d3e4, dimulski, djxploit, erosjohn, evmboi32, fnanni, forgebyola, forkforkdog, handsomegiraffe, immeas, israeladelaja, juancito, ktg, n0kto, neocrao, ni8mare, okolicodes, peanuts, petro_1912, shaflow2, shaka, swizz, ubermensch, ubl4nk, yotov721
2.0593 USDC - $2.06
After each battle, updateBattleRecord is called by the server to update battle records on-chain for a given fighter. In case of staking NRN or having some NRN at risk, _addResultPoints will be called, and it will calculate the potential amount of NRNs to put at risk in case of losing the battle or to retrieve from the stake-at-risk in case of winning in this line. The instruction curStakeAtRisk = (bpsLostPerLoss * (amountStaked[tokenId] + stakeAtRisk)) / 10**4;
is susceptible to rounding issues if the amount staked is small. In a situation where the amount staked is too small, the currStakeAtRisk
calculation will be rounded down to zero. So even if a fighter loses a battle, 0 NRNs will be transferred to the StakeAtRisk contract.
stakingFactor
(which is rounded up to 1 if it is 0) multiplied by eloFactor
as shown here.Consider the following scenario:
30 NRN
_addResultPoints
comes to this line attempting to calculate the amount of NRNs to put at risk. However, curStakeAtRisk
will be rounded to zero: curStakeAtRisk = (10 * (30 + 0)) / 10 ** 4 = 0
. As a result, 0 amounts of NRN will be transferred to the StakeAtRisk
contractfunction test_staking_little_amount_causes_to_not_having_a_risk_of_having_NRN_tokens_at_risk_but_earning_good_points() public { address player = vm.addr(3); _mintFromMergingPool(player); uint8 tokenId = 0; _fundUserWith4kNeuronByTreasury(player); vm.prank(player); // staking a small amount of $NRN _rankedBattleContract.stakeNRN(30, 0); assertEq(_rankedBattleContract.amountStaked(0), 30); // The battle ends, the fighter lost the battle vm.prank(address(_GAME_SERVER_ADDRESS)); _rankedBattleContract.updateBattleRecord(tokenId, 50, 2, 3500, true); uint256 nrnAtRisk = _stakeAtRiskContract.getStakeAtRisk(tokenId); assertEq(0, nrnAtRisk); // @audit : 0 NRN at risk even when losing battles }
Ran 1 test for test/RankedBattle.t.sol:RankedBattleTest [PASS] test_staking_little_amount_causes_to_not_having_a_risk_of_having_NRN_tokens_at_risk_but_earning_good_points() (gas: 776640) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.77ms Ran 1 test suite: 1 tests passed, 0 failed, 0 skipped (1 total tests)
Manual Review
The main reason for this issue is that a user can stake a very small amount of NRN causing some mathematical calculations to round to zero, consider checking for a minimum value of NRN to stake.
Other
#0 - c4-pre-sort
2024-02-22T15:40:06Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-22T15:40:13Z
raymondfam marked the issue as duplicate of #38
#2 - c4-judge
2024-03-07T02:49:49Z
HickupHH3 changed the severity to 2 (Med Risk)
#3 - c4-judge
2024-03-07T02:58:22Z
HickupHH3 changed the severity to 3 (High Risk)
#4 - c4-judge
2024-03-07T03:14:25Z
HickupHH3 marked the issue as satisfactory