Assignment - Limit Order Test

wallettest.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 => {
    //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)
        )
        await dex.depositEth({value: 10, from:accounts[0]})
        
        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 () => {
        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(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    
        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 () => {
        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);

        console.log(orderbook);

        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])
        }
    })
})

dex.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
pragma experimental ABIEncoderV2;

import "../contracts/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 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));
        }

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

tougher nut to crack compared to previous assignments however finally got a ā€˜somewhatā€™ solution. :partying_face:

it("user must have ETH deposited such that deposited eth >= buy order value", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.reverts(
            //function createLimitOrder(bool buyOrder, bytes32 ticker, uint amount, uint price)
            dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 10, 1, {from: accounts[0]})
        )
        await dex.depositEth({value: 10, from:accounts[0]})    
        await truffleAssert.passes(
            //function createLimitOrder(bool buyOrder, bytes32 ticker, uint amount, uint price)
            dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 10, 1, {from: accounts[0]})
        )
    })
    it("user must have enough tokens deposited such that token balance >= sell order amount", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.reverts(
            //function createLimitOrder(bool buyOrder, bytes32 ticker, uint amount, uint price)
            dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 10, 1, {from: accounts[0]})
        )
        await link.approve(dex.address, 500);
        await dex.deposit(10, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            //function createLimitOrder(bool buyOrder, bytes32 ticker, uint amount, uint price)
            dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 10, 1, {from: accounts[0]})
        )
    })
    it("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: 5000});
        await link.approve(dex.address, 1000);
        
        await dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 1, 50)
        await dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 1, 500)
        await dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 1, 100)
        await dex.createLimitOrder(true, web3.utils.fromUtf8("LINK"), 1, 400)

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

        for(let i=0;i<=book.length-1;i++){
            assert(book[i].price<book[i+1].price,"Buy-Side in the OB is not correctly sorted")
        }
    })
    it("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.deposit(5000, web3.utils.fromUtf8("LINK"));

        await dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 1, 50)
        await dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 1, 300)
        await dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 1, 500)
        await dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 1, 100)
        await dex.createLimitOrder(false, web3.utils.fromUtf8("LINK"), 1, 400)

        let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),1)
        
        for(let i=0;i<=book.length-1;i++){
            assert(book[i].price<book[i+1].price,"Sell-Side in the OB is not correctly sorted")
        }
    })

@Dominykas_Pozleviciu you are forgettting to add your token at the beginning of the test. call

await dex.addToken(web2.utils.fromUtf8("LINK), link.address)

also to help debug console to the log for example the output of the orderbook array to see what it look like. when you find errors go and you cant find it go up to the first point where your surfe its deffo working and then comment out everything else run your test, and if it passes uncomment that line and test then uncomment the next and test until you find the line that throwing the error. then at least you have a grounds to start debugging an fixing the issue

1 Like

Hey! Thanks for the advice!

I found out that I wrote my assertion wrongly: assert(orderbook[i] >= orderbook[i+1])

I forgot to add .price: assert(orderbook[i].price >= orderbook[i+1].price)

Now it works!!

2 Likes

ahh yess a common mistake lol. i remeber doing that also

1 Like

Hi everyone,

Here is a test file with a few unit tests on createLimitOrder.

The last test verifies that SELL orders were stored in ascending order.

Test file:

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




// Test ideas:

// ticker should be valid
// create limit order with price or amount zero should fail
// Token amount sufficient for sell
// ETH amount sufficient for buy (give up)
// sell order book ordered by ascending price


contract("DEX, createLimitOrder", accounts => {
    
    
    it("should handle invalid ticker", async() =>{
        let dex = await Dex.deployed()
     
        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.utf8ToHex("INVA"), Dex.Side.BUY, 1, 1)
            )
    })
    
    it("amount and price should not be zero", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()

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

        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.utf8ToHex("LINK"), Dex.Side.BUY, 1, 0)
        )

        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.utf8ToHex("LINK"), Dex.Side.BUY, 0, 1)
        )
    
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.utf8ToHex("LINK"), Dex.Side.BUY, 1, 1)
        )

    })

    it("user should have enough tokens for a sell order", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let linkTicker = web3.utils.utf8ToHex("LINK")

        await dex.addToken(linkTicker, link.address)

        await link.approve(dex.address, 500)

        await dex.deposit(29, linkTicker)

        await truffleAssert.reverts(
            // Create a sell order
            dex.createLimitOrder(linkTicker, Dex.Side.SELL, 2, 30)
        )

        await truffleAssert.passes(
            // Create a sell order with sufficient token balance
            dex.createLimitOrder(linkTicker, Dex.Side.SELL, 2, 29)
        )
    })

    
    it("sell order book should be ordered by price ascending", async() =>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let linkTicker = web3.utils.utf8ToHex("LINK")

        await dex.addToken(linkTicker, link.address)

        await link.approve(dex.address, 500)

        await dex.deposit(100, linkTicker)

        await dex.createLimitOrder(linkTicker, Dex.Side.SELL, 3, 5)
        await dex.createLimitOrder(linkTicker, Dex.Side.SELL, 2, 5)
        await dex.createLimitOrder(linkTicker, Dex.Side.SELL, 6, 5)

        let orderBook = await dex.getOrderBook(linkTicker, Dex.Side.SELL)

        console.log("Orderbook has " + orderBook.length + " elements")
        

        if (orderBook.length > 0)
        {
            for(let i=1; i<orderBook.length; i++){
                assert(orderBook[i-1].price <= orderBook[i].price, "wrong ordering at element " + i + " price "+orderBook[i].price);
            }
        }
        }
    )
    
})

Dex code

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.0;

import "./Wallet.sol";

contract Dex is Wallet
{
    enum Side{
        BUY,
        SELL
    }


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

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


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

    function createLimitOrder(bytes32 ticker, Side side, uint price, uint amount) tokenExists(ticker) public {
        require(price > 0, "Price cannot be zero");
        require(amount > 0, "Amount cannot be zero");

        if(side == Side.SELL)
        {
            require(balances[msg.sender][ticker] >= amount, "Insufficient balance");

            Order[] storage orders = orderBook[ticker][uint(side)];

            if (orders.length == 0){
                orders.push(Order(0,msg.sender, side, ticker, amount, price));
            }   
            else{
                bool done = false;

                uint i = orders.length-1;// point to last element
                while(!done)
                {
                    // last order price is smaller than new one
                    if(orders[i].price <= price)
                    {
                        //Insert new order

                        if (i== (orders.length-1)) // this is the last element
                        {
                            //then just push to end
                            orders.push(Order(0,msg.sender, side, ticker, amount, price));
                        }
                        else{
                        // Insert new order to the next position
                            orders[i+1] = Order(0,msg.sender, side, ticker, amount, price);                        
                        }
                        done = true;
                    }else
                    {
                        // Move up existing orders

                        if (i== (orders.length-1)) //this is the last element
                        {
                            // Move last element to new i+1 element
                            orders.push(orders[i]);
                        }
                        else{
                        // Move up previous order
                            orders[i+1] = orders[i];
                            
                        }
                    }

                    if (i>0)
                        i--;   
                    else
                        done = true;
                }
                
            }   
        }
    }
}
1 Like

Hi,

Iā€™m having issues with the Limit Order Function Solution Tests. They are saying it is using too much gas and I canā€™t figure out why. If anyone could help Iā€™d really appreciate it, Iā€™m getting very frustrated.

Here is the terminal outputā€¦ my files will follow belowā€¦ if you need anything else please let me know and thanks in advance for any help.

truffle(develop)> test
Using network 'develop'.


Compiling your contracts...
===========================
> Compiling .\contracts\Migrations.sol
> Compiling .\contracts\dex.sol
> Compiling .\contracts\tokens.sol
> Compiling .\contracts\wallet.sol
> Compiling .\node_modules\@openzeppelin\contracts\access\Ownable.sol
> Compiling .\node_modules\@openzeppelin\contracts\token\ERC20\ERC20.sol
> Compiling .\node_modules\@openzeppelin\contracts\token\ERC20\IERC20.sol
> Compiling .\node_modules\@openzeppelin\contracts\token\ERC20\extensions\IERC20Metadata.sol
> Compiling .\node_modules\@openzeppelin\contracts\utils\Context.sol
> Compiling .\node_modules\@openzeppelin\contracts\utils\math\SafeMath.sol
> Compilation warnings encountered:

    Warning: Return value of low-level calls not used.
  --> project:/contracts/wallet.sol:50:9:
   |
50 |         msg.sender.call{value : amount }("");
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

,Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient.
 --> project:/contracts/tokens.sol:7:5:
  |
7 |     constructor() ERC20("Chainlink", "LINK") public {
  |     ^ (Relevant source part starts here and spans across multiple lines).


> Artifacts written to C:\Users\Ryan\AppData\Local\Temp\test--12900-cZlbxbHEDCen
> Compiled successfully using:
   - solc: 0.8.0+commit.c7dfd78e.Emscripten.clang



  Contract: Dex
    1) should throw an error if ETH balance is too low when creating BUY limit order
    > No events were emitted
    āˆš should throw an error if token balance is too low when creating Sell limit order (2721ms)
    2) the BUY order book should be ordered on price from highest to lowest starting at index 0

    Events emitted during test:
    ---------------------------

    IERC20.Approval(
      owner: <indexed> 0x6c5e2b2eCc4f8C63F83fcF3711059bF8E5a5619D (type: address),
      spender: <indexed> 0x4a6E4cc62afE502937C161a14d24E38ABDdAb010 (type: address),
      value: 500 (type: uint256)
    )


    ---------------------------
[]
    3) the SELL order book should be ordered on price from lowest to highest starting at index 0

    Events emitted during test:
    ---------------------------

    IERC20.Approval(
      owner: <indexed> 0x6c5e2b2eCc4f8C63F83fcF3711059bF8E5a5619D (type: address),
      spender: <indexed> 0x4a6E4cc62afE502937C161a14d24E38ABDdAb010 (type: address),
      value: 500 (type: uint256)
    )


    ---------------------------

  Contract: Dex
    - should only be possible for owner to add tokens
    - should handle deposits correctly
    - should handle faulty withdrawals correctly
    - should handle correct withdrawals correctly


  1 passing (14s)
  4 pending
  3 failing

  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:11:9)

  2) Contract: Dex
       the BUY order book should be ordered on price from highest to lowest starting at index 0:
     Error: Returned error: VM Exception while processing transaction: out of gas
      at Context.<anonymous> (test\dextest.js:28:19)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)

  3) Contract: Dex
 at index 0:                                                                          at index 0:
     AssertionError: Unspecified AssertionError
      at Context.<anonymous> (test\dextest.js:50:9)
      at runMicrotasks (<anonymous>)
      at processTicksAndRejections (internal/process/task_queues.js:95:5)

truffle(develop)>

dex.sol

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

import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
import "./wallet.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";

contract Dex is Wallet {

    using SafeMath for uint256;
 
    enum Side {
        BUY, 
        SELL
    }
    /*
    like a bool, but can have more than 1 option and holds as a int (o,1)
    to use Order.side = Side.BUY 
    */

    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));
        }
        else if(side == Side.SELL){
            require(balances[msg.sender][ticker] >= amount);
        }
        
        Order[] storage orders = orderBook[ticker][uint(side)];
        //This is the list of orders [Order1, Order2, Order3] either BUY or SELL orders
        orders.push(
            Order(nextOrderId, msg.sender, side, ticker, amount, price)
        );

        //Bubble sort
        //mapping(bytes32 => mapping(uint => Order[])) public orderBook;
        
        uint256 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++;
    }

}

wallet.sol

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

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

contract Wallet is Ownable {

    using SafeMath for uint256;

    struct Token {
        bytes32 ticker;
        address tokenAddress;
    }

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

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

    modifier tokenExists(bytes32 _ticker) {
        require(tokenMapping[_ticker].tokenAddress != address(0));
        _;
    }

    function addToken(bytes32 _ticker, address _tokenAddress) onlyOwner external {
        tokenMapping[_ticker] = Token(_ticker,_tokenAddress);
        tokenList.push(_ticker);

    }

    function withdraw(uint256 _amount, bytes32 _ticker) tokenExists(_ticker) external {
        require(balances[msg.sender][_ticker] >= _amount);
        balances[msg.sender][_ticker] = balances[msg.sender][_ticker].sub(_amount);
        IERC20(tokenMapping[_ticker].tokenAddress).transfer(msg.sender,_amount);
    }

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

    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,'Insufficient balance');
        balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")].sub(amount);
        msg.sender.call{value : amount }("");
        
    }
}

dextest.js

const Link = artifacts.require("LINK")
const Dex = artifacts.require("Dex")
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))
    })
    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(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("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);
        dex.depositEth({value:3000})
        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);
        console.log(orderbook);
        assert(orderbook.length >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("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, 500);
        dex.depositEth({value:3000})
        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"), 0);
        console.log(orderbook);
        assert(orderbook.length >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

Hey @BMCx100, hope you are great.

Could you please share your truffle config?

That kind of issues I had solved in the past by activating the optimizer in the truffle config file.

image

Carlos Z

Hey Carlos,

Hereā€™s the truffle configā€¦ thank you so much for looking into it

/**
 * Use this file to configure your truffle project. It's seeded with some
 * common settings for different networks and features like migrations,
 * compilation and testing. Uncomment the ones you need or modify
 * them to suit your project as necessary.
 *
 * More information about configuration can be found at:
 *
 * trufflesuite.com/docs/advanced/configuration
 *
 * To deploy via Infura you'll need a wallet provider (like @truffle/hdwallet-provider)
 * to sign your transactions before they're sent to a remote public node. Infura accounts
 * are available for free at: infura.io/register.
 *
 * You'll also need a mnemonic - the twelve word phrase the wallet uses to generate
 * public/private key pairs. If you're publishing your code to GitHub make sure you load this
 * phrase from a file you've .gitignored so it doesn't accidentally become public.
 *
 */

// const HDWalletProvider = require('@truffle/hdwallet-provider');
//
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  /**
   * Networks define how you connect to your ethereum client and let you set the
   * defaults web3 uses to send transactions. If you don't specify one truffle
   * will spin up a development blockchain for you on port 9545 when you
   * run `develop` or `test`. You can ask a truffle command to use a specific
   * network from the command line, e.g
   *
   * $ truffle test --network <network-name>
   */

  networks: {
    // Useful for testing. The `development` name is special - truffle uses it by default
    // if it's defined here and no other network is specified at the command line.
    // You should run a client (like ganache-cli, geth or parity) in a separate terminal
    // tab if you use this network and you must also set the `host`, `port` and `network_id`
    // options below to some value.
    //
    // development: {
    //  host: "127.0.0.1",     // Localhost (default: none)
    //  port: 8545,            // Standard Ethereum port (default: none)
    //  network_id: "*",       // Any network (default: none)
    // },
    // Another network with more advanced options...
    // advanced: {
    // port: 8777,             // Custom port
    // network_id: 1342,       // Custom network
    // gas: 8500000,           // Gas sent with each transaction (default: ~6700000)
    // gasPrice: 20000000000,  // 20 gwei (in wei) (default: 100 gwei)
    // from: <address>,        // Account to send txs from (default: accounts[0])
    // websocket: true        // Enable EventEmitter interface for web3 (default: false)
    // },
    // Useful for deploying to a public network.
    // NB: It's important to wrap the provider as a function.
    // ropsten: {
    // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
    // network_id: 3,       // Ropsten's id
    // gas: 5500000,        // Ropsten has a lower block limit than mainnet
    // confirmations: 2,    // # of confs to wait between deployments. (default: 0)
    // timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    // skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    // },
    // Useful for private networks
    // private: {
    // provider: () => new HDWalletProvider(mnemonic, `https://network.io`),
    // network_id: 2111,   // This network is yours, in the cloud.
    // production: true    // Treats this network as if it was a public net. (default: false)
    // }
  },

  // Set default mocha options here, use special reporters etc.
  mocha: {
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    solc: {
    version: "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"
      // }
    }
  },

  // Truffle DB is currently disabled by default; to enable it, change enabled:
  // false to enabled: true. The default storage location can also be
  // overridden by specifying the adapter settings, as shown in the commented code below.
  //
  // NOTE: It is not possible to migrate your contracts to truffle DB and you should
  // make a backup of your artifacts to a safe location before enabling this feature.
  //
  // After you backed up your artifacts you can utilize db by running migrate as follows: 
  // $ truffle migrate --reset --compile-all
  //
  // db: {
    // enabled: false,
    // host: "127.0.0.1",
    // adapter: {
    //   name: "sqlite",
    //   settings: {
    //     directory: ".db"
    //   }
    // }
  // }
};

1 Like

My Solution:

const Dex = artifacts.require("Dex")
const Link =artifacts.require("Link")
const Eth=artifacts.require("Eth")
const truffleAssert= require('truffle-assertions')
contract("Dex", accounts => {
    it("Should be enough eth to buy", async ()=> {
       
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let eth = await Eth.deployed()
        await dex.addToken(web3.utils.fromUtf8("ETH"), eth.address)
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address)
        await link.approve(dex.address, 500)
        await eth.approve(dex.address, 100)
        await dex.deposit(500, web3.utils.fromUtf8("LINK"));
        await dex.deposit(100, web3.utils.fromUtf8("ETH"));
        await truffleAssert.passes(
            dex.limitOrder( 0, eth.address, true, web3.utils.fromUtf8("ETH"), 100, 1 )
        )
        
              
})
    it("Should be enough eth to sell", async ()=> {
       
        let dex = await Dex.deployed()
        let eth = await Eth.deployed()
        await dex.addToken(web3.utils.fromUtf8("ETH"), eth.address)
        await eth.approve(dex.address, 100)
        await dex.deposit(100, web3.utils.fromUtf8("ETH"));
        await truffleAssert.passes(
             dex.limitOrder( 0, eth.address, false, web3.utils.fromUtf8("ETH"), 100, 1 )
    )
        
          
})
    it("Should be orderd from the highest to the lowest", async ()=> {
       
        let dex = await Dex.deployed()
        let link = await Link.deployed()  
        await dex.limitOrder( 0, link.address, true , web3.utils.fromUtf8("LINK"), 300, 1 )
        await dex.limitOrder( 0, link.address, true , web3.utils.fromUtf8("LINK"), 200, 1 )
        await dex.limitOrder( 0, link.address, true , web3.utils.fromUtf8("LINK"), 100, 1 )
        await dex.limitOrder( 0, link.address, true , web3.utils.fromUtf8("LINK"), 50, 1 )
        
        let orderbook = dex.getOrderBook(web3.utils.fromUtf8("LINK"), true )

        for (let i=0; i<orderbook.lenght -1; i = i+1 ) {
            assert(orderbook[i].amount>=orderbook[i+1].amount,"Buy Orderbook is not sorted!")
        }

    })

})
1 Like

i would imagine that this is a runtime error in your dex file. could be a typo. commetn out out test line by line until you find thr line where it stops working, then go from there. try ir yourself and if you csnt get it ill work on your code.

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

contract("Dex", accounts => {
    //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 highest to lowest starting at index 0

    it("user must have enough ETH deposited for a buy order", async () => {
        let dex = await Dex.deployed();
        let eth = await Eth.deployed();

        await dex.addToken(web3.utils.fromUtf8("ETH"), eth.address);
        await dex.deposit(web3.utils.fromUtf8("ETH"), 10);
        // Parameters: Order.trader, Order.side, Order.ticker, Order.amount, Order.price
        await dex.createLimitOrder(accounts[0], Side.BUY, web3.utils.fromUtf8("LINK"), 5, 2);

        let ordersBuy = await dex.getOrderbook(web3.utils.fromUtf8("LINK"), Side.BUY);
        for (const order of ordersBuy) {
            let balance = await wallet.balances(order.trader, web3.utils.fromUtf8("ETH"));
            assert.ok(balance >= (order.price * order.amount));
        }
    });
    it("user must have enough tokens deposited for a sell order", async () => {
        let dex = await Dex.deployed();
        let link = await Link.deployed();

        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address);
        await dex.deposit(web3.utils.fromUtf8("LINK"), 10);
        // Parameters: Order.trader, Order.side, Order.ticker, Order.amount, Order.price
        await dex.createLimitOrder(accounts[0], Side.SELL, web3.utils.fromUtf8("LINK"), 10, 2);

        let ordersSell = dex.getOrderbook(web3.utils.fromUtf8("LINK"), Side.SELL);
        for (const order of ordersSell) {
            assert.ok(wallet.balances(order.trader, web3.utils.fromUtf8("LINK")) >= order.amount);
        }
    });
    it("BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {
        let dex = await Dex.deployed();
        
        // create 3 orders
        // Parameters: Order.trader, Order.side, Order.ticker, Order.amount, Order.price
        await dex.createLimitOrder(accounts[0], Side.BUY, web3.utils.fromUtf8("LINK"), 5, 1);
        await dex.createLimitOrder(accounts[0], Side.BUY, web3.utils.fromUtf8("LINK"), 5, 3);
        await dex.createLimitOrder(accounts[0], Side.BUY, web3.utils.fromUtf8("LINK"), 5, 2);
        
        let ordersBuy = dex.getOrderbook(web3.utils.fromUtf8("LINK"), Side.BUY);
        for (let i = 0; i < (ordersBuy.length - 1); i++) {
            assert.ok(ordersBuy[i].price >= ordersBuy[i + 1]);
        }
    });
    it("SELL order book should be ordered on price from highest to lowest starting at index 0", async () => {
        let dex = await Dex.deployed();

        // create 3 orders
        // Parameters: Order.trader, Order.side, Order.ticker, Order.amount, Order.price
        await dex.createLimitOrder(accounts[0], Side.SELL, web3.utils.fromUtf8("LINK"), 10, 1);
        await dex.createLimitOrder(accounts[0], Side.SELL, web3.utils.fromUtf8("LINK"), 10, 3);
        await dex.createLimitOrder(accounts[0], Side.SELL, web3.utils.fromUtf8("LINK"), 10, 2);
        
        let ordersSell = dex.getOrderbook(web3.utils.fromUtf8("LINK"), Side.SELL);
        for (let i = 0; i < (ordersSell.length - 1); i++) {
            assert.ok(ordersSell[i].price >= ordersSell[i + 1]);
        }
    });
    
})
1 Like

Hey. So this was one of the most annoying but also demanding assignments so far. I only understood the 3rd test function. I really donā€™t understand what other two are suppose to do. And while writing the test I had to actually develop the createLimitOrder() function, to see if the test is failing / passing properly. I think I almost broke my laptop during the tests. So for now Iā€™m done :slight_smile:

Dex_test.js - just one test that might take about a minute to finish
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
// const truffleAssert = require("truffle-assertions");

const linkTicker = web3.utils.fromUtf8("LINK");


const Type = {
    BUY: 0,
    SELL: 1
}

const Order = {
    trader: 0,
    amount: 1,
    price: 2
}

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

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

    describe("LINK", () => {
        it("2. Creates random number Orders Sells, between 10 - 20. Orders are sorted from bigger to smaller", async () => {
            const r = Math.floor(Math.random() * 10 + 10);

            for (let i=0; i<r; i++) {
                const price = Math.floor(Math.random() * 100 + 1);
                await dex.createLimitOrder(linkTicker, Type.SELL, 1, price, {from: accounts[0]});
            }

            const orderBook = await dex.getOrderBook(linkTicker, Type.SELL);

            for (let i=0; i<r-1; i++) {
                assert.isAtLeast(
                    Number(orderBook[i][Order.price]),
                    Number(orderBook[i+1][Order.price])
                );
            }
        });
    })
})

And the function

createLimitOrder()
    function createLimitOrder(bytes32 _ticker, Type _order, uint256 _amount, uint256 _price) public {
        Order[] storage orderBook_ = orderBook[_ticker][uint8(_order)];

        orderBook_.push(Order(msg.sender, _amount, _price));

        for (uint i=0; i<orderBook_.length; i++) {
            if (_price > orderBook_[i].price) {
                for (uint j=orderBook_.length-1; j>i; j--) {
                    orderBook_[j] = orderBook_[j-1];
                }

                orderBook_[i] = Order(msg.sender, _amount, _price);
                break;
            }
        }

    }

@mcgrane5 @thecil

Im getting an error with the dex.deposit eth function.

why is this failing ? I pulled the sample code from fillips github ( filipmartinsson / dex) in order to rule out config errors on my side so the code provided is actually from here ? do i have to change somehting in my config files ?

) Contract: Dex
       should throw an error if ETH balance is too low when creating BUY limit order:
     TypeError: dex.depositEth is not a function
      at Context.<anonymous> (test/dextest.js:15:13)
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
1 Like

hey @ckar. i will look into this for you in the morning is that ok

@mcgrane5 sure no problem. Let me know if you need more information from me.

As I can see from other replies to this assignment other students have created and imported a separate Eth token contract in order to complete this, I think this could maybe be related to the same error. Question is : is this necessary, afaik eth should not be required to be created as a separate token.

Thx for your help

Hey @ckar .

Well doesnā€™t the error say that dex.depositEth is not a function - usually when I see this kind of error it mostly means the depositEth() doesnt exist as a function. And Iā€™m watching the following video, where Filip is using this depositEth() function, however Iā€™m pretty sure he didnā€™t implement it yet.

What Iā€™m saying that I also noticed that depositEth() somehow didnā€™t exist there for me. And Iā€™ve been following lectures quite slowly.

When I looked into the code from Filip you provided. And there is no depositEth() on a Wallet.sol. Thereā€™s also no Dex.sol.

If you use this limit-order-test-assignment it should work

2 Likes

Thanks for your reply. This makes sense, I thought I had followed the code in the videos correctly but it makes sense that this function wasnā€™t implemented. I am pretty sure that the repo I cloned locally had all the contract files, probably the link i posted is wrong.

Iā€™ll check it out tonight and probably start over again completely, the lectures for this project are somewhat hard to follow.

Thanks again

3 Likes

@ckar. filip doesnt cod this function it was left out id say it was a mistake of thei rbehalf. The deposit eth function is payable see below you could use something like

function depositETH() public payable {
    require(msg.value > 0, "You cannot deposit a zero amount");
    
    balance[msg.sender]["ETH"] += msg.value;
}

alternatively you could make a mock token of ether which you could say could be akin to WETH instead. this way you would just copy your link contract into a new file and name it ETH and then tjust continue as nornal. It is probably better to to use the function above thoigh

2 Likes

Yeah it was a headache for me too. Especially that I continued with my own code. There is sth missing in the lectures about depositEth().

Also I couldnā€™t figure out why you canā€™t just use the deposit() with a ticker of ā€œETHā€. That throwed an error for me and after few tries I just left it as it was.

But yeah, for some reason Filip did implement a different to deposti except ETH.

1 Like