Project - DEX Final Code

Hi,
Here is my code, I expect to build a front end for it in a month of so, I feel that I will do first the Ethereum dApp programming course, it would be easier and good practice at the end of that one.
Besides this code mostly follows Philip’s code I made all the challenges myself and ended sometimes desperate, also it helped each time more, and by the end I could came up with solutions for my own, few times even they worked :wink:

dex.sol
pragma solidity ^0.8.0;

import "./Wallet.sol";


contract Dex is Wallet {

    using SafeMath for uint256;
    
    enum Side {BUY, SELL}

    struct Order{
        uint id;
        address trader;
        Side side;
        bytes32 ticker;
        uint amount;
        uint price;
        uint filled;
    }

    uint public nextOrderId =0;

    mapping (bytes32=> mapping(uint => Order[])) public orderBook;

    function getOrderBook (bytes32 ticker, Side side) view public returns (Order [] memory){

        return orderBook[ticker][uint(side)];
    }

    function createLimitOrder(Side side, bytes32 ticker, uint256 amount, uint256 price ) public {
      

       if(side == Side.BUY){
            
            require(balances[msg.sender]["ETH"] >= amount.mul(price));
        }
        if(side== Side.SELL){
            require(balances[msg.sender][ticker] >= amount);
        }

        Order[] storage orders = orderBook[ticker][uint(side)]; //this is a reference to storage, not storage
        
        orders.push(Order( nextOrderId, msg.sender, side, ticker, amount, price,0) ); //but it goes directly to storage
       
        //bubble sort
        uint i = orders.length>0 ? orders.length-1: 0; 

        if( side == Side.BUY){ //the highest price first
            while( i > 0 ) {
                if ( orders[i].price < orders[i-1].price){
                    break;
                }
                Order memory temp =orders[i];
                orders[i] = orders[i-1];
                orders[i-1] = temp;
                i--;
            }

        }
        else if(side== Side.SELL){
                while(i>0) {
                    if ( orders[i].price > orders[i-1].price){
                        break;
                    }
                    Order memory temp =orders[i];
                    orders[i] = orders[i-1];
                    orders[i-1] = temp;
                    i--;
            }
           
        }
        nextOrderId++;
        }

    function createMarketOrder(Side side, bytes32 ticker, uint amount) public {
       
        if(side== Side.SELL){
            require(balances[msg.sender][ticker] >= amount);
        }

        uint orderBookSide;
        if(side == Side.BUY){
            orderBookSide = 1;
        }
        else {
            orderBookSide=0;
        }
       
        Order[] storage orders = orderBook[ticker][uint(orderBookSide)];
       
        uint totalFilled = 0;
       
        for  (uint i= 0; i< orders.length && totalFilled< amount; i++){
            uint leftToFilled = amount.sub(totalFilled); 

            //How much we can fill from order[i]

            uint available= orders[i].amount.sub(orders[i].filled);
            uint filled =0; //Use instead of orders[i].filled because the transaction may fail
            if ( available <= leftToFilled ){

                filled = available; 

            }else if ( available > leftToFilled) {
                    filled = leftToFilled;
            }
            //Update totalFilled
            totalFilled= totalFilled.add(filled);

            //Here we have to difference between Buy side and sell Side

            if(side == Side.BUY){

                //verify that the buyer trader has enough ETH to cover the purchase (require)
                //execute the trade & shift balances betweent buyer/seller
                require(balances[msg.sender]["ETH"]>=filled.mul(orders[i].price), "something happens here");
              
        
            
                balances[orders[i].trader][ticker] = balances[orders[i].trader][ticker].sub(filled);
           
                balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(filled * orders[i].price);
                balances[orders[i].trader]["ETH"]=balances[orders[i].trader]["ETH"].add(orders[i].price * filled);

                balances[msg.sender][ticker] = balances[msg.sender][ticker].add(filled);
            }
            else if (side == Side.SELL){

                //execute the trade & shift balances betweent buyer/seller
                balances[orders[i].trader]["ETH"] = balances[orders[i].trader]["ETH"].sub(filled.mul(orders[i].price));
           
                balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(filled);
                balances[orders[i].trader][ticker]=balances[orders[i].trader][ticker].add(filled);

                balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(orders[i].filled);

            }
              orders[i].filled = filled;

        }

        while( orders.length >0 && orders[0].filled ==orders[0].amount ){

            for (uint i= 0; i< orders.length-1; i++){
                orders[i] = orders[i+1];
            }
            orders.pop();
        }
    }


}
wallet.sol
pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";

contract Wallet is Ownable{
    using SafeMath for uint256;
    struct Token {
        bytes32 ticker; 
        address tokenAddress; //we have to know where we need to do transfers calls to make the token contract (we will need the interface of the token)

    }

    mapping (bytes32 => Token) public tokenMapping; //information about the tokens
    bytes32[] public tokenList; //list to have ll of teh tickers ids

    mapping(address => mapping( bytes32 => uint256)) public balances; //we do not use string, cannot compare in solidity balances of the investors
    

    modifier tokenExist(bytes32 ticker){
        require(tokenMapping[ticker].tokenAddress != address(0), "token does not exists"); //requiere to token to exits ( if not it would be address 0)
        _;
         }


    function addToken(bytes32 ticker, address tokenAddress) external onlyOwner(){
        tokenMapping[ticker] = Token(ticker, tokenAddress);
        tokenList.push(ticker);
    }

    function deposit( uint amount, bytes32 ticker) external tokenExist(ticker){
       
        require(amount > 0, "You need to deposit at least some tokens");
     
        require(IERC20(tokenMapping[ticker].tokenAddress).balanceOf(msg.sender)>= amount," insufficient funds");

        IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender,address(this), amount);//the allowance has to be given before, we could check but it is checked already here
        balances[msg.sender][ticker]= balances[msg.sender][ticker].add(amount);


    }
    function depositEth() payable external {
        balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")].add(msg.value);
    }

    function withdraw(uint amount, bytes32 ticker) external tokenExist(ticker) { 

       
        require(amount > 0, "You need to withdraw at least some tokens");
        require(balances[msg.sender][ticker]>= amount, " insufficient balance");
        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount); //we  need to interact with the ERC 20 token contract now, we need the address of (IERC20) and the interface
        

    }

}
dextest.js
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const truffleAssert =require("truffle-assertions");

contract("Dex", accounts => {

    

    xit("The user have Eth deposited such that, deposited eth >= buy order value", async () => 
    {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await dex.addToken(web3.utils.fromUtf8("ETH"), link.address, {from: accounts[0]})
        await truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
        )
        await dex.depositEth({value: web3.utils.toWei("10", "ether")})
        //await console.log(dex.balances(accounts[0], web3.utils.fromUtf8("ETH")))
       
        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })
    
    
    xit("The user must have enough tokens deposited such that token balance >= sell order amount", async () =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
       // await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
        await link.approve(dex.address, 500);
        await dex.deposit(10, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })

    xit("The BUY order Book should be ordered on price from highest to lowest starting at index 0", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        //await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        //await link.approve(dex.address, 500);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
        assert (orderbook.length >0)
        for (let i = 0; i < orderbook.length - 1; i++) {
        //const element = array[index];
            assert(orderbook[i].price >= orderbook[i+1].price)
        }
    })

    xit("The SELL order Book should be ordered on price from lowest to highest starting at index 0 ", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        //await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        //await link.approve(dex.address, 1000);
        //await dex.deposit(800,web3.utils.fromUtf8("LINK") )
        
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
        //console.log(orderbook)
        //assert(orderbook.length==4)
           assert (orderbook.length >0)
         for (let i = 0; i < orderbook.length - 1; i++) {
        //const element = array[index];
           assert(orderbook[0].price <= orderbook[i+1].price)
        }
    })

   
    
})

    //Tests for Market Order  

   contract ("Dex", accounts => {

    //when creating a SELL market order, the seller needs to have enough tokens for the trade
    it("when creating a SELL market order, the seller needs to have enough tokens for the trade", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await truffleAssert.reverts(
            dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 10 )
        )
        await link.approve(dex.address, 500);
        await dex.deposit(10, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 10)
        )

    })
    
    //when creating a BUy market order, the buyer needs to have enough ETH for the trade 
    it (" when creating a BUY market order, the buyer needs to have enough ETH for the trade ", async () =>{

        let dex = await Dex.deployed()
        let link = await Link.deployed()
       
        await dex.addToken(web3.utils.fromUtf8("ETH"), link.address, {from: accounts[0]})
        await dex.depositEth({value: web3.utils.toWei("10", "ether")})
        //await console.log(dex.balances(accounts[0], web3.utils.fromUtf8("ETH")))
       
        await truffleAssert.passes(
            dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 10)
        )

    })
        
    it ( "Market orders should not fill more limit orders than the market orders amount", async ()=>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await dex.depositEth({value: 10000});

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);

        assert(orderbook.length ==0, "buy side Orderbook is not empty")

     

        //send link to the accounts 1 ,2,3 froom account 0
        await link.transfer(accounts[1],50)
        await link.transfer(accounts[2],50)
        await link.transfer(accounts[3],50)

        let balance = await link.balanceOf(accounts[1]);
        console.log(balance.toNumber());

        //aprove DEX for Accounts 1,2,3 

        await link.approve(dex.address, 50, {from : accounts[1]});
        await link.approve(dex.address, 50, {from : accounts[2]});
        await link.approve(dex.address, 50, {from : accounts[3]});


        //deposit Link into DEX for accounts, 1,2,3

        await dex.deposit(50, web3.utils.fromUtf8("LINK"), {from: accounts[1]});
        await dex.deposit(50, web3.utils.fromUtf8("LINK"), {from: accounts[2]});
        await dex.deposit(50, web3.utils.fromUtf8("LINK"), {from: accounts[3]});

        //fill up the orderBook
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 300, {from: accounts[1]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 400, {from: accounts[2]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 500, {from: accounts[3]})

        //Create a market order that should fill 2/ orders in the book
        await dex.createMarketOrder(0,web3.utils.fromUtf8("LINK"), 10)

        orderbook= await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1); 

        assert(orderbook.length == 1, "Sell side Orderbook should only have 1 order left");
        assert(orderbook[0].filled ==0, " sell side order should have 0 filled");


    })

    //Market orders should be filled until the order book is empty 
    it ("Market orders should be filled until the order book is empty", async ()=>{
        let dex = await Dex.deployed();

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
        await console.log(orderbook.length)
        assert(orderbook.length ==1, "Sell side Orderbook should have 1 order left ");


        //fill up the orderbook again 
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 400, {from: accounts[1]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 500, {from: accounts[2]})


        let balanceBefore = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"))

        await dex.createMarketOrder(0,web3.utils.fromUtf8("LINK"), 150);

        //check the buyer balance after
        let balanceAfter = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK", 50 ));

        assert (balanceBefore+15, balanceAfter);
    })

   


    
     //the ETH balance of the buyer should decrease with the filled amount 
     it ("the ETH balance of the buyer should decrease with the filled amount ", async ()=>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        //set some order depositing link in account 1
        await link.approve(dex.address, 500, {from:  accounts[1]});
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300, {from: accounts[1]})

        //check the buyer balance before trade

        let balanceBefore = await dex.balances(accounts[0], web3.utils.fromUtf8("ETH"));
        await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"),1);
        let balanceAfter = await dex.balances(accounts[0], web3.utils.fromUtf8("ETH"));
        assert.equal(balanceBefore-300, balanceAfter);

        
     })

     //the token balances of the sellers should decrease wiht the filled amount.

     it("the token balances of the sellers should decrease with the filled amount.", async ()=>{

        let dex = await Dex.deployed()
        let link = await Link.deployed()

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);

        //assert(orderbook.length ==0, "buy side Orderbook is not empty")
        await link.transfer(accounts[2],500)
            //he says accoutn1 already have links.
            //seler 2 desposits link
        //await link.approve(dex.address, 500, {from: accounts[1]});
        await link.approve(dex.address, 500, {from: accounts[2]});
        //await dex.deposit(100, web3.utils.fromUtf8("LINK"), {from : accounts[1]})
        await dex.deposit(100, web3.utils.fromUtf8("LINK"), {from : accounts[2]})
        //set some buy limit orders
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300, {from: accounts[1]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400, {from: accounts[2]})
       
       
        let account1balanceBefore = await dex.balances(accounts[1], web3.utils.fromUtf8("LINK")); //get the balances previous the sale
        let account2balanceBefore = await dex.balances(accounts[2], web3.utils.fromUtf8("LINK"));
      
        await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 2)
        
        let account1balanceAfter = await dex.balances(accounts[1], web3.utils.fromUtf8("LINK")); //balance after the sale
        let account2balanceAfter = await dex.balances(accounts[2], web3.utils.fromUtf8("LINK")); 

        assert.equal(account1balanceBefore -1, account1balanceAfter);
        assert.equal(account2balanceBefore -1, account2balanceAfter);
        
         
     })

    //filled limit orders should be removed from the orderbook
    it("filled limit orders should be removed from the orderbook", async ()=>{

        let dex = await Dex.deployed()
        let link = await Link.deployed()

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);

        assert(orderbook.length ==0, "sell side Orderbook is not empty")

        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300, {from: accounts[1]})
        await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1)

        orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);

        assert(orderbook.length == 0, "Seel side Orderbook should be empty after trade");

      })

    //partly filled limit orders should modified to represent the filled/remaining amount
    it("limit orders filled property should be set correctly after a trade ", async ()=>{
        let dex = await Dex.deployed()
        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);

        assert(orderbook.length ==0, "sell side Orderbook is not empty")

        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 300, {from: accounts[1]})
        await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 2)

        orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
        assert.equal(orderbook[0].filled , 2);
        assert.equal(orderbook[0].amount, 5);
        
      })


   })
walletTest.js
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const truffleAssert =require("truffle-assertions");

contract("Dex", accounts => {

    it("should only be possible for owner to add tokens", async () => 
    {
       // await deployer.deploy(Link);
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        

        await truffleAssert.passes(
            dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        )
        await truffleAssert.reverts(
            dex.addToken(web3.utils.fromUtf8("Ljhkhlk"), link.address, {from: accounts[1]})
        )

    })

    it("should handle deposit correctly", async ()=>{
    let dex = await Dex.deployed();
    let link = await Link.deployed();
    await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
    await link.approve( dex.address, 500);
    await dex.deposit(100, web3.utils.fromUtf8("LINK"));
    let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));

    assert.equal(balance,100);
       

     } )

     it("should handle withdraws correctly",  async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        await link.approve( dex.address, 500);
        await dex.withdraw(50, web3.utils.fromUtf8("LINK") )
        let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));
        assert.equal(balance.toNumber(),50);
        await truffleAssert.reverts(
             dex.withdraw(50, web3.utils.fromUtf8("LINK"), {from:accounts[1]} )
        )
     })
})
1 Like

Hi guys,
I complete (with some reservations) my Dex code
I try to test myself in this code, specifically I want to use for my code:

   - Use a different logic to find the order.
   - Use PriceFeed from chainlink.
   - Complete orders that the book cannot fulfill with the use of AMM such as (Uniswap / traderJoe);
   - Set the rates to be paid to set up the order.

but I have encountered many problems for my incompetence:

  - when I tested the swap in a separate code using the KOVAN network, the truffle gave me the type of error ("low nonce .......") or a direct crash.
  - I failed in the fixed tax function, because I am not particularly good at math, and dividing solidly is a nightmare for me.
  - I don't know how much my feature spends and if it's really usable for its cost.
  - and I am unable to develop a front end for my code.

In addition my code is a maze.

WALLET CODE:

// SPDX-License-Identifier: Leluk911
pragma solidity 0.8.0;
pragma experimental ABIEncoderV2;

import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "../node_modules/@openzeppelin/contracts/security/Pausable.sol";
import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Wallet is Ownable, Pausable {
    // event
    event depositTokenUser(address indexed, bytes32 indexed, uint256 indexed);
    event withdrawalUser(address indexed, bytes32 indexed, uint256 indexed);
    event depETH(uint256 indexed);

    // token avvalibe in this wallet
    struct Token {
        bytes32 tiker;
        address tokenAdr;
    }
    mapping(bytes32 => Token) tokenAvvalible;
    bytes32[] public tokenList;

    //---------------------------
    // BALANCE USER
    mapping(address => mapping(bytes32 => uint256)) balance;

    //------

    /// MODIFIER
    modifier tokenExist(bytes32 _tiker) {
        require(
            tokenAvvalible[_tiker].tokenAdr != address(0),
            "token not exist"
        );
        _;
    }

    //_____________

    function getTokenList() public view returns (bytes32[] memory) {
        return tokenList;
    }

    function setTokenList(bytes32 _tiker, address _tokenAdr)
        external
        onlyOwner
    {
        require(
            tokenAvvalible[_tiker].tokenAdr == address(0),
            "token just exist"
        );
        require(_tiker != "" && _tokenAdr != address(0), "insert correct data");
        tokenAvvalible[_tiker] = Token(_tiker, _tokenAdr);
        tokenList.push(_tiker);
    }

    function deposit(bytes32 _tiker, uint256 _amount)
        public
        whenNotPaused
        tokenExist(_tiker)
    {
        // user must approve this spend before transaction
        uint256 balanceWAllet = IERC20(tokenAvvalible[_tiker].tokenAdr)
            .balanceOf(address(this));
        IERC20(tokenAvvalible[_tiker].tokenAdr).transferFrom(
            msg.sender,
            address(this),
            _amount
        );
        require(
            IERC20(tokenAvvalible[_tiker].tokenAdr).balanceOf(address(this)) ==
                balanceWAllet + _amount,
            "trasfer not execute"
        );
        balance[msg.sender][_tiker] = _amount;
        emit depositTokenUser(msg.sender, _tiker, _amount);
    }

    function withdrawal(bytes32 _tiker, uint256 _amount)
        public
        virtual
        whenNotPaused
        tokenExist(_tiker)
    {
        require(
            balance[msg.sender][_tiker] >= _amount,
            "balance insufficent for this withdrawal"
        );
        balance[msg.sender][_tiker] -= _amount;
        uint256 balanceWAllet = IERC20(tokenAvvalible[_tiker].tokenAdr)
            .balanceOf(address(this));
        IERC20(tokenAvvalible[_tiker].tokenAdr).transfer(msg.sender, _amount);
        require(
            IERC20(tokenAvvalible[_tiker].tokenAdr).balanceOf(address(this)) ==
                balanceWAllet - _amount,
            "trasfer not execute"
        );
        emit withdrawalUser(msg.sender, _tiker, _amount);
    }

    function getBalance(bytes32 _tiker) public view returns (uint256) {
        return balance[msg.sender][_tiker];
    }

    function depositETH() external payable onlyOwner {
        emit depETH(msg.value);
    }

    function withdrawalOwner(bytes32 _tiker, uint256 _amount)
        external
        onlyOwner
        tokenExist(_tiker)
    {
        balance[address(this)][_tiker] -= _amount;
        IERC20(tokenAvvalible[_tiker].tokenAdr).transfer(msg.sender, _amount);
    }

    function withdrawalOwnerETH(uint256 _amount) external onlyOwner {
        require(address(this).balance >= _amount, "balance low");
        (bool success, ) = msg.sender.call{value: _amount}("");
        require(success, "transaction faill");
    }

    function getBalanceOwner(bytes32 _tiker)
        external
        view
        onlyOwner
        returns (uint256, uint256)
    {
        return (balance[address(this)][_tiker], address(this).balance);
    }
}

DEX CODE

// SPDX-License-Identifier: Leluk911
pragma solidity 0.8.0;
pragma experimental ABIEncoderV2;

import "../contracts/wallet.sol";
import "../contracts/utility.sol/AggregatorV3Interface.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeCast.sol";
import "../contracts/interface/Iuniswap.sol";

contract DEX is Wallet {
    using SafeCast for int256;

    AggregatorV3Interface private immutable priceFeed;

    // tesnet crash in this test
    address private immutable UNISWAP_V2_ROUTER;
    address private immutable WETH;

    /////

    constructor(
        address _addrAggregatorPRice,
        address _UNISWAP_V2_ROUTER,
        address _WETH
    ) {
        priceFeed = AggregatorV3Interface(_addrAggregatorPRice);
        UNISWAP_V2_ROUTER = _UNISWAP_V2_ROUTER;
        WETH = _WETH;
    }

    // EVENT

    enum Side {
        BUY,
        SELL
    }

    struct Order {
        uint256 Id;
        address Trader;
        bytes32 TikerOrd;
        uint256 amount;
        Side side;
        bytes32 tikerRequire;
        uint256 price;
    }
    uint256 NewID = 0;

    mapping(bytes32 => mapping(bytes32 => uint256)) price;

    mapping(bytes32 => mapping(bytes32 => mapping(uint256 => Order[])))
        public OrderBook;

    mapping(address => mapping(bytes32 => uint256)) balanceInOrder;

    // Token SEll => prezzo => Token BUY => Inf Ord

    function setOrder(
        bytes32 _tikerOrd,
        bytes32 _tikerRequire,
        uint256 _amount,
        uint256 _price,
        Side _side
    ) external tokenExist(_tikerRequire) tokenExist(_tikerOrd) {
        if (Side.BUY == _side) {
            require(
                balance[msg.sender][_tikerRequire] -
                    balanceInOrder[msg.sender][_tikerRequire] >=
                    _amount * _price
            );
            balanceInOrder[msg.sender][_tikerRequire] += _amount * _price;
        } else if (Side.SELL == _side) {
            require(
                balance[msg.sender][_tikerOrd] -
                    balanceInOrder[msg.sender][_tikerOrd] >=
                    _amount
            );
            balanceInOrder[msg.sender][_tikerOrd] += _amount;
        }

        Order memory newOrder = Order(
            NewID,
            msg.sender,
            _tikerOrd,
            _amount,
            _side,
            _tikerRequire,
            _price
        );
        OrderBook[_tikerOrd][_tikerRequire][uint256(_side)].push(newOrder);

        NewID++;
    }

    function getOrderUser(
        bytes32 _tikerOrd,
        bytes32 _tikerRequire,
        Side _side,
        uint256 _Id
    ) external view returns (Order memory) {
        for (
            uint256 i = 0;
            i < OrderBook[_tikerOrd][_tikerRequire][uint256(_side)].length;
            i++
        ) {
            if (
                OrderBook[_tikerOrd][_tikerRequire][uint256(_side)][i].Id == _Id
            ) {
                return OrderBook[_tikerOrd][_tikerRequire][uint256(_side)][i];
            }
        }
    }

    function deletOrder(
        bytes32 _tikerOrd,
        bytes32 _tikerRequire,
        Side _side,
        uint256 _Id
    ) external whenNotPaused tokenExist(_tikerOrd) tokenExist(_tikerRequire) {
        Order[] storage order = OrderBook[_tikerOrd][_tikerRequire][
            uint256(_side)
        ];

        for (uint256 i = 0; i < order.length; i++) {
            if (order[i].Id == _Id) {
                uint256 repayAmount = order[i].amount;
                bytes32 _tikerRepay = order[i].TikerOrd;
                order[i].amount = 0;

                deletAmountZero(
                    order[i].TikerOrd,
                    order[i].tikerRequire,
                    _side,
                    i
                );
                balanceInOrder[msg.sender][_tikerRepay] -= repayAmount;
            }
        }
    }

    function modifierOrder(
        bytes32 _tikerOrd,
        bytes32 _tikerRequire,
        uint256 _amount,
        uint256 _price,
        Side _side,
        uint256 _Id
    ) external whenNotPaused tokenExist(_tikerOrd) tokenExist(_tikerRequire) {
        Order[] storage order = OrderBook[_tikerOrd][_tikerRequire][
            uint256(_side)
        ];

        for (uint256 i = 0; i < order.length; i++) {
            if (order[i].Id == _Id) {
                order[i].price = _price;
                if (order[i].amount > _amount) {
                    uint256 repayDiff = order[i].amount - _amount;
                    balanceInOrder[msg.sender][order[i].TikerOrd] -= repayDiff;
                    order[i].amount = _amount;
                }
                if (order[i].amount < _amount) {
                    uint256 repayDiff = _amount - order[i].amount;
                    balanceInOrder[msg.sender][order[i].TikerOrd] += repayDiff;
                    order[i].amount = _amount;
                }
            }
        }
    }

    function getOrderBookSEll(bytes32 _tikerOrd, bytes32 _tikerRequire)
        public
        view
        returns (Order[] memory)
    {
        return OrderBook[_tikerOrd][_tikerRequire][1];
    }

    function getOrderBookBUY(bytes32 _tikerOrd, bytes32 _tikerRequire)
        public
        view
        returns (Order[] memory)
    {
        return OrderBook[_tikerOrd][_tikerRequire][0];
    }

    function set_price(
        bytes32 _tikerA,
        bytes32 _tikerB,
        //uint256 _price,
        address _addrPair
    ) external whenNotPaused tokenExist(_tikerA) tokenExist(_tikerB) onlyOwner {
        uint256 _price = SafeCast.toUint256(priceFeed.getprice(_addrPair));
        executeOrder(_tikerA, _tikerB, _price);
    }

    function executeOrder(
        bytes32 _tikerOrd,
        bytes32 _tikerRequire,
        uint256 _price
    ) internal {
        // ricerca ordine di venditA
        Order[] storage orderSell = OrderBook[_tikerOrd][_tikerRequire][1];
        Order[] storage orderBuy = OrderBook[_tikerOrd][_tikerRequire][0];

        for (uint256 iSEll = 0; iSEll < orderSell.length; iSEll++) {
            if (orderSell[iSEll].price == _price) {
                uint256 OrderMissFill = orderSell[iSEll].amount;

                for (uint256 iBUY = 0; iBUY < orderBuy.length; iBUY++) {
                    if (orderBuy[iBUY].price == _price) {
                        if (orderBuy[iBUY].amount > OrderMissFill) {
                            uint256 _iSELL = iSEll;

                            orderBuy[iBUY].amount -= OrderMissFill;
                            orderSell[iSEll].amount = 0;
                            // trasferimento token nei bilanci
                            // ordine di vendita
                            balance[orderSell[iSEll].Trader][
                                orderSell[iSEll].tikerRequire
                            ] += OrderMissFill * _price;
                            balance[orderSell[iSEll].Trader][
                                orderSell[iSEll].TikerOrd
                            ] -= OrderMissFill;
                            // svincolo bilancio in ordine

                            balanceInOrder[orderSell[iSEll].Trader][
                                orderSell[iSEll].TikerOrd
                            ] -= OrderMissFill;

                            // ordine d'acquisto

                            balance[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].TikerOrd
                            ] += OrderMissFill;

                            balance[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].tikerRequire
                            ] -= OrderMissFill * _price;
                            // svincolo bilancio in ordine

                            balanceInOrder[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].tikerRequire
                            ] -= OrderMissFill * _price;

                            // prima controllo se si aggiornano tutti i dati
                            OrderMissFill = 0;
                            deletAmountZero(
                                orderSell[iSEll].TikerOrd,
                                orderSell[iSEll].tikerRequire,
                                Side.SELL,
                                _iSELL
                            );
                        }
                        if (orderBuy[iBUY].amount < OrderMissFill) {
                            OrderMissFill -= orderBuy[iBUY].amount;
                            uint256 partialOrderFill = orderBuy[iBUY].amount;
                            orderBuy[iBUY].amount = 0;
                            orderSell[iSEll].amount -= partialOrderFill;
                            // trasferimento token nei bilanci
                            // ordine di vendita
                            balance[orderSell[iSEll].Trader][
                                orderSell[iSEll].tikerRequire
                            ] += partialOrderFill * _price;
                            balance[orderSell[iSEll].Trader][
                                orderSell[iSEll].TikerOrd
                            ] -= partialOrderFill;
                            // svincolo bilancio in ordine
                            balanceInOrder[orderSell[iSEll].Trader][
                                orderSell[iSEll].TikerOrd
                            ] -= partialOrderFill;

                            // ordine d'acquisto
                            balance[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].tikerRequire
                            ] += partialOrderFill;
                            balance[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].TikerOrd
                            ] -= partialOrderFill * _price;
                            // svincolo bilancio in ordine
                            balanceInOrder[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].tikerRequire
                            ] -= partialOrderFill * _price;
                            // prima controllo se si aggiornano tutti i dati
                            deletAmountZero(
                                orderBuy[iBUY].TikerOrd,
                                orderBuy[iBUY].tikerRequire,
                                Side.BUY,
                                iBUY
                            );
                        }
                        if (orderBuy[iBUY].amount == OrderMissFill) {
                            uint256 _iSELL = iSEll;

                            orderBuy[iBUY].amount = 0;
                            orderSell[iSEll].amount = 0;

                            // trasferimento token nei bilanci
                            // ordine di vendita
                            balance[orderSell[iSEll].Trader][
                                orderSell[iSEll].tikerRequire
                            ] += OrderMissFill * _price;
                            balance[orderSell[iSEll].Trader][
                                orderSell[iSEll].TikerOrd
                            ] -= OrderMissFill;
                            // svincolo bilancio in ordine

                            balanceInOrder[orderSell[iSEll].Trader][
                                orderSell[iSEll].TikerOrd
                            ] -= OrderMissFill;

                            // ordine d'acquisto

                            balance[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].TikerOrd
                            ] += OrderMissFill;

                            balance[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].tikerRequire
                            ] -= OrderMissFill * _price;
                            // svincolo bilancio in ordine

                            balanceInOrder[orderBuy[iBUY].Trader][
                                orderBuy[iBUY].tikerRequire
                            ] -= OrderMissFill * _price;

                            // prima controllo se si aggiornano tutti i dati
                            OrderMissFill = 0;

                            deletAmountZero(
                                orderBuy[iBUY].TikerOrd,
                                orderBuy[iBUY].tikerRequire,
                                Side.BUY,
                                iBUY
                            );
                            deletAmountZero(
                                orderSell[iSEll].TikerOrd,
                                orderSell[iSEll].tikerRequire,
                                Side.SELL,
                                _iSELL
                            );
                        }
                        if (OrderMissFill == 0) {
                            break;
                        }
                    }
                }

                if (OrderMissFill > 0) {
                    //use swap Amm

                    IERC20(tokenAvvalible[_tikerOrd].tokenAdr).approve(
                        UNISWAP_V2_ROUTER,
                        OrderMissFill
                    );

                    address[] memory path;
                    if (
                        tokenAvvalible[_tikerOrd].tokenAdr == WETH ||
                        tokenAvvalible[_tikerRequire].tokenAdr == WETH
                    ) {
                        path = new address[](2);
                        path[0] = tokenAvvalible[_tikerOrd].tokenAdr;
                        path[1] = tokenAvvalible[_tikerRequire].tokenAdr;
                    } else {
                        path = new address[](3);
                        path[0] = tokenAvvalible[_tikerOrd].tokenAdr;
                        path[1] = WETH;
                        path[2] = tokenAvvalible[_tikerRequire].tokenAdr;
                    }
                    IUniswapV2Router(UNISWAP_V2_ROUTER)
                        .swapExactTokensForTokens(
                            OrderMissFill,
                            OrderMissFill * _price,
                            path,
                            address(this),
                            block.timestamp
                        );

                    OrderMissFill = 0;
                    //break;
                }
            }
        }
    }

    function deletAmountZero(
        bytes32 _tikerA,
        bytes32 _tikerB,
        Side _side,
        uint256 _index
    ) internal {
        if (OrderBook[_tikerA][_tikerB][uint256(_side)][_index].amount == 0) {
            uint256 lastElement = OrderBook[_tikerA][_tikerB][uint256(_side)]
                .length - 1;

            OrderBook[_tikerA][_tikerB][uint256(_side)][_index] = OrderBook[
                _tikerA
            ][_tikerB][uint256(_side)][lastElement];

            OrderBook[_tikerA][_tikerB][uint256(_side)].pop();
        }
    }

    function getBalanceInOrder(bytes32 _tiker)
        external
        view
        tokenExist(_tiker)
        returns (uint256)
    {
        return balanceInOrder[msg.sender][_tiker];
    }

    function withdrawal(bytes32 _tiker, uint256 _amount)
        public
        override
        whenNotPaused
        tokenExist(_tiker)
    {
        require(
            balance[msg.sender][_tiker] - balanceInOrder[msg.sender][_tiker] >=
                _amount,
            "balance insufficent for this withdrawal"
        );

        balance[msg.sender][_tiker] -= _amount;
        uint256 balanceWAllet = IERC20(tokenAvvalible[_tiker].tokenAdr)
            .balanceOf(address(this));
        IERC20(tokenAvvalible[_tiker].tokenAdr).transfer(msg.sender, _amount);
        require(
            IERC20(tokenAvvalible[_tiker].tokenAdr).balanceOf(address(this)) ==
                balanceWAllet - _amount,
            "trasfer not execute"
        );
        emit withdrawalUser(msg.sender, _tiker, _amount);
    }
}
/*
    1 creare un orderBook con id prezzi 
        ogni aggiornamento prezzi si prende il prezzo che corrisponde e si filla l'ordine 
    
    2 settare ordine    -> ok 
    3 modificare ordine  -> ok
    4 cancellare ordine -> ok
    5 fillare ordini in automatico nel order book sell/buy -> ok 
    7 settare fee per ordini limite  -> no
    
    BONUS 
        1 se l'order book non soddisfa l'user, si ricorre ad un Amm decentralizato per fillare l'ordine. -> Ok ma non testato perche truffle crasha.
 */

INTERFACE UNISWAP
( i take this code on internet because on uniswap was more complicate)

// SPDX-License-Identifier: Leluk911
pragma solidity 0.8.0;
pragma experimental ABIEncoderV2;

interface IUniswapV2Router {
    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);
}

Chainlink price feed:

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

interface AggregatorV3Interface {
    function decimals() external view returns (uint8);

    function description() external view returns (string memory);

    function version() external view returns (uint256);

    function getprice(address _addrPair) external returns (int256);

    // getRoundData and latestRoundData should both raise "No data present"
    // if they do not have data to report, instead of returning unset values
    // which could be misinterpreted as actual reported values.
    function getRoundData(uint80 _roundId)
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );

    function latestRoundData()
        external
        view
        returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        );
}

contract Price {
    AggregatorV3Interface internal priceFeed;

    function getprice(address _addrPair) external returns (int256) {
        priceFeed = AggregatorV3Interface(_addrPair);
        return getThePrice();
    }

    /**
     * Returns the latest price
     */
    function getThePrice() public view returns (int256) {
        (
            uint80 roundID,
            int256 price,
            uint256 startedAt,
            uint256 timeStamp,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        return price;
    }
}

TEST BEFORE chainlink and swap implemetation…
(pricefeed i have testing in other code)

const Dex = artifacts.require("DEX");
const Dai = artifacts.require("DAI");
const Wavax = artifacts.require("WAVAX");
const Joe = artifacts.require("JOE");
const truffleAssert = require("truffle-assertions");


contract("wallet", accounts => {

    let dex;
    let dai;
    let wavax;
    let joe;
    let balanceSEller
    let balanceBuyer
    let balanceInOrder

    let seller = accounts[0];
    let buyer = accounts[1];

    before(async function () {
        dex = await Dex.deployed();
        dai = await Dai.deployed();
        wavax = await Wavax.deployed();
        joe = await Joe.deployed();
        await dex.setTokenList(web3.utils.fromUtf8("DAI"), dai.address);
        await dex.setTokenList(web3.utils.fromUtf8("WAVAX"), wavax.address);
        await dex.setTokenList(web3.utils.fromUtf8("JOE"), joe.address);
        await wavax.approve(dex.address, 1000);
        await dex.deposit(web3.utils.fromUtf8("WAVAX"), 1000);
        await dai.transfer(buyer, 10000000);
        await dai.approve(dex.address, 100000, { from: buyer });
        await dex.deposit(web3.utils.fromUtf8("DAI"), 100000, { from: buyer });

    });
    it("Get balance after swap", async () => {

        balanceSEller = await dex.getBalance(web3.utils.fromUtf8("DAI"), { from: buyer });
        balanceBuyer = await dex.getBalance(web3.utils.fromUtf8("WAVAX"));

        console.log(`bilancio del SEllER => ${balanceSEller}`);
        console.log(`bilancio del BUYER => ${balanceBuyer}`);
    })
    it("Set Order in order book SELL", async () => {
        await dex.setOrder(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 2, 100, 1);
        await dex.setOrder(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 1, 100, 1);

        await dex.setOrder(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 3, 100, 0, { from: buyer });

        await dex.setOrder(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 1, 1000, 1);



        await dex.setOrder(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 1, 1000, 0, { from: buyer });

    });
    it("Get OrderBook SELL", async () => {
        let ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));
        let ordBUY = await dex.getOrderBookBUY(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));

        //console.log(ordSEll);
        //console.log(ordBUY);
    })
    it("Set price pair", async () => {
        // qua chiama anche l'esecuzione dell'ordine
        await dex.set_price(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 100, { gas: 3000000 });
        //await dex.set_price(web3.utils.fromUtf8("JOE"), web3.utils.fromUtf8("DAI"), 80);
    })
    it("Get OrderBook SELL", async () => {
        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));
        ordBUY = await dex.getOrderBookBUY(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));

        //console.log(ordSEll);
        //console.log(ordBUY);
    })
    it("Get balance before swap", async () => {

        balanceSEller = await dex.getBalance(web3.utils.fromUtf8("DAI"), { from: buyer });
        balanceBuyer = await dex.getBalance(web3.utils.fromUtf8("WAVAX"), { from: buyer });

        console.log(`bilancio del SEllER => ${balanceSEller}`);
        console.log(`bilancio del BUYER => ${balanceBuyer}`);
    })
    it("Modifier  Order in order book SELL, increas amount", async () => {
        // id 3, da trovare il modo per recuperarlo.


        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));
        balanceInOrder = await dex.getBalanceInOrder(web3.utils.fromUtf8("WAVAX"));

        //console.log(`balance in order ${balanceInOrder}`);
        //console.log(ordSEll);

        await truffleAssert.passes(
            dex.modifierOrder(
                web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 5, 200, 1, 3
            )
        );
        balanceInOrder = await dex.getBalanceInOrder(web3.utils.fromUtf8("WAVAX"));

        console.log(`balance in order ${balanceInOrder}`);
        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"))


        //console.log(ordSEll)


    });
    it("Modifier  Order in order book SELL, decreas amount", async () => {
        // id 3, da trovare il modo per recuperarlo.


        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));
        balanceInOrder = await dex.getBalanceInOrder(web3.utils.fromUtf8("WAVAX"));

        console.log(`balance in order ${balanceInOrder}`);
        console.log(ordSEll);

        await truffleAssert.passes(
            dex.modifierOrder(
                web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 2, 800, 1, 3
            )
        );
        balanceInOrder = await dex.getBalanceInOrder(web3.utils.fromUtf8("WAVAX"));

        console.log(`balance in order ${balanceInOrder}`);
        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"))


        console.log(ordSEll)


    });
    it("Delet  Order in order book SELL", async () => {
        // id 3, da trovare il modo per recuperarlo.


        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"));
        balanceInOrder = await dex.getBalanceInOrder(web3.utils.fromUtf8("WAVAX"));

        //console.log(`balance in order ${balanceInOrder}`);
        //console.log(ordSEll);

        await truffleAssert.passes(
            dex.deletOrder(
                web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"), 1, 3
            )

        );
        balanceInOrder = await dex.getBalanceInOrder(web3.utils.fromUtf8("WAVAX"));

        //console.log(`balance in order ${balanceInOrder}`);
        ordSEll = await dex.getOrderBookSEll(web3.utils.fromUtf8("WAVAX"), web3.utils.fromUtf8("DAI"))


        //console.log(ordSEll)


    });
})

this code aren’t complete , but when i will take new skills , i will complete this

1 Like

hey @LELUK911. im very impressed that your taking so much time to explore other solutions and to look at the code of the likes of usinswpa etc. its hows your passion and hunger and you will do well im very impressed.

I am going to give you a few pieces of advice of your code above. Firstly i want to talk about your idea of using the chainlink oracle to get price data and use that for the token prices. Sadly this will not work the way you expect. The reason is because the dex that you have developed here is a orderbook dex. these are fundamentally different to the way AMM’s and most decentralised exhanges work. An oderbook dex mathes buyers with sellers. What this means is the negoitatedd price of th asset is always going to be the price that someonbdy is least willing to sell there asset for and on the other side the price at which the user is mist prepared to buy an asset for. Now thats a mouthful so what do i mean by this.

Well the way the orderbook dex works is that the orders in the buy order book are ordered from highest to lowest. meaning the the order with the highest price is at the top of the book. Conversely the sell side is ordered in revers meaning that the lowest priced order is at the tp of the orderbook. What this means is that the current price of any token is just going to be the price of the order in each orderbook thats at the top of the list. This is how buyers are matched its like a bid. i could come in and choose to sell my ether fro 10 euro, then someone else who wants their order settled first could sell theirs for 9 euro meaning that thwir order would go to the top of the book and get settled before mine.

So your idea of using chainlink here wont work. Chainlinks price oracle is used in smart contract when the user wants to query the price of some asset amounst dencetralised exchanges for example to get the truest price and then use that data in some function. It wont work here because users in an orderbook dex are ultimately the price deciders it all comes down to supply and demand.

Even in AMM’s you wouldnt not use chainlink to determine the price of an asset. the way an AMM (automated market maker) works is that their is the concept of a token pair. Say ETH/DAI. there will be a smart contract which represents this unique pair. And the purpose of this smart contract amongst other things is to keep track of how much ether and how much DAI there is in the smart contract. this is called the reserves. In AMM’s we have a vairant called the constant product AMM or CPMM for short. The way this works is that the product of the reserves in both asset ETH and DAI must always equal some constant number

Res(ETH) * Res(DAI) = Constant

so for an AMM the way in which the price of a token pair is caluclated is by dividning the reserves at any given time. So for the example of ETH and DAI the etehr price would be

ETHprice = Res(ETH) / Res(DAI)

and for DAI

DAIprice = Res(DAI) / Res(ETH)

and the way in which the price changes is determined by the change in the amount of reserves each asset have in the smart contract. we can consider this example as follows. In this example lets say that we create an DAI/ETH pair such that we provide 10 ether at 4000 dollars and 40,000 DAI at 1 dollar per dai. Effectively we now have satisfied the constant product rule

𝐸𝑆 = 10, 𝐸𝑃 = 4000$
𝐷𝑠 = 40,000, 𝐷𝑃 = 1$
𝑤ℎ𝑒𝑟𝑒 𝐸𝑆 = 𝐸𝑡ℎ 𝑆𝑢𝑝𝑝𝑙𝑦, 𝐸𝑃 = 𝐸𝑡ℎ 𝑝𝑟𝑖𝑐𝑒, 𝐷𝑠 = 𝐷𝑎𝑖 𝑠𝑢𝑝𝑝𝑙𝑦, 𝐷𝑝 = 𝐷𝑎𝑖 𝑝𝑟𝑖𝑐𝑒

The in order to calculate the constant product we use the formula above. Thus, in this scenario here the the ETH/DAI pair we would have

𝑥 = 10 × 4000 = 40,000
≫ 𝑦 = 1 × 40,000 = 40,000
≫ 𝑥 × 𝑦 = 𝑘
≫ 𝑘 = 40,000 × 40,000 = 1,600,000,000

So if we just saw this equation, we could determine the ratio of ETH/DAI = 1,600,000,000 / 40,000 (which is the price of Ethereum in this scenario)! Now let’s say someone would like to purchase $4000 worth of ETH from the pool. What they’re essentially doing is increasing (𝑦) by 4000 (depositing DAI) and decreasing (𝑥) by 1 (withdrawing ETH). Thus our new equation for the their price has the form:

(𝐸𝑃 × (𝐸𝑆 − 𝐸𝑡ℎ𝑂𝑢𝑡)) × ((𝐷𝑃 × 𝐷𝑠) + 𝐷𝑎𝑖𝐼𝑛) = 1,600,000,000
(𝐸𝑃 × (𝐸𝑆 − 1)) × ((𝐷𝑃 × 𝐷𝑠) + 4000) = 1,600,000,000
(𝐸𝑝 × 9) × (1 × 40,000) + 4000) = 1,600,000,000
(𝐸𝑝 × 9) × (40,000 + 4000) = 1,600,000,000
(𝐸𝑝 × 9) =1,600,000,000 / 40,000 + 4000
𝐸𝑝 = 9 × 1,600,000,000 / 44,000 = 4040.40

and in this example we can see that the price of ether has gone up. So hopefully this explaination shows why you cannot use chainlink as a price setter for your dex token price.

The next thing i want to say is that using the uniswap code with your code wont work because they are an AMM and your code is an oderbook style dex. I do understand why you were thinking of this tho as i way in your boots not so long ago. So your wondering how to keep the price of say eth in your dex code the same as that in real life. And the short awnser is you cant. the big issue is liquidity dexes with bad liquidity suffer from bad prices. and the only way to get good liqudidty is for users to be constantly using the dex. now there is another way you could do it but its a bit complicated and advanced for me to explain here but feel free to private message me and ill explain. it involes using bots to actively trade both sides of of dex simulating users so that the price is kept relatively similar to real life.

One last thing you could try is to make an AMM dex using th euniswap contracts. The you could use chainlink keeprs which is a service that allows you to execute smart contract functuons via off chain events. so if you did this you could create a limit oeder dex for uniswap whereby you submit a limit order and it only gets executed when the price of the token pair reaches a certain price. Another student actually build a project aroun dthis concept last summer i think. i can get the repo now.
https://github.com/mcgraneder/limiswap

this could be of some inspiration to you.

if your intrested in developing a front end private message me and i will help you get started.

1 Like

just as a final not i said a lot there and i dont want you thinking im crashing your ship. You are wayyyyyyyy ahead in terms of what most people are trying when they take this course. your are so on the right path. i didnt even know what chainlink was when i took this course so just keep at it, keep experimenting, keep coding, keep having fun and youll be a pro in no time. i mean this

1 Like

hi mcgrane5, thank for this response really precise and exhaustive. Above all i appreciated explanation on AMM’s math that i immagined how function , but now i know , and i will study your response.

You have understood good because i used the oracle, but i thinked that if i deployed this contract for exempl on avalanche, nobody will use my contract for low liquidity, as i thinked , boom one if use my dex, and othe order book is full, the slippage and fees are more advan, but if for exempl user bid “avax”, he whait that when really avax’s value is equal at order’s price, it would fill, and and sooner than to miss order, i prefer use AMM.

in my idea i less word , my dex behaved as “SERUM” in reverse. take the liquidity from other Amm.
but in effect my ideas and my code are more confuse, and i will learn at stay on focus on my code’sobject.

but now i courius to know function Amm as “Curve” and “Solidly” and how it set a price . i will sure read gitHub of this Amm.
I see also in your gitHub some interessant for me and will read and study they.
For front-end i can doing a simple “ugly” page , but i dotn know now how connetc my contract at front end, but now prefer fixed back end in my mind and next study code and style develope front-end.

In the end i more that happy your response on my code, because the next time i can doing better, and then this is a test for my skills and was useless copy code the course… i prefer r write wrong personale code.

i hope my translate will understandable.

1 Like

hey @LELUK911. Great that your exporeing cure Fi too thats some real good defi stuff there and your right especially with low slippage. ok for frontend you dont need to know how to make anything super fancy at the start. i would take the Dapp programming course if you want to know how to make a front end with vanilla js. this is good to learn the basicis however you really want to learn react. there is a course here on the academy on react but if you want my gods honest opinion i wouldnt take it because its the old react (class components) and eveyone uses (functional components these days. so if you took that course youd end up having to learn react twice. freecodecamp has a great 7 hour course thats very modern its really good. but definitley learn vanilla js first i would look at dapp programming as i said and also some of the free content on the moralis YT channel. they actually have a goood tutorial on making a dex its very simple but it will show you how to get started with vanilla js html and css for frontend

1 Like

Dex

//SPDX-License-Identifier: UNLICENSED

import "./wallet.sol";

pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

contract Dex is Wallet{
    using SafeMath for uint256;
    uint256 public nextOrderID = 0;

    enum Side {
        BUY,
        SELL
    }

    struct Order {
        uint id;
        address trader;
        Side side;
        bytes32 ticker;
        uint amount;
        uint price;
        uint filled;
    }

    mapping(bytes32 => mapping(uint => Order[])) orderBook;
    function  getOrderBook(bytes32 ticker, Side side) view public returns(Order[] memory){
        return orderBook[ticker][uint(side)];
    }

    function createLimitOrder(Side side, bytes32 ticker, uint amount, uint price) public{
        if(side == Side.BUY){
            require(balances[msg.sender]["ETH"] >= amount.mul(price));
        }
        if(side == Side.SELL){
            require(balances[msg.sender][ticker] >= amount);
        }

        Order[] storage orders = orderBook[ticker][uint(side)];
        orders.push(
            Order(nextOrderID, msg.sender, side, ticker, amount, price, 0)
        );
        
        uint i = orders.length > 0 ? orders.length - 1 : 0;
        if(side == Side.BUY){
            while(i > 0){
                if(orders[i - 1].price > orders[i].price) {
                    break;
                }
                Order memory orderToMove = orders[i - 1];
                orders[i - 1] = orders[i];
                orders[i] = orderToMove;
                i--;
            }
        }
        else if(side == Side.SELL){
            while(i > 0){
                if(orders[i - 1].price < orders[i].price) {
                    break;   
                }
                Order memory orderToMove = orders[i - 1];
                orders[i - 1] = orders[i];
                orders[i] = orderToMove;
                i--;
            }
        }
        nextOrderID++;
    }


    

    function marketOrder(Side side, bytes32 ticker, uint amount) public{
        if(side == Side.SELL){
            require(balances[msg.sender][ticker] >= amount, "Insufficient amount of tokens");
        }

        uint orderBookSide;
        if(side == Side.BUY){
            orderBookSide = 1;
        } else {
            orderBookSide = 0;
        }
        Order[] storage orders = orderBook[ticker][orderBookSide];
        
        uint totalfilled = 0;
        uint cost = 0;
        uint left;
        uint filled;
        uint remainder;
        for(uint i = 0; i < orders.length && amount > totalfilled; i++){
            left = orders[i].amount - orders[i].filled;
            remainder = amount - totalfilled;
            if(left < remainder){
                cost = left.mul(orders[i].price);
                orders[i].filled = orders[i].amount;
                filled = left;
                totalfilled = totalfilled + left;
            } else {
                cost = (remainder).mul(orders[i].price);
                orders[i].filled = orders[i].filled.add(remainder);
                filled = remainder;
                totalfilled = amount;
            }

            if(side == Side.BUY){
                require(cost <= balances[msg.sender]["ETH"]);
                balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(cost);
                balances[msg.sender]["LINK"] = balances[msg.sender]["LINK"].add(filled);

                balances[orders[i].trader]["ETH"] = balances[msg.sender]["ETH"].add(cost);
                balances[orders[i].trader]["LINK"] = balances[msg.sender]["LINK"].sub(filled);
            }

            if(side == Side.SELL){
                balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(cost);
                balances[msg.sender]["LINK"] = balances[msg.sender]["LINK"].sub(filled);

                balances[orders[i].trader]["ETH"] = balances[msg.sender]["ETH"].sub(cost);
                balances[orders[i].trader]["LINK"] = balances[msg.sender]["LINK"].add(filled);
            }

            if(totalfilled >= amount){
                break;
            }            
            
        }
    
        while(orders.length > 0 && orders[0].filled == orders[0].amount){
            for(uint i = 0; i < orders.length-1; i++){
                orders[i] = orders[i+1];
            }
            orders.pop(); 
        }
    }
    
}

dextest

const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = require('truffle-assertions');


contract.skip("DexTester", accounts => {
    it("the user  have ETH deposited such that deposited eth >= buy order value", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        //createLimitOrder should take care of approve and deposit
        await dex.depositEth({value: 10, from: accounts[0]})
        await truffleAssert.passes(
            dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 5, {from: accounts[0]})
        )
    })
    it("the user have ETH deposited such that deposited eth < buy order value", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        //createLimitOrder should take care of approve and deposit
        await truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 50000, 50000, {from: accounts[0]})
        )
    })

    it("the user have tokens deposited such that token balance < sell order amount", async () =>{
        
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 500000, 50, {from: accounts[0]})
        )
    })

    it("the user have enough tokens deposited such that token balance >= sell order amount", async () =>{
        
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address, 30);
        await dex.deposit(1, web3.utils.fromUtf8("LINK"));

        //createLimitOrder should take care of approve and deposit
        
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 5, {from: accounts[0]})
        )
    })

    it("order book BUY side should be sorted from highest to lowest", async () =>{
        
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await dex.depositEth({value: 5000, from: accounts[0]});
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 2, 10);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 3, 20);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 30);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 4, 40);
        
        
        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
        
        for(let i = 0; i < orderbook.length - 1; i++){
            
            assert(parseInt(orderbook[i].price) >= parseInt(orderbook[i+1].price), "not right buy order");
        }
    })

    it("order book SELL side should be sorted from lowest to highest", async () =>{
        
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await link.approve(dex.address, 1000);
        await dex.deposit(200, web3.utils.fromUtf8("LINK"));
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 2, 10);
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 3, 20);
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 30);
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 4, 40);


        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
       
        for(let i = 0; i < orderBook.length - 1; i++){
            
            assert(parseInt(orderBook[i].price) <= parseInt(orderBook[i+1].price), "not right sell order");
        }
    })

})

marketordertest

const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = require('truffle-assertions');


contract("MarketTester", accounts => {
    it("When creating SELL market order the seller needs to have enough tokens for the trade", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})

        let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"))
        assert.equal(balance.toNumber(), 0, "Initial LINK balance is not 0");

        await truffleAssert.reverts(dex.marketOrder(1, web3.utils.fromUtf8("LINK"), 30));        
    })
    
    it("Market orders can still be submitted even if order book is empty", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await dex.depositEth({value: 1, from: accounts[0]});

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
        assert(orderbook.length == 0, "Buy side should be empty");
        
        truffleAssert.passes(dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 30));
        
    })
    it("Market order should be filled until order book emtpy or 100% filled", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        await dex.depositEth({value: 30, from: accounts[0]});
        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);
        assert(orderbook.length == 0, "orderbook should be empty");
        
        await link.transfer(accounts[1], 50);
        await link.approve(dex.address, 8, {from: accounts[1]});
        await dex.deposit(7, web3.utils.fromUtf8("LINK"), {from: accounts[1]});
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 5, {from: accounts[1]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 2, 3, {from: accounts[1]})

        await truffleAssert.passes(dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 6), {from: accounts[0]});
        
        orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);
        await assert(orderbook.length == 1, "orderbook should have only one entry");

        //orderbook consumed
        await truffleAssert.passes(dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 2), {from: accounts[0]});
        orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);
        await assert(orderbook.length == 0);
        
    })
    it("The ETH balance of the buyer should decrease accordingly", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        await dex.depositEth({value: 50, from: accounts[0]});
        
        await link.transfer(accounts[1], 50);
        await link.approve(dex.address, 30, {from: accounts[1]});
        
        let before = await dex.balances(accounts[0], web3.utils.fromUtf8("ETH"));
        
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 5, {from: accounts[1]})
        await dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 5)

        let remainder = await dex.balances(accounts[0], web3.utils.fromUtf8("ETH"));
        
        await assert.equal(before.toNumber()-25, remainder.toNumber(), "eth should be reduced by cost amount");
        
    })
    it("the token balance of the buyer should increase with the filled amounts", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);
        assert(orderbook.length == 0, "still have remainders from before");

        await dex.depositEth({value: 50, from: accounts[0]});
        await link.transfer(accounts[1], 50);
        await link.approve(dex.address, 30, {from: accounts[1]});
        await dex.deposit(10, web3.utils.fromUtf8("LINK"), {from: accounts[1]});

        let before = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 7, 7, {from: accounts[1]})
        
        await dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 7)

        let remainder = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));
        
        assert.equal(before.toNumber()+7, remainder.toNumber(), "Link tokens should be reduced by the amount sold");
        
    })
    it("Filled limit orders should be removed from the orderbook", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);
        await assert(orderbook.length == 0, "still have remainders from before");
        
        await dex.depositEth({value: 80, from: accounts[0]});
        await link.transfer(accounts[1], 50);
        await link.approve(dex.address, 16, {from: accounts[1]});
        await dex.deposit(16, web3.utils.fromUtf8("LINK"), {from: accounts[1]});
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 7, 7, {from: accounts[1]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 2, 1, {from: accounts[1]})
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 7, 7, {from: accounts[1]})

        await dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 7)

        orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
        

        await assert.equal(orderbook.length, 2, "removal not performed?");

        await dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 14)

    })
    it("Partly filled limit orders should be marked", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);
        assert(orderbook.length == 0, "still have remainders from before");

        await dex.depositEth({value: 51, from: accounts[0]});
        await link.transfer(accounts[1], 50);
        await link.approve(dex.address, 16, {from: accounts[1]});
        await dex.deposit(7, web3.utils.fromUtf8("LINK"), {from: accounts[1]});
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 7, {from: accounts[1]})

        await dex.marketOrder(0, web3.utils.fromUtf8("LINK"), 5)
        orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);

        assert.equal(orderbook[0].filled, 5);
        assert.equal(orderbook[0].amount, 10);
    
    })
})

1 Like

ok ok , i like tutorial on frecodecamp.
i have complete Javascript course on moralis, but i will read how connect front-end contract.
in the nexts weeks i will study react. Now i have a more clear plan on my next steps., and i have more on study now,
is a good challenge!

ah , i want tell you if exist a complete tutorial on truffle test, because i have some difficult at it work as i want.

1 Like

for truffle test it can be tricky but it always comes down to stupid syntaxual mistakes in your tests most times and not your actual code. but yeah yu can rewartch the tutorials here or theres some on YT

@thecil, @REGO350 @filip

This project involves ERC20 smart contracts, a token, a wallet and a DEX contract. This project is in React framework, with Hardhat and Ethers js. I started a year ago when I was finishing the Ethereum 201 course with Filip from Moralis. It took me about a year to first learn more by taking the Crypto Kitties course with Truffle, web3 and Ganache. Then, I took the React course with Zsolt Nagy and built an exchange with React. Then I did some more learning on my own and finally came back to develop the front-end for the smart contract DEX, using React, EThers js and Hardhat. I’ve learned a lot and hope I keep on making progress and eventually work as a blockchain developer, very soon.

DEX

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 < 0.9.0;
pragma abicoder v2;

import "./Wallet.sol";
import "hardhat/console.sol";

contract Dex is Wallet {

  using SafeMath for uint256;

  enum Side {BUY, SELL} // 0, 1

  struct Order {
    uint256 id;
    address trader;
    Side side;
    bytes32 ticker;
    uint256 amount;
    uint256 price;
    uint256 filled;
  }

  uint public nextOrderId = 0;


  mapping(bytes32 => mapping(uint256 => Order[])) public OrderBook;

  event LimitOrder(address indexed trader, Side side, bytes32 indexed ticker, uint256 amount, uint256 price);
  event MarketOrder(address indexed trader, Side side, bytes32 indexed ticker, uint256 amount);

  function getOrderBook(bytes32 ticker, Side side) public view returns(Order[] memory) {
    return OrderBook[ticker][uint(side)];
  }

  function depositEth() external payable {
    balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")].add(msg.value);
  }

  function createLimitOrder(Side side, bytes32 ticker, uint256 amount, uint256 price) public {
      if(side == Side.BUY) {
        require(balances[msg.sender]["ETH"] >= amount.mul(price), "Not enough Eth balance");
        
      } else if (side == Side.SELL) {
        require(balances[msg.sender][ticker] >= amount, "Low token balance");
      }

      Order[] storage orders = OrderBook[ticker][uint(side)];
      orders.push(
        Order(nextOrderId, msg.sender, side, ticker, amount, price, 0));

       //Bubble sort
      uint i = orders.length > 0 ? orders.length - 1 : 0;
   

      if(side == Side.BUY){
        while(i > 0) {
          if (orders[i - 1].price > orders[i].price) {
            break; // if index minus 1; price is greater than the next indexed order price, stop - it's already ordered
          }
          Order memory swap = orders[i - 1];
          orders[i - 1] = orders[i];
          orders[i] = swap;
          i--; // this swaps the orders so that they are in the correct order
        }
        
      }
      else if(side == Side.SELL){
        while(i > 0) {
          if (orders[i -1].price < orders[i].price) {
            break;
          }
          Order memory swap = orders[i - 1];
          orders[i - 1] = orders[i];
          orders[i] = swap;
          i--;
        }
        
      }
      nextOrderId++;

      emit LimitOrder(msg.sender, side, ticker, amount, price);
      
  }

  function createMarketOrder(Side side, bytes32 ticker, uint amount) public {
    if(side == Side.SELL) {
      // wrap this into an if statement for sell orders
      require(balances[msg.sender]["ETH"] >= amount, "Insufficient balance");
    }

    uint orderBookSide;

    if(side == Side.BUY) {
      orderBookSide = 1;
    } else {
      orderBookSide = 0;
    }
    
    Order[] storage orders = OrderBook[ticker][orderBookSide];

    uint totalFilled = 0;

    for (uint256 i = 0; i < orders.length && totalFilled < amount; i++) {

      uint leftToFill = amount.sub(totalFilled); //amount minus totalFill // 200
      uint availableToFill = orders[i].amount.sub(orders[i].filled); //order.amount - order.filled // 100
      uint filled = 0;
      
      // how much we can fill from order[i]
      // update totalFilled; (once exiting loop)
      if(availableToFill > leftToFill) {
        filled = leftToFill; //fill the entire market order
      } else { //availableToFill <= leftToFill
        filled = availableToFill; //fill as much as is available in order[i]
      }

      totalFilled = totalFilled.add(filled);
      orders[i].filled = orders[i].filled.add(filled);
      uint cost = filled.mul(orders[i].price);

      // execute the trade & shift balances between buyer/seller (of each order -- subtract the balance)
      if(side == Side.BUY) {
        //verify buyer has enough ETH to cover the purchase (require)
         require(balances[msg.sender]["ETH"] >= cost);
         //execute the trade:

         //msg.sender is the buyer
         //transfer (add) ETH from Buyer to Seller and (sub) cost
         balances[msg.sender][ticker] = balances[msg.sender][ticker].add(filled);
         balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(cost);

         //transfer (sub) Tokens from Seller to Buyer and (add) cost
         balances[orders[i].trader][ticker] = balances[orders[i].trader][ticker].sub(filled);
         balances[orders[i].trader]["ETH"] = balances[orders[i].trader]["ETH"].add(cost);
           
      }
      else if(side == Side.SELL) {
        //execute the trade:

        //msg.sender is the seller
        //transfer (sub) ETH from Buyer to Seller and (add) cost
        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(filled);
        balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(cost);
        
        //transfer (add) Tokens from Seller to Buyer and (sub) cost
        balances[orders[i].trader][ticker] = balances[orders[i].trader][ticker].add(filled);
        balances[orders[i].trader]["ETH"] = balances[orders[i].trader]["ETH"].sub(cost);
      }
    }

  // remove 100% filed orders
  // continue to loop if our orders are filled and amounts are met and when the length of the array is greater than zero, otherwise, stop
    while(orders.length > 0 && orders[0].filled == orders[0].amount) {
      // Remove the top element in the orders array by overwriting every element
      // with the next element in the order list
      for(uint256 i = 0; i < orders.length - 1; i++) {
        orders[i] = orders[i + 1];
      }
      orders.pop();

    }

    emit MarketOrder(msg.sender, side, ticker, amount);

  }

  function getTokenListLength() public view returns (uint256) {
    return tokenList.length;
  }


}

Wallet

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 < 0.9.0;
import "hardhat/console.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Wallet is Ownable {

  using SafeMath for uint256;

  struct Token {
    bytes32 ticker;
    address tokenAddress;
  }

  mapping(bytes32 => Token) public tokenMapping;
  bytes32[] public tokenList;

  mapping(address => mapping(bytes32 => uint256)) public balances;

  modifier tokenExists(bytes32 ticker){
    require(tokenMapping[ticker].tokenAddress != address(0), "Token does not exist");
    _;
  }

  function addToken(bytes32 ticker, address tokenAddress) external onlyOwner {
    tokenMapping[ticker] = Token(ticker, tokenAddress);
    tokenList.push(ticker);
    //bytes32("LINK"); // BYTES 32 VALUE FROM A STRING
  }

  function deposit(uint256 amount, bytes32 ticker) external tokenExists(ticker) {
    IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);
    balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
  }

  function withdraw(uint256 amount, bytes32 ticker) external tokenExists(ticker) onlyOwner {
    require(balances[msg.sender][ticker] >= amount, "Balance not sufficient");
    
    balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);
    IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
  }

}


Token

// SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 < 0.9.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "hardhat/console.sol";

contract RealToken is ERC20 {

  constructor() ERC20("RealToken", "RETK") {
    _mint(msg.sender, 100000 * (10 ** 18));
  }
}

Unit Testing Hardhat

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Market Test", () => {
  let Dex, dex, RealToken, realToken, owner, addr1, addr2, addr3, addr4;

  beforeEach(async () => {
    Dex = await ethers.getContractFactory("Dex");
    dex = await Dex.deploy();

    RealToken = await ethers.getContractFactory("RealToken");
    realToken = await RealToken.deploy();

    //Testing from a different account
    [owner, addr1, addr2, addr3, addr4] = await ethers.getSigners();
  });

  describe("Deployment", () => {
    it("should set the right owner", async () => {
      // dex owner to equal owner address (msg.sender)
      expect(await dex.owner()).to.equal(owner.address);
    });

    it("should check that the Link total supply is equal to the owner's balance", async () => {

      const ownerBalance = await realToken.balanceOf(owner.address);
      expect(await realToken.totalSupply()).to.equal(ownerBalance);
    });
  });
  describe("Transactions", () => {
    it("Should throw an error when creating a sell market order without adequate token balance", async () => {

      const initialTokenBalance = await dex.balances(addr1.address, ethers.utils.formatBytes32String("RETK"));
      console.log("Init Token Balance: ", initialTokenBalance.toNumber());

      await expect(
        dex.connect(addr1).createMarketOrder(1, ethers.utils.formatBytes32String("RETK"), 10)
      ).to.be.reverted;
      
    });
    it("Market BUY orders can be submitted even if the order book is empty", async () => {

      const orderBookBefore = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 0); // get buy side

      expect(orderBookBefore.length).to.equal(0);
      console.log("Orderbook length before: ", orderBookBefore.length);

      const marketOrderTx = await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 10);
      await marketOrderTx.wait();

      const orderBookAfter = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 0); // get buy side
      console.log("Orderbook length after: ", orderBookAfter.length);

      // Should be empty
      expect(orderBookAfter.length).to.equal(0);
    });
    it("Market orders should not fill more limit orders than the market order amount", async () => {

      //Get the sell side orderbook
      const orderbook = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1);
      expect(orderbook.length).to.equal(0);
      console.log("Orderbook SELL length start:", orderbook.length);

      await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);

      const depositEthTx = await dex.depositEth({ value: 30000 });
      await depositEthTx.wait();

      // Send RETK tokens to accounts 1, 2, 3 from accounts 0
      const transferTx1 = await realToken.transfer(addr1.address, 150);
      await transferTx1.wait();

      const transferTx2 = await realToken.transfer(addr2.address, 150);
      await transferTx2.wait();

      const transferTx3 = await realToken.transfer(addr3.address, 150);
      await transferTx3.wait();

      // Approve DEX for accounts 1, 2, 3
      const approveTx1 = await realToken.connect(addr1).approve(dex.address, 50);
      await approveTx1.wait();
      const approveTx2 = await realToken.connect(addr2).approve(dex.address, 50);
      await approveTx2.wait();
      const approveTx3 = await realToken.connect(addr3).approve(dex.address, 50);
      await approveTx3.wait();

      //Deposit LINK into DEX for accounts 1, 2, 3
      const depositTx1 = await dex.connect(addr1).deposit(50, ethers.utils.formatBytes32String("RETK"));
      await depositTx1.wait();
      const depositTx2 = await dex.connect(addr2).deposit(50, ethers.utils.formatBytes32String("RETK"));
      await depositTx2.wait();
      const depositTx3 = await dex.connect(addr3).deposit(50, ethers.utils.formatBytes32String("RETK"));
      await depositTx3.wait();

      // Fill the Limit sell order book
      const limitSellTx1 = await dex.connect(addr1).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 300);
      await limitSellTx1.wait();

      const limitSellTx2 = await dex.connect(addr2).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 400);
      await limitSellTx2.wait();

      const limitSellTx3 = await dex.connect(addr3).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 500);
      await limitSellTx3.wait();

      // Create market order which fills 2/3 orders in the book
      const marketOrderTx = await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 10);
      await marketOrderTx.wait();
      console.log("Market Order BUY: ", marketOrderTx);

      //Get the sell side orderbook
      const orderbookAfter = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1);

      expect(orderbookAfter.length).to.equal(1);
      console.log("Orderbook length: ", orderbookAfter.length)
      expect(orderbookAfter[0].filled).to.equal(0);
      console.log("Orderbook filled: ", orderbookAfter[0].filled.toNumber());
      // Note: I think "filled" is 0 because it has not been filled. The market order was only for 10 link. 
      // Two Sell orders where filled but one remains (5 link) as it should only fill the the amount the market order was for.

    });
    it("Market orders should be filled until the order book is empty", async () => {
      // This is checked by creating a buying order for more Link coins than there are available, until the order book gets emptied.

      // add Link Token, deposit Eth 

      // Link transfers Link token to 3 accounts,
      // Link approves amount of Link for Dex to spend for those 3 accounts on their behalf
      // Dex deposits Link from those same 3 accounts

      // begin limit sell orders

      // check orderbook sell side orders
      // check owner link balance before market order
      // create market buy order
      // check owner link balance after market order

      // assert/expect
      // balance before + amount bought should equal balance after
      await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);

      await dex.depositEth({ value: 30000 });

      // realToken token transfers to address 1, 2, 3, again since order book is empty
      await realToken.transfer(addr1.address, 150);
      await realToken.transfer(addr2.address, 150);
      await realToken.transfer(addr3.address, 150);

      // approve dex for address 1, 2, 3
      await realToken.connect(addr1).approve(dex.address, 50);
      await realToken.connect(addr2).approve(dex.address, 50);
      await realToken.connect(addr3).approve(dex.address, 50);

      // deposit realToken to address 1, 2, 3
      await dex.connect(addr1).deposit(50, ethers.utils.formatBytes32String("RETK"));
      await dex.connect(addr2).deposit(50, ethers.utils.formatBytes32String("RETK"));
      await dex.connect(addr3).deposit(50, ethers.utils.formatBytes32String("RETK"));


      // create limit sell orders
      const limitSellTx1 = await dex.connect(addr1).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 300);
      await limitSellTx1.wait();

      const limitSellTx2 = await dex.connect(addr2).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 400);
      await limitSellTx2.wait();

      const limitSellTx3 = await dex.connect(addr3).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 500);
      await limitSellTx3.wait();

      //Get sell side orderbook
      const orderbook = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1);
      expect(orderbook.length).to.equal(3, "Sell side Orderbook should have 3 orders");
      console.log("Orderbook SELL length start:", orderbook.length);

      const balanceBefore = await dex.balances(owner.address, ethers.utils.formatBytes32String("RETK"));
      console.log("RETK balance before BUY order: ", balanceBefore.toNumber());

      await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 50);

      const balanceAfter = await dex.balances(owner.address, ethers.utils.formatBytes32String("RETK"));
      console.log("RETK balance after BUY order: ", balanceAfter.toNumber());

      expect(balanceBefore + 15).to.equal(balanceAfter);
      console.log("RETK balance before BUY order - expect: ", balanceAfter.toNumber());
    });
    it("The eth balance of the buyer should decrease with the filled amount", async () => {

      await dex.depositEth({ value: 3000 });

      await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);

      await realToken.connect(addr1).approve(dex.address, 150);

      await realToken.transfer(addr1.address, 10);

      const depositTx = await dex.connect(addr1).deposit(10, ethers.utils.formatBytes32String("RETK"));
      await depositTx.wait();

      const limitOrderTx = await dex.connect(addr1).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 1, 300);
      await limitOrderTx.wait();
      console.log("Limit order tx: ", limitOrderTx);

      //Check buyer ETH balance before trade
      const balanceBefore = await dex.balances(owner.address, ethers.utils.formatBytes32String("ETH"))
      console.log("Eth balance before trade: ", balanceBefore.toNumber());

      const marketOrderTx = await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 1);
      await marketOrderTx.wait();
      console.log("Market order tx: ", marketOrderTx);

      //Check buyer ETH balance after trade
      const balanceAfter = await dex.balances(owner.address, ethers.utils.formatBytes32String("ETH"));
      console.log("Eth balance after trade: ", balanceAfter.toNumber());

      expect(balanceBefore - 300).to.equal(balanceAfter);
    });
    it("the token balances of the limit order sellers, should decrease with the filled amounts", async () => {
      // add Eth, Token, 
      // Approve, Transfer, for two accounts 1, 2
      const depositEthTx = await dex.depositEth({ value: 3000 });
      await depositEthTx.wait();

      const addTokenTx = await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);
      await addTokenTx.wait();

      const approveTx1 = await realToken.connect(addr1).approve(dex.address, 150);
      await approveTx1.wait();

      const approveTx2 = await realToken.connect(addr2).approve(dex.address, 150);
      await approveTx2.wait();

      const transferTx1 = await realToken.transfer(addr1.address, 10);
      await transferTx1.wait();

      const transferTx2 = await realToken.transfer(addr2.address, 10);
      await transferTx2.wait();

      // deposit realToken to address 1, 2
      const depositReceipt1 = await dex.connect(addr1).deposit(10, ethers.utils.formatBytes32String("RETK"));
      await depositReceipt1.wait();

      const depositReceipt2 = await dex.connect(addr2).deposit(10, ethers.utils.formatBytes32String("RETK"));
      await depositReceipt2.wait();

      //Create limit orders, accounts 1, 2
      const limitOrderTx1 = await dex.connect(addr1).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 1, 100);
      await limitOrderTx1.wait();
      console.log("Limit order Sell: ", limitOrderTx1);

      const limitOrderTx2 = await dex.connect(addr2).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 1, 100);
      await limitOrderTx2.wait();
      console.log("Limit order Sell: ", limitOrderTx2);

      //Check sellers RETK balance before trade, accounts 1, 2
      const account1RETKBalanceBefore = await dex.balances(addr1.address, ethers.utils.formatBytes32String("RETK"));
      console.log("RETK balance before trade, acc1: ", account1RETKBalanceBefore.toNumber());

      const account2RETKBalanceBefore = await dex.balances(addr2.address, ethers.utils.formatBytes32String("RETK"));
      console.log("RETK balance before trade, acc2: ", account2RETKBalanceBefore.toNumber());

      //Create market BUY order
      const marketOrderTx = await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 2);
      await marketOrderTx.wait();
      console.log("Market order Buy: ", marketOrderTx);

      //Check sellers RETK balance after trade for accounts 1, 2
      const account1RETKBalanceAfter = await dex.balances(addr1.address, ethers.utils.formatBytes32String("RETK"));
      console.log("RETK balance after trade, acc1: ", account1RETKBalanceAfter.toNumber());

      const account2RETKBalanceAfter = await dex.balances(addr2.address, ethers.utils.formatBytes32String("RETK"));
      console.log("RETK balance after trade, acc2: ", account2RETKBalanceAfter.toNumber());

      expect(account1RETKBalanceBefore - 1).to.equal(account1RETKBalanceAfter);
      expect(account2RETKBalanceBefore - 1).to.equal(account2RETKBalanceAfter);

    });
    it("Filled limit orders should be removed from the orderbook", async () => {
      // deposit eth, add token, approve, transfer
      const depositEthTx = await dex.depositEth({ value: 3000 });
      await depositEthTx.wait();

      const addTokenTx = await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);
      await addTokenTx.wait();

      const approveTx1 = await realToken.connect(addr3).approve(dex.address, 150);
      await approveTx1.wait();

      const transferTx1 = await realToken.transfer(addr3.address, 10);
      await transferTx1.wait();

      // deposit to dex
      const depositReceipt1 = await dex.connect(addr3).deposit(10, ethers.utils.formatBytes32String("RETK"));
      await depositReceipt1.wait();

      // Create Limit order
      const limitOrderTx1 = await dex.connect(addr3).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 1, 100);
      await limitOrderTx1.wait();
      console.log("Limit order Sell: ", limitOrderTx1);

      // check order book, SELL side, before trade
      const orderbookBefore = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1)
      console.log("Orderbook length, SELL, Before: ", orderbookBefore.length); 

      expect(orderbookBefore.length).to.equal(1);

      // Create Market order
      const marketOrderTx = await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 1);
      await marketOrderTx.wait();

      // check order book, SELL side, after trade
      const orderbookAfter = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1)
      console.log("Orderbook length, SELL, After: ", orderbookAfter.length); 

      expect(orderbookAfter.length).to.equal(0);
    });
    it("Limit orders with the filled property, should be updated after each trade", async () => {
       // deposit eth, add token, approve, transfer
       const depositEthTx = await dex.depositEth({ value: 3000 });
       await depositEthTx.wait();
 
       const addTokenTx = await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);
       await addTokenTx.wait();
 
       const approveTx1 = await realToken.connect(addr3).approve(dex.address, 150);
       await approveTx1.wait();
 
       const transferTx1 = await realToken.transfer(addr3.address, 10);
       await transferTx1.wait();
 
       // deposit to dex
       const depositReceipt1 = await dex.connect(addr3).deposit(10, ethers.utils.formatBytes32String("RETK"));
       await depositReceipt1.wait();
 
       // Create Limit order
       const limitOrderTx1 = await dex.connect(addr3).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 5, 100);
       await limitOrderTx1.wait();
       console.log("Limit order Sell: ", limitOrderTx1);
 
       // check order book, SELL side, before trade
       const orderbookBefore = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1);
       console.log("Orderbook amount, SELL, Before: ", orderbookBefore[0].amount.toNumber()); 
       console.log("Orderbook filled, SELL, Before: ", orderbookBefore[0].filled.toNumber()); 

       expect(orderbookBefore[0].amount).to.equal(5, "Expecting 5");
       expect(orderbookBefore[0].filled).to.equal(0, "Expecting 0");
 
       // Create Market order
       const marketOrderTx = await dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 2);
       await marketOrderTx.wait();
 
       // check order book, SELL side, after trade
       const orderbookAfter = await dex.getOrderBook(ethers.utils.formatBytes32String("RETK"), 1)
       console.log("Orderbook amount, SELL, After: ", orderbookAfter[0].amount.toNumber()); 
       console.log("Orderbook filled, SELL, After: ", orderbookAfter[0].filled.toNumber()); 

       expect(orderbookAfter[0].amount).to.equal(5);
       expect(orderbookAfter[0].filled).to.equal(2);

    });
    it("should throw an error when creating a Buy market order with insufficient ETH balance", async () => {
      // using two accounts to create market orders which fail since their respective, Eth balances are insufficient
      await dex.depositEth({ value: 100 });
      await dex.connect(addr4).depositEth({ value: 10 });

      await dex.addToken(ethers.utils.formatBytes32String("RETK"), realToken.address);

      await realToken.connect(addr1).approve(dex.address, 150);
      await realToken.connect(addr2).approve(dex.address, 150);

      await realToken.transfer(addr1.address, 10);
      await realToken.transfer(addr2.address, 10);

      const depositTx1 = await dex.connect(addr1).deposit(10, ethers.utils.formatBytes32String("RETK"));
      await depositTx1.wait();

      const depositTx2 = await dex.connect(addr2).deposit(10, ethers.utils.formatBytes32String("RETK"));
      await depositTx2.wait();

      const limitOrderTx = await dex.connect(addr1).createLimitOrder(1, ethers.utils.formatBytes32String("RETK"), 1, 300);
      await limitOrderTx.wait();
      console.log("Limit order tx: ", limitOrderTx);

      //Check buyer ETH balance before trade
      const balanceBefore1 = await dex.balances(owner.address, ethers.utils.formatBytes32String("ETH"))
      console.log("Eth balance 1 before trade: ", balanceBefore1.toNumber());

      const balanceBefore2 = await dex.balances(addr4.address, ethers.utils.formatBytes32String("ETH"))
      console.log("Eth balance 2 before trade: ", balanceBefore2.toNumber());

      await expect(
        dex.createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 1)
      ).to.be.reverted;

      await expect(
        dex.connect(addr4).createMarketOrder(0, ethers.utils.formatBytes32String("RETK"), 1)
      ).to.be.reverted;

      //Check buyer ETH balance after trade
      const balanceAfter1 = await dex.balances(owner.address, ethers.utils.formatBytes32String("ETH"));
      console.log("Eth balance after trade: ", balanceAfter1.toNumber());

      const balanceAfter2 = await dex.balances(addr4.address, ethers.utils.formatBytes32String("ETH"));
      console.log("Eth balance 2 after trade: ", balanceAfter2.toNumber());

      expect(balanceBefore1).to.equal(balanceAfter1);
      expect(balanceBefore2).to.equal(balanceAfter2);

    });
  });

});










!

Here is a link to my repo and I will have another link once I launch it on a testnet
https://github.com/brlojam4932/dex2-app.git

My DEX is now on the Robsten testnet. It sitll needs work but for now it’s live
https://ecstatic-brahmagupta-3f9a21.netlify.app

1 Like

Heu @bjamRez. wooooooow man this is very very very gooooood. Youve come a long way i rememebr you posting your kitties dapp in the dapp course too. This is very impressive, its not easy to go out on your own and make a frontend for the code here and not many people do it so to see you come back to this project after such a time is really amazing. The frontent looks very sick too. would be great if you could deploy it on netlify too just like your last so users can use it for themselves. brooo your soo going to be able to get a job real soon keeeeppppp upppp the hussstleee brotthhaaa

Big Hats off man.

If you dont mind i had a look through your source code and i thought id share with you some cool tricks to optimize your react code. There is one main thing that could be approved in your app and that is the idea of creating components to offload some of your logic too. although you do have many components, they are really only styled html and css and dont take in any props to update state etc. this is absolutley fine but in practice you should put logic associated with a component in that component. There is very good reason fro this and it comes down to your page rerendring. So as you know each time a state updates then your page is going to rerender to display the new changs to your site. So the larger you end up making a component means that more and more rerenders are going to happen. the bigger and more bloated components get the more this can slow down your app due to “needless rerenders”.

I can explain in the following way. Say i have a nav bar and a modal just below it, however i code the nav bar and modal in app. say the navbar has a button on it, In this setup whenever i clock the button, both the navbar and the modal are going to rerender. in some sceanrios you have have state already modified in the modal and you only want to button click to udate the navabr, well then in this scenario any state you have in the modal can be reset to default.

Thus the solution is to seperate the navbar state and the modal state into two seperate components so that they dont effect each other when they change. This is the real putpose of componentns, destructuring logic into smaller bitesize pieces.

One other thing that i can reccomned as an optimisaton is your constant redefining of your web3 provider and instance. Instead of doing tis in every function, what you can do is to define web3 in index.js and pass web3 as props to your app so it accessible globally. something like this. The first thing you can do is to go to index.js and replace <App/> in the rect dom render with a component called <LoadingContainer/>
index.js

import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.min.css';
import './scss/index.scss';
import LoadingContainer from './LoadingContainer';

ReactDOM.render(<LoadingContainer />, document.getElementById('root'));

then create LoadingContainer.js. in this file well init web3, your dex and the account and pass these as props to your entire app component

import React, { useState, useEffect } from "react";
import ethers from "ethers"
import App from './App.js';

function index() {
  const [web3, setWeb3] = useState(undefined);
  const [accounts, setAccounts] = useState([]);
  const [contracts, setContracts] = useState(undefined);

  useEffect(() => {
    const init = async () => {
      const provider = new ethers.providers.Web3Provider(window.ethereum);
      const signer = provider.getSigner();
      const dex = new ethers.Contract(dexContractAddress, Dex.abi, signer);
      const accounts = await provider.send("eth_requestAccounts", []);

      setWeb3(provider);
      setContracts(dex);
      setAccounts(accounts);
    }
    init();
  // eslint-disable-next-line
  }, []);

  const isReady = () => {
    return (
      typeof web3 !== 'undefined' 
      && typeof contracts !== 'undefined'
      && accounts.length > 0
    );
  }

  if (!isReady()) {
    return <div>Loading...</div>;
  }

return (
    <App
      web3={web3}
      accounts={accounts}
      contracts={contracts}
    />
  );
);
}

export default LoadingContainer;

something along the lines of above, this will save you from having to keep redeclaring your web3 instance in every function. you could alternatively just have that useEffect in app itself at the top so its up to you.

The next thig you should look into is styled components. These are great for making much more eadable css. and you can easily pass props to styled components so that you can change an attributes css value depending on the state in your app. for example the colour of an attribute. see below

import styled from "styled-components"

const styledDiv = styled.div``
    color: ${(props) => props.color ? "Black" : "White"}:
`

then in your return say you have some set state
const MyComponent = () => {
    const [state, setState] = useState(false)

    return (
        <>
            //will render color black or white depending on value of state
            <MyComponent color={state} />
        </>
    )
}

The next thing i noticied, is you have a lot of states that are array objects. this is very very good great too with the dstructing i use this type of data storing myself and its very useful. however i dont see any useeffect to help presist this data each time thepage is refreshed. If you want to update these values you can greate an initial useEffect that runs on every page refresh that will automatically call you getBalance, getApprovals, getOrderbook etc functions

useEffect(() => {

    ///call getbalance, getOrderbook.... here
}, [])

lastly there is a micro ptimisation that you could also implement to only have to create one or two functions to execute all of your contract interactions. For example createLimitOrder and CreateMarketOrder are very similar, this is doubly true for the sides. Likewise Deposit and Withdraw are very simialr. So what you can do is create a generic function fot the limit/market orders and the deposit/withdraw functions. Then you can pass in your params and the contract function you want to exdcute as arguments to this function consider below

const genericTransaction = (functionParams, contractFunction) => {

     //then you would just call
     dex.contractFunction(...params).then(() => {.....}

}

//then when it comes to calling this function for a specific contract intraction 
type we. can do the following. For example limit order

onClick={() => genericTransaction(
[
        0,
        ethers.utils.formatBytes32String(data.get("ticker")),
        data.get("amount"),
        ethers.utils.parseEther(data.get("price")
],
       dex.createLimitOrder

)}

//note params are passed in as an array and we destructure it in the contract call.

also i have one little gem that your going to love. its a beautiful trick. So dont you find it annoying to have to constanly approve a token deposit before doing it. its really bad UX and having multiple metamask popups show in a row can lead to really difficult to debug errors if your user accepts one but declines the other for example. Well there is a way you can approve a user for an initie amount of tokens so you only ever have to approve a token once thats it. to do it you can use the uint256 equivilence of infinity. in your appove function simple past in this as the amount and boom your done

115792089237316195423570985008687907853269984665640564039457584007913129639935

Other than this man these are just som obbservation si had for some optimisation but again mannnn this is so impressive. propobaly the most impressive dex ive seen someone come along and post in this forum. Huge congratulations. Please keep sharing your projects i love them. and yeah do deploy also id love to try it out

1 Like

Also if you like to of my help on advanced reat topics, thigs that will really send you next level, such as using web3RReact for multiple wallet integration, state managent with redux and context api or even the practices that i generally follow be sure to hit me up or dm me. you should look into web3react youll lov eit. they also have a built in way to stop the nasty web3 undefined error

1 Like

Hello @mcgrane5
Wow thanks for the thorough comments. Yes - I see I need to make those changes you mentioned. I am not sure weather to post it with Netlify now or wait until I make these changes. They might take me some time…but yeah - thanks!

…and yep I am interesed in following up with more advanced topics…like you mentioned.
I do plant to take the web3React Moralis course next and plant to hit you up, definitely - you sound like you really know your stuff.
Here are a couple of links for previous projects, one is the Exchange in the React course with Zsolt and the second one is just a tutorial I followed. This one more professional code since I just simply followed the tutorial and I will try to post references to give credit to other youTubers whom I learned from.

Falcon Exchange (no smart contracts, just front-end, axios, Coin Gecko api, charts, news page, exchanges page)
React App (elated-bassi-9cac7a.netlify.app)
https://github.com/brlojam4932/falcon-app.git

Sensei Crypto Wallet App (YouTube tutorial: https://youtu.be/Wn_Kb3MR_cU)
Vite App (bencryptoprojects.com)
https://github.com/brlojam4932/web3-app.git

link to the Falcon Trades app here in Moralis Forum:

…I just updated my original post with the new Netlify link to my DEX project. Now that is on an actual blockchain, I am seeing all kinds of much needed improvements but it’s finally published

1 Like

sorry fot taking so long to get back but wow man im super impresssed, the falcon app looks very nice the chars are nice and responsive i really like this one. and the dex is very impressive to hats off to you. id even say your ready to start appplyimg for jobs my guy you have a lot of projects under yourbelt you easuily be able to land a job as a sc dev or frontend software engineer

1 Like

Thanks @mcgrane5 Evan, nice to hear what you had say about me being ready… yes I will start doing that - thank you.

1 Like

I would like to complete this assignment but, I cannot get past the deploying to a live network part. Everything is working up unto this point. All tests successful. However, in the video, “Deploying our contract to a live network”, I get these error messages: when I migrate and/or migrate --reset:

Any ideas on what is going on here?

1 Like

unfortunately this is just the joys of migrating to testnet or mainnet using truffle. youll just have to play around with tweaking some of the different params in truffle confih such as skipDryRuns and timeOut etc. sometimes bad internet can affect things. also use kovan its the best testnet

1 Like

it might take you 50 tries before its sucessful. but wjen deploying here use truffle migrate without the reset flag

1 Like

because if you have 4 contracts and 3 of them deploy but the last fails and u use reset flag it will start all over again whereas without reset it will start from the third contract

1 Like