Light ModeLight
Light ModeDark

One Bug Per Day

One H/M every day from top Wardens

Checkmark

Join over 465 wardens!

Checkmark

Receive the email at any hour!

Ad

depositYBLendSGLLockXchainTOLP function of the MagnetarAssetXChainModule will not work because it transfers Singularity tokens to the user before _withdrawToChain

mediumCode4rena

Lines of code

https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarAssetXChainModule.sol#L85-L114 https://github.com/Tapioca-DAO/tapioca-periph/blob/032396f701be935b04a7e5cf3cb40a0136259dbc/contracts/Magnetar/modules/MagnetarAssetCommonModule.sol#L52-L63

Vulnerability details

Description

In depositYBLendSGLLockXchainTOLP function of the MagnetarAssetXChainModule, after lending to SGL, it will wrap the received Singularity tokens into the TOFT wrapping token of it. Afterward, it attempts to transfer those wrapped tokens cross-chain, then unwrap them in the destination chain to lock + participate

solidity
function depositYBLendSGLLockXchainTOLP(DepositAndSendForLockingData memory data) public payable { ... uint256 fraction = _depositYBLendSGL(data.depositData, data.singularity, IYieldBox(yieldBox), data.user, data.lendAmount); // wrap SGL receipt into tReceipt // ! User should approve `address(this)` for `IERC20(data.singularity)` ! uint256 toftAmount = _wrapSglReceipt(IYieldBox(yieldBox), data.singularity, data.user, fraction, data.assetId); data.lockAndParticipateSendParams.lzParams.sendParam.amountLD = toftAmount; // decode `composeMsg` and re-encode it with updated params (uint16 msgType_,, uint16 msgIndex_, bytes memory tapComposeMsg_, bytes memory nextMsg_) = TapiocaOmnichainEngineCodec.decodeToeComposeMsg(data.lockAndParticipateSendParams.lzParams.sendParam.composeMsg); LockAndParticipateData memory lockData = abi.decode(tapComposeMsg_, (LockAndParticipateData)); lockData.fraction = toftAmount; data.lockAndParticipateSendParams.lzParams.sendParam.composeMsg = TapiocaOmnichainEngineCodec.encodeToeComposeMsg(abi.encode(lockData), msgType_, msgIndex_, nextMsg_); // send on another layer for lending _withdrawToChain( MagnetarWithdrawData({ yieldBox: yieldBox, assetId: data.assetId, unwrap: false, lzSendParams: data.lockAndParticipateSendParams.lzParams, sendGas: data.lockAndParticipateSendParams.lzSendGas, composeGas: data.lockAndParticipateSendParams.lzComposeGas, sendVal: data.lockAndParticipateSendParams.lzSendVal, composeVal: data.lockAndParticipateSendParams.lzComposeVal, composeMsg: data.lockAndParticipateSendParams.lzParams.sendParam.composeMsg, composeMsgType: data.lockAndParticipateSendParams.lzComposeMsgType, withdraw: true }) ); }

After lending into the Singularity contract, _wrapSglReceipt is used to wrap the received Singularity tokens into TOFT tokens. Thus, they will be able to be sent cross-chain by using _withdrawToChain with composed messages including the lockAndParticipate option to perform these actions after receiving tokens

The _withdrawToChain function attempts to withdraw YieldBox shares of this contract (address(this)) to obtain tokens before sending them cross-chain (see code snippet). However, _wrapSglReceipt has sent tokens to the user after wrapping. Therefore, there are no tokens or YieldBox shares existing in this contract, resulting in _withdrawToChain reverting afterward.

solidity
function _wrapSglReceipt(IYieldBox yieldBox, address sgl, address user, uint256 fraction, uint256 assetId) internal returns (uint256 toftAmount) { IERC20(sgl).safeTransferFrom(user, address(this), fraction); (, address tReceiptAddress,,) = yieldBox.assets(assetId); IERC20(sgl).approve(tReceiptAddress, fraction); toftAmount = ITOFT(tReceiptAddress).wrap(address(this), address(this), fraction); IERC20(tReceiptAddress).safeTransfer(user, toftAmount); }

Impact

depositYBLendSGLLockXchainTOLP function of MagnetarAssetXChainModule will be broken

Tools Used

Manual review

Recommended Mitigation Steps

Should deposit into YieldBox for address(this) after wrapping Singularity tokens in the _wrapSglReceipt function as following:

solidity
function _wrapSglReceipt(IYieldBox yieldBox, address sgl, address user, uint256 fraction, uint256 assetId) internal returns (uint256 toftAmount) { IERC20(sgl).safeTransferFrom(user, address(this), fraction); (, address tReceiptAddress,,) = yieldBox.assets(assetId); IERC20(sgl).approve(tReceiptAddress, fraction); toftAmount = ITOFT(tReceiptAddress).wrap(address(this), address(this), fraction); //deposit to YieldBox for this IERC20(tReceiptAddress).safeApprove(address(yieldBox), toftAmount); yieldBox.depositAsset(assetId, address(this), address(this), toftAmount, 0); }

Assessed type

Other