Unsafe ERC20 transfer operations allow silent failures and incompatibility with non-standard tokens
The protocol uses raw ERC20 transfer/transferFrom calls and require-wrapped transfers without SafeERC20, which can silently fail or break against non-standard tokens like USDT.
Description
The protocol uses two unsafe patterns for ERC20 token transfers throughout the codebase:
Pattern 1: Unchecked transfers
// TreasuryBTC.sol:277function recoverERC20(address tokenAddress, uint256 amount) external onlyOwner {require(tokenAddress != address(stablecoin), "Cannot recover stablecoin");require(amount > 0, "Amount must be > 0");IERC20(tokenAddress).transfer(owner(), amount); // No return value check}
This pattern appears in:
TreasuryBTC.sol:277-recoverERC20()DonationVault.sol:266-recoverTokens()NexaloStaking.sol:286-recoverToken()
These transfers have no return value validation. If the token returns false on failure (instead of reverting), the function continues execution as if the transfer succeeded, causing state updates without actual token movement.
Pattern 2: Require-wrapped transfers (compatibility issue)
// TreasuryBTC.sol:158require(stablecoin.transfer(msg.sender, userReward), "Transfer failed");
This pattern assumes all ERC20 tokens return a boolean value. However, some tokens (like certain implementations) may:
- Return nothing (void) instead of bool
- Not fully comply with ERC20 standard return values
- Cause unexpected behavior with strict ABI decoding
Additionally, the protocol doesn't handle tokens that return false on failure instead of reverting. While require() catches explicit reverts, it doesn't protect against tokens that return false to signal failure.
Recommendation
Adopt OpenZeppelin's SafeERC20 library for all ERC20 token interactions:
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";contract TreasuryBTC is Ownable, ReentrancyGuard {using SafeERC20 for IERC20;IERC20 public immutable stablecoin;IERC20 public immutable nxlToken;// Replace all transfers:// OLD: require(stablecoin.transfer(msg.sender, amount), "Transfer failed");// NEW: stablecoin.safeTransfer(msg.sender, amount);// OLD: require(stablecoin.transferFrom(msg.sender, address(this), amount), "Transfer failed");// NEW: stablecoin.safeTransferFrom(msg.sender, address(this), amount);// OLD: IERC20(token).transfer(owner(), amount);// NEW: IERC20(token).safeTransfer(owner(), amount);}
Apply this pattern to all contracts.
Resolution
Nexalo: Fixed.
Zealynx: Verified. Fixed.

