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: 2/283
Findings: 8
Award: $6,523.25
π Selected for report: 1
π 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
safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data)
is not overridden to check _ableToTransfer()
in FighterFarm transfers.
Therefore it is possible to hold more than MAX_FIGHTERS_ALLOWED
fighters and transfer staked fighters.
This may enable the user to initiate more than 10 battles per day for a fighter, and may DoS updateBattleRecord()
.
FighterFarm inherits from ERC721 which contains three transfer functions. Two of these are overridden to include a require(_ableToTransfer(tokenId, to));
function _ableToTransfer(uint256 tokenId, address to) private view returns(bool) { return ( _isApprovedOrOwner(msg.sender, tokenId) && balanceOf(to) < MAX_FIGHTERS_ALLOWED && !fighterStaked[tokenId] ); }
prevents holding more than MAX_FIGHTERS_ALLOWED
fighters and prevents transfers of staked fighters.
But the public safeTransferFrom()
which includes the data
parameter is not overridden and therefore void of these new restrictions.
It is therefore possible to transfer fighters such that the recipient holds more than MAX_FIGHTERS_ALLOWED
fighters, and to transfer staked fighters.
A consequence of this is that the new owner has a fresh voltage and battles can therefore be initiated without limit.
Since points are added to or deducted from accumulatedPointsPerFighter
and accumulatedPointsPerAddress
in parallel at every win or loss, transferring the fighter to a new address, for which then accumulatedPointsPerAddress
is 0
(or just different) _addResultPoints()
will attempt to remove more points then possible, since the points are calculated based on the tokenId
rather than the fighterOwner
. This will then cause a DoS of updateBattleRecord()
due to underflow.
Override _transfer()
, _beforeTokenTransfer()
or _afterTokenTransfer()
instead.
DoS
#0 - c4-pre-sort
2024-02-23T06:02:22Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-23T06:02:34Z
raymondfam marked the issue as duplicate of #739
#2 - c4-judge
2024-03-11T02:58:11Z
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
User can freely set the DNA when redeeming mint passes such that the user has complete control over the choice of element
, weight
and physicalAttributes
of the minted fighter.
(Note that also iconsTypes
can be set freely by the user, which is probably by design, as well as fighterTypes
, which on the other hand does seem to go against the stated intention to make dendroids very rare and more difficult to obtain.)
FighterFarm.redeemMintPass()
simply takes a string[] calldata mintPassDnas
each element of which is then hashed as uint256(keccak256(abi.encode(mintPassDnas[i])))
and passed as the dna
to each fighter created via _createNewFighter()
.
In _createNewFighter()
this dna
is used to set (element, weight, newDna) = _createFighterBase(dna, fighterType)
which is calculated as
uint256 element = dna % numElements[generation[fighterType]]; uint256 weight = dna % 31 + 65; uint256 newDna = fighterType == 0 ? dna : uint256(fighterType);
numElements
is by default 3
.
This is then used to set the FighterPhysicalAttributes
via AiArenaHelper.createPhysicalAttributes()
. If fighterType == 0
and so dendroidBool == true
, this always returns the same result, otherwise the DNA may have an influence in the following way:
uint256 rarityRank = (dna / attributeToDnaDivisor[attributes[i]]) % 100; uint256 attributeIndex = dnaToIndex(generation, rarityRank, attributes[i]); finalAttributeProbabilityIndexes[i] = attributeIndex;
which is iterated 6 times.
attributeToDnaDivisor
are by default [2, 3, 5, 7, 11, 13]
so over the 6 attributes the 6 rarityRank
takes on at most (actually less) 2 * 3 * 5 * 7 * 11 * 13 * 100 = 3003000
different combinations.
Counting also the element
and weight
combinations we only need to brute force 3 * 31 * 3003000 = 279_279_000
different DNAs to get precisely the desired fighter.
Thus can the user completely bypass the pseudo-randomness imposed by the DNA mechanism.
Set the DNA in some other manner, ideally using Chainlink VRF, or by block.prevrandao
, or by incorporating the DNA in the mint passes themselves and have the signer of these set them.
Invalid Validation
#0 - c4-pre-sort
2024-02-22T08:22:15Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T08:22:22Z
raymondfam marked the issue as duplicate of #33
#2 - c4-pre-sort
2024-02-26T00:54:12Z
raymondfam marked the issue as duplicate of #1626
#3 - c4-judge
2024-03-06T03:36:31Z
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
By letting the user choose fighterType
for the token he is about to reroll, the user can influence the number of rerolls he can attempt, the fighter's new element
, weight
and physicalAttributes
.
FighterFarm.reRoll()
lets the user choose the fighterType
.
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] = ""; } }
fighterType
is used in three places.
The user can select fighterType
to maximize maxRerollsAllowed
.
In _createFighterBase()
the choice of fighterType
has the following effect.
uint256 element = dna % numElements[generation[fighterType]]; uint256 weight = dna % 31 + 65; uint256 newDna = fighterType == 0 ? dna : uint256(fighterType); return (element, weight, newDna);
That is, it may be chosen to get better attributes.
In createPhysicalAttributes()
the choice of generation[fighterType]
has the following effect.
if (dendroidBool) { return FighterOps.FighterPhysicalAttributes(99, 99, 99, 99, 99, 99); } else { uint256[] memory finalAttributeProbabilityIndexes = new uint[](attributes.length); uint256 attributesLength = attributes.length; for (uint8 i = 0; i < attributesLength; i++) { 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) ) { finalAttributeProbabilityIndexes[i] = 50; } else { uint256 rarityRank = (dna / attributeToDnaDivisor[attributes[i]]) % 100; uint256 attributeIndex = dnaToIndex(generation, rarityRank, attributes[i]); finalAttributeProbabilityIndexes[i] = attributeIndex; } } return FighterOps.FighterPhysicalAttributes( finalAttributeProbabilityIndexes[0], finalAttributeProbabilityIndexes[1], finalAttributeProbabilityIndexes[2], finalAttributeProbabilityIndexes[3], finalAttributeProbabilityIndexes[4], finalAttributeProbabilityIndexes[5] ); }
If dendroidBool == false
(in which case fighterType = 0
and newDna == dna
) the choice of generation
determines the attribute probabilities used to determine the attributeIndex
.
Since dendroidBool = fighterType == 1
let fighterType
be calculated based on the fighter's dendroidBool
. (Or refactor by completely removing fighterType
from the codebase to avoid this logical redundancy.)
Access Control
#0 - c4-pre-sort
2024-02-22T02:47:57Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T02:48:03Z
raymondfam marked the issue as duplicate of #306
#2 - c4-judge
2024-03-05T04:37:36Z
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: 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
Points can be gained even when nothing is staked, with no risk of loss. These points earn the user claimable NRN tokens.
If nothing is staked and the fighter wins then the number of points credited to it and its owner is points = stakingFactor[tokenId] * eloFactor
, where eloFactor > 0
is provided by the game server and stakingFactor[tokenId] = _getStakingFactor(tokenId, stakeAtRisk)
. _getStakingFactor()
returns at least 1
.
Thus points >= 1
always, and even an unstaked win will gain points.
If the unstaked fighter loses then either points are lost as normal, or
bool success = _neuronInstance.transfer(_stakeAtRiskAddress, curStakeAtRisk); if (success) { _stakeAtRiskInstance.updateAtRiskRecords(curStakeAtRisk, tokenId, fighterOwner); amountStaked[tokenId] -= curStakeAtRisk; }
is executed, but curStakeAtRisk = (bpsLostPerLoss * (amountStaked[tokenId] + stakeAtRisk)) / 10**4
is 0
since nothing has been staked so nothing is transferred and nothing is lost.
These points can then be converted to NRN through claimNRN()
.
This is thus a foolproof way to earn arbitrary amounts of NRN. Note also that since nothing is staked the fighter can be transferred freely and when it is the new owner has a fresh voltage and battles can therefore be initiated without limit.
Let the range of _getStakingFactor()
extend down to 0
.
Context
#0 - c4-pre-sort
2024-02-22T17:27:11Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-22T17:27:21Z
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:39:47Z
HickupHH3 marked the issue as satisfactory
π Selected for report: haxatron
Also found by: 0xAlix2, 0xCiphky, 0xStriker, 0xaghas, 0xbranded, 0xlamide, 0xmystery, 0xvj, 14si2o_Flint, Aamir, AgileJune, Aymen0909, DanielArmstrong, DarkTower, Draiakoo, EagleSecurity, Giorgio, Krace, KupiaSec, MidgarAudits, MrPotatoMagic, PoeAudits, Ryonen, SpicyMeatball, Topmark, Tychai0s, VAD37, Varun_05, VrONTg, WoolCentaur, _eperezok, alexxander, alexzoid, aslanbek, blutorque, btk, cartlex_, d3e4, devblixt, evmboi32, fnanni, immeas, jesjupyter, juancito, ke1caM, klau5, ktg, lil_eth, merlinboii, nuthan2x, peter, petro_1912, pkqs90, pynschon, radin100, sandy, sashik_eth, shaka, sl1, soliditywala, t0x1c, ubl4nk, visualbits, vnavascues
0.5612 USDC - $0.56
When generation
is incremented all fighters will be created with element = 0
, except those minted from the merging pool, giving those an unfair advantage.
In FighterFarm.sol numElements
is a mapping of the number elements by generation.
numElements[0] = 3
by default in the constructor.
It's only use is in _createFighterBase()
to set element
from dna
: uint256 element = dna % numElements[generation[fighterType]];
.
generation
is initialized as [0,0]
, but can be incremented by the owner in incrementGeneration()
. It cannot be decremented.
If it is incremented then numElements[generation[fighterType]]
will return 0
and element
will therefore invariably be set to 0
.
element
is set in this way whenever a fighter is created via claimFighters()
or redeemMintPass()
, or when changed via reRoll()
. Only mintFromMergingPool()
allows setting a custom element
. This gives an unfair advantage to fighters minted from the merging pool, allowing them to have the element which has the relative advantage over element 0
.
Implement functionality which sets numElements
, independently or when generation
is incremented.
Context
#0 - c4-pre-sort
2024-02-22T19:12:46Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-22T19:12:53Z
raymondfam marked the issue as duplicate of #45
#2 - c4-judge
2024-03-07T06:53:30Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-08T03:20:33Z
HickupHH3 marked the issue as partial-50
6516.2249 USDC - $6,516.22
Only a tiny fraction, $0.0002427 \%$, of all rarity rank combinations are possible. The probability distribution of the possible combinations (assuming the DNA is uniformly random) is not uniform: $24 \%$ of combinations are twice as likely as the rest.
AiArenaHelper.createPhysicalAttributes()
may set six finalAttributeProbabilityIndexes
using the following calculation.
uint256 rarityRank = (dna / attributeToDnaDivisor[attributes[i]]) % 100; uint256 attributeIndex = dnaToIndex(generation, rarityRank, attributes[i]); finalAttributeProbabilityIndexes[i] = attributeIndex;
attributeToDnaDivisor
is by default [2, 3, 5, 7, 11, 13]
. Each rarityRank
by itself is in the range 0..99
, and ostensibly the total number of combinations of the six rarityRank
s should then be $100^6$. However, only $2,427,000$ different combinations are possible, which is only $0.0002427 \%$ of all combinations that should be possible.
As a function of dna
the vector of the six rarityRank
s repeats every $3,003,000$ integers ($=2 \cdot 3 \cdot 5 \cdot 7 \cdot 11 \cdot 13 \cdot 100$). But it turns out that $576,000$ of these appear twice. For example, a dna
of 0
or 1
yield the same combination of rarityRank
s ($[0,0,0,0,0,0]$), as do 16
and 17
($[8,5,3,2,1,1]$), and 22
and 23
($[11,7,4,3,2,1]$), etc.
That is, about $24 \%$ of the combinations are twice as likely as the rest.
The rarityRank
s are input to dnaToIndex()
which then will also be biased and restricted in output (depending on the attribute probabilities).
Extract multiple small random numbers by repeatedly taking the modulo and dividing. I.e.
- uint256 rarityRank = (dna / attributeToDnaDivisor[attributes[i]]) % 100; + uint256 rarityRank = dna % 100; + dna /= 100;
Math
#0 - c4-pre-sort
2024-02-25T04:37:15Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-25T04:37:30Z
raymondfam marked the issue as duplicate of #440
#2 - c4-judge
2024-03-15T06:33:55Z
HickupHH3 marked the issue as selected for report
#3 - HickupHH3
2024-03-15T06:34:48Z
agree that rarityRank
determination isn't uniformly random.
#4 - c4-judge
2024-03-20T03:25:53Z
HickupHH3 marked issue #1456 as primary and marked this issue as a duplicate of 1456
#5 - c4-judge
2024-03-20T03:26:02Z
HickupHH3 changed the severity to 3 (High Risk)
#6 - c4-judge
2024-03-20T07:23:03Z
HickupHH3 marked the issue as satisfactory
#7 - c4-judge
2024-03-22T13:29:00Z
HickupHH3 changed the severity to 2 (Med Risk)
#8 - c4-judge
2024-03-22T13:29:18Z
HickupHH3 marked the issue as not a duplicate
#9 - c4-judge
2024-03-22T13:29:24Z
HickupHH3 marked the issue as primary issue
#10 - c4-judge
2024-03-22T13:29:28Z
HickupHH3 marked the issue as selected for report
π Selected for report: ktg
Also found by: 0xCiphky, 0xDetermination, 0xRiO, 0xWallSecurity, 0xlemon, 0xvj, AlexCzm, BARW, Blank_Space, Draiakoo, FloatingPragma, Giorgio, Matue, McToady, MrPotatoMagic, Silvermist, SpicyMeatball, Tendency, Topmark, Tumelo_Crypto, _eperezok, agadzhalov, ahmedaghadi, alexxander, aslanbek, cats, d3e4, denzi_, dutra, evmboi32, fnanni, givn, handsomegiraffe, haxatron, immeas, juancito, ke1caM, kiqo, klau5, krikolkk, niser93, peter, petro_1912, pkqs90, rspadi, sl1, stakog, visualbits, vnavascues, yotov721
1.876 USDC - $1.88
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L503-L504 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/MergingPool.sol#L142
Claiming rewards in the merging pool, i.e. minting fighters from the merging pool, fails to perform checks that the custom attributes are valid numbers.
A fighter's element
and weight
are numbers, each representing one of a small set of features. These are set by FighterFarm._createNewFighter()
using the customAttributes
parameter. In most cases they are there calculated by FighterFarm._createFighterBase()
(by passing customAttributes[0] = 100
) which returns them in the ranges 0..2
(by default at least) and 65..95
respectively.
However, when creating a fighter via mintFromMergingPool()
(by the user's calling MergingPool.claimRewards()
) the user can set his own customAttributes
and no check is performed on these that they are within the valid ranges (provided customAttributes[0] != 100
).
Perform the requisite checks in _createNewFighter()
or mintFromMergingPool()
.
Invalid Validation
#0 - c4-pre-sort
2024-02-24T09:12:29Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2024-02-24T09:12:39Z
raymondfam marked the issue as duplicate of #226
#2 - c4-judge
2024-03-11T10:31:19Z
HickupHH3 marked the issue as satisfactory
π Selected for report: klau5
Also found by: 0rpse, 0xBinChook, 0xDetermination, 0xGreyWolf, 0xLogos, 0xWallSecurity, 0xaghas, 0xgrbr, 0xkaju, 0xlyov, AlexCzm, BARW, Blank_Space, BoRonGod, Daniel526, DanielArmstrong, Draiakoo, FloatingPragma, Giorgio, Greed, Jorgect, Matue, McToady, MidgarAudits, Nyxaris, PUSH0, PedroZurdo, Pelz, PoeAudits, Silvermist, SpicyMeatball, Tekken, Tricko, Tumelo_Crypto, VAD37, WoolCentaur, Zac, alexzoid, andywer, aslanbek, bgsmallerbear, cats, d3e4, desaperh, dimulski, dutra, erosjohn, evmboi32, favelanky, fnanni, forkforkdog, gesha17, givn, grearlake, haxatron, honey-k12, iamandreiski, immeas, juancito, kaveyjoe, ke1caM, kiqo, klau5, korok, lil_eth, lsaudit, n0kto, ni8mare, niser93, pa6kuda, peanuts, peter, shaka, sl1, soliditywala, solmaxis69, t0x1c, tallo, thank_you, tpiliposian, visualbits, vnavascues, web3pwn, yotov721
0.0352 USDC - $0.04
https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L214 https://github.com/code-423n4/2024-02-ai-arena/blob/cd1a0e6d1b40168657d1aaee8223dc050e15f8cc/src/FighterFarm.sol#L379
The user can transfer his token to an address generated by brute force to enable him to reRoll()
a fighter with optimal DNA and thus optimal attributes. Or similarly possibly get a signed message for this address to claimFighters()
.
In FighterFarm.sol msg.sender
is used to determine the DNA of the created fighter in claimFighters()
and reRoll()
. The DNA is used as the seed to a pseudo-random selection of the fighter's traits and attributes.
An address can be generated by brute force which would give a DNA which generates the desired and most advantageous attributes. The user can then simply get a signed message for this address or transfer his token to this address to be able to create a fighter with these attributes.
Whether this exploit is possible in the case of claimFighters()
depends on the conditions on which the delegated signer signs the message, but no such obstacle applies to the case of reRoll()
.
Use a seed which is not as easily influenceable by the user, such as Chainlink VRF, by block.prevrandao
, or set by the delegated signer.
Context
#0 - c4-pre-sort
2024-02-24T02:04:34Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T02:04:41Z
raymondfam marked the issue as duplicate of #53
#2 - c4-judge
2024-03-06T03:54:09Z
HickupHH3 marked the issue as satisfactory
#3 - c4-judge
2024-03-15T02:10:54Z
HickupHH3 changed the severity to 2 (Med Risk)
#4 - c4-judge
2024-03-22T04:23:11Z
HickupHH3 marked the issue as duplicate of #376
π Selected for report: klau5
Also found by: 0rpse, 0xBinChook, 0xDetermination, 0xGreyWolf, 0xLogos, 0xWallSecurity, 0xaghas, 0xgrbr, 0xkaju, 0xlyov, AlexCzm, BARW, Blank_Space, BoRonGod, Daniel526, DanielArmstrong, Draiakoo, FloatingPragma, Giorgio, Greed, Jorgect, Matue, McToady, MidgarAudits, Nyxaris, PUSH0, PedroZurdo, Pelz, PoeAudits, Silvermist, SpicyMeatball, Tekken, Tricko, Tumelo_Crypto, VAD37, WoolCentaur, Zac, alexzoid, andywer, aslanbek, bgsmallerbear, cats, d3e4, desaperh, dimulski, dutra, erosjohn, evmboi32, favelanky, fnanni, forkforkdog, gesha17, givn, grearlake, haxatron, honey-k12, iamandreiski, immeas, juancito, kaveyjoe, ke1caM, kiqo, klau5, korok, lil_eth, lsaudit, n0kto, ni8mare, niser93, pa6kuda, peanuts, peter, shaka, sl1, soliditywala, solmaxis69, t0x1c, tallo, thank_you, tpiliposian, visualbits, vnavascues, web3pwn, yotov721
0.0352 USDC - $0.04
The best out of the entire sequence of reroll results can be selected, instead of having to take a chance at each reroll attempt.
FighterFarm.reRoll()
allows the holder of a fighter to pseudo-randomly set new traits for his fighter, for a cost, a limited number of times.
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] = ""; } }
The randomness is created in _createFighterBase()
which is seeded by uint256 dna = uint256(keccak256(abi.encode(msg.sender, tokenId, numRerolls[tokenId])))
. msg.sender
, tokenId
and numRerolls[tokenId]
are knowable in advance for all rerolls attempted by the user. This means that he can precalculate the results of all of his allowed rerolls and select the best one, which gives him an advantage over the regular user who would, as is intended, have to submit to the mercy of each random roll.
Set the DNA in some other unpredictable manner, ideally using Chainlink VRF, or by block.prevrandao
.
Other
#0 - c4-pre-sort
2024-02-24T02:04:52Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2024-02-24T02:05:02Z
raymondfam marked the issue as duplicate of #53
#2 - c4-judge
2024-03-06T03:49:26Z
HickupHH3 changed the severity to 3 (High Risk)
#3 - c4-judge
2024-03-06T03:54:11Z
HickupHH3 marked the issue as satisfactory
#4 - c4-judge
2024-03-15T02:10:54Z
HickupHH3 changed the severity to 2 (Med Risk)
#5 - c4-judge
2024-03-22T04:23:12Z
HickupHH3 marked the issue as duplicate of #376