###### tags: `Exploit Analysis Report` EVO DeFi Analysis === > Copyright © 2022 by Verilog Solutions. All rights reserved. > April 29, 2022 > by **Verilog Solutions** Inspired by the recent token bridge exploits, Verilog research team surveyed the existing cross-chain bridge solutions and decided to perform an independent, external security analysis on EVO DeFi Bridge. EVO DeFi Bridge is a cross-chain bridge connected to multiple EVM-compatible chains, including Ethereum, BSC, Fantom, Avalanche, and Oasis Emerald ParaTime. Verilog Solutions has conducted on-chain analytics and external analysis on the open-sourced portion of EVO DeFi Bridge in hope of improving the safety of funds and transparency across the crypto ecosystem. To conduct the analysis, the Verilog team used various techniques including but not limited to event emission extraction, cross-chain fuzzy matching, and byte code comparison. The goal of this analysis is to discover whether proper collateralization and governance are in place for EVO DeFi Bridge. It is important to note that this analysis was conducted unilaterally without correspondence from EVO DeFi Bridge, and some parts of the source code are not available to the Verilog team. Therefore, this analysis is based on incomplete information and best-effort conjugation. **This report is not intended to be used as any financial advice.** --- ## Table of Content [TOC] --- ## Smart Contract Analysis The EVO DeFi Bridge Contract Analysis 1. address `0x06c30Af8A82AAf9cFd319f8644584276Bfbec42f` This address has been used on BSC, Polygon, Oasis, Fantom, Heco, Ethereum, Avalanche, Arbitrum, Cronos, Moonriver and OKEXchain. However, only the contract on BSC has been verified, thus this is the only contract we can analyze. 2. address `0x7cca0859058aa03301106ab8aba1707a16a30821` This address has been used on HarmonyONE, Gnosis and Optimism. However, none of the addresses has verified contracts. ### Bytecode Analysis We inspect the bytecode and source code of EVO DeFi Bridge contract in all the aforementioned chains. The code is available at [our GitHub repo](https://github.com/Verilog-Solutions/Crosschain-Tracing-Analysis/tree/main/BytecodeAnalysis). 1. We start with the only available source code, [EVO DeFi Bridge @ BSC](https://bscscan.com/address/0x06c30Af8A82AAf9cFd319f8644584276Bfbec42f#code). We also obtain its opcode and bytecode. 2. We obtain the opcode representation of EVO DeFi Bridge @ Arbitrum, Ethereum, Fantom and Polygon. a. We compare the opcode diff between BSC's and every other. We observe that Arbitrum, Fantom and Polygon have the exact same opcode with BSC, so we confirm that **within these three blockchain, EVO DeFi Bridge operates using the same logic.** b. Comparing the opcode in BSC and in Ethereum, we observe a few different opcodes (as follows). Given only 27 out of 4864 opcodes are different and the others remain the same, the Ethereum version is very likely to be identical with ``` Unknown Opcode 3d => RETURNDATASIZE Unknown Opcode 3e => RETURNDATACOPY Unknown Opcode 1b => SHL Unknown Opcode 47 => SELFBALANCE ``` 3. We obtain the bytecode of representation of EVO DeFi Bridge @ Avalanche, Cronos, Gnosis, HarmonyONE, Heco, Moonriver and Optimistic. a. We observe that the bytecode of EVO DeFi Bridge @ Avalanche is identical to that in BSC, so we confirm EVO DeFi Bridge shares the same logic with Avalanche and BSC. b. Although Heco, Cronos, Moonriver, OKEX, HarmonyOne, Gnosis, Optimism are different from the original lBSC version. {Heco, Cronos, Moonriver} share the same set of bytecode; {OKEX, HarmonyOne, Gnosis, Optimism} share another same set of bytecode. 4. We continue to explore similarities among the BSC version, Heco version and OKEX version. We decompile the bytecode using Panoramix decompiler. 5. We found that the Heco version and OKEX version implement the same set of ABI as the BSC version: ``` decompiled interface => source code interface withdrawn(bytes32 _param1) => mapping(bytes32 => bool) public withdrawn; owner() => address public owner operator(address _operator) => mapping(address => uint8) public operator _fallback() payable => receive() external payable setOwner(address _new) => setOwner(address newOwner) unknown4b63d0a1(uint256 _param1, uint256 _param2) => setOperatorMode(address account, uint8 mode) unknown94241002() => withdraw(Withdraw[] calldata ws) unknown8033d687(?) => take(IERC20 token, uint amount, address payable to) unknown90cfe778(?) => deposit(IERC20 token, uint amount, uint8 to, bool bonus, bytes calldata recipient) ``` ### Owner Analysis( ) There are in total 5 write functions & 3 read functions in EVO DeFi Bridge BSC Contract: ``` solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; import './interfaces/IBridgePool.sol'; contract BridgePool is IBridgePool { address public owner; /* operator modes: 1 - contract:creator 2 - contract:withdrawer 4 - withdrawer 8 - taker */ mapping(address => uint8) public operator; mapping(bytes32 => bool) public withdrawn; bool private entered = false; modifier nonReentrant() { require(!entered, 'reentrant call'); entered = true; _; entered = false; } constructor () { owner = tx.origin; } function setOwner(address newOwner) external { require(msg.sender == owner, 'forbidden'); owner = newOwner; } ..... ``` The above contract shows that deployer of the contract has been set as the native owner of this contract, where owner can transfer ownership to another wallet address. The deployer - [0x40e0dcd7024030c7b5e1d474fe95aaf7bb880ad0](https://bscscan.com/address/0x40e0dcd7024030c7b5e1d474fe95aaf7bb880ad0) found out in the contract deployment transaction tx [0x2a788fa2eb1c4313d4bff1ae19e24ac5bfbd6c15b56c75ff512b16993905612d](https://bscscan.com/tx/0x2a788fa2eb1c4313d4bff1ae19e24ac5bfbd6c15b56c75ff512b16993905612d) on BSCscan. ![contract deployment tx](https://hackmd.io/_uploads/S1z_FTX79.png) After contract deployment, the deployer did not set new owner. Thus, until now, this address is still the owner of contract address `0x06c30Af8A82AAf9cFd319f8644584276Bfbec42f` on BSC, Polygon, Oasis, Fantom, Heco, Ethereum, Avalanche, Arbitrum, Cronos, Moonriver and OKEXchain. ### setOperatorMode( ) ```solidity= function setOperatorMode(address account, uint8 mode) external { require(msg.sender == owner, 'forbidden'); operator[account] = mode; } ``` The owner of this smart contract can set operator mode for input `account`. ### deposit( ) ```solidity= function deposit(IERC20 token, uint amount, uint8 to, bool bonus, bytes calldata recipient) override external payable nonReentrant() { // // ed only direct call or 'contract:creator' or 'contract:withdrawer' require(tx.origin == msg.sender || (operator[msg.sender] & (1 | 2) > 0), 'call from unauthorized contract'); require(address(token) != address(0) && amount > 0 && recipient.length > 0, 'invalid input'); if (address(token) == address(1)) { require(amount == msg.value, 'value must equal amount'); } else { safeTransferFrom(token, msg.sender, address(this), amount); } emit Deposited(msg.sender, address(token), to, amount, bonus, recipient); } ``` This deposit function allows users to deposit certain token to this smart contract. There are in total 5 inputs: 1. `IERC20 token` - deposit ERC20 token address 2. `uint amount` - amount of ERC20 token 3. `to` - might be chainID 4. `bool bonus` - small amount of gas incentive trigger 5. `bytes calldata recipient` - recipient address on other chains ### withdraw( ) ```solidity= function withdraw(Withdraw[] calldata ws) override external nonReentrant() { // allowed only 'withdrawer' or 'withdrawer' through 'contract:withdrawer' require(operator[msg.sender] == 4 || (operator[tx.origin] == 4 && operator[msg.sender] == 2), 'forbidden'); for (uint i = 0; i < ws.length; i++) { Withdraw memory w = ws[i]; require(!withdrawn[w.id], 'already withdrawn'); withdrawn[w.id] = true; if (address(w.token) == address(1)) { require(address(this).balance >= w.amount + w.bonus, 'too low token balance'); (bool success, ) = w.recipient.call{value: w.amount}(''); require(success, 'native transfer error'); } else { require( w.token.balanceOf(address(this)) >= w.amount && address(this).balance >= w.bonus, 'too low token balance' ); safeTransfer(w.token, w.recipient, w.amount); } if (w.bonus > 0) { // may fail on contracts w.recipient.call{value: w.bonus}(''); } if (address(w.token) != address(1) && w.feeAmounts.length > 0) { for (uint j = 0; j < w.feeAmounts.length; j++) { require(w.token.balanceOf(address(this)) >= w.feeAmounts[j], 'too low token balance'); safeTransfer(w.token, w.feeTargets[j], w.feeAmounts[j]); } } emit Withdrawn(w.id, address(w.token), w.recipient, w.amount); } } ``` withdraw function to allow users to receive the token from the dst chain. ### take( ) - ** high centralization risks ** ```soldiity= function take(IERC20 token, uint amount, address payable to) external override nonReentrant() { // allowed only 'taker' require(operator[msg.sender] == 8, 'forbidden'); if (address(token) == address(1)) { to.transfer(amount); } else { safeTransfer(token, to, amount); } } ``` As we previously mentioned, `setOperatorMode()` function can set input `address account` to different mode options. This `take()` function only allows `msg.sender` is 8 to pass the require conditions. Thus, 8 should be taker mode. **The taker has the right to send tokens out from this contract.** --- ## Taker Activity Analysis The on-chain transaction tx shows the following addresses have the taker right on Avalanche,BSC,Ethereum,Fantom,Polygon,Heco and Cronos Chain: | Name | Address | | -------| ------------------------------------------ | | taker1 | 0xf758E719d88862F87585F20Acc81417Cae72Df22 | | taker2 | 0x40E0DcD7024030c7b5E1d474fe95Aaf7Bb880ad0 | | taker3 | 0x2074fF8f6625A9eEE2e3Eac2Fd658df18A5C5166 | NOTE: `0x40E0DcD7024030c7b5E1d474fe95Aaf7Bb880ad0` is also the contract deployer A more detialed Tx list can be found here: https://docs.google.com/spreadsheets/d/1plFLA7_W4rEa6Orgi2s5zMNvZiA5W_XhY7oMjRkaGCs/edit?usp=sharing **Summary of Taker Activity** | Network | Taker Name|Token| Value | | ------- | --------- | --- | -------- | |Avalanche|taker1 |USDC |205,200.0 | |Avalanche|taker1 |USDT |225,000.0 | |Avalanche|taker1 |WETH |370.5 | |Avalanche|taker2 |USDT |20,000.0 | |BSC |taker1 |USDC |7,654,200.0 | |BSC |taker1 |USDT |25,280,600.0 | |BSC |taker1 |WBTC |172.3 | |BSC |taker1 |WETH |1,201.0 | |BSC |taker2 |USDC |1,620.0 | |BSC |taker2 |USDT |401,006.368 | |Cronos |taker1 |USDC |263,000.0 | |Cronos |taker1 |USDT |258,500.0 | |Cronos |taker1 |WETH |4.0 | |Ethereum |taker1 |USDC |91,261.2513 | |Ethereum |taker1 |USDT |2,925,264.94 | |Ethereum |taker1 |WBTC |7.0303518 | |Ethereum |taker1 |WETH |376.174583 | |Ethereum |taker2 |USDT |100.0 | |Fantom |taker1 |USDC |505,000.0 | |Fantom |taker1 |USDT |3,310,000.0 | |Fantom |taker1 |WBTC |60.0 | |Fantom |taker1 |WETH |610.0 | |Heco |taker1 |USDT |82,000.0 | |Polygon |taker1 |USDC |539,200.0 | |Polygon |taker1 |USDT |302,000.0 | |Polygon |taker1 |WETH |20.0 | |Polygon |taker2 |USDT |12.0 | **Summary of Set Taker Activity** |Network|Taker Name|Txn Hash| | --------- | --- | ---- | |Avalanche|taker3|0xfb7d7405a22eb052cbb0457c1460ad45b7b8ff70282e542549438edb14e3d9b6| |Avalanche|taker2|0x69eed4f24f2a405b053eeb43ae3f3623817208495755a14a772fffde9e717d7e| |Avalanche|taker1|0x45adc2829b62ce6585efddd89026fc990e6a5b37abefb591e014e74be6cbe4f4| |BSC|taker3|0xba5c0add385bf81c91201e0a6d1cf5c7ea794ce3e52eae69445cc00461f22285| |BSC|taker2|0x65e34d693f446938d97d4bb9a8b5d42a29dd10263c608e0a0ab58ef23a14b949| |BSC|taker1|0xdfaec4de42dd10b46b722107dd742b3b958cac24d59f6ab7ded5dd5d61c4579b| |Ethereum|taker3|0xb25236a24f19f56dc6007153b6810ada0109625f007dec1122027b6e7d7afe61| |Ethereum|taker2|0x9c4b385f9dd15a431dac06770e5c9c1bacf0fd637129b59a23bb43ef9dfce649| |Ethereum|taker1|0x1380619a43d61e79bbebab768f0f4bb444ed113b25b32ee32e01cc6018364cad| |Fantom|taker3|0xfa640d40549a6bda93cd17d1e2ec9c622fa4036110a19ee12b55195564090bcb| |Fantom|taker2|0xf016860a66b8f3aa803e02e84a7297b04a61b919c3e4230808aa1fc8fb4e830a| |Fantom|taker1|0xba9f32a3d5a0956835fd6d40d28fa1824517d95119319896331c1fa2a30b6551| |Polygon|taker3|0x162c70e61c78cba6dae84fb7aa906e9b1f26b0208ed4f80b1201347f64d69caa| |Polygon|taker2|0x25cfe73cfaa932bbe604f4ff4d85592725d584066b94e56e5431907c618ca30c| |Polygon|taker1|0x8312d3f6b97bb9b27e5012434bb1fc293b43220d1c6f03a7a78621ed53d0c34f| |Cronos|taker3|0x04793b215cc49b650504b761203e6f7465432a32bce3e49faaff815fc48ed34a| |Cronos|taker2|0xbcac54315fd871694a6cf85941b250ff5ecc90ca36dfb077715a46e51997adbb| |Cronos|taker1|0x085ee90a637f6cfdcc482cdc2393b30737a456167faf332ba3a281edb465d7bc| |Heco|taker3|0xc61707f6f64e25d807d70d71f324d55d39873912b9ddf99310463dbbc566d7ba| |Heco|taker2|0x6a2e78154dfbaf29a4a8da5baad721f83d46514d024b206c58180b1ed33d00a7| |Heco|taker1|0xa1f051496d186cfb5d18a50b9bb97a47f2a6bc33fe3536e4a0f7440332762099| --- ## Disclaimer The scope of our audit is limited to a review of code and only the code we note as being within the scope of our audit detailed in this report. It is important to note that the Solidity code itself presents unique and unquantifiable risks since the Solidity language itself remains under current development and is subject to unknown risks and flaws. Verilog in no way claims any guarantee of security or functionality of the technology we agree to analyze. In addition, Verilog reports do not provide any indication of the technologies proprietors, business, business model or legal compliance. As such, reports do not provide investment advice and should not be used to make decisions about investment or involvement with any particular project. Verilog has the right to distribute the Report through other means, including via Verilog publications and other distributions. Verilog makes the reports available to parties other than the Clients (i.e., “third parties”).