If USDT is used for a sale at some point - either through a direct sale on the NFT collection, or sent to the collection from a marketplace sale - it will remain in the contract, as getTokenPayout(address(USDT)) calls systematically revert:
on Ethereum, USDT.transfer does not return a bool, meaning this check will always fail.
Impact
enforcer/PA1D does not have any other ERC20 withdrawal function. While enforcer/PA1D is meant to be used via delegate calls from a NFT collection contract, if the NFT contract does not have any withdrawal function either, this dust mentioned above is effectively lost.
Tools Used
Manual Analysis
Mitigation
Use OpenZeppelin's librarySafeERC20.safeTransfer():
If the ERC20 token does not return any boolean upon transfer like USDT, the call still goes through. It will only revert if the ERC20.transfer call reverts or return false.
Lines of code
https://github.com/code-423n4/2022-10-holograph/blob/f8c2eae866280a1acfdc8a8352401ed031be1373/contracts/enforcer/PA1D.sol#L416
Vulnerability details
If
USDT
is used for a sale at some point - either through a direct sale on the NFT collection, or sent to the collection from a marketplace sale - it will remain in the contract, asgetTokenPayout(address(USDT))
calls systematically revert:on Ethereum,
USDT.transfer
does not return a bool, meaning this check will always fail.Impact
enforcer/PA1D
does not have any other ERC20 withdrawal function. Whileenforcer/PA1D
is meant to be used via delegate calls from a NFT collection contract, if the NFT contract does not have any withdrawal function either, this dust mentioned above is effectively lost.Tools Used
Manual Analysis
Mitigation
Use OpenZeppelin's library
SafeERC20.safeTransfer()
: If the ERC20 token does not return any boolean upon transfer like USDT, the call still goes through. It will only revert if theERC20.transfer
call reverts or return false.