Decoding the Crypto Exchange: An In-Depth Look at CrytpoEx Smart Contracts

Decoding the Crypto Exchange: An In-Depth Look at CrytpoEx Smart Contracts

Cryptocurrencies have revolutionized the way we transact and exchange value. One fascinating aspect of this ecosystem is the creation of decentralized exchanges, which operate through smart contracts , the ability to create and manage orders is paramount.

This is your ultimate guide to understand working of a Cryptocurrency Exchange and get a overview of how to design a smart contract for the requirements.

Let's dive into the workings of a cryptocurrency exchange smart contract written in Solidity that I created for my CryptoEx Project which you can find on my github.

Link to the Smart Contract.

Link to CryptoEx Project.

The Basics

The contract begins by setting the licensing information and importing required modules. It introduces variables like feeAccount and feePercent, responsible for managing transaction fees. The contract tracks token balances, orders, and trade-related events.

The heart of this contract lies in its ability to manage transaction fees, track token balances, orders, and trade events for which it establishes mappings to track token balances, orders, and their states.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.17;

import "../node_modules/hardhat/console.sol";
import "./Token.sol";

contract Exchange {
    address public feeAccount;
    uint256 public feePercent;
    mapping (address => mapping(address => uint256)) public tokens;
    mapping (uint256 => _Order) public orders;
    mapping (uint256 => bool) public orderCancelled;
    mapping (uint256 => bool) public ordersFilled;
    uint256 orderCount;

    // Events for user interactions
    event Deposit(address token , address user , uint256 amount , uint256 balance);
    event Withdraw(address token , address user , uint256 amount , uint256 balance);
    event Order(uint256 id,  
        address user,
        address tokenGet,
        uint256 amountGet,
        address tokenGive,
        uint256 amountGive,
        uint256 timestamp);
    event Trade(uint256 id ,
        address user ,
        address tokenGet ,
        uint256 amountGet , 
        address tokenGive , 
        uint256 amountGive,
        address creator,
        uint256 timestamp
    );    

    // Struct to define the order structure
    struct _Order {
        uint256 id;  
        address user;
        address tokenGet;
        uint256 amountGet;
        address tokenGive;
        uint256 amountGive;
        uint256 timestamp;
    }

    // Constructor initializes the contract with fee details
    constructor(address _feeAccount, uint256 _feePercent) {
        feeAccount = _feeAccount;
        feePercent = _feePercent;
    }

Deposit and Withdrawal

Deposit

The depositToken function enables users to deposit tokens into the exchange. It utilizes the transferFrom function to move tokens from the user's address to the contract. The user's balance is then updated, and an event is emitted.

// DEPOSIT TOKENS
function depositToken(address _token , uint256 _amount) public {
    // Transfer tokens to exchange
    require(Token(_token).transferFrom(msg.sender , address(this) , _amount));

    // Update User's Balance
    tokens[_token][msg.sender] = tokens[_token][msg.sender] + _amount;

    // Emit an event
    emit Deposit(_token, msg.sender , _amount, tokens[_token][msg.sender]);
}

Withdrawal

The withdrawToken function allows users to withdraw tokens. It transfers tokens from the contract back to the user and updates their balance accordingly.

// WITHDRAW TOKENS
function withdrawToken(address _token, uint256 _amount) public {
    // Transfer tokens to the user
    Token(_token).transfer(msg.sender , _amount);

    // Update User's Balance
    tokens[_token][msg.sender] = tokens[_token][msg.sender] - _amount;

    // Emit a Withdrawal Event
    emit Withdraw(_token, msg.sender , _amount, tokens[_token][msg.sender]);
}

Token Balances

function balanceOf(address _token , address _user)  public view returns (uint256) {
    return tokens[_token][_user];
}

The balanceOf function serves as a transparency tool, allowing users to check their token balances on the exchange.

Creating Orders

The Token Give and Token Get

When users decide to make an order, they are essentially expressing their desire to trade one token for another. In the smart contract, this is represented by the makeOrder function. Here, users specify the token they want to receive (Token Get) and the token they are willing to spend (Token Give).

solidityCopy code// MAKE AN ORDER
function makeOrder(
    address _tokenGet,
    uint256 _amountGet,
    address _tokenGive,
    uint256 _amountGive
) public {
    // Ensure the user has enough tokens to fulfill the order
    require(balanceOf(_tokenGive, msg.sender) >= _amountGive);

    // Increment the order count
    orderCount = orderCount + 1;

    // Create and store the order
    orders[orderCount] = _Order(
        orderCount,
        msg.sender,
        _tokenGet,
        _amountGet,
        _tokenGive,
        _amountGive,
        block.timestamp
    );

    // Order creation event
    emit Order(
        orderCount,
        msg.sender,
        _tokenGet,
        _amountGet,
        _tokenGive,
        _amountGive,
        block.timestamp
    );
}

In essence, a user is putting forward an order saying, "I want to receive a certain amount of Token Get and I'm willing to give a specific amount of Token Give."

Order Cancellation

The Power to Revoke

No transaction system is complete without a way to cancel or revoke an order. In our decentralized exchange smart contract, the cancelOrder function allows users to do just that. Users can cancel an order they've placed under specific conditions.

solidityCopy code// CANCEL AN ORDER
function cancelOrder(uint256 _id) public {
    // Fetch the order to be canceled
    _Order storage _order = orders[_id];

    // Ensure the order exists
    require(_order.id == _id);

    // Mark the order as cancelled
    orderCancelled[_id] = true;
}

Users can revoke an order by providing its unique identifier (_id). The contract then checks if the order exists and sets the orderCancelled status to true, indicating that this order should no longer be considered for execution.

Filling Orders

The Thrill of the Trade

When it comes to executing a trade, the fillOrder function plays a crucial role. This function checks several conditions before confirming a trade.

solidityCopy code// FILL AN ORDER
function fillOrder(uint256 _id) public {
    // Conditions to make a trade
    // 1. Must be a valid order id
    // 2. Order must not be filled
    // 3. Order must not be cancelled
    require(_id > 0 && _id <= orderCount, "Order does not exist");
    require(!ordersFilled[_id]);
    require(!orderCancelled[_id]);

    // Fetch the order to be filled
    _Order storage _order = orders[_id];

    // Execute the trade
    trade(
        _order.id,
        _order.user,
        _order.tokenGet,
        _order.amountGet,
        _order.tokenGive,
        _order.amountGive
    );

    // Mark the order as filled
    ordersFilled[_order.id] = true;
}

This function ensures that the order is valid, not already filled, and not canceled before proceeding with the trade. If all conditions are met, the trade function is called to execute the trade, and the order is marked as filled.

Internal Trade Logic

The Engine Room

The trade function handles the intricate details of the trade, from calculating fees to updating token balances between users and the exchange.

solidityCopy code// INTERNAL TRADE FUNCTION
function trade(
    uint256 _id,
    address user,
    address _tokenGet,
    uint256 _amountGet,
    address _tokenGive,
    uint256 _amountGive
) internal {
    // Calculate the fee amount
    uint256 feeAmount = (_amountGet * feePercent) / 100;

    // Swap tokens between exchange, user, and fee account
    tokens[_tokenGet][msg.sender] = tokens[_tokenGet][msg.sender] - (_amountGet + feeAmount);
    tokens[_tokenGet][user] = tokens[_tokenGet][user] + _amountGet;
    tokens[_tokenGet][feeAccount] = tokens[_tokenGet][feeAccount] + feeAmount;

    // Swap tokens for the other side of the trade
    tokens[_tokenGive][user] = tokens[_tokenGive][user] - _amountGive;
    tokens[_tokenGive][msg.sender] = tokens[_tokenGive][msg.sender] + _amountGive;

    // Emit a Trade Event
    emit Trade(
        _id,
        msg.sender,
        _tokenGet,
        _amountGet,
        _tokenGive,
        _amountGive,
        user,
        block.timestamp
    );
}

This function calculates the transaction fee, swaps tokens between users and the exchange, and emits a trade event for transparency.

This smart contract automates the trading process on a decentralized exchange. Users can deposit, withdraw, create, and fill orders, all managed by a secure and transparent codebase. The contract provides a foundation for decentralized exchanges, eliminating the need for intermediaries and enhancing the efficiency of peer-to-peer transactions within the cryptocurrency ecosystem.

Understanding such smart contracts is crucial for anyone venturing into the world of decentralized finance, providing insights into the mechanics that power these innovative financial systems.