Assignment - Limit Order Test

Solution:

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

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

    it("the user have ETH deposited such that deposited eth < buy order value, should revert", async () =>{
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        
        //createLimitOrder should take care of approve and deposit
        truffleAssert.reverts(
            dex.createLimitOrder("LINK", 5000000, 50000000000, 0, {from: accounts[0]})
        )
    })

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

        //createLimitOrder should take care of approve and deposit
        truffleAssert.passes(
            dex.createLimitOrder("LINK", 500, 50, 1, {from: accounts[0]})
        )
    })

    it("the user have tokens deposited such that token balance < sell order amount", async () =>{
        
        let dex = await Dex.deployed();
        let link = await Link.deployed();

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

    it("order book BUY side should be sorted from highest to lowest", async () =>{
        
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 2, 1, 0, {from: accounts[0]});
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 3, 2, 0, {from: accounts[1]});
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 3, 0, {from: accounts[2]});
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 4, 4, 0, {from: accounts[3]});

        var p1 = 1;
        var p2 = 2;
        let orderBook = await dex.getOrderBook;
        for(let i = 0; i < orderBook.length-1; i++){
            p1 = orderBook[i].price;
            p2 = orderBook[i+1].price;
            truffleAssert(p1 >= p2);
        }
    })

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

        var p1 = 1;
        var p2 = 2;
        let orderBook = await dex.getOrderBook;
        for(let i = 0; i < orderBook.length-1; i++){
            p1 = orderBook[i].price;
            p2 = orderBook[i+1].price;
            assert(p1 <= p2);
        }
    })

})
1 Like

when running the test for the next step I keep getting:

  1. ā€œbefore eachā€ hook: before test for ā€œthe user have ETH deposited such that deposited eth < buy order value, should revertā€

Any clues?

1 Like

Hello!
This was trickyšŸ˜ƒ

//The user must have ETH deposit such that deposited eth >= buy order value 
//The user must have enough tokens deposited such that token balance > sell order amount 
//The first order([0]) in the Buy order book should have the highest price 

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

Contract("Dex", accounts => {
it("should have enough ETH deposit to place buy order", async () => {
    let dex = await Dex.deployed()

    dex.deposit(1000, web3.utils.fromUtf8("ETH"), {from: accounts[0]})
        dex.deposit(1000, web3.utils.fromUtf8("ETH"), {from: accounts[1]})

        truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 100, {from: accounts[0]})
        )
        truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 300, {from: accounts[1]})
        )
})

it("should have enough tokens to deposit to place sell order", async () => {
    
    let dex = await Dex.deployed()

    dex.deposit(1000, web3.utils.fromUtf8("LINK"), {from: accounts[0]})
    dex.deposit(1000, web3.utils.fromUtf8("LINK"), {from: accounts[1]})

    truffleAssert.passes(
        dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 100, 100, {from: accounts[0]})
    )
    truffleAssert.reverts(
        dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 2000, 300, {from: accounts[1]})

    )
})

it("Sell order book should be ordered on price from lowest to highest at index 0", async () => {
    let dex = await Dex.deployed()

    dex.deposit(10000, web3.utils.fromUtf8("LINK"), {from: accounts[0]})
    dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 100, 400, {from: accounts[0]})
    dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 100, 300, {from: accounts[0]})
    dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 100, 100, {from: accounts[0]})
    
    assert.equal(dex.orderBook[web3.utils.fromUtf8("LINK")][1][0].price, 100)
    assert.equal(dex.orderBook[web3.utils.fromUtf8("LINK")][1][1].price, 300)
    assert.equal(dex.orderBook[web3.utils.fromUtf8("LINK")][1][2].price, 400)
    
})

})
1 Like

My tests:

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 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

contract("Dex", accounts => {
    it("should only allow buy order if the user has deposited ETH >= the amount of the buy order", async() => {
        let dex = await Dex.deployed();

        await dex.deposit(10, web3.utils.fromUtf8("ETH"), {from: accounts[0]})

        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 1000, {from: accounts[0]})
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 15, 1000, {from: accounts[0]})
        )
    })
    it("should only allow sell order if the user has deposited LINK >= the amount of the buy order", async() => {
        let dex = await Dex.deployed();

        await dex.deposit(1000, web3.utils.fromUtf8("LINK"), {from: accounts[0]})

        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 1000, {from: accounts[0]})
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 2000, {from: accounts[0]})
        )
    })
        
    it("BUY order book should be ordered on price from highest to lowest starting at index 0", async() => {
        
        let link = await Link.deployed()
        let dex = await Dex.deployed()
        await link.approve(dex.address,500);
        await dex.depositEth({value: 2500});
        await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 600)
        await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 500)

        let book = await dex.getOrderBook("LINK",0);

        for(let i=0;i<book.length-1;i++){
            truffleAssert(book[i].price > book[i+1].price);
        }       
    
    })
      
      it("Sell order book should be ordered on price from lowest to highest starting at index 0", async() => {
        
        let link = await Link.deployed()
        let dex = await Dex.deployed()
        await link.approve(dex.address,500);
        await dex.depositEth({value: 2500});
        await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 600)
        await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1, 500)

        let book = await dex.getOrderBook("LINK",0);

        for(let i=0;i<book.length-1;i++){
            truffleAssert(book[i].price < book[i+1].price);
        } 
        
    
    })
}
)

Hi everyone! I have a question about this. I canā€™t wrap my head around this dex.depositEth() function that I see Filip do, and some of the solutions here in the forum.
for example:

contract("Dex", accounts => {
    //The user must have ETH deposited such that deposited eth >= buy order value
    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)
        )
    })

Where is this depositEth() function coming from? What am I missing here? Shouldnā€™t this be a function that we previously defined in our Dex contract, or is it something in-built?

Thanks for your help

2 Likes

@Giupit his is a function thats missing it got cut out of one of the videos. The correct implementation

function depositETH() public payable { 
    balances[msg.sender]["ETH"] += msg.value;
 }

This function deposis real ether inyo the contract, you dont need to make any extra etg contract or amythung we just use thr balance mapping to store the users eth bal

1 Like

Hello everyone,

I just finished the Limit Order Test assignment, and it was kind off difficult for me.
Iā€™ve never worked this way, but it was really nice working this way.
I feel like if youā€™re working this way, it would be much easier making the actual function.

If there are any tips, feel free to say it.
Is there an way where you donā€™t have to make a new function to deposit ETH?
Like using fallback function?

Any how, here is my solution.

// The user must have ETH deposited such that deposited ETH >= buy order value
// The user has not enough ETH deposited such that buy order value >= ETH. Assert this will revert
            // input in chronological order: 
            // Side (BUY = 0, SELL = 1)
            // Ticker
            // amount
            // price


// The user must have enough tokens deposited such that token balance >= sell order amount
// The user has not enough tokens deposited such that sell order amount >= token balance. Assert this will revert

// The BUY order book should be ordered on price from highest to lowest starting at index 0
// The BUY order book should be ordered on price from lowest to highest starting at index 0. Assert this will revert.

const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssertions = require('truffle-assertions');
const TruffleAssert = require('truffle-assertions') // Now you can use the TruffleAssert. commands

contract("Dex", accounts => {
    let dex;
    let link;

    before(async function(){
        dex = await Dex.deployed();
        link = await Link.deployed();
    });

    it("should only be possible to buy a token if there's enough ETH deposited", async() =>{
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})

        await TruffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 2) 
        )

        await dex.depositETH({value: 5})
        await TruffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 2)
        )
    })


    it("should only be possible to sell a token if the balance >= token balance", async() =>{
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})

        await TruffleAssert.reverts(
           dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 6, 2)
        )
        await link.approve(dex.address, 6);
        await dex.deposit(6, web3.utils.fromUtf8("LINK"), {from: accounts[0]})
        
        await TruffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 6, 2, {from: accounts[0]})
        )
    }) 


    it("should be ordered on price from highest to lowest starting at index 0", async() =>{
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})

        await link.approve(dex.address, 400)
        await dex.depositETH({value: 80})
        await dex.deposit(400, web3.utils.fromUtf8("LINK"));

        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 250)
        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, 150)

        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, "Orderbook sequence incorrect.");
        }
    }) 

    it("should be ordered on price from highest to lowest starting at index 0", async() =>{
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})

        await link.approve(dex.address, 400)
        await dex.depositETH({value: 80})
        await dex.deposit(400, web3.utils.fromUtf8("LINK"));

        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 250)
        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, 150)

        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, "Orderbook sequence incorrect.");
        } 
    }) 
})
1 Like

the purpose of a fallback function is to execute some code whenever someone sends ether to a smart contract externally. Fallback doesnt serve as a means to deposit as we are trying to do here. you need the deposit function so that the user could click some button on a frontend ui to increment there balance. you can have it as one function if you want where if chaned the deposit logic based on an if statement. You could say if(ticker === "ETH") then deposit with msg.value, else deposit usint amount argument and call tansferfrom.

1 Like

it doesnt make a difference tho

1 Like

Isnā€™t it an possibility to use

function deposit() payable external {}

You can then update the balance in mapping within this function right?
Just curious if thatā€™s even a possibility.

1 Like

yeah u can use that, although i would hav eit as public since your not going to be calling the deposit function from another smart contract

1 Like

I see, thanks mate!!
Was just curious if there are other ways to make it happen.

Thank you for explaining :+1:t3::muscle:t3:

1 Like

no worries at all g!!!

1 Like

Okay, apparently thereā€™s a revert on the third test and the fourth test got the wrong sequence.

dexttest.js


//The first order ([0]) in the BUY order book should have the highest price

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

contract("Dex", accounts => {
    
    let dex;
    let link;

    before(async function(){
        dex = await Dex.deployed();
        link = await Link.deployed();
    });
    //The user must have ETH deposited such that deposited eth >= buy order value
    it("should throw an error if ETH balance is too low when creating BUY limit order", async () => {

        await truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
        )
        await dex.depositEth({value: 10})
        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })
    //The user must have enough tokens deposited such that token balance >= sell order amount
    it("should throw an error if token balance is too low when creating SELL limit order", async () => {

        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from:accounts[0]})
        await link.approve(dex.address, 500);
        await dex.deposit(10, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })
    //The BUY order book should be ordered on price from highest to lowest starting at index 0
    it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {

        await link.approve(dex.address, 800);
        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, "Orderbook sequence incorrect.");
        }
    })
    //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 () => {

        await link.approve(dex.address, 800);
        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, "Orderbook sequence incorrect.");
        }

        console.log(orderBook)
    })
})

dex.sol

pragma solidity ^0.8.10;
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;
        uint256 amount;
        uint256 price;
    }

    uint public nextOrderID = 0;

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

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

    function depositEth() public payable { 
        balances[msg.sender]["ETH"] += 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));
        }

        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 Logic

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

        if(side == Side.BUY){

            while(i > 0){
                if(orders[i].price -1 > orders[i].price){
                    break;
                }
                else{
                    Order memory orderToMove = orders[i -1]; 
                    orders[i -1] = orders[i]; 
                    orders[i] = orderToMove; 
                    i--; 
                }
            }

        }

        else if(side == Side.SELL){
            //Sorting logic for the SELL side
            // Sorting it from Low to High
            while(i > 0){
                if(orders[i].price -1 < orders[i].price){
                    break;
                }
                else{
                    Order memory orderToMove = orders[i -1]; 
                    orders[i -1] = orders[i]; 
                    orders[i] = orderToMove; 
                    i--;                  
                }
            } 
        }        
        nextOrderID++;
    }
}

I feel like the third test is being reverted because of the require statement.

            require(balances[msg.sender]["ETH"] >= amount.mul(price));

Hope someone knows what I did wrong

1 Like

I have solved the problem.

I just had to change the -1 to the right position.

else if(side == Side.SELL){
            //Sorting logic for the SELL side
            // Sorting it from Low to High
            while(i > 0){
                if(orders[i -1].price < orders[i].price){
                    break;
                }

It now works as it should be.
The only problem I now have is the second test.

The problem is with the require statement on the BUY side.

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

        else if(side == Side.SELL){
            require(balances[msg.sender][ticker] >= amount);
        }

If I change the .mul to .div it works.
This is not the solution thoā€¦
I only have problems understanding why amount should be multiplied with price.
Why is it not working??
Can anyone help me out?

1 Like

i cant see without you showing your errors. i would change it from

require(balances[msg.sender]["ETH"] >= amount.mul(price));

to

require(balances[msg.sender]["ETH"] >= amount * price;

and the reason we make sure its greater than the amount by the pice is because were dealing with balances here so if somone wants to buy 100 link at a price of 10 eth per link then they need 100 by 10 = 1000 eth to be able to make the purchase. if its failing when you have it as mul its probably becaus eyour eth balance is insufficent

i awlays say this to students and its a really good habbit to get into when doing tests like this. so what do we know, well we know that the that the second test is the one thats failing. Ok so do we know where the error is? no we dont sadly. so how can we find the error. well how about we comment out every line in the test accept the first one. then run the test. if it passes then we uncomment the next line and run the test. if that passes then we uncomment the third line and run the test. we rinse and repeat this process until we figure out what line is causing the error, once we know this well be able to debug muc easier

1 Like

i would try depsiting 20 link instead of 10 in the second test and see if that works

1 Like

This did the trick.
I changed the depositEth amount to 10 000 to be sure and removed the .mul to *.
This did the trick.

Your insight is well appreciated!
Thanks mate

1 Like

no worries at all glad to help

1 Like