Skip to main content

The Router Contract: Comparison between Soroswap and UniswapV2

Until now, we have developed the Factory and the Pair contract. The factory knows how many pairs it has created and can give us their contract addresses. Also, if we want to trade between token A and token B, and if that pair exists, the Factory will also give us the corresponding pair contract address. But what happens if there are two pairs, A-B and B-C, and a user wants to make a trade between A and C? We could first trade A to B and then B to C! This is why we have the Router Contract. The Router Contract allows swapping of tokens when a direct pair does not exist. It also handles liquidity provision and manages deposit and withdrawal functions for liquidity providers within the Soroswap ecosystem.

The native token: ETH, WETH, and XLM.

In the UniswapV2Router02, there is a very important distinction between any ERC20 tokens and the native token ETH (and its wrapped version WETH). In particular, in the contract we find:
With UniswapV2Router02, if you are using ETH, you’ll need to use special functions:
  • addLiquidityETH instead of addLiquidity
  • removeLiquidityETH instead of removeLiquidity
  • removeLiquidityETHWithPermit instead of removeLiquidityWithPermit
  • Instead of just having swapExactTokensForTokens and swapTokensForExactTokens; Uniswap Router needs to add 4 extra functions: swapExactETHForTokens, swapTokensForExactETH, swapExactTokensForETH, swapETHForExactTokens.
  • Instead of just having swapExactTokensForTokensSupportingFeeOnTransferTokens, Uniswap Router needs to add 2 extra functions: swapExactETHForTokensSupportingFeeOnTransferTokens and swapExactTokensForETHSupportingFeeOnTransferTokens
This is because it’s very common that in Blockchains, the native token (in this case ETH in Ethereum) is treated differently, so the smart contract needs to trigger different functions when transferring the native token. What about in Soroban? Well, in Soroban, we won’t need to have all these extra functions! This is because the native XLM token has its own token contract address and can be treated as any other token complying with the token interface. You can find more information about this in the Soroban Token Playground Chapter 8, 9, 10, and 11. Conclusion: none of the ETH-specific variants were needed. SoroswapRouter was written, and it is deployed on mainnet with the following public functions:
None of the following UniswapV2Router02 functions were written:
Also, because we will treat XLM as any other token, we won’t need to define its address. We won’t need to write something like:
Finally, there is also no need for this function:
as XLM can only be sent using the native contract.

SafeMath

The Router relies on the same overflow and underflow protections as the rest of Soroswap, described in the SoroswapLibrary comparison.

Deadlines

In Ethereum we can send a transaction with low gas price, and that transaction could be accepted 5, 10 or more minutes later. In order to avoid unwanted transactions after a period of time, Uniswap introduces the deadline parameter and the ensure(deadline) modifier:
In Soroban, we can think that having a transaction waiting for minutes is something that can never happen. But because we don’t want to say never, Soroswap will include this “deadline modifier.” Modifiers do not currently exist in the soroban-sdk. So we’ll need to build a special function using the env.ledger().timestamp() object.

Permit

In UniswapV2Router02 we use the permit method of the UniswapV2ERC20.sol contracts that is defined here:
This function implements EIP-2612. This avoids the user to use 2 transactions approve and `transfer, which allows users to modify the allowance mapping using a signed message. this function allows a token holder to grant permission for a specific address (spender) to spend their tokens up to a certain amount (value) with a specified expiration time (deadline). The signature ensures the authenticity of the permit, and the function enforces that the permit hasn’t expired and that it was signed by the legitimate token owner. In Soroban we don’t need to implement something like this, as we use the require.auth() method when sending tokens from the user into the smart contract. Conclusion: We don’t need to impement the removeLiquidityWithPermit, neither the removeLiquidityETHWithPermit functions (neither removeLiquidityETHWithPermitSupportingFeeOnTransferTokens)

Fees on Transfer

In UniswapV2, there is a special function that allows the user to swap tokens that charge a fee when doing a transfer. This tokens are popular in the deflactionist community, and indeed it made Uniswap upgrade their original UniswapV2Router (latter called UniswapV2Router01) contract to a UniswapV2Router02 version! You can read all the discussion of the Uniswap design in this issue In the code we see many functions that have the word SupportingFeeOnTransferTokens:
  • _swapSupportingFeeOnTransferTokens
  • swapExactTokensForTokensSupportingFeeOnTransferTokens
  • swapExactETHForTokensSupportingFeeOnTransferTokens
  • swapExactTokensForETHSupportingFeeOnTransferTokens
  • removeLiquidityETHSupportingFeeOnTransferTokens
But in fact there is only two functions that do all the work: _swapSupportingFeeOnTransferTokens and removeLiquidityETHSupportingFeeOnTransferTokens These functions work equal to _swap and removeLiquidityETH but succeeds for tokens that take a fee on transfer. Because Soroswap wanted to support all types of tokens, the plan at the time was to carry this logic into the SoroswapRouter contract.

library UniswapV2Library

UniswapV2Router uses the UniswapV2Library. Soroswap developed its own equivalent, compared function by function in the SoroswapLibrary comparison.

Where this landed

The Rust was written. SoroswapRouter is deployed on mainnet, and its current interface is documented under Contracts.