Closed sherlock-admin4 closed 6 months ago
Can you provide the PoC? I think you might miss the SGETH transfer
code, and I attach the code snippet here:
function transfer(address dst, uint wad) external override returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad) public override nonReentrant returns (bool) {
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
// always decrement the src (payer) address
balanceOf[src] -= wad;
if(noUnwrapTo[dst]){
// we do *not* unwrap
balanceOf[dst] += wad;
emit Transfer(src, dst, wad);
} else {
// unwrap and send native gas token
totalSupply -= wad; // if its getting unwrapped, decrement the totalSupply
(bool success, ) = dst.call{value: wad}("");
require(success, "SGETH: failed to transfer");
emit TransferNative(src, dst, wad);
}
return true;
}
pool's swapRemote
being called by Router
, at that time, _to
is StargateComposer
, which will not unwrap because noUnwrapTo[dst]
is true
, then the Router
call the StargateComposer
's sgReceive
.
In StargateComposer
's sgReceive
, it call SGETH
's transfer
with dst
(which is to
) is WooCrossChainRouterV4
, according to noUnwrapTo[dst]
is false
, the wad
SGETH will be unwrapped and transfer the wad
native ETH to WooCrossChainRouterV4
.
Example txn in Optimism(you can see the Transfer
event emits first and TransferNative
event emits later then by the SGETH
contract address):
https://optimistic.etherscan.io/tx/0xd94786a40ff4864704d9122437214952a7258e9c704acc24015d0801d68fcc1d
1 comment(s) were left on this issue during the judging contest.
WangAudit commented:
sorry but I don't see where we call stargate's
swapRemote
; in woffi'scrossSwap
we callbridgeByStargate
and it calls stargatesswap
notswapRemote
and I don't where we useswapRemote
; therefore; I assume it's invalid; the report doesn't say it either; watson just said user wants to initiates the swap and then whe LZ calls stargatesswapRemote
but I don't see it gets called
request poc
PoC requested from @mstpr
Requests remaining: 7
mstpr-brainbot
high
If the users requested
toToken
is ETH then the users funds will be stuck in the bridgeSummary
Users can initiate a cross chain swap from one chain to other as long as both chains are allowed. Users can select the destination token to any token, including native ETH. If the chosen token is ETH then the bridging will revert, hence the users funds will be stuck.
Vulnerability Detail
Let's assume an user comes and initiates a bridge from chainX to chainY with
fromToken
WETH and destination chaintoToken
as ETH:When the LayerZero endpoint calls the StargateBridge in destination chain, the bridge will call the StargateRouter's swapRemote function in destination chain.
When we come to these lines in below code snippet in the
swapRemote
execution flow, the pool id will be fetched and the pool id's underlyingtoken
will be the SGETH token in that chain. (Example: https://etherscan.io/address/0x101816545F6bd2b1076434B54383a1E633390A2E#readContract SGETH pool in mainnet, check the "token" variable, it is the SGETH token, not ETH or WETH)Then, the pools
swapRemote
function will be called which as we can see in the SGETH pool implementation, the "to" address will receive the SGETH tokens. Which the "to" address is the WooCrosschainRouter contract that will be receiving SGETH tokens.Then, the IStargateReceiver interface
sgReceive
function will be called in the WooCrosschainRouter contract and as we can observe the 4th argument, which corresponds tobridgedToken
in WooCrosschainRouter implementation, will be pool.token(), which is SGETH.When the
sgReceive
function called, since the bridgedToken is SGETH the first "if" statement will be executed, which is an internal call to_handleNativeReceived()
. Also, the decoded payloadstoToken
variable will be the ETH placeholder address since the user sent the request as the bridge destination token as ETH.So far the WooCrosschainRouter has SGETH tokens in its balance and user requested ETH. Since the
toToken
==ETH_PLACEHOLDER_ADDR
the first "if" statement will be executed. However, this is the part where the tx will revert because the function will try to send ETH from the WooCrosschainRouter to user but the router does not have ETH balance yet. Router has SGETH tokens.Impact
Users bridged funds in source chain will be stuck and will not receive any tokens in exchange in destination chain. Loss of funds.
Code Snippet
https://github.com/sherlock-audit/2024-03-woofi-swap/blob/65185691c91541e33f84b77d4c6290182f137092/WooPoolV2/contracts/CrossChain/WooCrossChainRouterV4.sol#L66-L181
https://github.com/sherlock-audit/2024-03-woofi-swap/blob/65185691c91541e33f84b77d4c6290182f137092/WooPoolV2/contracts/CrossChain/WooCrossChainRouterV4.sol#L269-L383
Tool used
Manual Review
Recommendation