Platform: Code4rena
Start Date: 13/12/2023
Pot Size: $36,500 USDC
Total HM: 18
Participants: 110
Period: 8 days
Judge: 0xTheC0der
Id: 311
League: ETH
Rank: 57/110
Findings: 1
Award: $51.14
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: ast3ros
Also found by: 0xG0P1, KupiaSec, Pechenite, cccz, deth, dimulski, mojito_auditor, osmanozdemir1, peanuts, rvierdiiev, zhaojie
51.1381 USDC - $51.14
When createPiece()
in CultureIndex.sol
is called it takes the totalSupply ot tokens in the block
newPiece.totalVotesSupply = _calculateVoteWeight( erc20VotingToken.totalSupply(), erc721VotingToken.totalSupply() );
and calculates the totalVoteSupply by calling the _calculateVoteWeight()
function. Later on the totalVoteSupply is used to calculate the quroumVotes needed for the piece to be able to be auctioned newPiece.quorumVotes = (quorumVotesBPS * newPiece.totalVotesSupply) / 10_000;
. A malicious creator can back run the createPice transaction and mint himself NontrasferableERC20Votes in the same block the piece is created. Thus the newly minted votes won't count towards the quorumVotes ratio, but he will still be able to vote for that piece because he minted tokens in the same block which the piece was created. For example if at the time of piece creation the tokens supply is 10 and quorumVotesBPS is 60%, that means for the piece to be eligible for auction is should have at least 6 token votes. Now if a malicious piece creator back runs the transaction and mint 6 tokens he can guarantee that his piece will successfully pass the quorumVotes requirement. However his 6 mitned tokens after a piece is created shouldn't be eligible for voting.As well as the correct quorum should be 60% * 16 = 3.75(I have used small integers to better ilustrate the problem, 3.75 wont round down as we are dealing with 1e18 integers, also these are just example values). This can be abused by malicious creators especially in the beginning as they are less total votes, if he is successful in passing the quorum in such a way and his piece is the top voted he will receive the rewards associated with a piece being successfully auctioned. This also breaks an Invarian specified by the protocol : Only snapshotted (at art piece creation block) vote weights should be able to update the total vote weight of the art piece. eg: If you received votes after snapshot date on the art piece, you should have 0 votes.
Keep in mind that transactions are ordered firstly by nonce in the MEV pool, so back running a transaction is not that hard when you first create a createPiece()
transaction followed by calling buyToken()
in the ERC20TokenEmitter.sol
contract.
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
Manual review
Instead of
newPiece.totalVotesSupply = _calculateVoteWeight( erc20VotingToken.totalSupply(), erc721VotingToken.totalSupply() );
use
newPiece.totalVotesSupply = _calculateVoteWeight( ec20VotingToken.getPastTotalSupply(block.number -1), erc721VotingToken.getPastTotalSupply(block.number -1) );
Timing
#0 - c4-pre-sort
2023-12-23T03:06:07Z
raymondfam marked the issue as sufficient quality report
#1 - c4-pre-sort
2023-12-23T03:06:19Z
raymondfam marked the issue as duplicate of #260
#2 - c4-pre-sort
2023-12-24T05:40:06Z
raymondfam marked the issue as duplicate of #409
#3 - c4-judge
2024-01-05T22:40:57Z
MarioPoneder marked the issue as satisfactory