Platform: Code4rena
Start Date: 23/11/2022
Pot Size: $24,500 CANTO
Total HM: 5
Participants: 37
Period: 5 days
Judge: berndartmueller
Total Solo HM: 2
Id: 185
League: ETH
Rank: 13/37
Findings: 2
Award: $73.58
๐ Selected for report: 0
๐ Solo Findings: 0
๐ Selected for report: 0xSmartContract
Also found by: Deivitto, Josiah, RaymondFam, aphak5010, cccz, cryptonue, gzeon, joestakey, keccak123, martin, peritoflores, rotcivegaf
370.8153 CANTO - $59.89
_safeMint()
should be used rather than _mint()
wherever possibleIn Turnstile.sol
, eventually it is called ERC721 _mint()
at register()
. Calling _mint()
this way does not ensure that the receiver of the NFT is able to accept them, making possible to lose them.
_safeMint()
should be used with as it checks to see if a user can properly accept an NFT and reverts otherwise.
There is no check of the address provided by the mint NFT that it implements ERC721Receiver
.
_mint()
is discouraged in favor of _safeMint()
which ensures that the recipient is either an EOA or implements IERC721Receiver
.
Both open OpenZeppelin and solmate have versions of this function so that NFTs arenโt lost if theyโre minted to contracts that cannot transfer them back out.
https://github.com/code-423n4/2022-11-canto/blob/162e0d2d957154c034cf5d24d402b0c9d55512c1/CIP-001/src/Turnstile.sol#L92 _mint(_recipient, tokenId);
Use _safeMint()
as suggested by OpenZeppelin or include the check before minting.
However, consider the risk of reentrancies
Important functions like distributeFees
are only usable by admins
Given that Turnstile is derived from Ownable
, the ownership management of this contract defaults to Ownable
โs transferOwnership()
and renounceOwnership()
methods which are not overridden here.
Such critical address transfer/renouncing in one-step is very risky because it is irrecoverable from any mistakes
Scenario: If an incorrect address, e.g. for which the private key is not known, is used accidentally then it prevents the use of all the onlyOwner()
functions forever, which includes the changing of various critical addresses and parameters. This use of incorrect address may not even be immediately apparent given that these functions are probably not used immediately.
When noticed, due to a failing onlyOwner()
function call, it will force the redeployment of these contracts and require appropriate changes and notifications for switching from the old to new address. This will diminish trust in the protocol and incur a significant reputational damage.
Ownable
https://github.com/code-423n4/2022-11-canto/blob/162e0d2d957154c034cf5d24d402b0c9d55512c1/CIP-001/src/Turnstile.sol#L12
contract Turnstile is Ownable, ERC721Enumerable {Consider using Ownable2Step
from OZ
This mitigates risk because if an incorrect address is used in step (1) then it can be fixed by re-approving the correct address. Only after a correct address is used in step (1) can step (2) happen and complete the address/ownership change.
Also, consider adding a time-delay for such sensitive actions. And at a minimum, use a multisig owner address and not an EOA.
In withdraw
return value is not being used, so it can be removed.
Remove return value
#0 - c4-judge
2023-01-02T12:59:05Z
berndartmueller marked the issue as grade-b
๐ Selected for report: Tricko
Also found by: 0xhacksmithh, AkshaySrivastav, Awesome, Beepidibop, Deivitto, DijkstraDev, Dinesh11G, Englave, JC, Rahoz, RaymondFam, ReyAdmirado, SaeedAlipoor01988, Sathish9098, abiih, aphak5010, chaduke, chrisdior4, exolorkistis, gzeon, martin, nicobevi, oyc_109, peritoflores, rotcivegaf
84.7394 CANTO - $13.69
unchecked
A a - b
operation already checked for b
not being able to be bigger than a
can be unchecked in order to save gas as it can't underflow
Add unchecked
to
balances[_tokenId] = earnedFees - _amount;
unchecked
Operation is most likely to not cause overflow
distributeFees
is a onlyOwner
operation
For overflowing it needs to reach the value of type(uint256).max
what is:
115792089237316195423570985008687907853269984665640564039457584007913129639935
The way for balances[_tokenId]
to be increased is by calling this function that only owners can call, what uses msg.value
for overflowing
It's most likely no admin calls the function with that msg.value
so it can be unchecked
in order to save gas
Consider using unchecked
for
balances[_tokenId] += msg.value;
#0 - c4-judge
2022-11-29T19:37:28Z
berndartmueller marked the issue as grade-b