Decentralized finance (often stylized as DeFi) is a form of finance built on blockchain technology. It does not rely on traditional financial institutions such as brokers, exchanges, or banks to provide financial instruments. Instead, DeFi utilizes smart contracts on the blockchain to carry out financial activities. There are numerous arbitrage opportunities in DeFi, including but not limited to liquidation and price arbitrage. This article will analyze some potential arbitrage opportunities in decentralized exchanges (DEXs) and aggregators at the smart contract code level.
Uniswap is a decentralized cryptocurrency trading platform that employs the Automated Market Maker (AMM) model. There are currently two popular versions: Uniswap V2 and Uniswap V3. We will analyze the potential arbitrage opportunities in each version separately.
Uniswap V2 Router
In Uniswap V2, users generally interact with the Pair and Factory contract through the Router contract. Typically, the Router only serves as an intermediary for token transfers during transactions and does not store tokens. However, due to various reasons such as airdrops or transfer errors, some tokens may end up being stored in the Router contract. So, how can these tokens be extracted?
By analyzing the Uniswap V2 Router02 contract’s code, we found the
This function is used to remove liquidity when one of the tokens is WETH. When calling the
removeLiquidity function, the
to address passed in is
address(this), which means the two tokens will first be transferred to the Router contract, and then the Router contract will transfer the two tokens to the specified address. Although the amount of WETH transferred is returned by
removeLiquidity and cannot be modified, the amount of the other token transferred is
balanceOf(address(this)), which is the balance of that token in the Router contract.
Based on the analysis above, we can derive an arbitrage process:
- Monitor the presence of ERC20 tokens in the Router02 contract;
addLiquidityETHto add liquidity for the ERC20 token and WETH;
removeLiquidityETHSupportingFeeOnTransferTokensto remove liquidity.
- If the token has not previously added liquidity with WETH, there will be a small loss of liquidity (MINIMUM_LIQUIDITY) when adding liquidity for the first time;
- It is currently unclear how to extract WETH and ETH from the Router02 contract.
Uniswap V2 Pair
The Uniswap V2 Pair contract, also known as the liquidity pool, stores the two tokens provided for liquidity. Since the Pair contract uses reserves to record balances instead of
balanceOf(address(this)), there may be a difference between balance and reserve when someone mistakenly transfers liquidity tokens directly into the contract. The Pair contract has a balancing function called
skim, which can be called to extract the tokens corresponding to this difference:
We can see that this function transfers the difference between the balance and reserve of the two liquidity tokens in the pool to the “to” address.
In addition to these two tokens, the liquidity pool may also contain other ERC20 tokens due to mistaken transfers, airdrops, and other reasons. How can we extract these tokens?
After analyzing the Pair contract code, it is found that there is no way to extract these tokens, with one exception: when the pool contains LP tokens of that pool.
In this case, we can call the
burn function of the Pair contract to remove liquidity and withdraw the corresponding two liquidity tokens:
Uniswap V3 SwapRouter
The Uniswap V3 SwapRouter contract may also have the same situation as the Uniswap V2 Router, containing ERC20 tokens and ETH. Fortunately, the SwapRouter contract provides several functions to conveniently extract the tokens inside.
To extract ERC20 tokens, we can use the
To extract ETH, we can use the refundETH function:
We can also directly call the unwrapWETH9 function to convert WETH back to ETH and extract it:
The above is an arbitrage analysis of the Uniswap V3 SwapRouter contract.
After analyzing the UniswapV3 Pool contract code, it is found that there is no way to extract other tokens in the contract, and there is no situation where the balance and reserve have a difference as in the Uniswap V2 Pair contract.
SushiSwap was initially a fork of Uniswap and later developed into an independent ecosystem, providing many different financial services and products.
Since SushiSwap is similar to Uniswap V2, the above arbitrage methods for Uniswap V2 are also applicable to SushiSwap.
SushiXSwap is a LayerZero-based cross-chain trading protocol launched by SushiSwap, supporting networks including Optimism, Arbitrum, Fantom, BNB Chain, Polygon, and Avalanche. Users can perform cross-chain transactions between supported networks and assets.
How to extract tokens from the SushiXSwap contract?
SushiXSwap mainly implements its functions through the
cook function, which provides a series of operations with the following supported action list:
One of the operations is
ACTION_DST_WITHDRAW_TOKEN, with the code implementation as follows:
First, decode the data passed into the
cook function, then determine if the amount is equal to 0. If it is, set the value of amount to the contract's ERC20 token balance or ETH balance. Finally, call
_transferTokens to transfer the tokens to the specified address:
So, we just need to construct the actions and data passed into the
cook function, set actions to
ACTION_DST_WITHDRAW_TOKEN, and construct the token, receiving address, and quantity in the data to transfer the tokens out of the SushiXSwap contract.
Sushi BentoBox is a component of the SushiSwap ecosystem. BentoBox is a highly flexible decentralized finance (DeFi) interest rate optimization product. Simply put, it is a smart contract platform that allows users to store, borrow, and earn interest. The primary purpose of BentoBox is to optimize user returns in the DeFi space.
The BentoBox contract on Ethereum holds a large number of tokens. Is there room for arbitrage in this contract?
In the BentoBox contract, users can deposit through the
deposit function, which is implemented as follows:
The user specifies the token address, deduction address, receiving address, amount, and share quantity. The function first performs a series of validations, then converts the amount or share. The key point is on lines 195–198, where a validation is performed:
amount <= _tokenBalanceOf(token).sub(total.elastic).
In the BentoBox contract, the balance of a particular token is recorded as
total.elastic, similar to the reserve in the Uniswap Pair contract. In some cases, it will generate a difference with
_tokenBalanceOf(token). We can utilize the characteristics of the
deposit function to convert the difference into our balance within the BentoBox contract.
Therefore, when passing in parameters, set the token to the token address with the difference, set the amount value to the difference, set ‘from’ to the BentoBox contract address, and set ‘to’ to your address. At line 207, because the address is the BentoBox contract address, no transfer will occur, only balancing the
_tokenBalanceOf(token) values and converting it into the 'to' address's balance within the contract.
DODO is a decentralized trading platform that uses its unique Proactive Market Maker (PMM) algorithm to provide efficient on-chain liquidity for Web3 assets. DODO provides liquidity by itself and also aggregates liquidity from other exchanges.
DODO has a series of contracts, among which users exchange tokens through the DODOV2Proxy02 contract. Similar to the Uniswap Router contract, this contract may hold some tokens for various reasons. How can we extract these tokens?
externalSwap function exists in the DODOV2Proxy02 contract and is used to call external platforms aggregated by DODO for exchange, such as 0x and 1inch. The code implementation is as follows:
Lines 1719–1721 perform validation on the input parameters. Then, line 1724 checks whether fromToken is ETH. If not, it will transfer the caller’s tokens to the contract and then authorize. After analyzing the DODOAPPROVE contract code, it was found that setting fromTokenAmount to 0 can bypass this:
Next, it will validate the external contract to be called, and only those on the whitelist can be called. The
calldataConcat here are controlled by the user, so you can set
swapTarget to the contract address of 0x or 1inch, and set
calldataConcat to the encoding of the view function of their contract, making the returned value true and passing the subsequent require check:
After that, it will transfer all the toToken in the contract to the caller. The toToken can be an ERC20 token or ETH. After sending, it will check the minimum expected quantity. We can set the value of
minReturnAmount to a very small value to pass this check. The last two function calls are not important.
By following the above steps, we can extract ERC20 tokens and ETH from the DODOV2Proxy02 contract.
1inch is a decentralized exchange (DEX) aggregator that pools liquidity from multiple DEXs to provide users with the best token exchange prices. By integrating liquidity from different sources, 1inch helps users optimize their trades and find the most favorable prices across platforms. 1inch’s smart contracts automatically trade between various decentralized exchanges, allowing users to easily get the best prices and lowest slippage between different exchanges. Additionally, 1inch offers other features such as liquidity mining and governance tokens.
The main contracts for 1inch are AggregationRouter, with V4 and V5 versions being more commonly used. These two contracts may hold some tokens for various reasons, and we can extract tokens from the contract by constructing input parameters for functions.
The AggregationRouterV5 contract has a
swap function, implemented as follows:
After verifying the minReturnAmount in desc, it retrieves srcToken and dstToken from desc. Lines 986–997 can be bypassed by constructing the flags and srcToken in the desc structure:
_execute function is executed, which performs a call and verifies the execution status. Since the executor is user-supplied, we can use the 0 address to bypass this:
Next, it retrieves the balance of dstToken in the contract. Lines 1007–1018 can be bypassed by constructing the flags and minReturnAmount in the desc:
Finally, it transfers the entire balance of dstToken in the contract to the dstReceiver address, which is also controlled by the user:
By following the steps above, we can construct the parameters for the
swap function to extract tokens from the AggregationRouterV5 contract.
AggregationRouterV4 is not very different from AggregationRouterV5. AggregationRouterV4 also has a
swap function, implemented as follows:
It can be seen that the implementation of the
swap function in AggregationRouterV4 is the same as that of AggregationRouterV5, except that AggregationRouterV5 has optimized th e call. Therefore, the same method as AggregationRouterV5 can be used to extract tokens from the AggregationRouterV4 contract.
This article provides a brief introduction to some decentralized exchanges and aggregators and explores potential arbitrage opportunities. The principle of arbitrage is analyzed from the contract code level, but whether it can be successful in practice depends on many factors, such as gas, node speed, etc.
At Eocene Research, we provide the insights of intentions and security behind everything you know or don’t know of blockchain, and empower every individual and organization to answer complex questions we hadn’t even dreamed of back then.