Project - DEX Final Code

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

I tried both but, I would not have known to stick with one. very helpful thanks!

I had a feeling about that. So, I am not dumb lol!

1 Like

Ah…that is helpful. I did try changing a timeout but, did not know if I was on the right track. this is helpful!

yeah its the type of thing that sometimes will work first time and others u can be all day trying to deploy. just keep at it and it will work eventually

1 Like

or if u want a quick fix take tour contracts and compile them in remix and deploy using injected web3. this is what i do most times and only work in vs code when my contracts are either near complettion or else im dealing with a complex web of lodes of contracts too nig to deal with in remix. but since your doing this course u should try to deploy it manually too for the learning experience

1 Like

Will do!

thank you very much!

Yes! I would like to see this problem through

1 Like

I am going to move onto the Kitties course to get a grasp on web3.js.

1 Like