QA Report #390
Labels
bug
Something isn't working
high quality QA
QA (Quality Assurance)
Assets are not at risk. State handling, function incorrect as to spec, issues with clarity, syntax
Overview
Table of Contents
_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()
order.baseAsset
infillOrder()
will breakexercise()
string.concat()
orbytes.concat()
public
functions not called by the contract should be declaredexternal
instead1. Low Risk Issues
1.1. Wrong price on chains other than Ethereum mainnet
PuttyV1 was deployed on both Ethereum and Optimism (native token: OP token). Here, it assumes that
msg.value
will be in Ether:This might turn wrong if not careful. Consider checking chainId before making this shortcut.
1.2. Funds can be locked
There isn't a withdraw mechanism and several payable methods are implemented:
1.3.
_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.Be careful however to respect the CEI pattern or add a re-entrancy guard as
_safeMint
adds a callback-check (_checkOnERC721Received
) and a maliciousonERC721Received
could be exploited if not careful. The CEIP is already well respected in the solution.Reading material:
1.4. A malicious owner can keep the fee rate at zero, but if a large value transfer enters the mempool, the owner can jack the rate up to the maximum
The
PuttyV2.withdraw()
function fetches the currentfee
from the contract's state. This fee can be changed by the Admin/DAO.Mitigation: Make sure a governance timelock is in place.
1.5.
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.1.6. Use a 2-step ownership transfer pattern
Contracts inheriting from OpenZeppelin's libraries have the default
transferOwnership()
function (a one-step process). It's possible that theonlyOwner
role mistakenly transfers ownership to a wrong address, resulting in a loss of theonlyOwner
role.Consider overriding the default
transferOwnership()
function to first nominate an address as thependingOwner
and implementing anacceptOwnership()
function which is called by thependingOwner
to confirm the transfer.2. Non-Critical Issues
2.1. Fee On Transfer Token usage as
order.baseAsset
infillOrder()
will breakexercise()
As the protocol explicitly mentioned it wouldn't support Fee On Transfer or Rebase tokens, this is just an informative finding. Consider documentation to users that the protocol won't work with such tokens, so they don't fill orders with them.
2.2. Misleading comment
Issue with comment
As an auditor, I personally misunderstood this comment. Consider changing it:
2.3. Typos
2.4. Use
string.concat()
orbytes.concat()
Solidity version 0.8.4 introduces
bytes.concat()
(vsabi.encodePacked(<bytes>,<bytes>)
)Solidity version 0.8.12 introduces
string.concat()
(vsabi.encodePacked(<str>,<str>)
)2.5. It's better to emit after all processing is done
2.6.
public
functions not called by the contract should be declaredexternal
insteadThe text was updated successfully, but these errors were encountered: