Sablier

Sablier
DeFiFoundry
53,440 USDC
View results
Submission Details
Severity: low
Invalid

WETH incompatible on Blast when invoking the safeTransferFrom method in SablierV2BatchLockup

Summary

In the SablierV2BatchLockup contract, there are several instances where the contract uses safeTransferFrom for ERC-20 tokens. This method is compatible with most ERC-20 tokens, including the standard WETH9 implementation on Ethereum, Optimism, Polygon, and BSC.

WETH9.sol
if (src != msg.sender && allowance[src][msg.sender] != uint(- 1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}

Vulnerability Details

However, on networks like Blast, it uses different WETH implementation that do not handle the
src == msg.sender case, the safeTransferFrom method can fail, causing the creation of streams to be unsuccessful.

In the SablierV2BatchLockup contract, the _handleTransfer function uses safeTransferFrom to transfer tokens from the user to the contract.
This function assumes the safeTransferFrom method will handle the transfer correctly if the sender has approved the necessary allowance.

On networks like Blast, Wrapped Arbitrum, Wrapped Fantom and the WETH implementations do not handle the src == msg.sender case.

Impact

When the _handleTransfer function fails, the streams cannot be created, causing the entire batch
lockup process to fail.

function _handleTransfer(address sablierContract, IERC20 asset, uint256 amount) internal {
// Transfer the assets to the batchLockup contract.
asset.safeTransferFrom({ from: msg.sender, to: address(this), value: amount });
// Approve the Sablier contract to spend funds.
_approve(sablierContract, asset, amount);
}

The issue arises because asset.safeTransferFrom expects the WETH contract to handle the src == msg.sender case,
which is not implemented in some WETH contracts on certain networks ie BLAST.
https://blastscan.io/address/0x4300000000000000000000000000000000000004#code

POC

Make sure to run this test with this flag:

forge test --match-test testPoC_TransferFromRevert --fork-url https://rpc.blast.io

pragma solidity ^0.8.0;
import "forge-std/Test.sol";
import "forge-std/console.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
contract WETHTransferTest is Test {
address user = address(0xf683Ce59521AA464066783d78e40CD9412f33D21);
address recipient = address(0x2);
// WETH address on Blast network
IERC20 public constant WETH = IERC20(0x4300000000000000000000000000000000000004);
error InsufficientAllowance();
function testWETHTransferFromRevert() public {
// If this fails, ensure the user has more than 1 ether worth of WETH on Blast network
assert(WETH.balanceOf(user) > 1 ether);
// Start impersonating the user
vm.startPrank(user);
// Expect the transferFrom call to revert with InsufficientAllowance error
vm.expectRevert(InsufficientAllowance.selector);
WETH.transferFrom(user, recipient, 1 ether);
// Stop impersonating the user
vm.stopPrank();
}
}

Tools Used

Foundry/Forge

Recommendations

Use this implementation instead.

function _handleTransfer(address sablierContract, IERC20 asset, uint256 amount) internal {
// Check if the asset is WETH and handle accordingly.
if (address(asset) == address(weth)) {
// Transfer WETH directly to the contract.
weth.safeTransfer(address(this), amount);
} else {
// Transfer the assets to the batchLockup contract.
asset.safeTransferFrom({ from: msg.sender, to: address(this), value: amount });
}
// Approve the Sablier contract to spend funds.
_approve(sablierContract, asset, amount);
}
Updates

Lead Judging Commences

inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement
golanger85 Submitter
about 1 year ago
inallhonesty Lead Judge
about 1 year ago
inallhonesty Lead Judge about 1 year ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity
Assigned finding tags:

Info/Gas/Invalid as per Docs

https://docs.codehawks.com/hawks-auditors/how-to-determine-a-finding-validity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.