Platform: Code4rena
Start Date: 30/03/2022
Pot Size: $30,000 USDC
Total HM: 21
Participants: 38
Period: 3 days
Judge: Michael De Luca
Total Solo HM: 10
Id: 104
League: ETH
Rank: 13/38
Findings: 2
Award: $890.06
🌟 Selected for report: 1
🚀 Solo Findings: 1
🌟 Selected for report: kirk-baird
Also found by: 0xDjango, Dravee, Ruhum, TomFrenchBlockchain, WatchPug, defsec, hubble, hyh, leastwood, minhquanym
85.0569 USDC - $85.06
https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/royalty-vault/contracts/RoyaltyVaultFactory.sol#L57-L59 https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/royalty-vault/contracts/RoyaltyVault.sol#L67-L70 https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/royalty-vault/contracts/RoyaltyVault.sol#L40 https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/core-contracts/contracts/CoreCollection.sol#L307
A ProxyVault
's platformFee
may be set to arbitrary values resulting in either a DOS attack or claiming 100% of any royalties received as platform fees. A DOS attack would freeze all affected NFTs as well as the royalties.
ProxyVault.sendToSplitter
uses the platformFee
variable to determine how much of the accrued royalties should be kept as a platform fee and how much to release to the Splitter
(the collection's team).
A platformFee
of 100% is represented by platformFee = 10000
. Clearly if platformFee > 10000
the contract will try to send more platform fees than it has received in royalties, resulting in all calls to ProxyVault.sendToSplitter
reverting.
If we look at the functions which allow us to update platformFee
we can see there's no input validation preventing someone from giving a value greater than 10000.
Notably all transfers of a collection will call ProxyVault.sendToSplitter
so as well as preventing the team from collecting royalties, all NFTs in the collection will be frozen.
A malicious admin could then DOS a collection completely with a single transaction. They could also simply prevent the team from collecting any royalties at all by reserving it as a platform fee.
Choose a sensible maximum platform fee (well below 100%) and revert someone attempts to set it above that value.
#0 - liveactionllama
2022-03-31T16:56:24Z
Adding note from warden TomFrenchBlockchain:
It seems that
RoyaltyVaultFactory
in the Joyn contest is actual a test contract despite looking like the main factory for this contract and is out of scope. This impacts one of my findings but the finding is STILL IN SCOPE.On my finding "Platform fee may be set at or greater than 100% to claim all royalties or DOS a collection" I used
RoyaltyVaultFactory
as the example to show that the platform fee can be set greater than 100%. It's important to note that the real contract which will be deployingProxyVaults
(SplitFactory
) has the same issue and so the finding SHOULD NOT be marked invalid.
#1 - sofianeOuafir
2022-04-14T20:31:17Z
duplicate of #9
🌟 Selected for report: TomFrenchBlockchain
805.0047 USDC - $805.00
https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/core-contracts/contracts/CoreCollection.sol#L161-L163 https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/core-contracts/contracts/CoreCollection.sol#L307 https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/royalty-vault/contracts/RoyaltyVault.sol#L43-L50 https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/splits/contracts/Splitter.sol#L149-L169
Collection owners will likely lose money by claiming fees unless the fees from a single NFT sale outweighs the cost of claiming it (not guaranteed).
Consider a new Collection
with a RoyaltyVault
and Splitter
set and a nonzero mint fee.
When calling mintToken
, the _handlePayment
function is called
https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/core-contracts/contracts/CoreCollection.sol#L161-L163
This will transfer the minting fee to the RoyaltyVault
contract.
On each transfer of an NFT within the collection (for instance in the _mint
call which occurs directly after calling _handlePayment
), the Collection
contract will call sendToSplitter
on the RoyaltyVault
:
https://github.com/code-423n4/2022-03-joyn/blob/c9297ccd925ebb2c44dbc6eaa3effd8db5d2368a/core-contracts/contracts/CoreCollection.sol#L307
This function will forward the collection owners' portion of the minting on to the Splitter
contract but another important thing to note is that we call Splitter.incrementWindow
.
This results in the fees newly deposited into the Splitter
contract being held in a separate "window" to the fees from previous or later mints and need to be claimed separately. Remember that this process happens on every NFT sale so the only funds which will be held in this window will be the minting fees for this particular mint.
From this we can see that the claim
function will only claim the fraction of the fees which are owed to the caller from a single NFT mint.
Note that we can attempt to claim from multiple windows in a single transaction using claimForAllWindow
but as the name suggests it performs an unbounded loop trying to claim all previous windows (even ones which have already been claimed!) and it is likely that with a new window for every NFT sold this function will exceed the gas limit (consider an 10k token collection resulting in trying to do 10k SSTOREs at 20k gas each.), leaving us to claim each window individually with claim
.
We're then forced to claim the royalties from each NFT sold one by one, having to send huge numbers of calls to claim
incurring the base transaction cost many times over and performing many ERC20 transfers when we could have just performed one.
Compound on this that this needs to be repeated by everyone included in the split, multiplying the costs of claiming.
Medium risk as it's gas inefficiency to the point of significant value leakage where collection owners will lose a large fraction of their royalties.
It doesn't seem like the "window" mechanism does anything except raise gas costs to the extent that it will be very difficult to withdraw fees so it should be removed.
#0 - sofianeOuafir
2022-04-14T19:26:20Z
This is a very fair point and we'll consider fixing this issue.
#1 - deluca-mike
2022-04-22T04:11:34Z
Aside from the very valid points made by the warden, it seems that the heavy functions called from the _beforeTokenTransfer
also create a lot of friction for the NFT owners. Might make more sense to have royalty splitting happen asynchronously from NFT transfers (i.e. let the cost of splitting be the burden of royalty stakeholders, not NFT holders).
If NFT transferring becomes too costly, someone could make a "de-joyn" contract which can "re-tokenize" NFTs sent to it, so that they can be transferred without having to worry about _beforeTokenTransfer
(or royalties, for that matter).
See Recommended Mitigation Steps in #37 for more info.