Summary
The L1ERC20Bridge
will be incompatible with void return tokens such as USDT on Ethereum mainnet.
Vulnerability Details
The L1ERC20Bridge
contract uses the IERC20
interface to manage token approvals directly:
function _approveFundsToAssetRouter(address _from, IERC20 _token, uint256 _amount) internal returns (uint256) {
uint256 balanceBefore = _token.balanceOf(address(this));
_token.safeTransferFrom(_from, address(this), _amount);
@> bool success = _token.approve(address(L1_ASSET_ROUTER), _amount);
@> if (!success) {
@> revert ApprovalFailed();
@> }
uint256 balanceAfter = _token.balanceOf(address(this));
return balanceAfter - balanceBefore;
}
https://github.com/Cyfrin/2024-10-zksync/blob/cfc1251de29379a9548eeff1eea3c78267288356/era-contracts/l1-contracts/contracts/bridge/L1ERC20Bridge.sol#L227C5-L237C6
@> bool success = IERC20(_l1Token).approve(address(L1_ASSET_ROUTER), 0);
@> if (!success) {
@> revert ApprovalFailed();
@> }
https://github.com/Cyfrin/2024-10-zksync/blob/cfc1251de29379a9548eeff1eea3c78267288356/era-contracts/l1-contracts/contracts/bridge/L1ERC20Bridge.sol#L206C9-L210C10
The IERC20
interface explicitly instructs the Solidity to compiler to parse for boolean
returndata, which when absent (such as in a call to approve(address,uint256)
on USDT), will cause the transaction to revert
:
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
}
https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7/advanced#code
Impact
Inability to bridge USDT.
Tools Used
Manual Review
Recommendations
Use IERC20.safeApprove
instead.