The Router Contract: Comparison between Soroswap and UniswapV2
esteblock
Posted on September 12, 2023
Check this and more in docs.soroswap.finance
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:
address public immutable override WETH;
With UniswapV2Router02, if you are using ETH, you'll need to use special functions:
-
addLiquidityETH
instead ofaddLiquidity
-
removeLiquidityETH
instead ofremoveLiquidity
-
removeLiquidityETHWithPermit
instead ofremoveLiquidityWithPermit
- Instead of just having
swapExactTokensForTokens
andswapTokensForExactTokens
; 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
andswapExactTokensForETHSupportingFeeOnTransferTokens
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 XML 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:
Only the following functions will be written:
function addLiquidity
function removeLiquidityWithPermit
function swapExactTokensForTokens
function swapTokensForExactTokens
function swapExactTokensForTokensSupportingFeeOnTransferTokens
The following functions won't be written:
function addLiquidityETH
function removeLiquidityETH
function removeLiquidityETHWithPermit
function swapExactETHForTokens
function swapTokensForExactETH
function swapExactTokensForETH
function swapETHForExactTokens
function swapExactETHForTokensSupportingFeeOnTransferTokens
function swapExactTokensForETHSupportingFeeOnTransferTokens
Also, because we will treat XML as any other token, we won't need to define its address. We won't need to write something like:
address public immutable override WETH;
Finally, there is also no need for this function:
receive() external payable {
assert(msg.sender == WETH); // only accept ETH via fallback from the WETH contract
}
as XML can only be sent using the native contract.
SafeMath
In Solidity, the SafeMath library is used to validate arithmetic operations and prevent integer overflow/underflow. Should such a situation arise, the library throws an exception, which effectively reverts the transaction.
In Rust, we can achieve similar functionality by activating the overflow check flag during the compilation process with the following code:
[profile.release]
overflow-checks = true
Additionally, we use an overflow-safe implementation of functions checked_add
, checked_mul
, checked_div
, and checked_sub
. You can explore these functions and test their functionality in this repository: Overflow Soroban Repository
Also, we have overflow-safe functions checked_add
, checked_mul
, checked_div
and checked_sub
You can check and test these techniques in the following repository: Overflow Soroban Repository
In conclusion, Soroswap prevents overflows by leveraging these techniques.
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:
modifier ensure(uint deadline) {
require(deadline >= block.timestamp, 'UniswapV2Router: EXPIRED');
_;
}
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:
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
bytes32 digest = keccak256(
abi.encodePacked(
'\x19\x01',
DOMAIN_SEPARATOR,
keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
)
);
address recoveredAddress = ecrecover(digest, v, r, s);
require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
_approve(owner, spender, value);
}
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 in Soroswap we want to support all types of tokens, we will implement these logics in the SoroswapRouter contract!
library UniswapV2Library
UniswapV2Router uses the UniswapV2Library. This is something that Soroswap will also develop
What's next?
Write rust!
Posted on September 12, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024