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: 50/110
Findings: 2
Award: $80.96
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: osmanozdemir1
Also found by: 0xDING99YA, BARW, Brenzee, DanielArmstrong, KupiaSec, SpicyMeatball, bart1e, deepplus, haxatron, rouhsamad, rvierdiiev
51.1381 USDC - $51.14
Token quote calculations will be incorrect because of inflated bookkeeping of emittedTokenWad
ERC20TokenEmitter.sol
contract uses vrgdac.yToX
function to calculate token quote for ETH and vrgdac.xToY
to calculate token quote for payment. Both of these functions rely on emittedTokenWad
.
function getTokenQuoteForEther(uint256 etherAmount) public view returns (int gainedX) { ... return vrgdac.yToX({ timeSinceStart: toDaysWadUnsafe(block.timestamp - startTime), sold: emittedTokenWad, amount: int(etherAmount) }); } function getTokenQuoteForPayment(uint256 paymentAmount) external view returns (int gainedX) { ... return vrgdac.yToX({ timeSinceStart: toDaysWadUnsafe(block.timestamp - startTime), sold: emittedTokenWad, amount: int(((paymentAmount - computeTotalReward(paymentAmount)) * (10_000 - creatorRateBps)) / 10_000) }); }
emittedTokenWad
is the value of how many ERC20 vote tokens are minted. emittedTokenWad
is incremented in ERC20TokenEmitter.buyToken
function - this value is incremented by the amount of tokens that are minted for buyers and if totalTokensForCreators > 0
is true, it is also incremented by the amount of tokens that would have been minted for the creators:
emittedTokenWad += totalTokensForBuyers; if (totalTokensForCreators > 0) emittedTokenWad += totalTokensForCreators;
But the issue is that if totalTokensForCreators > 0
is true, it does not always mean that the tokens for creators will be minted, because of the "if" statement line 201:
if (totalTokensForCreators > 0 && creatorsAddress != address(0)) { _mint(creatorsAddress, uint256(totalTokensForCreators)); }
Meaning that if the totalTokensForCreators > 0
and creatorsAddress === address(0)
, no tokens will be minted for the creators, but emittedTokenWad
will still be incremented by the amount of tokens that would have been minted for the creators, which will cause incorrect calculations of token quotes.
Manual Review
Move the increment of emittedTokenWad
in the "if" statement where the tokens are minted for creators. This way emittedTokenWad
will only be incremented if the tokens are actually minted.
if (totalTokensForCreators > 0 && creatorsAddress != address(0)) { emittedTokenWad += totalTokensForCreators; _mint(creatorsAddress, uint256(totalTokensForCreators)); }
Context
#0 - c4-pre-sort
2023-12-21T23:32:31Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-12-21T23:33:48Z
raymondfam marked the issue as duplicate of #25
#2 - c4-pre-sort
2023-12-22T03:29:48Z
raymondfam marked the issue as sufficient quality report
#3 - c4-pre-sort
2023-12-22T03:30:48Z
raymondfam marked the issue as duplicate of #194
#4 - c4-judge
2024-01-06T13:52:53Z
MarioPoneder marked the issue as satisfactory
🌟 Selected for report: ayden
Also found by: 0xCiphky, AS, Brenzee, Inference, KupiaSec, SpicyMeatball, ast3ros, ayden, fnanni, hals, ktg, mahdirostami, nmirchev8, rvierdiiev, wangxx2026
29.8238 USDC - $29.82
Auction will never be settled if protocol decides that the creators will only receive ERC20 votes and no ETH. (entropyRateBps = 0
)
When auction is settled, creators of the art piece get share from the auction amount as rewards. There are 2 type of rewards:
Protocol can control the split between those rewards by setting entropyRateBps
value. The closer the entropyRateBps
value is to 0, the more ERC20 vote tokens creators will receive and the less ETH they will receive and vice versa. If entropyRateBps = 0
, creators will only receive ERC20 vote tokens and no ETH.
ERC20 vote tokens for creators are minted via erc20TokenEmitter.buyToken
function.
if (creatorsShare > ethPaidToCreators) { creatorTokensEmitted = erc20TokenEmitter.buyToken{ value: creatorsShare - ethPaidToCreators }( vrgdaReceivers, vrgdaSplits, IERC20TokenEmitter.ProtocolRewardAddresses({ builder: address(0), purchaseReferral: address(0), deployer: deployer }) ); }
The issue is that the arrays vrgdaSplits
and vrgdaReceivers
that are required for erc20TokenEmitter.buyToken
function are not set if entropyRateBps = 0
. Line 383
//Build arrays for erc20TokenEmitter.buyToken uint256[] memory vrgdaSplits = new uint256[](numCreators); address[] memory vrgdaReceivers = new address[](numCreators); ... //Transfer creator's share to the creator, for each creator, and build arrays for erc20TokenEmitter.buyToken if (creatorsShare > 0 && entropyRateBps > 0) { for (uint256 i = 0; i < numCreators; i++) { ICultureIndex.CreatorBps memory creator = verbs.getArtPieceById(_auction.verbId).creators[i]; vrgdaReceivers[i] = creator.creator; vrgdaSplits[i] = creator.bps; ... } }
This will cause the erc20TokenEmitter.buyToken
function to fail, because the function requires vrgdaSplits
to add up to 100% (10_000).
for (uint256 i = 0; i < addresses.length; i++) { if (totalTokensForBuyers > 0) { // transfer tokens to address _mint(addresses[i], uint256((totalTokensForBuyers * int(basisPointSplits[i])) / 10_000)); } bpsSum += basisPointSplits[i]; } require(bpsSum == 10_000, "bps must add up to 10_000");
As a result, the auction will never be settled if protocol decides that the creators will only receive ERC20 votes as rewards.
Manual review
Update the code to the following:
if (creatorsShare > 0) { for (uint256 i = 0; i < numCreators; i++) { ICultureIndex.CreatorBps memory creator = verbs.getArtPieceById(_auction.verbId).creators[i]; vrgdaReceivers[i] = creator.creator; vrgdaSplits[i] = creator.bps; if (entropyRateBps == 0) continue; //Calculate paymentAmount for specific creator based on BPS splits - same as multiplying by creatorDirectPayment uint256 paymentAmount = (creatorsShare * entropyRateBps * creator.bps) / (10_000 * 10_000); ethPaidToCreators += paymentAmount; _safeTransferETHWithFallback(creator.creator, paymentAmount); } }
This will ensure that the auction can be settled with entropyRateBps = 0
.
Context
#0 - c4-pre-sort
2023-12-21T23:38:18Z
raymondfam marked the issue as insufficient quality report
#1 - c4-pre-sort
2023-12-21T23:38:34Z
raymondfam marked the issue as duplicate of #24
#2 - c4-pre-sort
2023-12-23T02:21:50Z
raymondfam marked the issue as sufficient quality report
#3 - c4-pre-sort
2023-12-23T02:48:35Z
raymondfam marked the issue as duplicate of #335
#4 - c4-judge
2024-01-06T01:25:32Z
MarioPoneder changed the severity to QA (Quality Assurance)
#5 - c4-judge
2024-01-06T15:11:22Z
This previously downgraded issue has been upgraded by MarioPoneder
#6 - c4-judge
2024-01-06T15:12:21Z
MarioPoneder marked the issue as duplicate of #160
#7 - c4-judge
2024-01-06T15:13:22Z
MarioPoneder marked the issue as satisfactory