CertiK reviewed Wido (Wido - Audit) on 13 Dec 2022: 5 findings, 0 critical; all marked fixed.
Report hosted on skynet.certik.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 0 | 0 | 1 |
| Low | 0 | 0 | 4 |
In the contract `WidoRouter`, function `executeOrder()` takes `feeBps` as input, which specifies how many fees to be charged. ```solidity=279 function executeOrder( Order calldata order, Step[] calldata route, uint256 feeBps, address partner ) ``` Later, the variable `feeBps` will be passed to the function `_collectFees()` to calculate the `fee` that will be transferred to the `bank` address. ```solidity function _collectFees(address fromToken, uint256 amount, uint256 feeBps) private { require(feeBps <= 100, "Fee out of range"); uint256 fee = (amount * feeBps) / 10000; if (fromToken == address(0)) { bank.safeTransferETH(fee); } else { ERC20(fromToken).safeTransfer(bank, fee); } } ``` However, there is no restriction for the input `feeBps` in the linked functions. Consequently, users can set the input `feeBps` to zero and thus bypassing the `require()` statement.
In the function `executeOrderWithSignature()`, users can sign and delegate the transaction to another party. ```solidity=316 function executeOrderWithSignature( Order calldata order, Step[] calldata route, uint8 v, bytes32 r, bytes32 s, uint256 feeBps, address partner ) external override nonReentrant { require(verifyOrder(order, v, r, s), "Invalid order"); nonces[order.user]++; _executeOrder(order, route, order.user, feeBps); emit FulfilledOrder(order, msg.sender, order.user, feeBps, partner); } ``` However, the contract only verifies the `Order` struct signed by the users and does not validate input `route`. In this case, the attacker can spoof a malicious `route` payload, which calls a malicious contract. Therefore, users might suffer unexpected loss. Due to the protection on `minOutputAmount`, which enforces the minimum output amount from the swap, otherwise, it will revert. ```solidity if (toTokenBalance < order.minOutputAmount) revert SlippageTooHigh(order.minOutputAmount, toTokenBalance); ``` However, users might mistakenly set the `minOutputAmount,` thus creating a chance for attackers to steal their funds.
The Wido project allows user to call arbitrary addresses to swap their tokens, which is specified in the `route` calldata. ```solidity function _executeSteps(Step[] calldata route) private { ``` The concern is that if a vulnerable swap contract is used during the call (i.e., in `route` input), it could lead to users' asset loss. For example, a user wants to swap tokens in a certain `route`, however, one of the swap contract in the `route` is vulnerable and drains 10% of the amount from user's input amount. Therefore, after swapping, the user to will lose 10% of the tokens during this swap.
In the `WidoRouter` contract, the low-level call is implemented to perform swap operation in a flexible way. ```solidity=129 (bool success, bytes memory result) = step.targetAddress.call{value: value}(editedSwapData); ``` As per the current design, users will approve funds to the WidoManager contract instead of the `WidoRouter` contract, which will prevent attackers from directly triggering the low-level call with `transferFrom()` invocation to drain users' assets. Users should never approve the WidoRouter contract. If users approve `WidoRouter` contract by mistake, it could lead to a potential loss of funds.
In the `WidoRouter` contract, users can swap tokens via the `executeOrder()` function call with a designated input `Order` struct. ```solidity function executeOrder( Order calldata order, Step[] calldata route, uint256 feeBps, address partner ) ``` ```solidity struct Order { OrderInput[] inputs; OrderOutput[] outputs; address user; uint32 nonce; uint32 expiration; } ``` The `order.inputs` specifies the amount of `from` tokens to be swapped. The exact amount (`order.inputs`) tokens will be pulled from user's address during the `_executeOrder()` operation. After the swap, all the `toTokens` in the contract will be transferred to user's address. However, due to the various swapping mechanisms of different protocols, there might be some potential `fromToken` leftovers in the `WidoRouter` contract during the swap. For example, a user wants to swap 100 tokens. The exact 100 tokens will be pulled from the user's address to the `WidoRouter` contract. If the external swap protocol only takes 98 tokens for swapping, there will be two tokens left in the `WidoRouter`. Consequently, other users could exploit the low-level call to transfer the leftovers to their own addresses. It is also worth mentioning that if the swapping is performed via multiple paths, there could be multiple token leftovers inside the contract.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (5)
1,044 projects reviewed · 1,375 reports · latest May 2025
See CertiK’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us