DEX (decentralized exchange)
Introduction
A decentralized exchange (DEX, for short), is a peer-to-peer marketplace where transactions occur directly between cryptocurrency traders. Unlike centralized exchanges like Binance, DEXs don’t allow for exchanges between fiat and cryptocurrencies; instead, they exclusively trade cryptocurrency tokens for other cryptocurrency tokens.
Decentralized exchanges are essentially a set of programs (smart contracts). They establish the prices of various cryptocurrencies algorithmically and use "liquidity pools," in which investors lock funds in exchange for interest-like rewards, to facilitate trades.
While transactions on a centralized exchange are recorded in that exchange's internal database, DEX transactions are settled directly on the blockchain.
DEXs are usually built on open-source code, meaning that anyone interested can see exactly how they work. This also means that developers can adapt existing code to create new competing projects, which is how Uniswap's code has been adapted by a whole host of DEXs with "swap" in their names, such as Sushiswap and Pancakeswap.
The exchange uses Gear fungible tokens (GFT-20) underneath for the tokens and Gear-lib FT wrapper for the pair to keep track of the liquidity.
Math
As it was said all the prices are algorithmically calculated. Investors provide funds to the liquidity pools and price is calculated according to the amount of tokens in the reserves using the following formula:
, where - are the reserves of token0 and token1 respectively provided by the investors, and - is the constant.
All the prices/amounts are calculated in the way that the MUST remain constant. This basically means that the more token0 you have in the pool, the lower price of token1 will be when performing a swap.
Factory program description
Taking into account that there might be a large amount of trading pairs, there should be a way to monitor them/deploy another one and etc. That's where a factory comes into play. Factory helps to create a new pair and monitor all the existing pairs.
Actions
All the actions are straightforward. There’s an action to initialize a factory, create a pair and modify fee related stuff.
pub type TokenId = ActorId;
/// Initializes a factory.
///
#[derive(Debug, Encode, Decode, TypeInfo)]
pub struct InitFactory {
/// The address that can actually set the fee.
pub fee_to_setter: ActorId,
/// Code hash to successfully deploy a pair with this program.
pub pair_code_hash: [u8; 32],
}
#[derive(Debug, Encode, Decode, TypeInfo)]
pub enum FactoryAction {
/// Creates an exchange pair
///
/// Deploys a pair exchange program and saves the info about it.
/// # Requirements:
/// * both `TokenId` MUST be non-zero addresses and represent the actual fungible-token contracts
///
/// On success returns `FactoryEvery::PairCreated`
CreatePair(TokenId, TokenId),
/// Sets fee_to variable
///
/// Sets an address where the fees will be sent.
/// # Requirements:
/// * `fee_to` MUST be non-zero address
/// * action sender MUST be the same as `fee_to_setter` in this program
///
/// On success returns `FactoryEvery::FeeToSet`
SetFeeTo(ActorId),
/// Sets fee_to_setter variable
///
/// Sets an address that will be able to change fee_to
/// # Requirements:
/// * `fee_to_setter` MUST be non-zero address
/// * action sender MUST be the same as `fee_to_setter` in this program
///
/// On success returns `FactoryEvery::FeeToSetterSet`
SetFeeToSetter(ActorId),
/// Returns a `fee_to` variables.
///
/// Just returns a variable `fee_to` from the state.
///
/// On success returns `FactoryEvery::FeeTo`
FeeTo,
}
Events
All the actions above have the exact counterparts:
#[derive(Debug, Encode, Decode, TypeInfo)]
pub enum FactoryEvent {
PairCreated {
/// The first token address
token_a: TokenId,
/// The second token address
token_b: TokenId,
/// Pair address (the pair exchange program).
pair_address: ActorId,
/// The amount of pairs that already were deployed through this factory.
pairs_length: u32,
},
FeeToSet(ActorId),
FeeToSetterSet(ActorId),
FeeTo(ActorId),
}
Program metadata and state
Metadata interface description:
pub struct ContractMetadata;
impl Metadata for ContractMetadata {
type Init = In<InitFactory>;
type Handle = InOut<FactoryAction, FactoryEvent>;
type Reply = ();
type Others = ();
type Signal = ();
type State = Out<State>;
}
To display the full program state information, the state()
function is used:
#[no_mangle]
extern "C" fn state() {
reply(common_state())
.expect("Failed to encode or reply with `<ContractMetadata as Metadata>::State` from `state()`");
}
To display only necessary certain values from the state, you need to write a separate crate. In this crate, specify functions that will return the desired values from the State
struct. For example - gear-foundation/dapps/dex/factory/state:
#[metawasm]
pub trait Metawasm {
type State = dex_factory_io::State;
fn fee_to(state: Self::State) -> ActorId {
state.fee_to
}
fn fee_to_setter(state: Self::State) -> ActorId {
state.fee_to_setter
}
fn pair_address(pair: Pair, state: Self::State) -> ActorId {
state.pair_address(pair.0, pair.1)
}
fn all_pairs_length(state: Self::State) -> u32 {
state.all_pairs_length()
}
fn owner(state: Self::State) -> ActorId {
state.owner_id
}
}
type Pair = (FungibleId, FungibleId);
Interfaces
According to the list of actions, there are functions to cover all interfaces:
/// Sets a fee_to address
/// `fee_to` MUST be a non-zero address
/// Message source MUST be a fee_to_setter of the program
/// Arguments:
/// * `fee_to` is a new fee_to address
fn set_fee_to(&mut self, fee_to: ActorId);
/// Sets a fee_to_setter address
/// `fee_to_setter` MUST be a non-zero address
/// Message source MUST be a fee_to_setter of the program
/// Arguments:
/// * `fee_to_setter` is a new fee_to_setter address
fn set_fee_to_setter(&mut self, fee_to_setter: ActorId);
/// Creates and deploys a new pair
/// Both token address MUST be different and non-zero
/// Also the pair MUST not be created already
/// Arguments:
/// * `token_a` is the first token address
/// * `token_b` is the second token address
async fn create_pair(&mut self, mut token_a: ActorId, mut token_b: ActorId);
Source code
The source code of this example of DEX factory program and the example of an implementation of its testing is available on gear-foundation/dapps/dex/factory.
See also an example of the program testing implementation based on gtest
: tests/utils/factory.rs.
For more details about testing programs written on Gear, refer to the Program Testing article.