`mTOFT` can be forced to receive the wrong ERC20 leading to token lockup
Due to Stargate's functionality of swapping one token on the source chain to another token on the destination chain, it is possible to force `mTOFT` to receive the wrong ERC20 token leading to token lockup.\\nTo give an example, a user can:\\nProvide USDC on Ethereum and receive USDT on Avalanche.\\nProvide USDC on Avalanche and receive USDT on Arbitrum.\\netc.\\nThe issue here is that poolIds are not enforced during the rebalancing process. As it can be observed the `bytes memory _ercData` is not checked for its content.\\n```\\n## Balancer.sol\\n\\nfunction _sendToken(\\n        address payable _oft,\\n        uint256 _amount,\\n        uint16 _dstChainId,\\n        uint256 _slippage,\\n>        bytes memory _data\\n    ) private {\\n        address erc20 = ITOFT(_oft).erc20();\\n        if (IERC20Metadata(erc20).balanceOf(address(this)) < _amount) {\\n            revert ExceedsBalance();\\n        }\\n        {\\n>            (uint256 _srcPoolId, uint256 _dstPoolId) = abi.decode(_data, (uint256, uint256));\\n            _routerSwap(_dstChainId, _srcPoolId, _dstPoolId, _amount, _slippage, _oft, erc20);\\n        }\\n    }\\n```\\n\\nIt is simply decoded and passed as is.\\nThis is a problem and imagine the following scenario:\\nA Gelato bot calls the rebalance method for `mTOFT` that has USDC as erc20 on Ethereum.\\nThe bot encodes the `ercData` so `srcChainId = 1` pointing to USDC but `dstChainId = 2` pointing to USDT on Avalanche.\\nDestination `mTOFT` is fetched from `connectedOFTs` and points to the `mTOFT` with USDC as erc20 on Avalanche.\\nStargate will take USDC on Ethereum and provide USDT on Avalanche.\\n`mTOFT` with USDC as underlying erc20 on Avalanche will receive USDT token and it will remain lost as the balance of the `mTOFT` contract.\\nAs this is a clear path for locking up wrong tokens inside the `mTOFT` contract, it is a critical issue.
The `initConnectedOFT` function should enforce the poolIds for the src and dst chains.The rebalance function should just fetch these saved values and use them.\\n```\\n \\n@@ // Remove the line below\\n164,14 // Add the line below\\n176,12 @@ contract Balancer is Ownable {\\n      * @param _dstChainId the destination LayerZero id\\n      * @param _slippage the destination LayerZero id\\n      * @param _amount the rebalanced amount\\n// Remove the line below\\n     * @param _ercData custom send data\\n      */\\n     function rebalance(\\n         address payable _srcOft,\\n         uint16 _dstChainId,\\n         uint256 _slippage,\\n// Remove the line below\\n        uint256 _amount,\\n// Remove the line below\\n        bytes memory _ercData\\n// Add the line below\\n        uint256 _amount\\n     ) external payable onlyValidDestination(_srcOft, _dstChainId) onlyValidSlippage(_slippage) {\\n         {\\n@@ // Remove the line below\\n188,13 // Add the line below\\n204,13 @@ contract Balancer is Ownable {\\n             if (msg.value == 0) revert FeeAmountNotSet();\\n             if (_isNative) {\\n                 if (disableEth) revert SwapNotEnabled();\\n                 _sendNative(_srcOft, _amount, _dstChainId, _slippage);\\n             } else {\\n// Remove the line below\\n                _sendToken(_srcOft, _amount, _dstChainId, _slippage, _ercData);\\n// Add the line below\\n                _sendToken(_srcOft, _amount, _dstChainId, _slippage);\\n             }\\n\\n \\n@@ // Remove the line below\\n221,7 // Add the line below\\n237,7 @@ contract Balancer is Ownable {\\n      * @param _dstOft the destination TOFT address\\n      * @param _ercData custom send data\\n      */\\n// Remove the line below\\n    function initConnectedOFT(address _srcOft, uint16 _dstChainId, address _dstOft, bytes memory _ercData)\\n// Add the line below\\n    function initConnectedOFT(address _srcOft, uint256 poolId, uint16 _dstChainId, address _dstOft, bytes memory _ercData)\\n         external\\n         onlyOwner\\n     {\\n@@ // Remove the line below\\n231,10 // Add the line below\\n247,8 @@ contract Balancer is Ownable {\\n         bool isNative = ITOFT(_srcOft).erc20() == address(0);\\n         if (!isNative && _ercData.length == 0) revert PoolInfoRequired();\\n \\n// Remove the line below\\n        (uint256 _srcPoolId, uint256 _dstPoolId) = abi.decode(_ercData, (uint256, uint256));\\n// Remove the line below\\n\\n         OFTData memory oftData =\\n// Remove the line below\\n            OFTData({srcPoolId: _srcPoolId, dstPoolId: _dstPoolId, dstOft: _dstOft, rebalanceable: 0});\\n// Add the line below\\n            OFTData({srcPoolId: poolId, dstPoolId: poolId, dstOft: _dstOft, rebalanceable: 0});\\n \\n         connectedOFTs[_srcOft][_dstChainId] = oftData;\\n         emit ConnectedChainUpdated(_srcOft, _dstChainId, _dstOft);\\n \\n     function _sendToken(\\n         address payable _oft,\\n         uint256 _amount,\\n         uint16 _dstChainId,\\n// Remove the line below\\n        uint256 _slippage,\\n// Remove the line below\\n        bytes memory _data\\n// Add the line below\\n        uint256 _slippage\\n     ) private {\\n         address erc20 = ITOFT(_oft).erc20();\\n         if (IERC20Metadata(erc20).balanceOf(address(this)) < _amount) {\\n             revert ExceedsBalance();\\n// Remove the line below\\n        }\\n// Add the line below\\n            }\\n         {\\n// Remove the line below\\n            (uint256 _srcPoolId, uint256 _dstPoolId) = abi.decode(_data, (uint256, uint256));\\n// Remove the line below\\n            _routerSwap(_dstChainId, _srcPoolId, _dstPoolId, _amount, _slippage, _oft, erc20);\\n// Add the line below\\n            _routerSwap(_dstChainId, _amount, _slippage, _oft, erc20);\\n         }\\n     }\\n \\n     function _routerSwap(\\n         uint16 _dstChainId,\\n// Remove the line below\\n        uint256 _srcPoolId,\\n// Remove the line below\\n        uint256 _dstPoolId,\\n         uint256 _amount,\\n         uint256 _slippage,\\n         address payable _oft,\\n         address _erc20\\n     ) private {\\n         bytes memory _dst = abi.encodePacked(connectedOFTs[_oft][_dstChainId].dstOft);\\n// Add the line below\\n        uint256 poolId = connectedOFTs[_oft][_dstChainId].srcPoolId;\\n         IERC20(_erc20).safeApprove(address(router), _amount);\\n         router.swap{value: msg.value}(\\n             _dstChainId,\\n// Remove the line below\\n            _srcPoolId,\\n// Remove the line below\\n            _dstPoolId,\\n// Add the line below\\n            poolId,\\n// Add the line below\\n            poolId,\\n             payable(this),\\n             _amount,\\n             _computeMinAmount(_amount, _slippage),\\n```\\n\\nAdmin is trusted but you can optionally add additional checks inside the `initConnectedOFT` function to ensure that the poolIds are correct for the src and dst mTOFTs.
The impact of this vulnerability is critical. It allows for locking up wrong tokens inside the mTOFT contract causing irreversible loss of funds.
```\\n## Balancer.sol\\n\\nfunction _sendToken(\\n        address payable _oft,\\n        uint256 _amount,\\n        uint16 _dstChainId,\\n        uint256 _slippage,\\n>        bytes memory _data\\n    ) private {\\n        address erc20 = ITOFT(_oft).erc20();\\n        if (IERC20Metadata(erc20).balanceOf(address(this)) < _amount) {\\n            revert ExceedsBalance();\\n        }\\n        {\\n>            (uint256 _srcPoolId, uint256 _dstPoolId) = abi.decode(_data, (uint256, uint256));\\n            _routerSwap(_dstChainId, _srcPoolId, _dstPoolId, _amount, _slippage, _oft, erc20);\\n        }\\n    }\\n```\\n