QA Report #236
Labels
bug
Something isn't working
QA (Quality Assurance)
Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax
sponsor disputed
Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue
Overview
Table of Contents
whenNotPaused
is not used oncreateBasket
createBasket
usesbasketImplementation
for salt, butcreateVault
doesn't usevaultImplementation
for saltnibbledTokens
arrayAccessControlMechanism.sol
: propose/accept pattern is redundant sincegrantRole
can be used to push a roleTWAV
is only over four blockstransfer()
/transferFrom()
withIERC20
transfer()
/transferFrom()
not checkedreceive()
functioninitialize()
functions_safeMint()
should be used rather than_mint()
wherever possibleabi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such askeccak256()
__gap[50]
storage variable to allow for new storage variables in later versionsinitialize()
functions are front-runnable in the solutionconstant
instead of duplicating the same string>= 0.8
: SafeMathLow Risk Issues
1.
whenNotPaused
is not used oncreateBasket
While
whenNotPaused
is used oncreateVault
:This seems to have been forgotten on
createBasket
:2.
createBasket
usesbasketImplementation
for salt, butcreateVault
doesn't usevaultImplementation
for salt3. Add constructor initializers
As per OpenZeppelin’s (OZ) recommendation, “The guidelines are now to make it impossible for anyone to run
initialize
on an implementation contract, by adding an empty constructor with theinitializer
modifier. So the implementation contract gets initialized automatically upon deployment.”Note that this behaviour is also incorporated the OZ Wizard since the UUPS vulnerability discovery: “Additionally, we modified the code generated by the Wizard 19 to include a constructor that automatically initializes the implementation when deployed.”
Furthermore, this thwarts any attempts to frontrun the initialization tx of these contracts:
4. DDOS on
nibbledTokens
arrayThe minimum price to create a vault is pretty trivial, 1 gwei:
An attacker could spam and bloat the size of the
nibbledTokens
array to the pointgetVaults()
becomes unusable5. Check Effects Interactions pattern not respected
To avoid unexpected behavior in the future (be it for the solution or for a fork), it's recommended to always follow the CEI pattern.
Consider always moving the state-changes before the external calls.
Affected code:
6.
AccessControlMechanism.sol
: propose/accept pattern is redundant sincegrantRole
can be used to push a role7.
TWAV
is only over four blocksSomeone does not have to maintain price variation for long to reject a buyout. This can result in blocking of buyouts.
8. Unsafe casting may overflow
SafeMath and Solidity 0.8.* handles overflows for basic math operations but not for casting.
Consider using OpenZeppelin's SafeCast library to prevent unexpected overflows when casting from uint256 here:
9. Basic ecrecover is used
Basic ecrecover is used, leading to being able to approve transfers from the zero address. Any tokens sent to 0 address can be recovered by another user.
Consider using OpenZeppelin’s ECDSA library
10. Missing address(0) checks
(Output from Slither) Consider adding an
address(0)
check here:11. Unsafe use of
transfer()
/transferFrom()
withIERC20
Some tokens do not implement the ERC20 standard properly but are still accepted by most code that accepts ERC20 tokens. For example Tether (USDT)'s
transfer()
andtransferFrom()
functions do not return booleans as the specification requires, and instead have no return value. When these sorts of tokens are cast toIERC20
, their function signatures do not match and therefore the calls made, revert. Use OpenZeppelin’sSafeERC20
'ssafeTransfer()
/safeTransferFrom()
insteadBad:
Good (using OpenZeppelin's
SafeERC20
):Consider using
safeTransfer
/safeTransferFrom
here:12. Return values of
transfer()
/transferFrom()
not checkedNot all
IERC20
implementationsrevert()
when there's a failure intransfer()
/transferFrom()
. The function signature has aboolean
return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually making a payment.Bad:
Good (using
require
):Consider wrapping transfers in
require()
statements consistently here:Alternatively, SafeERC20 could be used here, as stated before
13. Unused/empty
receive()
functionIf the intention is for the Ether to be used, the function should call another function, otherwise it should revert
14. Lack of event emission after critical
initialize()
functionsSimilar issue in the past: here
To record the init parameters for off-chain monitoring and transparency reasons, please consider emitting an event after the
initialize()
functions:15. Prevent accidentally burning tokens
Transferring tokens to the zero address is usually prohibited to accidentally avoid "burning" tokens by sending them to an unrecoverable zero address.
Consider adding a check to prevent accidentally burning tokens here:
16.
_safeMint()
should be used rather than_mint()
wherever possible_mint()
is discouraged in favor of_safeMint()
which ensures that the recipient is either an EOA or implementsIERC721Receiver
. 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.17.
abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such askeccak256()
Similar issue in the past: here
Use
abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g.abi.encodePacked(0x123,0x456)
=>0x123456
=>abi.encodePacked(0x1,0x23456)
, butabi.encode(0x123,0x456)
=>0x0...1230...456
). If there is only one argument toabi.encodePacked()
it can often be cast tobytes()
orbytes32()
instead.18. Upgradeable contract is missing a
__gap[50]
storage variable to allow for new storage variables in later versionsSee this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future.
19. All
initialize()
functions are front-runnable in the solutionConsider adding some access control to them or deploying atomically or using constructor
initializer
:20. Use a
constant
instead of duplicating the same string21. Calculation doesn't match with comment
Non-Critical Issues
1. Typos
2. Deprecated library used for Solidity
>= 0.8
: SafeMath3. Commented code
4. Missing friendly revert strings
5. Use a more recent version of solidity
From solidity version of at least 0.8.4 , you can use
bytes.concat()
instead ofabi.encodePacked(<bytes>,<bytes>)
Use a solidity version of at least 0.8.12 to get
string.concat()
instead ofabi.encodePacked(<str>,<str>)
The text was updated successfully, but these errors were encountered: