👩💼Architecture
Rango Smart Contracts Architecture
Last updated
Rango Smart Contracts Architecture
Last updated
The contract code and architecture is discussed briefly to give an overview of the smart contracts. However, the contracts should only be used through Rango's API and contract details that are not discussed here.
Rango Smart Contracts Code is open source and available at this Github repository:
Rango v2 contracts are designed to handle swaps and bridge transactions and also message passing transactions.
There are two main type of contracts:
Diamond
: Handles swap and bridge transactions (RangoDiamond.sol)
Middlewares
: Receives token and message on destination chain and handles the transaction
RangoDiamond is based on EIP-2535 and v3 implementation. The functionality and support for each bridge is handled by a separate facet. All swapping protocols are handled by a single facet. For example:
Axelar (Satellite): RangoSatelliteFacet.sol
THORChain: RangoThorchainFacet.sol
Stargate: RangoStargateFacet.sol
Stargate Middleware: RangoStargateMiddleware.sol
1inch, Paraswap, uniswap v2, v3 & forks etc: RangoSwapperFacet.sol
Example scenario: Swap 100 USDT on chain A to DAI on chain B
Example scenario: Swap 100 USDT on chain A to BUSD on chain B through a Middleware
contract
Example scenario: Swap 100 USDT on chain A to BUSD on chain B through a Middleware
contract and call a third-party dApp contract.
Bridge Facets follow a pattern of having two external
functions which are the main entry points:
One function for direct bridging:
Stargate: function StargateBridge(...) external
Wormhole: function wormholeBridge(...) external
Another function for swap and bridge:
Stargate: function StargateSwapAndBridge(...) external
Wormhole: function wormholeSwapAndBridge(...) external
To handle the interaction with the bridge, each bridge has one or more internal
functions where the name is like doBridge
:
Stargate: function doStargateSwap(...) internal
Symbiosis: function doSymbiosisBridge(...) internal
In summary, the contract code architecture looks like this:
The swap functionality for each bridge is very similar and the core implementation is handled by LibSwapper.sol and RangoSwapperFacet.sol
To handle swap logic, we use two structs SwapRequest
and Call
. A swap transaction has one SwapRequest
and one or more swap Call
:
The SwapRequest
defines which tokens is to be swapped and which token should be received and some helper data:
The Call
defines the contract that should be given approval (spender
) and the contract to be called (target
) and the calldata to be sent to target contract (callData
). Note that Call
also defines swapFromToken and swapToToken which might be different from SwapRequest
. This is because a swap can have multiple Call
s and the SwapRequest
needs to know initial and final token, but each Call
might have its own tokens.
An example of helper function that uses SwapRequest
and Call
s in bridges is provided here:
In the case of sending a message across chains, we use a standard struct called RangoInterChainMessage
. This object is used when a swap or contract call is needed on the destination chain. The message is received in the destination chain and is handled by the Middleware
Contract specific to that bridge.
Each bridge has its own messaging model if it supports message passing. Therefore, there is no standard or pattern for interchain message calls for different bridges. But to keep the code clean, we have defined a parent contract(RangoBaseInterchainMiddleware.sol
) which all middlewares can inherit from in order to have functionalities such as whitelists and refunding.