Gas parameters for Stargate swap are hardcoded leading to stuck messages
The `dstGasForCall` for transferring erc20s through Stargate is hardcoded to 0 in the `Balancer` contract leading to `sgReceive` not being called during Stargate swap. As a consequence, the `sgReceive` has to be manually called to clear the `cachedSwapLookup` mapping, but this can be DoSed due to the fact that the `mTOFT::sgReceive` doesn't validate any of its parameters. This can be exploited to perform a long-term DoS attack.\\nGas parameters for Stargate\\nStargate Swap allows the caller to specify the:\\n`dstGasForCall` which is the gas amount forwarded while calling the `sgReceive` on the destination contract.\\n`dstNativeAmount` and `dstNativeAddr` which is the amount and address where the native token is sent to.\\nInside the `Balancer.sol` contract, the `dstGasForCall` is hardcoded to 0. The `dstGasForCall` gets forwarded from Stargate `Router` into the Stargate `Bridge` contract.\\n```\\n    function swap(\\n        uint16 _chainId,\\n        uint256 _srcPoolId,\\n        uint256 _dstPoolId,\\n        address payable _refundAddress,\\n        Pool.CreditObj memory _c,\\n        Pool.SwapObj memory _s,\\n        IStargateRouter.lzTxObj memory _lzTxParams, \\n        bytes calldata _to,\\n        bytes calldata _payload\\n    ) external payable onlyRouter {\\n        bytes memory payload = abi.encode(TYPE_SWAP_REMOTE, _srcPoolId, _dstPoolId, _lzTxParams.dstGasForCall, _c, _s, _to, _payload);\\n        _call(_chainId, TYPE_SWAP_REMOTE, _refundAddress, _lzTxParams, payload);\\n    }\\n\\n    function _call(\\n        uint16 _chainId,\\n        uint8 _type,\\n        address payable _refundAddress,\\n        IStargateRouter.lzTxObj memory _lzTxParams,\\n        bytes memory _payload\\n    ) internal {\\n        bytes memory lzTxParamBuilt = _txParamBuilder(_chainId, _type, _lzTxParams);\\n        uint64 nextNonce = layerZeroEndpoint.getOutboundNonce(_chainId, address(this)) + 1;\\n        layerZeroEndpoint.send{value: msg.value}(_chainId, bridgeLookup[_chainId], _payload, _refundAddress, address(this), lzTxParamBuilt);\\n        emit SendMsg(_type, nextNonce);\\n    }\\n```\\n\\nIt gets encoded inside the payload that is sent through the LayerZero message. The payload gets decoded inside the `Bridge::lzReceive` on destination chain. And `dstGasForCall` is forwarded to the `sgReceive` function:\\n```\\n## Bridge.sol\\n\\n function lzReceive(\\n        uint16 _srcChainId,\\n        bytes memory _srcAddress,\\n        uint64 _nonce,\\n        bytes memory _payload\\n    ) external override {\\n        if (functionType == TYPE_SWAP_REMOTE) {\\n            (\\n                ,\\n                uint256 srcPoolId,\\n                uint256 dstPoolId,\\n>                uint256 dstGasForCall,\\n                Pool.CreditObj memory c,\\n                Pool.SwapObj memory s,\\n                bytes memory to,\\n                bytes memory payload\\n            ) = abi.decode(_payload, (uint8, uint256, uint256, uint256, Pool.CreditObj, Pool.SwapObj, bytes, bytes));\\n```\\n\\nIf it is zero like in the `Balancer.sol` contract or its value is too small the `sgReceive` will fail, but the payload will be saved in the `cachedSwapLookup` mapping. At the same time the tokens are transferred to the destination contract, which is the `mTOFT`. Now anyone can call the `sgReceive` manually through the `clearCachedSwap` function:\\n```\\n    function clearCachedSwap(\\n        uint16 _srcChainId,\\n        bytes calldata _srcAddress,\\n        uint256 _nonce\\n    ) external {\\n        CachedSwap memory cs = cachedSwapLookup[_srcChainId][_srcAddress][_nonce];\\n        require(cs.to != address(0x0), "Stargate: cache already cleared");\\n        // clear the data\\n        cachedSwapLookup[_srcChainId][_srcAddress][_nonce] = CachedSwap(address(0x0), 0, address(0x0), "");\\n        IStargateReceiver(cs.to).sgReceive(_srcChainId, _srcAddress, _nonce, cs.token, cs.amountLD, cs.payload);\\n    }\\n```\\n\\nAlthough not the intended behavior there seems to be no issue with erc20 token sitting on the `mTOFT` contract for a shorter period of time.\\nsgReceive\\nThis leads to the second issue. The `sgReceive` function interface specifies the `chainId`, `srcAddress`, and `token`.\\nIn the current implementation, the `sgReceive` function doesn't check any of these parameters. In practice this means that anyone can specify the `mTOFT` address as the receiver and initiate Stargate Swap from any chain to the `mTOFT` contract.\\nIn conjunction with the first issue, this opens up the possibility of a DoS attack.\\nLet's imagine the following scenario:\\nRebalancing operation needs to be performed between `mTOFT` on Ethereum and Avalanche that hold `USDC` as the underlying token.\\nRebalancing is initiated from Ethereum but the `sgReceive` on Avalanche fails and 1000 USDCs are sitting on `mTOFT` contract on Avalanche.\\nA griever noticed this and initiated Stargate swap from Ethereum to Avalanche for 1 `USDT` specifying the `mTOFT` contract as the receiver.\\nThis is successful and now `mTOFT` has 1 `USDT` but 999 `USDC` as the griever's transaction has called the `sgRecieve` function that pushed 1 `USDC` to the `TOFTVault`.\\nAs a consequence, the `clearCachedSwap` function fails because it tries to transfer the original 1000 `USDC`.\\n```\\n    function sgReceive(uint16, bytes memory, uint256, address, uint256 amountLD, bytes memory) external payable {\\n        if (msg.sender != _stargateRouter) revert mTOFT_NotAuthorized();\\n\\n        if (erc20 == address(0)) {\\n            vault.depositNative{value: amountLD}();\\n        } else {\\n>            IERC20(erc20).safeTransfer(address(vault), amountLD); // amountLD is the original 1000 USDC\\n        }\\n    }\\n```\\n\\nThe only solution here is to manually transfer that 1 USDC to the `mTOFT` contract and try calling the `clearCachedSwap` again.\\nThe griever can repeat this process multiple times.
The `dstGasForCall` shouldn't be hardcoded to 0. It should be a configurable value that is set by the admin of the `Balancer` contract.\\nTake into account that this value will be different for different chains.\\nThe recommended solution is:\\n```\\n contract Balancer is Ownable {\\n     using SafeERC20 for IERC20;\\n\\n// Add the line below\\n    mapping(uint16 => uint256) internal sgReceiveGas;\\n\\n// Add the line below\\n    function setSgReceiveGas(uint16 eid, uint256 gas) external onlyOwner {\\n// Add the line below\\n        sgReceiveGas[eid] = gas;\\n// Add the line below\\n    }\\n// Add the line below\\n\\n// Add the line below\\n    function getSgReceiveGas(uint16 eid) internal view returns (uint256) {\\n// Add the line below\\n        uint256 gas = sgReceiveGas[eid];\\n// Add the line below\\n        if (gas == 0) revert();\\n// Add the line below\\n        return gas;\\n// Add the line below\\n    }\\n// Add the line below\\n\\n// Remove the line below\\n    IStargateRouterBase.lzTxObj({dstGasForCall: 0, dstNativeAmount: 0, dstNativeAddr: "0x0"}),\\n// Add the line below\\n    IStargateRouterBase.lzTxObj({dstGasForCall: getSgReceiveGas(_dstChainId), dstNativeAmount: 0, dstNativeAddr: "0x0"}),\\n```\\n
Hardcoding the `dstGasCall` to 0 in conjuction with not checking the `sgReceive` parameters opens up the possibility of a long-term DoS attack.
```\\n    function swap(\\n        uint16 _chainId,\\n        uint256 _srcPoolId,\\n        uint256 _dstPoolId,\\n        address payable _refundAddress,\\n        Pool.CreditObj memory _c,\\n        Pool.SwapObj memory _s,\\n        IStargateRouter.lzTxObj memory _lzTxParams, \\n        bytes calldata _to,\\n        bytes calldata _payload\\n    ) external payable onlyRouter {\\n        bytes memory payload = abi.encode(TYPE_SWAP_REMOTE, _srcPoolId, _dstPoolId, _lzTxParams.dstGasForCall, _c, _s, _to, _payload);\\n        _call(_chainId, TYPE_SWAP_REMOTE, _refundAddress, _lzTxParams, payload);\\n    }\\n\\n    function _call(\\n        uint16 _chainId,\\n        uint8 _type,\\n        address payable _refundAddress,\\n        IStargateRouter.lzTxObj memory _lzTxParams,\\n        bytes memory _payload\\n    ) internal {\\n        bytes memory lzTxParamBuilt = _txParamBuilder(_chainId, _type, _lzTxParams);\\n        uint64 nextNonce = layerZeroEndpoint.getOutboundNonce(_chainId, address(this)) + 1;\\n        layerZeroEndpoint.send{value: msg.value}(_chainId, bridgeLookup[_chainId], _payload, _refundAddress, address(this), lzTxParamBuilt);\\n        emit SendMsg(_type, nextNonce);\\n    }\\n```\\n