Assignment - Limit Order Test

I worked on the logic but had to refer to the presented solution for syntax, and I will revisit once I progress further with the Dex contract.

(edited with tests passing now)

dex.sol

// SPDX-License-Identifier: MIT

pragma solidity 0.8.0;
pragma experimental ABIEncoderV2;

import "./dexWallet.sol";

contract Dex is dexWallet {

    using SafeMath for uint256;

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

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

    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, 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)
          );

        //Bubble sort, only good for small datasets
        uint i = orders.length > 0 ? orders.length - 1 : 0; //inline if statement

        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++;
    }
}

dextest.js

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

contract("Dex", accounts => {

  //it should confirm that user's eth >= buy order value

      it("should confirm that user's ETH deposit >= buy order value", async () => {
            let dex = await Dex.deployed()
            let link = await Link.deployed()
            await truffleAssert.reverts(
                  dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 110, 1)
            )
            await dex.depositEth({value: 100});
            await truffleAssert.passes(
                  dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 90, 1)
            )
        })


  //it should confirm that user's asset >= sell order value

        it("should confirm that user's asset LINK >= sell 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 link.approve(dex.address, 100);
            await dex.deposit(100, web3.utils.fromUtf8("LINK"));
            await truffleAssert.reverts(
                  dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 110, 1)
            )
            await truffleAssert.passes(
                  dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 90, 1)
            )
        })


  //it should reorder SELL orders low to high, with lowest price entry as 1st item in array

        it("should reorder SELL orders low to high, with lowest price entry as 1st item in array", 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(270, web3.utils.fromUtf8("LINK"));

            await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 90)
            await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
            await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 80)

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

            console.log(orderBook[0].price);
            console.log(orderBook[1].price);
            console.log(orderBook[2].price);

          /*
            //print the whole console
            //console.log(await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1))
            //or
            console.log(orderBook);

            //this line checks that we have entries in the orderBook
            assert(orderBook.length>0);

            //could not get this testing method to work - test failing even though sorting correctly
            for (let i = 0; i < orderBook.length - 1; i++) {
                assert(orderBook[i].price <= orderBook[i+1].price, "somethin aint right")
            } */
        })



  //it should reorder BUY orders high to low, with highest price entry as 1st item in array

        it("should reorder BUY orders high to low, with highest price entry as 1st item in array", async () => {
            let dex = await Dex.deployed()
            let link = await Link.deployed()
            await link.approve(dex.address, 500);
            await dex.depositEth({value: 500});
            await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 95)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 105)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 85)

            let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
            //console.log(orderBook);
            console.log(orderBook[0].price);
            console.log(orderBook[1].price);
            console.log(orderBook[2].price);
        })


})
1 Like

Order limit test assigment:

The User must have deposited ETH >= buy order value
`   it("The User must have deposited ETH >= buy order value", async function() {
    let dex = await Dex.deployed();
    let token = await Token.deployed();    
    //createLimitOrder(SIDE,TOKEN,price, amount)


    await truffleAssert.reverts(
      dex.createLimitOrder(0,web3.utils.fromUtf8(token.symbol),10,1)
    )
     
    await dex.depositEth({value: 100});

    await truffleAssert.passes(
      dex.createLimitOrder(0,web3.utils.fromUtf8(token.symbol),100,1)
    )

   })
`
The User must have enough tokens such that Token balance >= sell order
   it("The User must have enough tokens such that Token balance >= sell order", async function() {
    let dex = await Dex.deployed();
    let token = await Token.deployed();
    
    await truffleAssert.reverts(
      dex.createLimitOrder(0,web3.utils.fromUtf8(token.symbol),100,1)

    )

    await token.approve(dex.address,100,{from:accounts[1]});
    await dex.deposit(100, web3.utils.fromUtf8(token.symbol),
                     {from:accounts[1]});
    await truffleAssert.passes(
      dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),50,1, {from:accounts[1]})
    )
    
    

   })
The first BUY order in by order book should have the highest price
   it("The first BUY order ([0]) in by order book should have the highest price", async function() {
    let dex = await Dex.deployed();
    let token = await Token.deployed();
    
    await token.approve(dex.address,1000,{from:accounts[2]});
    await dex.deposit(100, web3.utils.fromUtf8(token.symbol),
                      {from:accounts[2]});
    await dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),100,1,
                              {from:accounts[1]});
    await dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),300,1,
                              {from:accounts[1]});
    await dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),500,1,
                              {from:accounts[1]});
    //getOrderBook(TOKEN,SIDE)
    let orderBook = await dex.getOrderBook(web3.utils.fromUtf8(token.symbol),0);

    assert(orderBook.length >0);
    for(let i=0;i<orderBook.length -1;i++){
      assert(
        orderBook[i] >= orderBook[i+1], "Orderbook is in the wrong order." 
      )

    }

    
  })
The first SELL order in by order book should have the lowest price
  it("The first SELL order ([0]) in by order book should have the lowest price", async function() {
    let dex = await Dex.deployed();
    let token = await Token.deployed();
    
    await token.approve(dex.address,1000,{from:accounts[2]});
    await dex.deposit(100, web3.utils.fromUtf8(token.symbol),
                      {from:accounts[2]});
    await dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),100,1,
                              {from:accounts[1]});
    await dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),300,1,
                              {from:accounts[1]});
    await dex.createLimitOrder(1, web3.utils.fromUtf8(token.symbol),500,1,
                              {from:accounts[1]});
    let orderBook = await dex.getOrderBook(web3.utils.fromUtf8(token.symbol),1);

    assert(orderBook.length >0);
    for(let i=0;i<orderBook.length -1;i++){
      assert(
        orderBook[i] =< orderBook[i+1], "Orderbook is in the wrong order." 
      )

    }

    
  })

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

//The user must have ETH deposited such that deposited ETH >= buy order value
//The user must have enough tokens deposited such that token balance >= sell order amount
//The BUY order book should be organized on price from highest to lowest starting at index 0
//The SELL order book should be organized on price from lowest to highest starting at index 0

contract("Dex", accounts => {
    it("should throw an error if ETH balance is too low when creating BUY limit order", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.reverts(
            await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1));
        await dex.depositETH({value: 100});
            await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1);
        await truffleAssert.passes(
            await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1));
    })
    it("should throw an error if token balance is too low when creating SELL limit order", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.reverts(
            await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1));
        await link.approve(dex.address,500);
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1));
    })
    it("The BUY order book should be organized on price from highest to lowest starting at index 0", async() => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address,500);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 400);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 51);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 49);
        
        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
        for (let i = 0; i < orderbook.length - 1; i++) {
            const element = array[index];
            assert(orderbook[i] >= orderbook[i+1])
    }
    
    })
    it("The SELL order book should be organized on price from lowest to highest starting at index 0", async() => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address,500);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 400);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 51);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 49);
        
        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
        for (let i = 0; i < orderbook.length - 1; i++) {
            const element = array[index];
            assert(orderbook[i] >= orderbook[i+1])
        }

    })
})
1 Like

Here is my tests. I was a bit unsure how to put it together as I still am working out the limit order and I think I would need to add functionality to deposit and track Ether balances.

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

contract("Dex", accounts => {
    it("user buy order must have sufficient eth balance to create new order", async =>{
        let dex = await Dex.depolyed()
        dex.deposit("ETH", 2, {from: accounts[0]})

        truffleAssert.passes(
            dex.createLimitOrder("LINK", true, 1, 1, {from: accounts[0]})
        )
        truffleAssert.passes(
            dex.createLimitOrder("LINK", true, 1, 2, {from: accounts[0]})
        )
        truffleAssert.reverts(
            dex.createLimitOrder("LINK", true, 1, 4, {from: accounts[0]})
        )

        truffleAssert.reverts(
            dex.createLimitOrder("LINK", true, 1, 1, {from: accounts[1]})
        )
    })


    it("user sell order must have suffcient token balance", async => {
        let dex = await Dex.depolyed()
        let link = await Link.depolyed()
        dex.addToken("LINK", link.address)

        dex.deposit("LINK", 500, {from: accounts[0]})

        truffleAssert.passes(
            dex.createLimitOrder("LINK", false, 10, 200, {from: accounts[0]})
        )
        truffleAssert.passes(
            dex.createLimitOrder("LINK", false, 10, 500, {from: accounts[0]})
        )
        truffleAssert.reverts(
            dex.createLimitOrder("LINK", false, 10, 2000, {from: accounts[0]})
        )
        truffleAssert.reverts(
            dex.createLimitOrder("LINK", false, 10, 200, {from: accounts[1]})
        )
    })

    it("first buy in order array should have the highest price", async => {
        let dex = await Dex.depolyed()
        dex.deposit("ETH", 2, {from: accounts[0]})
        dex.deposit("ETH", 2, {from: accounts[1]})
        dex.deposit("ETH", 2, {from: accounts[2]})
        dex.deposit("ETH", 2, {from: accounts[3]})

        dex.createLimitOrder("LINK", true, 1, 1, {from:accounts[0]})
        dex.createLimitOrder("LINK", true, 4, 1, {from:accounts[1]})
        dex.createLimitOrder("LINK", true, 2, 1, {from:accounts[2]})
        dex.createLimitOrder("LINK", true, 3, 1, {from:accounts[3]})

        asser.equal(dex.orderBook.buy[0], 1)
    })
})

1 Like

Thanks! That makes sense

Haven’t actually made the function yet, but I think this is the idea:

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

contract("Dex", accounts => {
  it("should only permit BUY order when user has enough ETH deposited", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

    await truffleAssert.reverts (
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 1)
    ) // reverts if no ETH balance

    dex.depositETH({value: 1})
    await truffleAssert.passes(
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 1)
    ) // passes if enough ETH
  })

  it("should only permit SELL order when user has sufficient token balance", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    dex.depositETH({value: 1})

    await truffleAssert.reverts(
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 1)
    ) // revert if no tokens

    await link.approve(dex.address, 1); //approve LINK token
    await dex.deposit(1, web3.utils.fromUtf8("LINK"));
    await truffleAssert.passes(
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 1)
    ) // pass if enough tokens
  })

  it("should store bids in the BUY order book from highest price to lowest price", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    dex.depositETH({value: 5})
    await link.approve(dex.address, 3); //approve LINK token
    await dex.deposit(3, web3.utils.fromUtf8("LINK"));

    dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 1)
    dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 3)
    dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 2)

    let buyorderbook = dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
    let ordered = true;
    for (let i = 1 ; i < buyorderbook.length; i++){
      if(buyorderbook[i] > buyorderbook[i-1])
        ordered = false;
    }

    await truffleAssert.passes(ordered)
  })

  it("should store bids in the SELL order book from lowest price to highest price", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    dex.depositETH({value: 5})
    await link.approve(dex.address, 3); //approve LINK token
    await dex.deposit(3, web3.utils.fromUtf8("LINK"));

    dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 2)
    dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 1)
    dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 3)

    let sellorderbook = dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
    let ordered = true;
    for (let i = 1 ; i < sellorderbook.length; i++){
      if(sellorderbook[i] < sellorderbook[i-1])
        ordered = false;
    }

    await truffleAssert.passes(ordered)
  })

})

Hi guys, this is my code. I failed to do the third exercise, which consist in ordered the book. Honestly, I do not understand where to start in this third part. I felt this happens because I need to see more and more Solidity code to feel more comfortable.

After uploading this code I will check the video and erase or add what is missing.

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

contract("Dex", accounts => {
    // 1) The user must have ETH deposited such that: deposited eth >= buy order value
    it("The user must have ETH deposited", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();
    
        await truffleAssert.reverts(
            dex.createLimitOrder(max_amount, min_amount, BUY, web3.utils.fromUtf8('LINK'))
        );

        await dex.deposit(deposit_amount);

        await truffleAssert.passes(
            dex.createLimitOrder(max_amount, min_amount, BUY, web3.utils.fromUtf8('LINK'))
        );
    }) // deposit_amount: amount to be deposited

    // 2) The user must have enough token deposited such that: token balance >= sell order amount
    it("The user must have enough token deposited", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        await truffleAssert.reverts(
            dex.createLimitOrder(max_amount, min_amount, SELL, web3.utils.fromUtf8('LINK'))
        );

        await dex.addToken(web3.utils.fromUtf8('LINK'), link.address, {from: accounts[0]});
        await link.approve(dex.address, amount_to_approve);
        await dex.deposit(deposit_amount, web3.utils.fromUtf8('LINK'));

        await truffleAssert.passes(
            dex.createLimitOrder(max_amount, min_amount, SELL, web3.utils.fromUtf8('LINK'))
        );
        
    }) // max_amount = deposit_amount

// 3) The BUY order book should be ordered on price from high to low at the starting index 0
})
1 Like

Maybe someone can help me with this. I’ve done the tests and implemented the createLimitOrder function, but I keep getting this error message when I run the Truffle test:

  1) Contract: Dex
       should throw an error if ETH balance is too low when creating BUY limit order:
     AssertionError: Failed with Error: Returned error: VM Exception while processing transaction: out of gas
      at passes (node_modules/truffle-assertions/index.js:142:11)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)
      at Context.<anonymous> (test/dextest.js:22:7)

Out of gas? How can that be? I even tried implementing BOTH the tests and the function exactly the way Filip coded it, and I still get the same error. Any idea what I could be doing wrong here?

1 Like

hey @Attiss this could be an issue with your dex contract can you sgare the code ill have a look

Thanks @mcgrane5 - here’s the Dex contract:

pragma solidity 0.8.0;
pragma experimental ABIEncoderV2;

import "./dex_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; // a mapping for buy book and for sell book

  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));
    }
    else 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));

    //bubble sort the arrays
    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 temp = orders[i-1];
          orders[i-1] = orders[i];
          orders[i] = temp;
          i--;
      }
    }

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

  // function createMarketOrder(Side side, bytes32 ticker, uint amount) public {
  //   ////
  // }

}

and also the wallet (sorry there’s a million comments as I was working thru…):

pragma solidity 0.8.0;

//interface to provide function headers to interact with other token Contracts
import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
//import SafeMath
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";
//import Ownable
import "../node_modules/@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; // to store tickers

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

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

    //function to add information about tokens to storage
    function addToken(bytes32 ticker, address tokenAddress) onlyOwner external {
      tokenMapping[ticker] = Token(ticker, tokenAddress); // create new token and add ticker tokenMapping
      tokenList.push(ticker); // add to list of token IDs
    }

    function deposit(uint amount, bytes32 ticker) tokenExists(ticker) external {
      //check done with tokenExists modifier

      //transfer FROM msg.sender to contract address
      IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);

      //update balance
      balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
    }

    function withdraw(uint amount, bytes32 ticker) tokenExists(ticker) external {
      //check if token exists done with tokenExists modifier

      //check balance of msg.sender
      require(balances[msg.sender][ticker] >= amount, "Insufficient balance");

      //subtract amount using SafeMath
      balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);

      //use interface to interact with ERC20 contracts
      IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
      //msg.sender is the owner of the tokens - - dex just holds them!
    }

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

    // function withdrawEth(uint amount) external {
    // require(balances[msg.sender][bytes32("ETH")] >= amount,'Insuffient balance');
    // balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")].sub(amount);
    // msg.sender.call{value:amount}("");
    // }
}
1 Like

perfect. Could i ask for the tests aswell im gonna try run your code on my machine so i can debug. give me a while just in college here

The only test that’s failing is this one:


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

contract("Dex", accounts => {
  it("should throw an error if ETH balance is too low when creating BUY limit order", async () => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()
      await truffleAssert.reverts(
          dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
      dex.depositEth({value: 10})
      await truffleAssert.passes(
          dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
  })

})

Everything else passes. I had this coded a little differently, was getting the error, then I switched to Filip’s code (that’s what I’ve pasted here), but the error persists.

1 Like

Working on it now for you

hey @Attiss. So i wasnt able to replicate your exact error but the test would not run. The problem was that you had a runtime error in your contract. These can be hard to identify sometimes and also soldiity can give weird runtime error messages at times that arent particularily useful in terms of debugging. And i would say the error you were getting was because of the dex runtime error Anyway the error was in your create limit order function when you went to initialise a limit order you were only passing in 6 out of 7 arguments. you never passed in the “filled” attricbute which is to be set at 0 on initialisation.

Secondly your tests had a problem i always say this but lets look at your original test

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

contract("Dex", accounts => {
  it("should throw an error if ETH balance is too low when creating BUY limit order", async () => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()
      await truffleAssert.reverts(
          dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
      dex.depositEth({value: 10})
      await truffleAssert.passes(
          dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
  })

})

first of all you should expect this test to pass for a few reasons. firstly you are not adding the link token to your dex. without calling the addToken function for link your dex contract wont know what “link” is as when we call the addToken function we pass in the contract address of link and it is by doing this, that is how the dex contract can access the various functions and information that resides in your link token contract. So you need to add the token. you also need to approve the dex to spend link tokens if you do not do this you will not be able to deposit link or create sell limit orders so always always approve.

other than this the test should run let me know. I have attached the modified test and the modified dex below

DEX

pragma solidity 0.8.0;
pragma experimental ABIEncoderV2;

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; // a mapping for buy book and for sell book

  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));
    }
    else 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));

    //bubble sort the arrays
    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 temp = orders[i-1];
          orders[i-1] = orders[i];
          orders[i] = temp;
          i--;
      }
    }

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

  // function createMarketOrder(Side side, bytes32 ticker, uint amount) public {
  //   ////
  // }

}

TEST

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

t = require('truffle-assertions');

contract("Dex", accounts => {
  it("should throw an error if ETH balance is too low when creating BUY limit order", async () => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      await dex.addToken( web3.utils.fromUtf8("LINK"), link.address)
      await link.approve(dex.address, 100000)


      
      await truffleAssert.reverts(
        dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )

      await dex.deposit(100, web3.utils.fromUtf8("LINK"))
      await dex.depositEth({value: 10, from:accounts[0]})
    
      await truffleAssert.passes(
        dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
      
      
  })

})

1 Like

@mcgrane5 my man! That took care of it. Thanks SO much for your help - - - you’ve renewed my faith in this forum.

I have one more testing question that maybe you can help me with:
When you run your tests, after each it("blah...blah...blah")...
statement, we always declare a new set of variables for the deployed contracts which will be tested. Does this initialize the state of the contracts? For example, if call await dex.depositEth({value: 10, from:accounts[0]}) in one test, and then don’t do anything with that 10 deposited ETH, will it still be there when the next test is run, or does declaring let dex = await Dex.deployed() in the next test reset the state?

2 Likes

hey @Attiss no worries im always here never be shy to ask. just tage or dm me with questions

also about your question yes the value will carry over to the next test

Not sure this is 100% accurate, but will verify once I build the function a run the tests against it.

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

contract("Dex", accounts => {

    // User must have enough ETH deposited so that ETH >= buy order value
    it("User's ETH balance must be >= buy amount.", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        await truffleAssert.reverts(
            //token, buy/sell, amount, price
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 20, 1)
        )

        dex.depositETH({value: 20})
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 20, 1)
        )
    })

    // User must have enough tokens deposited so that token balance >= sell order amount
    it("User's token balance must be >= sell order amount.", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        
        await truffleAssert.reverts(
            //token, buy/sell, amount, price
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 20, 2)
        )

        await link.approve(dex.address, 20);
        await dex.deposit(20, web3.utils.fromUtf8("LINK"));

        await truffleAssert.passes(dex.address, 
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 20, 2)
        )
    })

    // BUY order book should appear in price from highest to lowest, starting at index 0
    it("BUY order book should be listed from highest to lowest value, starting at index 0.", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        await link.approve(dex.address, 100);
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 10, 4)
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 10, 2)
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 10, 8)

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

    })

    // SELL order book should appear in price from lowest to highest, starting at index 0
    it("SELL order book should be listed from lowest to highest value, starting at index 0.", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        await link.approve(dex.address, 100);
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 10, 8)
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 10, 2)
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 10, 4)

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

})
1 Like

Good day!

I get these errors while testing the createLimitOrder():
image

createLimitOrder() function:

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

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

       else 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)
        );

        // 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;
                }
                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++;
    }

Tests:

 it("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.depositEth({value: 3000});
        await link.approve(dex.address, 1000);
        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++) {
            
            assert(orderbook[i] >= orderbook[i+1])
        }
    })
    //The SELL order book should be ordered on price from lowest to highest starting at index 0
    it("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.depositEth({value: 3000});
        await link.approve(dex.address, 1000);
        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);
        assert(orderbook.length > 0);
        for (let i = 0; i < orderbook.length - 1; i++) {
            
            assert(orderbook[i] <= orderbook[i+1])
        }
    })

Thanks in advance!

2 Likes

Hey @Dominykas_Pozleviciu, hope you are well.

Could you please share the complete contract and complete unit test? i want to download it and replicate the process my self.

Carlos Z

1 Like