Omniscia Symbiosis Finance Audit
MetaRouterV2 Manual Review Findings
MetaRouterV2 Manual Review Findings
MRV-01M: Inexistent Validation of Calldata Slots
| Type | Severity | Location |
|---|---|---|
| Input Sanitization | ![]() | MetaRouterV2.sol:L58, L83 |
Description:
The low level assembly writes to the two call datas are meant to fill in the value of a particular argument for the external call, however, no validation is performed on the calldata that can lead to out-of-bounds writes in the blocks as well as generally unexpected behaviour.
Example:
53uint256 finalSwapAmountIn = secondSwapAmountIn;54if (_metarouteTransaction.secondSwapCalldata.length != 0) {55 bytes memory secondSwapCalldata = _metarouteTransaction.secondSwapCalldata;56
57 assembly {58 mstore(add(secondSwapCalldata, 100), secondSwapAmountIn)59 }60
61 IERC20(_metarouteTransaction.approvedTokens[approvedTokensLength - 2]).approve(62 _metarouteTransaction.secondDexRouter,63 secondSwapAmountIn64 );65
66 (bool secondSwapSuccess,) = _metarouteTransaction.secondDexRouter.call(67 secondSwapCalldata68 );69 require(secondSwapSuccess, "MetaRouterV2: second swap failed");70
71 finalSwapAmountIn = IERC20(72 _metarouteTransaction.approvedTokens[approvedTokensLength - 1]73 ).balanceOf(address(this));74}75
76IERC20(_metarouteTransaction.approvedTokens[approvedTokensLength - 1]).approve(77 _metarouteTransaction.relayRecipient,78 finalSwapAmountIn79);80
81bytes memory otherSideCalldata = _metarouteTransaction.otherSideCalldata;82assembly {83 mstore(add(otherSideCalldata, 100), finalSwapAmountIn)84}Recommendation:
We advise the calldata arguments to be validated by at least mandating they are of a particular length.
Alleviation:
After consideration of our exhibit & with the help of an external party, the Symbiosis Finance team identified a potential attack vector based on allowances set to the contract that arbitrary calls could exploit. The Symbiosis Finance team introduced the concept of a gateway contract that is meant to instead be set an allowance for by external users preventing the arbitrary calls performed by the MetaRouterV2 contract to be able to tap into allowances set for it. Additionally, the two arbitrary calls performed now cannot have the gateway contract as a target thereby completely nullifying any attack vector that would affect user funds and rendering the contract secure. After additional discussion with the Symbiosis Finance team, we concluded that malicious data stacks for the linked assembly blocks would only affect the caller and would not pose a threat to other users or the network's state. As a result, this exhibit is considered dealt with.
MRV-02M: Arbitrary Approvals
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | MetaRouterV2.sol:L36-L39, L61-L64, L76-L79 |
Description:
The contract performs arbitrary approve invocations which allow crafted payloads to extract any funds at rest within the contract.
Example:
18function metaRouteV2(19 MetaRouteStructs.MetaRouteTransactionV2 memory _metarouteTransaction20) external payable {21 uint256 firstSwapValue;22 uint256 approvedTokensLength = _metarouteTransaction.approvedTokens.length;23
24 if (!_metarouteTransaction.nativeIn) {25 TransferHelper.safeTransferFrom(26 _metarouteTransaction.approvedTokens[0],27 _msgSender(),28 address(this),29 _metarouteTransaction.amount30 );31 }32
33 uint256 secondSwapAmountIn = _metarouteTransaction.amount;34 if (_metarouteTransaction.firstSwapCalldata.length != 0) {35 if (!_metarouteTransaction.nativeIn) {36 IERC20(_metarouteTransaction.approvedTokens[0]).approve(37 _metarouteTransaction.firstDexRouter,38 _metarouteTransaction.amount39 );40 }41
42 (bool firstSwapSuccess,) = _metarouteTransaction43 .firstDexRouter44 .call{value : msg.value}(_metarouteTransaction.firstSwapCalldata);45
46 require(firstSwapSuccess, "MetaRouterV2: first swap failed");47
48 secondSwapAmountIn = IERC20(49 _metarouteTransaction.approvedTokens[1]50 ).balanceOf(address(this));51 }52
53 uint256 finalSwapAmountIn = secondSwapAmountIn;54 if (_metarouteTransaction.secondSwapCalldata.length != 0) {55 bytes memory secondSwapCalldata = _metarouteTransaction.secondSwapCalldata;56
57 assembly {58 mstore(add(secondSwapCalldata, 100), secondSwapAmountIn)59 }60
61 IERC20(_metarouteTransaction.approvedTokens[approvedTokensLength - 2]).approve(62 _metarouteTransaction.secondDexRouter,63 secondSwapAmountIn64 );65
66 (bool secondSwapSuccess,) = _metarouteTransaction.secondDexRouter.call(67 secondSwapCalldata68 );69 require(secondSwapSuccess, "MetaRouterV2: second swap failed");70
71 finalSwapAmountIn = IERC20(72 _metarouteTransaction.approvedTokens[approvedTokensLength - 1]73 ).balanceOf(address(this));74 }75
76 IERC20(_metarouteTransaction.approvedTokens[approvedTokensLength - 1]).approve(77 _metarouteTransaction.relayRecipient,78 finalSwapAmountIn79 );80
81 bytes memory otherSideCalldata = _metarouteTransaction.otherSideCalldata;82 assembly {83 mstore(add(otherSideCalldata, 100), finalSwapAmountIn)84 }85
86 (bool otherSideCallSuccess,) = _metarouteTransaction.relayRecipient87 .call(otherSideCalldata);88 require(otherSideCallSuccess, "MetaRouterV2: other side call failed");89}Recommendation:
While funds are not expected to remain at rest, it is still advisable to perform approvals only to authorized exchanges and to validate that a swap was indeed made before performing the final transaction. In general, the router should identify the amounts it received via the return arguments of the swaps rather than rely on dynamic balanceOf invocations.
Alleviation:
The Symbiosis Finance team stated that given the context of the contract any allowance will not pose a threat to other users or the network and as such they opt to not remediate it.
MRV-03M: Ill-Advised Allowance Pattern
| Type | Severity | Location |
|---|---|---|
| Standard Conformity | ![]() | MetaRouterV2.sol:L145-L148 |
Description:
The linked code performs an "infinity" allowance to the router it is meant to interact with, a programming paradigm that is advised against.
Example:
137function _swap(138 address _token,139 uint256 _amount,140 address _router,141 bytes memory _swapCalldata,142 uint256 _offset143) internal returns (bool success) {144 if (IERC20(_token).allowance(address(this), _router) < _amount) {145 IERC20(_token).approve(146 _router,147 type(uint256).max148 );149 }150
151 assembly {152 mstore(add(_swapCalldata, _offset), _amount)153 }154
155 (success, ) = _router.call(_swapCalldata);156}Recommendation:
We advise the allowance to be set to exactly the value necessary to avoid potential complications due to unspent allowance.
Alleviation:
The Symbiosis Finance team stated that given the context of the contract any allowance will not pose a threat to other users or the network and as such they opt to not remediate it.
MRV-04M: Improper receive Function
| Type | Severity | Location |
|---|---|---|
| Logical Fault | ![]() | MetaRouterV2.sol:L15 |
Description:
The MetaRouterV2 contract is able to receive native assets, however, no function exists in the contract that utilizes funds received as an argument.
Example:
15receive() external payable {}Recommendation:
Presumably, this function was introduced to allow native outputs in the swaps the contract performs, however, no outward native asset transfer is performed by the contract that utilizes converted or existing (address(this).balance) funds. As a result, we advise the function to be omitted from the contract.
Alleviation:
The receive function has been omitted from the codebase as per our recommendation.

