Assignment - Limit Order Test

// 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 ordered on price from highest to lowest starting at index 0
// The SELL order book should be ordered on price from lowest to highest starting at index 0

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

contract("Dex", accounts => {

    it("should only be possible to create a BUY limit order that the user can afford", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()

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

        await dex.depositETH({value: 3});

        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 3, 1)
        );
    })

    it("should only be possible to create a SELL limit order that the user can afford", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()

        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 5, 1)
        );

        await dex.addtoken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await link.approve(dex.address, 5)
      
        await dex.deposit(5, web3.utils.fromUtf8("LINK"));

        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 5, 1)
        );
    })

    it("should order the BUY order book highest to lowest", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address, 500);
        await dex.depositETH({value: 3000});
        dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 300)
        dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 100)
        dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 200)

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

    it("should order the SELL order book lowest to highest", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address, 500);
        await dex.depositETH({value: 3000});
        dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 300)
        dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 100)
        dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 200)

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

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

contract("Dex", accounts => {

    it("can't create an ETH order the user can't afford.", async () => {
        let dex = await Dex.deployed();

        // Deposit 100 ETH.
        await dex.depositETH({value: 100});

        // Make a valid order with 100 ETH, and an invallid order with 500 ETH.
        await truffleAssert.passes( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("LINK")));
        await truffleAssert.reverts( dex.createLimitOrder(1, 500, 500, web3.utils.fromUtf8("LINK")));
        assert( dex.getOrderBook().length > 0 );
    });

    it("can't create a token order the user can't afford", async () => {
        let dex = await Dex.deployed();
        let token = await Token.deployed();

        // Deposite 100 tokens.
        await token.approve(dex.address, 100);
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));

        // Make a valid order of 100 tokens, then make an invallid order with 500 tokens.
        await truffleAssert.passes( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("LINK")));
        await truffleAssert.reverts( dex.createLimitOrder(1, 500, 500, web3.utils.fromUtf8("LINK")));
        assert( dex.getOrderBook().length > 0 );
    });

    it("can't to create orders for unsupported tokens", async () => {
        let dex = await Dex.deployed();
        let token = await Token.deployed();

        // Deposite 100 tokens.
        await token.approve(dex.address, 100);
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));

        // Make orders to LINK and UNSPORTED.
        await truffleAssert.passes( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("LINK")));
        await truffleAssert.reverts( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("UNSPORTED")));
    });

    it("should order the BUY book in descending order.", async () => {
        let dex = await Dex.deployed();
        let token = await Token.deployed();

        // Deposite 100 tokens.
        await token.approve(dex.address, 100);
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));

        // Submit some orders.
        await dex.createLimitOrder(1, 10, 30, web3.utils.fromUtf8("LINK"));
        await dex.createLimitOrder(1, 10, 10, web3.utils.fromUtf8("LINK"));
        await dex.createLimitOrder(1, 10, 20, web3.utils.fromUtf8("LINK"));

        // Retrieve orderbook and check if ascending.
        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
        for(let i = 1; i < orderBook.length; i++) {
            assert(orderBook[i].price <= orderBook[i - 1].price);
        }
    });

    it("should order the SELL book in ascending order.", async () => {
        let dex = await Dex.deployed();
        let token = await Token.deployed();

        // Deposite 100 tokens.
        await token.approve(dex.address, 100);
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));

        // Submit some orders.
        await dex.createLimitOrder(1, 10, 30, web3.utils.fromUtf8("LINK"));
        await dex.createLimitOrder(1, 10, 10, web3.utils.fromUtf8("LINK"));
        await dex.createLimitOrder(1, 10, 20, web3.utils.fromUtf8("LINK"));

        // Retrieve orderbook and check if ascending.
        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
        for(let i = 1; i < orderBook.length; i++) {
            assert(orderBook[i].price >= orderBook[i - 1].price);
        }
    });
});
1 Like

not tested but here is the try

const Dex = artifacts.require("Dex")

const Link = artifacts.require("Link")

const truffleAssert = require('truffle-assertions');

contract("Dex", accounts => {

    it("User´s deposited eth is larger or equal to buy order amount", async => {

        let dex = await Dex.deployed()

        let link = await Link.deployed()

        await truffleAssert.reverts(

            dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"), 10,1)

        )

            await dex.deposit(100, 0)

            await truffleAssert.passes(

                dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"),10,1)

            )

    })

    it('should throw an error if token balance is too low when creating a SELL order', async () => {

        let dex = await Dex.deployed()

        let link = await Link.deployed()

        await truffleAssert.reverts(

          dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),10,1)

        );

   

        await dex.deposit(100, web3.utils.fromUtf8("LINK"))

   

        // Create SELL order for 5 LINK @ 2 ETH/LINK

        await truffleAssert.passes(

          dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),10,1)

        )

      })

      it("ordering the BUY book in descending order.", async () => {

        let dex = await Dex.deployed()

        let link = await Link.deployed()

        await dex.deposit(100, web3.utils.fromUtf8("LINK"))

        // Submit some orders.

        await dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"),10,1)

        await dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"),20,1)

        // Retrieve orderbook and check if ascending.

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

        for(let i = 1; i < orderBook.length; i++) {

            assert(orderBook[i].price <= orderBook[i - 1].price)

        }

})

     it("ordering the sell book in ascending order.", async () => {

      let dex = await Dex.deployed()

      let link = await Link.deployed()

      await dex.deposit(100, web3.utils.fromUtf8("LINK"))

      await dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),10,1)

      await dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),20,1)

   

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

      for(let i = 1; i < orderBook.length; i++) {

        assert(orderBook[i].price >= orderBook[i - 1].price)

    }

})

})
1 Like

Here is my updated code: Fixed errors and cleanup with grouping.

dextest.js
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link")
const truffleAssert = require('truffle-assertions')
const LINK = web3.utils.fromUtf8("LINK")
const BUY = 0
const SELL = 1

contract("Dex Contract Limit Order Test", accounts => {
  let dex
  let link
  before(async () => {
    dex = await Dex.deployed()
    link = await Link.deployed()
    await dex.addToken(LINK, link.address, {from: accounts[0]})
    await link.approve(dex.address, 500)
  })

  describe('Initial state', () => {
    it("should assert true when dex deployed", async function () {
      return assert.isTrue(true);
    });
  })
  
  describe('Limit Buying', () => {
    it("should throw an error when creating a BUY limit order without a positive ETH balance", async () => {
      await truffleAssert.reverts(
        dex.createLimitOrder(BUY, LINK, 10, 2)
      )
    });
    it("should allow creating a BUY limit order with a positive ETH balance", async () => {
      await dex.depositEth({value: 100})
      await truffleAssert.passes(
        dex.createLimitOrder(BUY, LINK, 50, 2)
      )
    });
  })
  
  describe('Limit Selling', () => {
    it("should throw an error when creating a SELL limit order without a positive Token balance", async () => {
      await truffleAssert.reverts(
        dex.createLimitOrder(SELL, LINK, 5, 10)
      )
    });
    it("should allow creating a SELL limit order with a positive Token balance", async () => {
      await dex.deposit(100, LINK)
      await truffleAssert.passes(
        dex.createLimitOrder(SELL, LINK, 5, 10)
      )
    });
  })
  
  describe('Orderbook Sorting', () => {
    it("should sort the BUY orders from highest price to lowest price starting at index 0", async () => {
      await dex.depositEth({value: 1000})
      //BUY side limit orders
      await dex.createLimitOrder(BUY, LINK, 1, 35)
      await dex.createLimitOrder(BUY, LINK, 2, 25)
      await dex.createLimitOrder(BUY, LINK, 4, 40)
      await dex.createLimitOrder(BUY, LINK, 1, 50)
      await dex.createLimitOrder(BUY, LINK, 1, 75)
      let orderbook = await dex.getOrderBook(LINK, BUY)
      assert(orderbook.length > 0)
      console.log(orderbook);
  
      //check orderbook sorted correctly
      for(let i = 0; i < orderbook.length -1; i++) {
        assert(orderbook[i].price >= orderbook[i +1].price)
      }
    });
    it("should sort the SELL orders from lowest price to highest price starting at index 0", async () => {
      await dex.deposit(100, LINK)
      //SELL side limit orders
      await dex.createLimitOrder(SELL, LINK, 10, 75)
      await dex.createLimitOrder(SELL, LINK, 15, 35)
      await dex.createLimitOrder(SELL, LINK, 20, 50)
      await dex.createLimitOrder(SELL, LINK, 40, 25)
      await dex.createLimitOrder(SELL, LINK, 15, 40)
      let orderbook = await dex.getOrderBook(LINK, SELL)
      assert(orderbook.length > 0)
      console.log(orderbook);
  
      //check orderbook sorted correctly
      for(let i = 0; i < orderbook.length -1; i++) {
        assert(orderbook[i].price <= orderbook[i +1].price)
      }
    });
  })
});
1 Like
Limit Order Test

This is my solution to the assignment.

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

contract("Dex", accounts => {
    //the user must have ETH deposited such that deposited Eth >= buy order value
    it("should only be possible to deposit ETH into a BUY order if owner has more than or equal to that amount", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        dex.depositEth({value: 5}) //ETH deposited
        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 1)
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 1)
        )
    })
    //the user must have enough tokens deposited such that token balance > sell order amount
    it("Should only be possible to place sell order with more than enough tokens", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        await link.approve(dex.address, 500);
        await dex.deposit(200, web3.utils.fromUtf8("LINK"));
        
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 100, web3.utils.toWei("2", "ether"))
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 300, web3.utils.toWei("2", "ether"))
        )
    })
    //the first order ([0]) in the BUY order book should have the highest price
    it("BUY orderbook 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 link.approve(dex.address, 1000);
        //add some orders in unordered price sequence
        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].price >= orderbook[i+1].price, "Order book not correctly sorted!");
        }
    })
    //the first order ([0]) in the SELL order book should have the lowest price
    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 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].price <= orderbook[i+1].price, "Order book not correctly sorted!")
        }
    })
})

Let me know if anything needs modifying. Thanks :smile: :+1:

2 Likes
const Dex = artifacts.require("dex")
const Link = artifacts.require("link")
const truffleAssertions = require('truffle-assertions');
const TA = require('truffle-assertions');

contract("Dex",accounts => {


        it("User has eth >= buy order", async () =>{

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

            await dex.addToken(web3.utils.fromUtf8("ETH"),eth.address);
            let eth_balance = await dex.balances[msg.sender][web3.utils.fromUtf8("ETH")]

            await TA.revert(
                dex.openBuyOrder(web3.utils.fromUtf8("ETH"),web3.utils.fromUtf8("LINK"),100)
            )

            await eth.approve(dex.address,100);
            await dex.deposit(web3.utils.fromUtf8("ETH"),10);

            await TA.passes(
                dex.openBuyOrder(web3.utils.fromUtf8("ETH"),web3.utils.fromUtf8("LINK"),100)
            )

        })


        it("User has token >= sell order", async () =>{

            let dex = await Dex.deployed()
            let link = await Link.deployed()
            
            await dex.addToken(web3.utils.fromUtf8("LINK"),eth.address)
            

            await TA.revert(
                dex.openSellOrder(web3.utils.fromUtf8("LINK"),web3.utils.fromUtf8("ETH"),100)
            )

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

            await TA.passes(
                dex.openSellOrder(web3.utils.fromUtf8("LINK"),web3.utils.fromUtf8("ETH"),100)
            )

        })

        it("order book in sequence", async () =>{

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

            dex.openLimitOrder("LINK",100,10)

             for (let i = 0; 1<dex.orderBook.length-1; i++){
                if (orderBook[web3.utils.fromUtf8("ETH")][i].price < orderBook[web3.utils.fromUtf8("ETH")][i+1].price){
                    let fail = 'false';
                }
            }
            assert.AssertString.isEqual(fail,'false')

        })


})
1 Like

I’m not extremely sure but let’s go

it("should have enough eth", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

    await truffleAssert.reverts(0, dex.limitOrder(0, web3.utils.fromUtf8("ETH"), 10, 500))

    await dex.deposit(10, web3.utils.fromUtf8("ETH"))

    await truffleAssert.passes(0, dex.limitOrder(0, web3.utils.fromUtf8("ETH"), 5, 500))

})

it("should have enough token", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

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

    await truffleAssert.reverts(dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 500))

    await dex.deposit(10, web3.utils.fromUtf8("LINK"))

    await truffleAssert.passes(dex.limitOrder(BUY, web3.utils.fromUtf8("LINK"), 5, 500))
})

it("should first in the order book have the highest price", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

   await dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 500)
   await dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 200)
   await dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 100)

    for(let i = 0; i < orderBook[web3.utils.fromUtf8("ETH")][0].length; i++){
    assert(orderBook[web3.utils.fromUtf8("ETH")][0][i] > orderBook[web3.utils.fromUtf8("ETH")][0][i+1]);
    }
})
2 Likes

Hi guys, I’m the only one that during the limitOrder test have the error “dex.depositEth is not a function” ?
(I don’t have this function in Wallet.sol)

1 Like

I need to be sure, is this all correct:

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

Improved after watching Limit Order Test Solution video

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

contract("Dex", async accounts => {
  describe("orderBook Tests", () => {

    it("Should only allow valid BUY limit orders", async() => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      //reverts when trader's eth balance <= buy order tokenAmount
      await truffleAssert.reverts(
        //params: side, ticker, tokenAmount, price
        dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //assert trader ETH balance >= tokenAmount*ETH price
      await truffleAssert.passes(
        dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
      )
    })

    it("Should only allow valid SELL limit orders", async() => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      //reverts when trader's token balance <= tokenAmount * 1/price
      await truffleAssert.reverts(
        dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
      )
      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //assert trader token balance >= tokenAmount * 1/price
      await truffleAssert.passes(
        dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
      )
    })

    it("Should sort Buy Orderbook from highest to lowest price", async() => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //create a series of buy limit orders, not sorted
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

      let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
      //console.log(orderbook), for debugging
      //assert Buy orderBook[0] is the highest price
        for (let i = 0; i < orderbook.length - 1; i++) { //orderbook.length - 1 since last item will have no item to compare against
          assert(orderbook[i].price >= orderbook[i+1].price, 'BuyOrderBook: orders not sorted correctly')
        }
    })

    it("Should sort Sell Orderbook from lowest to highest price", async() => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //create a series of sell limit orders, not sorted
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)

      let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
      //console.log(orderbook), for debugging
      //assert SELL orderBook[0] is the lowest price
        for (let i = 0; i < orderbook.length - 1; i++) { //orderbook.length - 1 since last item will have no item to compare against
          assert(orderbook[i].price <= orderbook[i+1].price, 'SellOrderBook: orders not sorted correctly')
        }
    })

    it("Should prevent limit orders for unsupported markets", async() => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //reverts when token not found
      await truffleAssert.reverts(
        dex.createLimitOrder(1, web3.utils.fromUtf8("Aave"), 10, 1)
      )
    })

    //Should be able to update an order
    it("Should allow user to update a limit order", async() => {
      let dex = await Dex.deployed()
      let link = await Link.deployed()

      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //create a series of buy and sell orders
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400) //sell order
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

      //update params: orderID, amount, price
      dex.updateLimitOrder(1, 2, 200)
      //check that order buy and sell order books are sorted correctly after update
      //check order fills when conditions are true
    })

    //Should be able to delete an order
    it("Should allow user to delete a limit order", async() => {

      //test setup
      dex.depositEth({value: 3000}) //deposit ETH into the contract
      await link.approve(dex.address, 500)
      await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
      await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

      //create a series of buy and sell orders
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
      dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400) //sell order
      dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

      //delete params: orderID, trader (msg.sender)
      await truffleAssert.passes(
        dex.deleteLimitOrder(1)
        //check that the orderID is associated with the trader
       //check that order buy and sell order books are sorted correctly after update
      )
    })

     it("Should fill limit order when BUY price >= SELL price", async() => {
       let dex = await Dex.deployed()
       let link = await Link.deployed()

       //test setup
       dex.depositEth({value: 3000}) //deposit ETH into the contract
       await link.approve(dex.address, 500)
       await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
       await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance

       //create a series of buy and sell orders
       dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
       dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400) //sell order
       dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

       await truffleAssert.passes(
         //TODO: ?? Fill should fire only when BUY price >= SELL price at the completion of order creation or update (limit or market)
         //What about partial fills??
         dex.fillLimitOrder(TODO)
         //Buyer ETH balance is decreased by order amount, token balance is increased by order amount
         //Seller ETH balance is increased by order amount, token balance is decreased by order amount
         //The buyer and sellers orders are removed from the orderBook
         //The BUY orderBook is updated to show the next highest price first
         //The SELL orderBook is updated to show the next lowest price first
         //The transaction is recorded for historical reference
       )
     })

  })
})

1 Like

I think you need to pass a bytes32 converted “ETH” as the param - e.g.,

  function depositETH() payable external {
    require(msg.value > 0, "DEX: ETH transfer should be greater than 0");
    balances[msg.sender][bytes32("ETH")]= balances[msg.sender][bytes32("ETH")].add(msg.value);

yeah, I correct it, thank you very much.
Unfortunately this isn’t the origin of the error that is stopping me aah.
In the first test I have an “out of gas” error and I don’t understand whyyyy?
This error accour after the Limit order solution error, after that i write the lines:

Order[] storage orders = orderBook[ticker][uint256(side)];
        orders.push(
            Order(nextOrderId, msg.sender, side, ticker, amount, price)
        );
1 Like

if you share your repo code i can have a look for you

Thanks man,
it’s just a matter of few lines:
Order[] storage orders = orderBook[ticker][uint256(side)]; orders.push( Order(nextOrderId, msg.sender, side, ticker, amount, price) );

With these I have the error:

 AssertionError: Failed with Error: Returned error: VM Exception while processing transaction: out of gas"

Without it goes smooth. Then there are the rest of my code but it doesn’t matter, i think.

Dex.sol

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

contract Dex is Wallet {
    using SafeMath for uint256;
    enum Side {
        BUY,
        SELL
    }
    struct Order {
        uint256 id;
        address trader;
        Side side;
        bytes32 ticker;
        uint256 amount;
        uint256 price;
    }

    uint256 public nextOrderId = 0;
    mapping(bytes32 => mapping(uint256 => Order[])) public orderBook;

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

    function createLimitOrder(
        Side side,
        bytes32 ticker,
        uint256 amount,
        uint256 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][uint256(side)];
        orders.push(
            Order(nextOrderId, msg.sender, side, ticker, amount, price)
        );
        /*require(orders.length > 0);
        if (side == Side.BUY) {
            for (uint256 i = orders.length - 1; i <= 0; i--) {
                if (orders[i].price < orders[i - 1].price) {
                    Order memory ordertoMove = orders[i - 1];
                    orders[i - 1] = orders[i];
                    orders[i] = ordertoMove;
                } else {
                    break;
                }
            }
        } else if (side == Side.SELL) {
            for (uint256 i = orders.length - 1; i >= 0; i--) {
                if (orders[i].price > orders[i - 1].price) {
                    Order memory ordertoMove = orders[i - 1];
                    orders[i - 1] = orders[i];
                    orders[i] = ordertoMove;
                } else {
                    break;
                }
            }
        }*/
        nextOrderId++;
    }

dextest.js

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 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++) {
            assert(orderbook[i].price >= orderbook[i + 1].price, "not in the right order in sell book")
        }
    })

I was checking online and there they talk about changing something about gas parameters or stuff like that in the truffle-config, but i don’t find anything that work for me for now.

Thank you mate.

1 Like

Assignment for test:

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

contract("Dex", accounts => {

    let dex, link;
    before(() => {
        dex = await Dex.deployed()
        link = await Link.deployed()

        await dex.addtoken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]}) 
    })

    it("should have enough ETH deposited to place a buy order", async() => {
        await truffleAssert.reverts(
            // Params order: id, isBuyOrder, ticker, amt, price
            dex.createLimitOrder(1001, 1, web3.utils.fromUtf8("LINK"), 100, 1)
        )
        await dex.depositETH({from: accounts[0], value: web3.utils.fromWei(5, "Ether")})
        await truffleAssert.passes(
            // Params order: id, isBuyOrder, ticker, amt, price
            dex.createLimitOrder(1001, 1, web3.utils.fromUtf8("LINK"), 100, 1)
        )
    })

    it("should have enough tokens deposited for sell order", async() => {
        await truffleAssert.reverts(
            dex.createLimitOrder(1002, 0, web3.utils.fromUtf8("LINK"), 100, 1.2)
        )
        await link.approve(dex.address, 10000)
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            dex.createLimitOrder(1002, 0, web3.utils.fromUtf8("LINK"), 100, 1.2)
        )
    })

   it("should have Buy orders arranged in descending order according to price", async() => {
        await dex.createLimitOrder(1003, 1, web3.utils.fromUtf8("LINK"), 100, 1.2)
        await dex.createLimitOrder(1004, 1, web3.utils.fromUtf8("LINK"), 100, 1.3)
        
        let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1)
        for(let i = 0; i < book.length; i++) {
            assert(book[i].price > book[i+1].price, "Not in correct order!")
        }
    })

    it("should have Sell orders arranged in ascending order according to price", async() => {
        await dex.createLimitOrder(1005, 0, web3.utils.fromUtf8("LINK"), 100, 1)
        await dex.createLimitOrder(1006, 0, web3.utils.fromUtf8("LINK"), 100, 1.3)
        
        let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
        for(let i = 0; i < book.length; i++) {
            assert(book[i].price < book[i+1].price, "Not in correct order!")
        }
    })

    it("should remove order from orderbook after order is completed", async() => {
        // Buy order
        await dex.createLimitOrder(1010, 1, web3.utils.fromUtf8("LINK"), 100, 1.2)
        await dex.createLimitOrder(1011, 1, web3.utils.fromUtf8("LINK"), 100, 1.3)
        // Sell order
        await dex.createLimitOrder(1012, 0, web3.utils.fromUtf8("LINK"), 100, 1.3)

        let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1)
        let highest_price = book[0].price
        // order with highest price of 1.3 is removed from order book
        assert.equal(highest_price , 1.2) 
    })

})
1 Like

Probably you need to activate the optimizer on the compiler settings, here is an example:

  // Configure your compilers
  compilers: {
    solc: {
       version: ">=0.6.0 <0.8.0",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      settings: {          // See the solidity docs for advice about optimization and evmVersion
       optimizer: {
         enabled: false,
         runs: 200
       },
       evmVersion: "byzantium"
      }
    }
  },

Carlos Z

This is my code for limit order test, I struggled for a while because I kept getting “out of gas” error, after lot of reading, staring and debugging, it turned out I was missing await in one of the statements.

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 not > limit BUY order value", async () => {
       // The user must have ETH deposited such that deposited eth >= buy order value
        let dex = await Dex.deployed() // Get wallet instance
        let link = await Link.deployed() // Get link instance

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

        await dex.depositEth({value: 3000})
        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })
    it("should throw error if token balance is not >= limit SELL order", async () => {
        // Sell order for a token: The user must have enough tokens deposited such that token balance > sell order amount
        let dex = await Dex.deployed() // Get wallet instance
        let link = await Link.deployed() // Get link instance
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)     
        )
        await link.approve(dex.address, 500)
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await dex.deposit(10, web3.utils.fromUtf8("LINK")) 
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)     
        )
    })
    it("should always have orders from from highest to lowest price in BUY order book.", async () => {
        // The first order ([0]) in the BUY order book should have the highest price  
        let dex = await Dex.deployed() // Get wallet instance
        let link = await Link.deployed() // Get link instance
        await link.approve(dex.address, 500);
        await dex.depositEth({value: 3000});
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

        let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
        assert(orderbook.length > 0)
        assert(orderbook[0].price == 300)
        for (let i = 0; i < orderbook.length - 1; i++) {
            assert(orderbook[i].price >= orderbook[i+1].price, "Not right order in orderBook")
        }
    })
    it("should always have orders from from lowest to highest price in SELL order book.", async () => {
        // The orders should always be from lowest to highest price in SELL order book .
        // The first order ([0]) in the SELL order book should have the lowest price  
        let dex = await Dex.deployed() // Get wallet instance
        let link = await Link.deployed() // Get link instance
        await link.approve(dex.address, 500);
        await dex.depositEth({value: 3000});
        // 1 was added in previous test
        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)
        assert(orderbook[1].price == 100)
        for (let i = 0; i < orderbook.length - 1; i++) {
            assert(orderbook[i].price <= orderbook[i+1].price, "Not right order in orderBook")
        }

    })
})


1 Like

@thecil thank you for your support man,even though
I had already changed it. @Onemilestone saved my life ahah. I was making the same stupid mistake. Now i can push forward.
Thank you guys.

@thecil
As a learning exercise, I am trying to add Update and Delete functionality to limit orders.

In the spirit of TDD (I love how Filip pushes for this), I wrote my tests first :slight_smile: .

I am working on the Delete function. My test is breaking because getTraderOrderBook() is returning an AssertionError(expected 0 to equal 3).

In order to get the BUY orderbook filtered for only the particular trader (address), I wrapped the orderBook mapping inside another mapping. Not sure if that works

I’m not sure whether my error is in my function or in my 3-level mapping.

Note about commented lines: I am using Hardhat. I tried debugging using console.log but hardhat returned TypeError: TypeError: Member “log” instead of an empty array.

Would be grateful for any hints or suggestions. I mean, delete and update, I should be able to do this! :slight_smile: :slight_smile:

Here is my code for getting an order book specific to the trader:

    //map ticker,side => Order
    mapping(bytes32 => mapping(uint256 => Order[])) public orderBook;

    //map trader,ticker,side => Order
    mapping(address => mapping(bytes32 => mapping(uint256 =>Order[]))) public traderOrderBook;

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

    function getTraderOrderBook(address trader, bytes32 ticker, Side side) public view returns (Order[] memory){
        //Order[] memory traderOrders = traderOrderBook[trader][ticker][uint256(side)];
        //console.log(traderOrders);
        //return traderOrders;
        return traderOrderBook[trader][ticker][uint256(side)];
    }

Here are my tests. I am using hardhat:

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

const Side = {
    BUY: 0,
    SELL: 1,
};

const Tokens = {
    LINK: ethers.utils.formatBytes32String('LINK'),
};

let Dex;
let dex;
let Link;
let link;
let owner;
let trader1;
let trader2;

describe("Limit order tests", function () {

    beforeEach(async function () {
        Dex = await ethers.getContractFactory('Dex');
        dex = await Dex.deploy();
        Link = await ethers.getContractFactory('Link');
        link = await Link.deploy();
        //USAGE NOTE: connect with signer object, pass param with signer.address property
        [owner, trader1, trader2] = await ethers.getSigners();
    });   

//other tests here, not shown

   describe("Update and delete limit order tests", function () {

        it("Should allow trader to delete a BUY order and the BUY order book should resort correctly", async () => {
            //Given owner with balances
            await dex.connect(owner).depositETH({ value: 10000});
            await dex.connect(owner).addToken(Tokens.LINK, link.address);
            await link.connect(owner).approve(dex.address, 1000);
            await dex.connect(owner).depositToken(300, Tokens.LINK);

            //And trader1 with balances
            await link.connect(owner).transfer(trader1.address, 300);
            await link.connect(trader1).approve(dex.address, 300);
            await dex.connect(trader1).depositToken(300, Tokens.LINK);
            await dex.connect(trader1).depositETH({ value: 10000});

            //And trader2 with balances
            await link.connect(owner).transfer(trader2.address, 300);
            await link.connect(trader2).approve(dex.address, 300);
            await dex.connect(trader2).depositToken(300, Tokens.LINK);
            await dex.connect(trader2).depositETH({ value: 10000});

            //And a BUY order book filled with orders from more than one trader
            await dex.connect(owner).createLimitOrder(Side.BUY, Tokens.LINK, 5, 1);
            await dex.connect(owner).createLimitOrder(Side.BUY, Tokens.LINK, 10, 7);
            await dex.connect(owner).createLimitOrder(Side.BUY, Tokens.LINK, 15, 4);
            await dex.connect(trader1).createLimitOrder(Side.BUY, Tokens.LINK, 5, 2);
            await dex.connect(trader1).createLimitOrder(Side.BUY, Tokens.LINK, 10, 8);
            await dex.connect(trader1).createLimitOrder(Side.BUY, Tokens.LINK, 15, 5);
            await dex.connect(trader2).createLimitOrder(Side.BUY, Tokens.LINK, 5, 3);
            await dex.connect(trader2).createLimitOrder(Side.BUY, Tokens.LINK, 10, 9);
            await dex.connect(trader2).createLimitOrder(Side.BUY, Tokens.LINK, 15, 6);
            let orderbook = await dex.getOrderBook(Tokens.LINK, Side.BUY);
            expect(orderbook.length).to.be.equal(9);

            //When trader1 deletes one of his buy orders
            let beforeTrader1BuyLimitOrders = await dex.getTraderOrderBook(trader1.address, Tokens.LINK, Side.BUY);
            expect(beforeTrader1BuyLimitOrders.length).to.be.equal(3); //<<--TEST BREAKS HERE, returns(0)
            let orderId = beforeTrader1BuyLimitOrders[1].OrderID; //pick one of the 3 trader1 orders to delete
            await dex.connect(trader1).deleteLimitOrder(orderId); //and delete the order

            //then the order is removed from the order book
            let afterTrader1BuyLimitOrders = await dex.getTraderOrderBook(trader1.address, Tokens.LINK, Side.BUY);
            expect(afterTrader1BuyLimitOrders.length).to.be.equal(2);
            expect(orderbook.length).to.be.equal(8);

            //and the BUY order book is resorted from highest price to lowest price
            for (let i = 0; i < orderbook.length - 1; i++) {
                expect(orderbook[i].price).to.be.lte(orderbook[i+1].price);
            }
        });

        it.skip("Should allow trader to delete a SELL order and the SELL order book should resort correctly", async () => {
            // Given an orderbook filled with orders from more than one trader
            // And a trader with several orders
            // When the trader deletes an order
            // Then all the following are true:
            // a trader may only delete orders that belong to the trader
            // the order is removed from the orderBook
            // the order book is resorted correctly
        });

        it.skip("Should allow trader to update and order and the order book should resort correctly", async () => {
            // Given an orderbook filled with orders from more than one trader
            // And a trader with several orders
            // When the trader updates an order
            // Then all the following are true:
            // a trader may only update orders that belong to the trader
            // the order is updated
            // the order book is resorted correctly
            // BUY order in middle of order book, order price is reduced >> bubble sort compares to right
            // BUY order in middle of order book, order price is raised >> bubble sort compares to left
            // BUY order is top of order book, order price is raised >> no need to sorted
            // BUY order is bottom of order book, order price is raised >> bubble sort compares to left
            // and so on
            // opposite for SELL orders
        });
    });
 });
1 Like

hey @pivot. great that your tyring to do extra things here with the delete and update function. The problem with deletion here is that the order of the orderbook matters. Since this is the case we cannot simply just do the usual method whereby we take the element we want to delete, move it to the last index and call .pop().Doing this will cause the orderbook to be come unsorted which is not what we want. If you really wanted to do it this way you could just resort after calling .pop() but there is a better way that more efficent

Instead what you can do to delete orders from the book is to use a similar approach. We first identify the index we want to delete. Then we run a loop going from this index to 1 minus the length of the array and we shift every eleemnt by 1. then we call .pop(). Ill write a dummy function so you can see what i mean

function deleteOrders(uint indexToDelete, uint side, bytes32 ticker) public onlyOwners {

    Order[] storage orders = traderOrderBook[ticker][uint256(side)];
    
  
            
       for (uint j = indexToDelete; j < orders.length-1; j++){
            orders[j] = orders[j+1];
       }    
       orders.pop();
    

Ill explain what this code does with an example. Say we have an array with 5 eleemtns ordered from 1 to 5 as shown below

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]

Now say we want to delete the third index. Byut we need to keep the array sorted. If we pass 3 as the “indexToDelete” var into the above function then we start our loop at that index and for each element including 3 up until the second last index in the array we swap them so the new array will look like

=> [ 1 ] [ 2 ] [ 4 ] [ 5 ] [ 5 ]

But now we need to call .pop() to get rid of the last unwanted element. and after this we have successfully removed the required eleemnt and the new resulting array is

=> [ 1 ] [ 2 ] [ 3 ] [ 4 ]

So thats for deleteion. If you want to update an order such as the price or maybe the amount its more complicated. This is because there are two scenaros.

  1. Update the amount => the orders position wont change (easy scenario)
  2. Update the price => The orders position will change (hard scenario)

I suggest you write a function called updateOrder(). Before you even enter the body you need to require that the order is not filled or partially filled at all. If this is the case then the function should revert as the user should not be able to update an order if its partially filled. The you need to handle both scenarios. For the first its easy just pass in the index of the order you want to modify and change the amount you could do this like order[indexToUpdate].amount = newAmount

In the other scenario you will need to resort after you update the price. To do this update the price as normal like order[indexToUpdate].amount = newAmount. Then after this resort the ordrbook. The most efficent way to do this would be to segregate your bubble sort logic into its own function called sortOrder() or something and then after you update the price you just need to call sort0rder(). also this goes with out saying in your market order function replace the bubble sort logic with sortOrder() too.

My last tip is you should not implement this and try to run your tests straight away they will most likley fail. You should instead launch up your terminal and manually do the tests yourself this way its easier to see where thing are going wrong and make fixes quicker rather than waiting like 30 seconds or more for your test script to finish each time you make a change to the contract code. If you need anymore help especially on the update order function share you code, i will clone it and write it out for you, but you should give it a good stab yourself first.

Happy coding,
Evan

2 Likes