←My Projects
Code4renaFebruary 2025· Rank #6
THORWallet
EVMSolidityDeFiCross-chainMultisig
Findings (1)
HighS-688
Locked Transfers Can Be Bypassed Using LayerZero Endpoint
Description
The Titn.sol contract includes a transfer restriction mechanism that prevents certain addresses from transferring TITN tokens when isBridgedTokensTransferLocked is true. However, this restriction can be bypassed if the recipient is the LayerZero endpoint (lzEndpoint).
function _validateTransfer(address from, address to) internal view {uint256 arbitrumChainId = 42161;if (from != owner() &&from != transferAllowedContract &&to != transferAllowedContract &&isBridgedTokensTransferLocked &&(isBridgedTokenHolder[from] || block.chainid == arbitrumChainId) &&to != lzEndpoint // ← this exemption is the vulnerability) {revert BridgedTokensTransferLocked();}}
The to != lzEndpoint condition creates an unintended escape hatch: any restricted address can send tokens to the LayerZero endpoint, which then forwards them elsewhere — effectively circumventing the lock entirely.
Impact
- Lock bypass: Users restricted from transferring TITN can route tokens through the LayerZero endpoint and forward them to unrestricted addresses.
- Cross-chain exploitation: Attackers can move locked TITN across chains, creating an unfair advantage over compliant users.
- Broken trust assumptions: The entire bridged token transfer lock mechanism is undermined, since the security guarantee it provides no longer holds.
Proof of Concept — Simulated Attack
isBridgedTokensTransferLockedis set totrue— direct transfers revert.- Restricted user attempts a normal transfer → blocked by
BridgedTokensTransferLocked(). - Instead, the user sends TITN to
lzEndpoint— the check passes due to theto != lzEndpointexemption. - Via LayerZero bridging mechanisms, tokens are forwarded to a new address where restrictions no longer apply.
- Transfer lock successfully bypassed. ✓
Recommended Fix
Remove the lzEndpoint exemption from _validateTransfer():
function _validateTransfer(address from, address to) internal view {uint256 arbitrumChainId = 42161;if (from != owner() &&from != transferAllowedContract &&to != transferAllowedContract &&isBridgedTokensTransferLocked &&(isBridgedTokenHolder[from] || block.chainid == arbitrumChainId)) {revert BridgedTokensTransferLocked();}}
Additionally:
- Ensure all LayerZero bridging functions enforce transfer locks before processing transactions.
- Write unit tests confirming that tokens sent via LayerZero are still subject to the same restrictions.