Assignment - Limit Order Test

const Dex = artifacts.require("Dex")
const DMED = artifacts.require("DMED")
const truffleAssert = require("truffle-assertions");
contract ("Dex", accounts => {
    it("User must have ETH such that deposited Eth >= buy order", async() => {
    let dex = await Dex.deployed()
    let Dmed = await DMED.deployed()
    await truffleAssert.reverts(
        dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 10,1)
    )
        dex.depositEth({value:10})
        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"),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 Dmed = await DMED.deployed()
            dex.addToken(web3.utils.fromUtf8("DMED"),Dmed.address)
            await truffleAssert.reverts(
                dex.addToken(web3.utils.fromUtf8("DMED"), Dmed.address, {from: accounts[0]})
)
        await Dmed.approve(dex.address, 500);
        await dex.deposit(100, web3.utils.fromUtf8("DMED"));
        await truffleAssert.passes(
            dex.sellLimitOrder(web3.utils.fromUtf8("DMED"), Dmed.address)
)
        })
        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 Dmed = await DMED.deployed()
            await Dmed.approve(dex.address, 1000);
            await dex.depositEth({value:4500});
            await dex.createLimitOrder(0,web3.utils.fromUtf8("DMED"), 1, 400)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 320)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 600)
            let orderbook=await dex.getOrderBook(web3.utils.fromUtf8("DMED"), 0)
            assert(orderbook.length>0);
            console.log(orderbook);
            for(i=0; i<orderbook.length - 1; i++){
                assert(orderbook[i].price >= orderbook.length[i+1].price, "Order book is not sorted")
            }
        })
        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 Dmed = await DMED.deployed()
            await Dmed.approve(dex.address, 1000);
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 400)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 320)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 600)
            let orderbook=await dex.getOrderBook(web3.utils.fromUtf8("DMED"), 0)
            assert(orderbook.length>0);
            console.log(orderbook);
            for(i=0; i<orderbook.length - 1; i++) {
                assert(orderbook[i].price >= orderbook.length[i+1].price, "Order book is not sorted")
            }
        })

    })
1 Like

So here are my smart contracts and tests and my error messages. I checked my code with @filip source code and a fellow smart contract developer and could not figure it out.
Wallet.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../@openzeppelin/contracts/access/Ownable.sol";

contract Wallet is Ownable {
    using SafeMath for uint256;
    struct Token {
        bytes32 ticker;
        address tokenAddress;
    }
    mapping(bytes32 => Token) public tokenMapping;
    bytes32[] public tokenList;
    mapping(address => mapping(bytes32 => uint256)) public balances;
    modifier tokenExists(bytes32 ticker) {
            require(tokenMapping[ticker].tokenAddress != address(0));
            _;


    }

    function addToken(bytes32 ticker, address tokenAddress) onlyOwner external {
        tokenMapping[ticker]=Token(ticker, tokenAddress);
        tokenList.push(ticker);
    }
    function deposit(uint amount, bytes32 ticker) external tokenExists(ticker) {
        require(tokenMapping[ticker].tokenAddress != address(0));
    IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);
            balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
    }


    function withdraw(uint amount, bytes32 ticker) external tokenExists(ticker) {
        require(balances[msg.sender][ticker]>= amount);
        //Cannot be 0, because then the token would not exist
    balances[msg.sender][ticker].sub(amount);
    IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);  
    }


}

Dex.sol (Tokens.sol)

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./Wallet.sol";
import "../@openzeppelin/contracts/utils/math/SafeMath.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.mul(price));
        }
        Order[] storage orders = OrderBook[ticker][uint(side)];
        orders.push(
            Order(nextOrderId, msg.sender, side, ticker, amount, price)
        );
        nextOrderId++;
    //bubble sort
    uint i = orders.length >0 ? orders.length -1:0;
        if(side==Side.BUY){
            i=0;
            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++;  
    }    
}


Testing js

const Dex = artifacts.require("Dex")
const DMED = artifacts.require("DMED")
const truffleAssert = require("truffle-assertions");
contract("Dex", accounts => {
it("should only be possible for owner to add tokens", async() => {
  let dex = await Dex.deployed()
  let Dmed = await DMED.deployed()
  await truffleAssert.passes(
    dex.addToken(web3.utils.fromUtf8("DMED"), Dmed.address, {from: accounts[0]})
  )
  await truffleAssert.reverts(
    dex.addToken(web3.utils.fromUtf8("DMED"), Dmed.address, {from: accounts[1]})
    )
    })
    it("should handle deposits correcty", async() => {
        let dex = await Dex.deployed()
        let Dmed = await DMED.deployed()
        await Dmed.approve(dex.address, 500);
        await dex.deposit(100, web3.utils.fromUtf8("DMED"));
        let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("DMED"))
        assert.equal(balance.toNumber(100), 100 ) 
        await truffleAssert.passes(
            dex.addToken(web3.utils.fromUtf8("DMED"), Dmed.address, {from: accounts[0]})
        )
        await truffleAssert.reverts(
          dex.addToken(web3.utils.fromUtf8("DMED"), Dmed.address, {from: accounts[1]})
          )
          })
          it("should handle faulty withdrawals correctly", async() => {
              let dex = await Dex.deployed()
              let Dmed = await DMED.deployed()
              await truffleAssert.reverts(dex.withdraw(500, web3.utils.fromUtf8("DMED")))
          })
          it("should handle correct withdrawals correctly", async() => {
            let dex = await Dex.deployed()
            let Dmed = await DMED.deployed()
            await truffleAssert.passes(dex.withdraw(100, web3.utils.fromUtf8("DMED")))
        })
    })


Dex testing

const Dex = artifacts.require("Dex")
const DMED = artifacts.require("DMED")
const truffleAssert = require("truffle-assertions");
contract ("Dex", accounts => {
    it("User must have ETH such that deposited Eth >= buy order", async() => {
        let dex = await Dex.deployed()
        let Dmed = await DMED.deployed()
        await truffleAssert.reverts(
            dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 10,1)
    )
    dex.depositEth({value:10})
    await truffleAssert.passes(
         dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"),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 Dmed = await DMED.deployed()
            
        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.fromUtf8("DMED"),10, 1)
)
        await Dmed.approve(dex.address, 500);
        await dex.deposit(100, web3.utils.fromUtf8("DMED"));
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("DMED"),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 Dmed = await DMED.deployed()
            await Dmed.approve(dex.address, 1000);
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 300)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 120)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 200)
            let orderbook= await dex.getOrderBook(web3.utils.fromUtf8("DMED"), 0)
            for(i=0; i<orderbook.length - 1; i++){
                const element = array[index];
                assert(orderbook[i] >= orderbook[i+1])
            }
        })
        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 Dmed = await DMED.deployed()
            await Dmed.approve(dex.address, 1000);
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 300)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 100)
            await dex.createLimitOrder(0, web3.utils.fromUtf8("DMED"), 1, 200)
            let orderbook=await dex.getOrderBook(web3.utils.fromUtf8("DMED"), 1)
            for(i=0; i<orderbook.length - 1; i++) {
                const elemtent = array[index];
                assert(orderbook[i] >= orderbook[i+1])
            }
        })

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

contract("Dex", (accounts) => {
  let Side = {
    BUY: 0,
    SELL: 1,
  };

  it("should only be possible for the owner to add tokens", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();
    await truffleAssert.passes(
      dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {
        from: accounts[0],
      })
    );

    await truffleAssert.passes(
      dex.addToken(web3.utils.fromUtf8("ETH"), link.address, {
        from: accounts[0],
      })
    );

    await truffleAssert.reverts(
      dex.addToken(web3.utils.fromUtf8("AAVE"), link.address, {
        from: accounts[1],
      })
    );
  });

  it("should not allow the same token ticker to be added twice by the owner", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

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

  it("should handle deposits correctly", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    await link.approve(dex.address, 500);

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

    let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));

    assert.equal(balance.toNumber(), 100);
  });

  it("should handle faulty withdrawals correctly", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    await truffleAssert.reverts(dex.withdraw(500, web3.utils.fromUtf8("LINK")));
  });

  it("should handle correct withdrawals correctly", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    await truffleAssert.passes(dex.withdraw(100, web3.utils.fromUtf8("LINK")));
  });

  it("should be able to place a BUY limit order where ETH balance > BUY price", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    await truffleAssert.reverts(
      dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, 30, 2)
    );

    dex.depositEth({ value: 500 });

    // 0 means BUY
    await truffleAssert.passes(
      dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, 30, 0)
    );
  });

  it("should be able to place a SELL limit order where token balance >= SELL order amount", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    await link.approve(dex.address, 500);

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

    // 1 means SELL
    await truffleAssert.passes(
      dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.SELL, 50, 4, {
        from: accounts[0],
      })
    );

    await truffleAssert.reverts(
      dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.SELL, 150, 4, {
        from: accounts[0],
      })
    );
  });

  it("should ensure the BUY order book is ordered on price from highest to lowest", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, 50, 4, {
      from: accounts[0],
    });
    dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, 100, 4, {
      from: accounts[0],
    });
    dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, 80, 4, {
      from: accounts[0],
    });

    let orderBook = await dex.getOrderBook(
      web3.utils.fromUtf8("LINK"),
      Side.BUY
    );
    // let orderBook = [100, 50, 250];

    for (let i = 0; i < orderBook.length - 1; i++) {
      console.log("i: " + i);
      for (let j = i + 1; j < orderBook.length; j++) {
        console.log("j: " + j);
        assert(orderBook[i] > orderBook[j]);
      }
    }
  });

  it("should ensure the SELL order book is ordered on price from lowest to highest", async () => {
    let dex = await Dex.deployed();
    let link = await Link.deployed();

    dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.SELL, 10, 4, {
      from: accounts[0],
    });
    dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.SELL, 20, 4, {
      from: accounts[0],
    });
    dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.SELL, 5, 4, {
      from: accounts[0],
    });

    let orderBook = await dex.getOrderBook(
      web3.utils.fromUtf8("LINK"),
      Side.SELL
    );
    // let orderBook = [5, 20, 30];

    for (let i = 0; i < orderBook.length - 1; i++) {
      console.log("i: " + i);
      for (let j = i + 1; j < orderBook.length; j++) {
        console.log("j: " + j);
        assert(orderBook[i] < orderBook[j]);
      }
    }
  });
});

1 Like

Hi @Samuel1

In another topic you posted an issue with your deposit function, function that reverts every time it is called.
Does this happen in your tests too?

In case it does make sure to create an instance of your contract only once (for example in a ā€˜beforeā€™ function) and do not create a new one each test.

let dex
let Dmed

async function before () {
     
     dex = await Dex.deployed()
     Dmed = await DMED.deployed()

}

So I inserted that in the DMED migration file, like so:

const DMED = artifacts.require("DMED");

module.exports = function(deployer) {
  deployer.deploy(DMED);
}
let dex
let Dmed

async function before () {
     
     dex = await Dex.deployed()
     Dmed = await DMED.deployed()

};

However my final four tests failed due to revert and substring.param function error. I changed my testing code and immulated a fellow student in the course with lots of coding experience but still couldnā€™t find a passing test. Here is my failing code:


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

contract ("Dex", accounts => {
    let Side = {
        BUY:0,
        SELL:1,
    };
    //The user must have  deposited such that deposited eth >= buy order value
    it("should be able to place a BUY limit order where ETH balance > BUY price", async() => {
        let dex = await Dex.deployed();
        let Dmed = await DMED.deployed();
         await truffleAssert.reverts(
         dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.BUY, 30, 2)
        );
        dex.depositETH({value:100});
        //0 means BUY
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.BUY, 30, 0)
         );
     });
        //The user must have enough tokens deposited such that token balance >= sell order amount
    it("should be able to to place a SELL limit order where token balance >= SELL order ", async() => {
        let dex = await Dex.deployed();
        let Dmed = await DMED.deployed();
        await Dmed.approve(dex.address, 500);
        await dex.deposit(100,web3.utils.fromUtf8("DMED"));
        //1 means SELL
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.SELL, 50, 4, {
                from:accounts[0],
            })
        );
        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.SELL, 150, 4, {
                from:accounts[0],

    })
        );
       
    });
        //The BUY order book should be ordered on price from highest to lowest starting at index 0
        it("should ensure the BUY Order book is ordered on price from highest to lowest starting at index 0", async() => {
            let dex = await Dex.deployed();
            let Dmed = await DMED.deployed();
            await dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.BUY, 10, 4, {
                from: accounts[0],
            });
            await dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.BUY, 20, 4, {
                from:accounts[0],
            });
            await dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.BUY, 8, 4, {
                from:accounts[0],
            });
            let orderBook= await dex.getOrderBook(
            web3.utils.fromUtf8("DMED"),
            Side.BUY
            );
        for (let i = 0; i < orderBook.length -1; i++) {
            console.log("i: " + i);
            for(let j = i + 1; j < orderBook.length; j++) {
                console.log("j: "+ j);
                assert(orderBook[i] < orderBook[j]);
            }
        }
    });

        //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 Dmed = await DMED.deployed();
            await dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.SELL, 10, 4, {
                from: accounts[0],
            });
            await dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.SELL, 20, 4, {
                from:accounts[0],
            });
            let orderBook= await dex.getOrderBook(
                web3.utils.fromUtf8("DMED"),
                Side.SELL
                );
            for (let i = 0; i < orderBook.length -1; i++) {
                console.log("i: " + i);
                for(let j = i + 1; j < orderBook.length; j++) {
                    console.log("j: "+ j);
                    assert(orderBook[i] < orderBook[j]);
            }
        }
        });

    });

So Deposit works in the first series of Testing, but when depositing ETH I am given this error.
image

dextest.js

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

contract("Dex", accounts => {
    it("must have enough balance to create buy order", async () => {
        let dex = await Dex.deployed();
        let bnb = await Bnb.deployed();
        await dex.addToken(web3.utils.fromUtf8(bnb.symbol()), bnb.address, {from: accounts[0]})
        // await bnb.approve(dex.address, 500);
        
        await truffleAssert.reverts(
             dex.createLimitOrder(0, web3.utils.fromUtf8("BNB"), 10, web3.utils.toWei('100', 'ether'), {from: accounts[0]})
        );

        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("BNB"), 10, web3.utils.toWei('1', 'ether'), {from: accounts[0]})
        );

       let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("BNB"), 0);
       assert.equal( orderBook[0].id, 0 );
       assert.equal( orderBook[0].trader, accounts[0] );
       assert.equal( orderBook[0].side, 0 );
       assert.equal( orderBook[0].ticker, 0x424e420000000000000000000000000000000000000000000000000000000000);
       assert.equal( orderBook[0].amount, 10 );
       assert.equal( orderBook[0].price, web3.utils.toWei('1', 'ether') );
    })

    it("must have enough tokens to create sell order", async () => {
        let dex = await Dex.deployed();
        let bnb = await Bnb.deployed();
        dex.addToken(web3.utils.fromUtf8("BNB"), bnb.address, {from: accounts[0]})
        // await bnb.approve(dex.address, 500);
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("BNB"), 10, web3.utils.toWei('2', 'ether'), {from: accounts[0]})
        );

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

        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("BNB"), 10, web3.utils.toWei('2', 'ether'), {from: accounts[0]})
       );

       let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("BNB"), 1);
       assert.equal( orderBook[0].id, 0 );
       assert.equal( orderBook[0].trader, accounts[0] );
       assert.equal( orderBook[0].side, 1 );
       assert.equal( orderBook[0].ticker, 0x424e420000000000000000000000000000000000000000000000000000000000);
       assert.equal( orderBook[0].amount, 10 );
       assert.equal( orderBook[0].price, web3.utils.toWei('2', 'ether') );
    })

    it("must have the buy order book sorted from biggest to lowest", async () => {
        let dex = await Dex.deployed();
        let bnb = await Bnb.deployed();
        // await bnb.approve(dex.address, 500);
        dex.createLimitOrder(0, web3.utils.fromUtf8("BNB"), 10, web3.utils.toWei('1', 'ether'), {from: accounts[0]})
        dex.createLimitOrder(0, web3.utils.fromUtf8("BNB"), 1, web3.utils.toWei('2', 'ether'), {from: accounts[0]})
        dex.createLimitOrder(0, web3.utils.fromUtf8("BNB"), 5, web3.utils.toWei('3', 'ether'), {from: accounts[0]})

        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("BNB"), 0);
        assert.equal( orderBook[0].price, web3.utils.toWei('3', 'ether'));
        assert.equal( orderBook[1].price, web3.utils.toWei('2', 'ether'));
        assert.equal( orderBook[2].price, web3.utils.toWei('1', 'ether'));
        assert.equal( orderBook[3].price, web3.utils.toWei('1', 'ether'));
    })

    it("must have the sell order book sorted from lowest to biggest", async () => {
        let dex = await Dex.deployed();
        let bnb = await Bnb.deployed();
        // await bnb.approve(dex.address, 500);
        dex.createLimitOrder(1, web3.utils.fromUtf8("BNB"), 10, web3.utils.toWei('1', 'ether'), {from: accounts[0]})
        dex.createLimitOrder(1, web3.utils.fromUtf8("BNB"), 1, web3.utils.toWei('2', 'ether'), {from: accounts[0]})
        dex.createLimitOrder(1, web3.utils.fromUtf8("BNB"), 5, web3.utils.toWei('3', 'ether'), {from: accounts[0]})

        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("BNB"), 1);
        assert.equal( orderBook[0].price, web3.utils.toWei('1', 'ether'));
        assert.equal( orderBook[1].price, web3.utils.toWei('2', 'ether'));
        assert.equal( orderBook[2].price, web3.utils.toWei('2', 'ether'));
        assert.equal( orderBook[3].price, web3.utils.toWei('3', 'ether'));
    })
})

Solution to pass tests
Dex.sol

pragma solidity ^0.8.0;
// pragma experimental ABIEncoderV2;

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[])) public orderBook;

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

    function sortOrderBook(Side side, bytes32 ticker) private {
        uint i = 0;
        uint j = 0;
        Order[] storage orderBookReference = orderBook[ticker][uint(side)];

        for ( i = 0; i < orderBookReference.length - 1; i++ )
        {
            for ( j = 0; j < orderBookReference.length - i - 1; j++)
            {
                if ( side == Side.BUY )
                {
                    if (orderBookReference[j].price < orderBookReference[j+1].price)
                    {
                        Order memory tempOrder = orderBookReference[j];
                        orderBookReference[j] = orderBookReference[j+1];
                        orderBookReference[j+1] = tempOrder;
                    } 
                }
                else
                {
                    if (orderBookReference[j].price > orderBookReference[j+1].price)
                    {
                        Order memory tempOrder = orderBookReference[j];
                        orderBookReference[j] = orderBookReference[j+1];
                        orderBookReference[j+1] = tempOrder;
                    } 
                }
            }
        }
    }

    function createLimitOrder(Side side, bytes32 ticker, uint amount, uint price) public {
        if ( side == Side.BUY )
        {
            require(msg.sender.balance >= ( amount * price ));
        }
        else
        {
            require(balances[msg.sender][ticker] >= amount , "Not enough tokens");
        }
        
        Order memory order;
        order.id = orderBook[ticker][uint(side)].length;
        order.trader = msg.sender;
        order.side = side;
        order.ticker = ticker;
        order.amount = amount;
        order.price = price;
        orderBook[ticker][uint(side)].push(order);
        sortOrderBook(side, ticker);
    }

}
1 Like

Hi @Samuel1

So I inserted that in the DMED migration file, like so

This one is not correct :slight_smile: The before function have to be wrote in the test file, not in the migration one.

In your test:


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

contract ("Dex", accounts => {
    let Side = {
        BUY:0,
        SELL:1,
    };

let dex
let Dmed

async function before () {
     
     dex = await Dex.deployed()
     Dmed = await DMED.deployed()

};


it ( [ ......... ]) {
   
   await truffleAssert.reverts(
         dex.createLimitOrder(web3.utils.fromUtf8("DMED"), Side.BUY, 30, 2)
        );
        dex.depositETH({value:100});

}


etc...

Cheers,
Dani

Here 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 have enough ETH deposited for buy order", 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 eth.approve(dex.address, 500)
        await dex.deposit(100, web3.utils.fromUtf8("ETH"))
        await truffleAssert.passes(
            dex.createLimitOrder(0, accounts[0], 0,  web3.utils.fromUtf8("ETH"), 100, 2900)
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(0, accounts[0], 0,  web3.utils.fromUtf8("ETH"), 200, 2900)
        )
    })
    it("should have enough token for sell order", async() => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let eth = await ETH.deployed()
        await dex.addToken(web3.utils.fromUtf8("Link"), link.address)
        await link.approve(dex.address, 500)
        await dex.deposit(400, web3.utils.fromUtf8("Link"))
        await truffleAssert.passes(
            dex.createLimitOrder(0, accounts[0], 0,  web3.utils.fromUtf8("ETH"), 100, 40)
        )
    })
    it("should be descendent for buy order", 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 eth.approve(dex.address, 500)
        await dex.deposit(200, web3.utils.fromUtf8("ETH"))
        await dex.createLimitOrder(0,accounts[0],0,web3.utils.fromUtf8("Link"),10,40)
        await dex.createLimitOrder(1,accounts[0],0,web3.utils.fromUtf8("Link"),10,30)
        await dex.createLimitOrder(2,accounts[0],0,web3.utils.fromUtf8("Link"),10,20)
        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("Link"),0)
        assert(orderBook[0].price >= orderBook[1].price, 'Not in order')
        assert(orderBook[1].price >= orderBook[2].price, 'Not in order')
    })
    it("should be ascendent for sell order", 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 dex.deposit(200, web3.utils.fromUtf8("Link"))
        await dex.createLimitOrder(0,accounts[0],1,web3.utils.fromUtf8("ETH"),10,20)
        await dex.createLimitOrder(1,accounts[0],1,web3.utils.fromUtf8("ETH"),10,30)
        await dex.createLimitOrder(2,accounts[0],1,web3.utils.fromUtf8("ETH"),10,40)
        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("ETH"),1)
        assert(orderBook[0].price <= orderBook[1].price, 'Not in order')
        assert(orderBook[1].price <= orderBook[2].price, 'Not in order')
    })
1 Like

Improved answer after implementing bubble sort.

Dex Code:

pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;

import "./Wallet.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.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[])) 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) tokenExist(ticker) public {
        if(side == Side.BUY){
            require(balances[msg.sender]["ETH"] >= amount.mul(price), "Not enough balance");
        }
        else if(side == Side.SELL){
            require(balances[msg.sender][ticker] >= amount, "Not enough balance");
        }
        //require(trader == msg.sender);
        //require();

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

        orders.push(
            Order(nextOrderId,msg.sender,side,ticker,amount,price)
            );

        uint length = orders.length;

        if (side == Side.BUY && length > 1){
            for(uint i = 1; i <= length-1; i++){
                if(orders[length-i].price >= orders[length-i-1].price){
                    Order memory tempOrder =  orders[length-i];
                    orders[length-i] = orders[length-i-1];
                    orders[length-i-1] = tempOrder;
                }
            }
        }
        else if (side == Side.SELL && length > 1){
            for(uint i = 1; i <= length-1; i++){
                if(orders[length-i].price <= orders[length-i-1].price){
                    Order memory tempOrder =  orders[length-i];
                    orders[length-i] = orders[length-i-1];
                    orders[length-i-1] = tempOrder;
                }
            }
        }

        nextOrderId++;

    }
    
}

Test code:

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 have enough ETH deposited for buy order", 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 eth.approve(dex.address, 100000)
        await dex.deposit(100000, web3.utils.fromUtf8("ETH"))
        await truffleAssert.passes(
            dex.createLimitOrder(0,  web3.utils.fromUtf8("ETH"), 10, 2900)
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(0,  web3.utils.fromUtf8("ETH"), 200, 2900)
        )
    })
    it("should have enough token for sell order", async() => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let eth = await ETH.deployed()
        await dex.addToken(web3.utils.fromUtf8("Link"), link.address)
        await link.approve(dex.address, 500)
        await dex.deposit(400, web3.utils.fromUtf8("Link"))
        await truffleAssert.passes(
            dex.createLimitOrder(1,  web3.utils.fromUtf8("Link"), 100, 40)
        )
    })
    it("should be descendent for buy order", 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 eth.approve(dex.address, 10000)
        await dex.deposit(10000, web3.utils.fromUtf8("ETH"))
        await dex.createLimitOrder(0,web3.utils.fromUtf8("Link"),10,40)
        await dex.createLimitOrder(0,web3.utils.fromUtf8("Link"),10,30)
        
        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("Link"),0)
        assert(orderBook.length > 1, "Orderbook is short")
        for (let i = 0; i < orderBook.length - 1; i++){
            assert(orderBook[i].price >= orderBook[i+1].price, 'Not in order')
        }
    })
    it("should be ascendent for sell order", 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 dex.deposit(200, web3.utils.fromUtf8("Link"))
        await dex.createLimitOrder(1,web3.utils.fromUtf8("Link"),10,30)
        await dex.createLimitOrder(1,web3.utils.fromUtf8("Link"),10,20)
        await dex.createLimitOrder(1,web3.utils.fromUtf8("Link"),10,40)
        let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("Link"),1)
        assert(orderBook.length > 1, "Orderbook is short")
        for (let i = 0; i < orderBook.length - 1; i++){
            assert(orderBook[i].price <= orderBook[i+1].price, 'Not in order')
        }
    })

})
2 Likes

FOR THOSE WHO DONT READ ā€” REMIX CAN SAVE A LOT OF TIME WHEN DEVELOPING COMPLEX FUNCTIONS LIKE YOUR OWN SORTING ALGORITHM)

Wow sure took me awhile to do this assignment but finally finished it. This will be a lengthy post of my approach and i hope that anyone who reads this will find my story useful and perhaps in some way or another. Ive a few tips to share from my hours of debugging for those of you who are curious. I didnt watch any of Filips videos so my solution may be a bit different. I wrote my own sorting algorithm and implemented (see code below) it although i had to get rid of the double mapping and made a struct for each the buy and sell orders along with two singular mappings to map to the buy and sell. Not as efficient but i wanted to implement my sorting algorithm. Im curious to see how filip does the bubble sort with the double map

hey @dan-i i actually have a question for you. Is it an industry common practice to write your tests before hand in code like filip wanted us to do. Because i was not able to do that. I didnt know what i needed until i can actually have code. So thats why i wrote my functions first it just makes way more sens to me and i dont know why filip did it like that. I curious to know if this is a common practice

Basically i didnt know how to go about writing tests before actually coding the function to the etst with. Im not able to think that far ahead of myself and i thought well im just going to code the limit order function first myself. And this is where things took awhile. So basically The biggest issue was the sorting function. I think Filip goes on to use bubble sort but i actually spend most of my time writing my own sorting function. I based it off a sorting fucntion called XOR sort which is pretty quick and i modified some base code i found on a medium post about sorting algorithms in js. I wanted to use a better sorting algorithm than bubble as its not the best by any meansā€¦ So sorting an array is fine but it took me ages to figure our how to sort the structs array by price.

(TIP #1 USE REMIX)
This is probably the best thing i did. Sorting algorithms can be hard especially in soliity theres no handy print statment that you can use for incremental testing and truffle is very slow to use if your constantlly deploying typing in the same 50 commands just so you can actually do some tests just to see that your algorithm isn working to yer again go back change it rinse and repeat. This can be very slow and innefficient. So what i did was i developed my sorting algorithm in remix. I first started out just trying to get it to work on its own on a regular array. Then after that i changed the array to a struct and tried to get it working to sort structs by attribute and i think my solution is pretty good. Then when i has that working i just copied it from remix into my dex.sol file and changed the variable names to make it compatible and hey presto my sorting algorithm was working.

So i thought i was done but 70% of the work was still ahead of me. I then wrote the first two tests now that my limit order function was finished and they were fine but when i got to testing the order of the orderbook things took a horrible turn. My tests woudnt work. I then began to change things up. I didnt know how to make the double mapping to the orderbook work with my sorting algorithm so i sperated my order structs into two. A buyOrderStruct and a sellOrderStruct. This now means that i have two limit order fucntions. Nmaely a buy limit and sell limit func. This does make more code but for now it will do i just wanted to see them green check marks more than anything else. So i changed my dex.sol file and added everything i needed to and i was pretty much there. For awhile my tests were failing even though i know they shouldnt have and even to prroove to myself i opened up the console and did the commands manually.

I feel that the truffle assertions uses awaits and all that which im not yet fully used to as i have never really programmed in js before this course and eth game programming so id say that had a few reasons to do with why my tests would run sometimes.

(TIP #2 TEST TEST TEST )
This is my second tip. Which is about the testing. Dont just run a test once. if you get a fail dont immediately try to go to google run it again. I found through my many hours of debugging that my tetsts would just randomly fail for no reason even though that same test passed like 5 minutes ago. This was mainly only with the limit order array checks but i think its to do with your code being save and the mst updated version being used. So the moral is to test test test the same thing many times until your absolutley sure that its working. Truffle assert seems like it just glitches sometimes so be sure to do multiple tetsts. Computers are werd.

And with that i was finished. I spend nearly 2 days doing all o that myself. The most time was done on the sorting algorithm as i tried my own homemade version before i used decided to use quick sort and my own algorithm ran but was way to slow and didnt work on the structs consistently. Im cant wait to finally watch Filips slution now. Ive branched off a lot from his dex conttratc what with my two seperate structs and all so id say ill try finish my own version and do his version at the same time. Obvioulst filips way will be better and im curious to see how he implemented bubble sort for the double mapping but going out on your own is good and i learned so much. Anyway my code is below and i hope some can find this post useful whether as motivation through the debugging phase or the two tips i found useful. The remix one especially probably saved me a days coding

//the user must have ETH deposited such that eth >= buy order value
//the user must have enough tokens deposited such that the token balance >= sell order price
//the buy order book should be ordered from highest to lowest starting and index 0

//we want to bring in our dex file which also
//contains the dex.Since dex inherits dex
//we do not need to require dex here
const Dex = artifacts.require("Dex");
const Token = artifacts.require("Token");
const Eth = artifacts.require("Eth");


//we need to import mocha to run tests
var truffleAssert = require("truffle-assertions");

//truffle uses mocha to run tests. Each time we define a contract
//statment like below an instance of our deployment is made andinside
//che contratc function we write our tests
contract('Dex', accounts => {
    //initialise test with 'it'
    it("User must have balance of ETH greater than buy order value", async () => {

        //deploye tokens and eth
        let dex = await Dex.deployed()
        let eth = await Eth.deployed()
        //we need to add eth before doing check
        await dex.addToken("ETH", eth.address, {from: accounts[0]});
        //then we approve and deposit as normal deposit
        await eth.approve(dex.address, 10000)
        await dex.deposit(1000, "ETH")
        
        //now we can do the check to see if we have enough eth for the
        //buy order value
        await truffleAssert.passes(
            await dex.createLimitBuyOrder("ETH", 10, 1)
        )

        //we need to also test if creating a limit order with insufficient balance fails
        await truffleAssert.reverts(
            dex.createLimitBuyOrder("ETH", 10000, 1)
        )
    })

     //the user must have enough tokens deposited such that the token balance >= sell order price
     it("User must have enough tokens deposited greater than sell order value", async () => {

        //deploye tokens and eth
        let dex = await Dex.deployed()
        let token = await Token.deployed()
        //we need to add eth before doing check
        await dex.addToken("LINK", token.address, {from: accounts[0]});
        //then we approve and deposit as normal deposit
        await token.approve(dex.address, 1000000);
        await dex.deposit(2000, "LINK");

        //now we can do the check to see if we have enough eth for the
        //buy order value
        await truffleAssert.passes(
            await dex.createLimitBuyOrder("LINK", 10, 1)
        )

        //we need to also test if creating a limit order with insufficient balance fails
        await truffleAssert.reverts(
            dex.createLimitBuyOrder("LINK", 10000, 1)
        )
    })

    it("The orders in the BuyOrder book must be in accensding order by price", async () => {

        
        //deploye tokens and eth
        let dex = await Dex.deployed()
        let eth = await Eth.deployed()
        //we need to add eth before doing check
        await dex.addToken("ETH", Eth.address)
        //then we approve and deposit as normal deposit
        await eth.approve(dex.address, 1000000)
        await dex.deposit(2000, "ETH")
        
        dex.createLimitBuyOrder("ETH", 50, 5)
        dex.createLimitBuyOrder("ETH", 20, 5)
        dex.createLimitBuyOrder("ETH", 40, 2)
        dex.createLimitBuyOrder("ETH", 30, 1)
        dex.createLimitBuyOrder("ETH", 10, 4)
    
        await dex.sortBuyOrder(0, 6)
        let buyOrder = await dex.getBuyOrders()
        for (let i = 0; i < buyOrder.length - 1; i++) {
            //console.log(buyOrder[i].price)
            await assert(buyOrder[i].price <= buyOrder[i + 1].price)
       }
       //truffleAssert.paases(buyOrder[0] == buyOrder[1])

        // let order = await dex.getOrder1();
        //console.log(order[0]);      
        
    })

    it("The orders in the Sellr book must also be in accensding order by price", async () => {

        
        //deploye tokens and eth
        let dex = await Dex.deployed()
        let link = await Token.deployed()
        //we need to add eth before doing check
        await dex.addToken("LINK", link.address)
        //then we approve and deposit as normal deposit
        await link.approve(dex.address, 1000000)
        await dex.deposit(2000, "LINK")
        
        dex.createLimitSellOrder("LINK", 40, 2)
        dex.createLimitSellOrder("LINK", 10, 5)
        dex.createLimitSellOrder("LINK", 35, 3)
        dex.createLimitSellOrder("LINK", 37, 6)
        dex.createLimitSellOrder("LINK", 50, 4)
    
        await dex.sortSellOrder(0, 4)
        let sellOrder = await dex.getSellOrders()
        for (let i = 0; i < sellOrder.length - 1; i++) {
            //console.log(sellOrder[i].price)
            await assert(sellOrder[i].price <= sellOrder[i + 1].price)
       }
       //truffleAssert.paases(buyOrder[0] == buyOrder[1])

        // let order = await dex.getOrder1();
        //console.log(order[0]);      
        
    })

    it("Orders can only be made for supported Tokens", async () => {

        //deploye tokens and eth
        let dex = await Dex.deployed()
        let link = await Token.deployed()
        let eth = await Token.deployed()
        //we need to add eth before doing check
        await dex.addToken("LINK", link.address)
        //then we approve and deposit as normal deposit
        await link.approve(dex.address, 1000000)
        await eth.approve(dex.address, 1000000)
        await dex.deposit(2000, "LINK")
        await dex.deposit(2000, "ETH")
        
        await truffleAssert.passes(await dex.createLimitBuyOrder("ETH", 10, 2))
        await truffleAssert.passes(await dex.createLimitSellOrder("LINK", 10, 2))
        await truffleAssert.reverts( dex.createLimitBuyOrder("FAKE", 10, 2))
        await truffleAssert.reverts(dex.createLimitSellOrder("FAKE", 10, 2))
        
    })

})

 

And here is my dex.sol contrac with my version of quicksort. I thing i did a good job on this algorithm i was very happy when i got it working because ive never sorted a struct before. Hope someone may find it useful

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

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

// import "../node_modules/@openzeppelin//contracts/access/Ownable.sol";

contract Dex is Wallet {

    enum Side {
        BUY,
        SELL
    }

    struct BuyOrder {

        address trader;
        string ticker;
        uint256 amount;
        uint id;
        uint price;
    }

    struct SellOrder {

        address trader;
        string ticker;
        uint256 amount;
        uint id;
        uint price;
    }

    mapping(uint => uint) helper;

    BuyOrder[] public buyOrders;
    SellOrder[] public sellOrders;
    
    
    mapping(string => uint) order_id;

    //need double mapping to map a token to a buy/sell buyOrders
    mapping(string => mapping(uint => BuyOrder[])) orderBook;

    function getOwner() public view override returns(address) {
        return msg.sender;
    }

    // function getOrder(string memory ticker) public view returns(BuyOrder[] memory) {
    //     //return buyOrders book
    //     return (orderBook[ticker][uint(side)]);
    // }

    function getBuyOrders() public view returns(BuyOrder[] memory) {
        //return buyOrders book
        //return buyOrders[0].amount;
        return (buyOrders);
    }

    function getSellOrders() public view returns(SellOrder[] memory) {
        //return buyOrders book
        //return buyOrders[0].amount;
        return (sellOrders);
    }

    //to create a limit buyOrders we need to pass in the token ticker, whethere it is a buy or sell buyOrders
    //the amount of tokens for the buyOrders and at what price we want to buy and sell at
    function createLimitBuyOrder(string memory ticker, uint amount, uint price ) public {

        uint id = 0;
        //handle first case for buyOrders if it is buy(We must have enough eth to make the buyOrders)
       
        //the balances mapping is inherited from our wallet contract
        require(balances[msg.sender]["ETH"] >= amount * price);

       
            //here we must require that enouhg tokens are deposited to meet the demand
            //of the sell buyOrders.amount
        require(balances[msg.sender][ticker] >= amount);
        
        BuyOrder memory newOrder;
        newOrder.trader = msg.sender;
        newOrder.ticker = ticker;
        newOrder.amount = amount;
        newOrder.id = id;
        newOrder.price = price;

        buyOrders.push(newOrder);
        id++;

    }

    //to create a limit sellOrders we need to pass in the token ticker, whethere it is a buy or sell buyOrders
    //the amount of tokens for the buyOrders and at what price we want to buy and sell at
    function createLimitSellOrder(string memory ticker, uint amount, uint price ) public {

        uint id = 0;
        //handle first case for buyOrders if it is buy(We must have enough eth to make the buyOrders)
       
        //the balances mapping is inherited from our wallet contract
        require(balances[msg.sender]["ETH"] >= amount * price);

       
            //here we must require that enouhg tokens are deposited to meet the demand
            //of the sell buyOrders.amount
        require(balances[msg.sender][ticker] >= amount);
        
        SellOrder memory newOrder;
        newOrder.trader = msg.sender;
        newOrder.ticker = ticker;
        newOrder.amount = amount;
        newOrder.id = id;
        newOrder.price = price;

        sellOrders.push(newOrder);
        id++;

    }
    
     

     //function to sort orderbook by price of token (XOR swap)
    function sortBuyOrder(int low, int high) public {
      //if the array is greater than length one we enter here
      if (low < high) {

        //define arbritarty pivot i just use the last array element
        int pivot = int(buyOrders[uint(high)].price);
        int i = (low-1); // index of smaller element
        for (int j = low; j < high; j++) {
          //important to convert pivot int. 
          if (int(buyOrders[uint(j)].price) <= pivot) {
            i++;
           //Note that the code does not swap the integers passed immediately, but first checks if their 
           //addresses are distinct. This is because, if the addresses are equal, the algorithm will fold to a 
           //triple *x ^= *x resulting in zero.
            if (i != j) {
              //i and j or i+1 and high might be the same address in the array. 
              //we need to avoid avoid that edge case we acn use the ^= operator:
              buyOrders[uint(i)].price ^= buyOrders[uint(j)].price;
              buyOrders[uint(j)].price ^= buyOrders[uint(i)].price;
              buyOrders[uint(i)].price ^= buyOrders[uint(j)].price;
            }
          }
        }
        if (i+1 != high) {
          //we do the same checks here again to make sure i + 1 is 
         //is not the same address
          buyOrders[uint(i+1)].price ^= buyOrders[uint(high)].price;
          buyOrders[uint(high)].price ^= buyOrders[uint(i+1)].price;
          buyOrders[uint(i+1)].price ^= buyOrders[uint(high)].price;
        }

        //update pivot
        pivot = i + 1;
        //Much like quicksort we now do a recursive call which calls soet again
        //and sorts our list until base case when the list in finished being sorted
        sortBuyOrder(low, pivot-1);
        sortBuyOrder(pivot+1, high);
      
      }
    }

      function sortSellOrder(int low, int high) public {
        if (low < high) {
          int pivot = int(sellOrders[uint(high)].price);
          int i = (low-1); // index of smaller element
          for (int j = low; j < high; j++) {
            if (int(sellOrders[uint(j)].price) <= pivot) {
              i++;
              if (i != j) {
                sellOrders[uint(i)].price ^= sellOrders[uint(j)].price;
                sellOrders[uint(j)].price ^= sellOrders[uint(i)].price;
                sellOrders[uint(i)].price ^= sellOrders[uint(j)].price;
              }
            }
          }
          if (i+1 != high) {
            sellOrders[uint(i+1)].price ^= sellOrders[uint(high)].price;
            sellOrders[uint(high)].price ^= sellOrders[uint(i+1)].price;
            sellOrders[uint(i+1)].price ^= sellOrders[uint(high)].price;
          }
          pivot = i + 1;
          sortSellOrder(low, pivot-1);
          sortSellOrder(pivot+1, high);
  
        }
      }

}

With that i want to make a few disclaimers and things that i thing may cause me trouble in the future. Its mainly to do with my data structures. I took our the Side side enum and created two separate structs one for buy/sell orders. This might cause a few problems down the line im not sure.

I also did not make my sorting algoorithm cater for the case where you have two orders at the same price they add together, if i have duplicates in my orderbooks they remain duplicates. This would not be hard to fix but im curious to see how filip handles this.

Also since i have two limit order functions i am also going to have to implement two market order. I am definitely going to changes this to get rid of redundant code but for me at this point getting it to worrk was the main thing.
Lastly Currently my sort function has to be called to sort the list. I probably should move it into the limit order functions so that it is called automatically at the end so that the order books are always sorted. This again is not hard but well see how it changes my testing bound to throw in a few bugs to fix up.

Anyways hope yous enjoyed. I rally do love posting here. Never been involved in an online community like this before. Its great

Happy coding

2 Likes

UPDATED SOLUTION**

So i just finished watching Filips videos on his solution for the function and the bubble sort implementation. I cant believe how simple it was to use the double mapping orderbook for both buy and sell instead of doing it the way i had previously with a seperate order book for buy limits and sell limits. So i modified my dex program to still use my my XOR sorting algorithm with filips approach to the double mapping and now i have gotten rid of the redundancy of code that i mentioned in my last post where i had dupilcate createLimitIrder functions and sorting functions for the buy/sell limit orders. So below is my new and approved solution with the robust XOR sorting algorithm

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

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

// import "../node_modules/@openzeppelin//contracts/access/Ownable.sol";

contract Dex is Wallet {

    uint private id = 0;
    enum Side {
        BUY,
        SELL
    }

    struct Order {

        address trader;
        string ticker;
        uint256 amount;
        Side side;
        uint id;
        uint price;
    }

    
    mapping(string => uint) order_id;

    //need double mapping to map a token to a buy/sell orderBook[ticker][uint(side)]
    mapping(string => mapping(uint => Order[])) orderBook;

    function getOwner() public view override returns(address) {
        return msg.sender;
    }

    function getOrders(Side side, string memory ticker) public view returns(Order[] memory) {
        //return orderBook[ticker][uint(side)] book
        return (orderBook[ticker][uint(side)]);
    }


    //to create a limit orderBook[ticker][uint(side)] we need to pass in the token ticker, whethere it is a buy or sell orderBook[ticker][uint(side)]
    //the amount of tokens for the orderBook[ticker][uint(side)] and at what price we want to buy and sell at
    function createLimitOrder(Side side, string memory ticker, uint amount, uint price ) public {

       
        //handle first case for orderBook[ticker][uint(side)] if it is buy(We must have enough eth to make the orderBook[ticker][uint(side)])
       
        //the balances mapping is inherited from our wallet contrac
        if (side == Side.BUY) {

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

        
              //here we must require that enouhg tokens are deposited to meet the demand
              //of the sell orderBook[ticker][uint(side)].amount
          require(balances[msg.sender][ticker] >= amount);
          
          Order[] storage newOrder = orderBook[ticker][uint(side)]; 
          newOrder.push(Order(msg.sender, ticker, amount, side, id, price));
          id++;

        }else {

          require(balances[msg.sender][ticker] >= amount * price);

        
              //here we must require that enouhg tokens are deposited to meet the demand
              //of the sell orderBook[ticker][uint(side)].amount
          require(balances[msg.sender][ticker] >= amount);
          
          Order[] storage newOrder = orderBook[ticker][uint(side)]; 
          newOrder.push(Order(msg.sender, ticker, amount, side, id, price));
          id++;
          


        }

    }

    function sortOrder(Side side, string memory ticker, int low, int high) public {
     // side = Side.BUY;
      if (side == Side.BUY) {

        if (low < high) {
          int pivot = int(orderBook[ticker][uint(side)][uint(high)].price);
          int i = (low-1); // index of smaller element
          for (int j = low; j < high; j++) {
            if (int(orderBook[ticker][uint(side)][uint(j)].price) <= pivot) {
              i++;
              if (i != j) {
                orderBook[ticker][uint(side)][uint(i)].price ^= orderBook[ticker][uint(side)][uint(j)].price;
                orderBook[ticker][uint(side)][uint(j)].price ^= orderBook[ticker][uint(side)][uint(i)].price;
                orderBook[ticker][uint(side)][uint(i)].price ^= orderBook[ticker][uint(side)][uint(j)].price;
              }
            }
          }
          if (i+1 != high) {
            orderBook[ticker][uint(side)][uint(i+1)].price ^= orderBook[ticker][uint(side)][uint(high)].price;
            orderBook[ticker][uint(side)][uint(high)].price ^= orderBook[ticker][uint(side)][uint(i+1)].price;
            orderBook[ticker][uint(side)][uint(i+1)].price ^= orderBook[ticker][uint(side)][uint(high)].price;
          }
          pivot = i + 1;
          sortOrder(side, ticker, low, pivot-1);
          sortOrder(side, ticker, pivot+1, high);

        }
      } else {
          if (low < high) {
            int pivot = int(orderBook[ticker][uint(side)][uint(high)].price);
            int i = (low-1); // index of smaller element
            for (int j = low; j < high; j++) {
              if (int(orderBook[ticker][uint(side)][uint(j)].price) >= pivot) {
                i++;
                if (i != j) {
                  orderBook[ticker][uint(side)][uint(i)].price ^= orderBook[ticker][uint(side)][uint(j)].price;
                  orderBook[ticker][uint(side)][uint(j)].price ^= orderBook[ticker][uint(side)][uint(i)].price;
                  orderBook[ticker][uint(side)][uint(i)].price ^= orderBook[ticker][uint(side)][uint(j)].price;
                }
              }
            }
            if (i+1 != high) {
              orderBook[ticker][uint(side)][uint(i+1)].price ^= orderBook[ticker][uint(side)][uint(high)].price;
              orderBook[ticker][uint(side)][uint(high)].price ^= orderBook[ticker][uint(side)][uint(i+1)].price;
              orderBook[ticker][uint(side)][uint(i+1)].price ^= orderBook[ticker][uint(side)][uint(high)].price;
            }
            pivot = i + 1;
            sortOrder(side, ticker, low, pivot-1);
            sortOrder(side, ticker, pivot+1, high);



      }
      
      }
    }
}
1 Like

After bouncing around a few videos over this part of the project, here are the test cases. I have learned so far in this part of the course. I raced through the previous videos pretty quickly, but had to slow right down for this project :slight_smile:

Since the unit tests in Solidity arenā€™t as self-contained as in other programming languages, Iā€™ve let the state on the contact ā€œspillā€ over to the next tests so that there was less repetitive lines of code. I just had to keep track of the amounts etc which wasnā€™t as a a headache in the end.

contract("Dex", accounts => {

    let dex;
    let link;

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

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

        await link.approve(dex.address, 500)
    });

    it("Should only be possible for owner to add tokens", async () => {
        await truffleAssert.passes( 
            dex.addToken(LINK, link.address, {from: accounts[0]})
        )
        await truffleAssert.reverts( 
            dex.addToken(LINK, link.address, {from: accounts[1]})
        )
    })

    it("Deposits work as expected", async () => {
        await dex.addToken(LINK, link.address)

        await dex.depositETH({value: 10})
        await dex.deposit(100, LINK)

        balance = await dex.balances(accounts[0], LINK)
        assert.equal(balance, 100)
        balance = await dex.balances(accounts[0], ETH)
        assert.equal(balance, 10)
    })

    it("Should handle faulty withdrawls correctly", async () => {
        truffleAssert.reverts(dex.withdraw(101, LINK))
    })

    it("Should handle valid withdrawls correctly", async () => {
        truffleAssert.passes(dex.withdraw(50, LINK))
    })

    it("The user must have ETH deposited such that deposited eth >= buy order value", async () => {
        await truffleAssert.reverts(dex.createLimitOrder(LINK, 100, 1, 3000))
        // User should have 10 ETH at this point of testing
        await truffleAssert.passes(dex.createLimitOrder(LINK, 3, 1, 300))
    })

    it("The user must have enough tokens deposited such that token balance >= sell order amount", async () => {
        // User should have 50 LINK at this point of testing
        await truffleAssert.reverts(dex.createLimitOrder(LINK, 51, 0, 3000))
        await truffleAssert.passes(dex.createLimitOrder(LINK, 25, 0, 300))
    })

    it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {
        // User should have 50 LINK at this point of testing
        await dex.createLimitOrder(LINK, 10, 1, 400)
        await dex.createLimitOrder(LINK, 10, 1, 200)
        await dex.createLimitOrder(LINK, 10, 1, 500)

        let orderbook = await dex.getOrderBook(LINK, 1);

        assert(orderbook.length > 0);
        for (let i = 0; i < orderbook.length - 1; i++) {
            console.log(orderbook[i].price)
        }
        for (let i = 0; i < orderbook.length - 1; i++) {
            assert(orderbook[i].price >= orderbook[i+1].price, 
                   "Incorrect order. Check console log above this error message.")
        }
    })

    it("The SELL order book should be ordered on price from lowest to highest starting at index 0", async () => {
        // User should have 50 LINK at this point of testing
        await dex.createLimitOrder(LINK, 10, 0, 400)
        await dex.createLimitOrder(LINK, 10, 0, 200)
        await dex.createLimitOrder(LINK, 10, 0, 123)

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

        assert(orderbook.length > 0);

        for (let i = 0; i < orderbook.length - 1; i++) {
            console.log(orderbook[i].price)
        }
        for (let i = 0; i < orderbook.length - 1; i++) {
            console.log(orderbook[i].price, orderbook[i+1].price)
            assert(orderbook[i].price <= orderbook[i+1].price, 
                   "Incorrect order. Check console log above this error message.")
        }
    })
})
1 Like

Hi @mcgrane5

hey @dan-i i actually have a question for you. Is it an industry common practice to write your tests before hand in code like filip wanted us to do. Because i was not able to do that. I didnt know what i needed until i can actually have code. So thats why i wrote my functions first it just makes way more sens to me and i dont know why filip did it like that. I curious to know if this is a common practice

I can give you a rule that you should apply in every project you will work on, does not matter the programming language you use.
You should always:

  • Write your function (or functions is you need to chain more of them);
  • Write tests to make sure the functions works;
  • Push to GitHub;

You should never push a piece of code to GitHub until you write tests for it.

Cheers,
Dani

1 Like

cheers. yeah that makes sense. Yeah i may. I liked mocha anyway seems like it saves some time although it doesnā€™t behave as expected sometimes (gave me errrors when i knew my code was right which makes wrtining tets a little more tedious). The awaits and stuff i still need to get used to as im not versed enough in js yet but i will be once try the js course after this

1 Like

Here are my tests for the Dex. I havenā€™t yet looked at Filipā€™s solution video, so I may well revise after seeing Filipā€™s approach.

First, my function headers for the unimplemented functions:

    function createLimitOrder(bytes32 ticker, Side side, uint price, uint amount)
        external
        returns(Order memory limitOrder) 
    {

    }

    function depositETH() public payable {

    }

    function getETHBalance() view public returns(uint ethBalance){

    }

My test/dexTest.js code:

// 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)
// The order can only be placed for a token known to the DEX
// The order created must contain the correct details


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


contract("Dex", accounts => {
    
    it("should have required ETH deposited in users account for the buy order", async () => {
        const dex = await Dex.deployed()
        const link = await LinkMock.deployed()

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

        await truffleAssert.reverts(
            dex.createLimitOrder(
               web3.utils.fromUtf8("LINK"),
               0,  //Buy-side
               1,  //price
               1,  //amount
               {from: accounts[0]}
            ),
            "Created buy-side limit order without any deposited ETH!"
       )

        await dex.depositETH({value: 100})

        await truffleAssert.reverts(
            dex.createLimitOrder(
                web3.utils.fromUtf8("LINK"),
                0,    //Buy-side
                101,  //price
                1,    //amount
                {from: accounts[0]}
            ),
            "Created buy-side limit order with to few deposited ETH!"
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(
               web3.utils.fromUtf8("LINK"),
               0,   //Buy-side
               10,  //price
               11,  //amount
               {from: accounts[0]}
           ),
           "Created buy-side limit order with to few deposited ETH given amount tokens required!"
        )
    })

    it("should have required tokens deposited in the users account for the sell order", async () => {
        const dex = await Dex.deployed()
        const link = await LinkMock.deployed()

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

        await truffleAssert.reverts(
            dex.createLimitOrder(
               web3.utils.fromUtf8("LINK"),
               1,  //Sell-side
               10, //price
               8,  //amount
               {from: accounts[0]}
            ),
            "Created sell-side limit order despite not having no tokens to sell!"
        )

        await dex.deposit(100, web3.utils.fromUtf8("LINK"))
        await truffleAssert.passes(
            dex.createLimitOrder(
               web3.utils.fromUtf8("LINK"),
               1,   //Sell-side
               13,  //price
               100, //amount
               {from: accounts[0]}
            ),
            "Has the tokens to sell but limit order creation still failed!"
        )
    })

    it("should have BUY order book ordered on price from highest to lowest (starting at index 0)", async () => {
        const dex = await Dex.deployed()
        const link = await LinkMock.deployed()

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

        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            0,   //Buy-side
            10,  //price
            20,  //amount
            {from: accounts[0]}
        )
        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            0,   //Buy-side
            12,  //price
            20,  //amount
            {from: accounts[0]}
        ) 
        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            0,   //Buy-side
            11,  //price
            20,  //amount
            {from: accounts[0]}
        ) 
        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            0,   //Buy-side
            9,  //price
            20,  //amount
            {from: accounts[0]}
        )     

        const buyOrderBook = await dex.getOrderBook(
            web3.utils.fromUtf8("LINK"),
            0  //Buy-side
        )
        assert(
            buyOrderBook.length == 4,
            `Expected 4 orders in buy-side order book, but there are ${buyOrderBook.length}!`
        )
        for (let order=0; order<buyOrderBook.length-1; order++){
            assert(buyOrderBook[order] >= buyOrderBook[order+1]
                `Buy order ${order}'s price (${buyOrderBook[order].price}) is not >= ` +
                `buy order ${order+1}'s price (${buyOrderBook[order+1].price})`                
            )
        }
    })

    it("should have SELL order book ordered on price from lowest to highest (starting at index 0)", async () => {
        const dex = await Dex.deployed()
        const link = await LinkMock.deployed()

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

        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            1,   //Sell-side
            10,  //price
            20,  //amount
            {from: accounts[0]}
        )
        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            1,   //Sell-side
            12,  //price
            20,  //amount
            {from: accounts[0]}
        ) 
        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            1,   //Sell-side
            11,  //price
            20,  //amount
            {from: accounts[0]}
        ) 
        await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            1,   //Sell-side
            9,  //price
            20,  //amount
            {from: accounts[0]}
        ) 

        const sellOrderBook = await dex.getOrderBook(
            web3.utils.fromUtf8("LINK"),
            1  //Sell-side
        )
        assert(
            sellOrderBook.length == 4,
            `Expected 4 orders in sell-side order book, but there are ${sellOrderBook.length}!`
        )

        for (let order=0; order<sellOrderBook.length-1; order++){
            assert(sellOrderBook[order].price <= sellOrderBook[order+1].price,
                `Sell order ${order}'s price (${sellOrderBook[order].price}) is not <= ` +
                `sell order ${order+1}'s price (${sellOrderBook[order+1].price})`
            )
        }
    })

    it("should only accept orders for known tokens", async () => {
        const dex = await Dex.deployed()
        const link = await LinkMock.deployed()

        // DON'T ADD "LINK" AS A KNOWN TOKEN
        // await dex.addToken(
        //     web3.utils.fromUtf8("LINK"),link.address, {from: accounts[0]}
        // )

        await dex.depositETH({value: 80})
        await truffleAssert.reverts(
            dex.createLimitOrder(
               web3.utils.fromUtf8("LINK"),
               0,  //Buy-side
               10, //price
               8,  //amount
               {from: accounts[0]}
           ),
           "Created buy-side limit order despite not knowing the token!"
        )
        await truffleAssert.reverts(
            dex.createLimitOrder(
               web3.utils.fromUtf8("LINK"),
               1,  //Sell-side
               10, //price
               8,  //amount
               {from: accounts[0]}
           ),
           "Created sell-side limit order despite not knowing the token!"
        )
    })

    it("should create buy and sell orders with the correct details", async () => {
        const dex = await Dex.deployed()
        const link = await LinkMock.deployed()

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

        // Buy-side order
        await dex.depositETH({value: 99})
        const buyOrder = await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            0,  //Buy-side
            9,  //price
            11, //amount
            {from: accounts[0]}
        )
        assert.equal(
            buyOrder.trader,
            accounts[0],
            "Order's account doesn't match user's account!"
        )
        assert.equal(
            buyOrder.side,
            0, //Buy-side
            "Order's side doesn't match user's input of buy-side!"
        )
        assert.equal(
            buyOrder.ticker,
            web3.utils.fromUtf8("LINK"),
            "Order's ticker doesn't match user's input ticker!"
        )
        assert.equal(
            buyOrder.price, 
            9,
            "Order's price doesn't match user's input price!"
        )
        assert.equal(
            buyOrder.amount,
            11,
            "Order's token amount doesn't match user's input amount!"
        )

        // Sell-side order
        const sellOrder = await dex.createLimitOrder(
            web3.utils.fromUtf8("LINK"),
            1,  //Sell-side
            10, //price
            12, //amount
            {from: accounts[0]}
        )
        assert.equal(
            sellOrder.trader,
            accounts[0],
            "Order's account doesn't match user's account!"
        )
        assert.equal(
            sellOrder.side,
            1, //Sell-side
            "Order's side doesn't match user's input of sell-side!"
        )
        assert.equal(
            sellOrder.ticker,
            web3.utils.fromUtf8("LINK"),
            "Order's ticker doesn't match user's input ticker!"
        )
        assert.equal(
            sellOrder.price, 
            9,
            "Order's price doesn't match user's input price!"
        )
        assert.equal(
            sellOrder.amount,
            11,
            "Order's token amount doesn't match user's input amount!"
        )
    })

})

And finally the output from the tests:

  Contract: Dex
    1) should have required ETH deposited in users account for the buy order
    > No events were emitted
    2) should have required tokens deposited in the users account for the sell order
    > No events were emitted
    3) should have BUY order book ordered on price from highest to lowest (starting at index 0)
    > No events were emitted
    4) should have SELL order book ordered on price from lowest to highest (starting at index 0)
    > No events were emitted
    5) should only accept orders for known tokens
    > No events were emitted
    6) should create buy and sell orders with the correct details
    > No events were emitted

  Contract: Dex
    āœ“ should only be possible for owner to add tokens (2532ms)
    āœ“ should handle deposits correctly (1259ms)
    āœ“ should handle faulty withdrawals correctly (386ms)
    āœ“ should handle permitted withdrawals correctly (479ms)


  4 passing (13s)
  6 failing

  1) Contract: Dex
       should have required ETH deposited in users account for the buy order:
     AssertionError: Did not fail
      at fails (node_modules/truffle-assertions/index.js:161:9)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)
      at Context.<anonymous> (test/dexTest.js:24:9)

  2) Contract: Dex
       should have required tokens deposited in the users account for the sell order:
     AssertionError: Did not fail
      at fails (node_modules/truffle-assertions/index.js:161:9)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)
      at Context.<anonymous> (test/dexTest.js:67:9)

  3) Contract: Dex
       should have BUY order book ordered on price from highest to lowest (starting at index 0):
     AssertionError: Expected 4 orders in buy-side order book, but there are 0!
      at Context.<anonymous> (test/dexTest.js:132:9)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)

  4) Contract: Dex
       should have SELL order book ordered on price from lowest to highest (starting at index 0):
     AssertionError: Expected 4 orders in sell-side order book, but there are 0!
      at Context.<anonymous> (test/dexTest.js:185:9)
      at runMicrotasks (<anonymous>)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)

  5) Contract: Dex
       should only accept orders for known tokens:
     AssertionError: Did not fail
      at fails (node_modules/truffle-assertions/index.js:161:9)
      at runMicrotasks (<anonymous>)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)
      at Context.<anonymous> (test/dexTest.js:208:9)

  6) Contract: Dex
       should create buy and sell orders with the correct details:
     AssertionError: Order's account doesn't match user's account!: expected undefined to equal '0xfC1d4eA100c57A6D975eD8182FaAcFD17871a1e4'
      at Context.<anonymous> (test/dexTest.js:247:16)
      at runMicrotasks (<anonymous>)
      at processTicksAndRejections (internal/process/task_queues.js:93:5)

2 Likes

This is my assignment,

dex.test.js

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

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

contract ("Dex", account => {
    it("should have ETH deposited more or equal buy order", async() => {

        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let ethBalance = await dex.balances(account[0], msg.value)

        await truffle.equal( ethBalance.toNumber(), >= 0 )

    })

    it("should have LINK deposited more than sell order", async() => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        let linkBalance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"))

        await truffle.equal( linkBalance.toNumber, > sellOrder )

    })

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

        await truffle.passes( sellOrder.account[0].toNumber(), > sellOrder.account[n].toNumber())

    })

})
1 Like

I need to say this assignment was kind of intimidating to me and it also took me a while to get it done.
Also I couldnā€™t really wrap my head around the 3rd and 4th check. Could someone please explain where const element = array[index];
comes from?
For the other checks please can someone have a look if my logic makes sense and let me know what to improve?

Thank you!

//User should have enough ETh so that Eth >= buy order;
//User should have enough token so that token >= sell order;
//Buy oder book should be in order begginning with highest price at index [0] down to lowest ,
//Sell order book should be in order beginning with lowest price at index [0] up to highest;
//User balance must be checked if correct after completed order;
//Must be checked if token is supported


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

contract ("DEX", accounts =>{

    it("should check user Eth balance before buy order" , async() => {
        let link = await Link.deployed();  
        let dex = await Dex.deployed(); 

        await dex.depositEth({value:1000});

     truffleAssert.passes(
           await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"), 1000 ,10)
        )

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

    })

    it("should check token balance before sell order", async() =>{
        let link = await Link.deployed();  
        let dex = await Dex.deployed(); 

        
      balances[accounts[0]][web3.utils.fromUtf8("LINK")] = 100;

        truffleAssert.passes(
            await dex.createLimitOrder(1,web3.utils.fromUtf8("LINK"),100,10)
        )

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

     it("should check that buy order book begins with highest price at index[0] down to lowest",async()=>{
            let link = await Link.deployed();  
            let dex = await Dex.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);
         for (let i = 0; i < orderbook.length - 1; i++) {
            const element = array[index];
            assert(orderbook[i] >= orderbook[i+1])
         }

         })

   
    it("should check that sell order book begins with lowest price at index[0] up to highest", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()

        await link.approve(dex.address, 500);
        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);
        for (let i = 0; i < orderbook.length - 1; i++) {
            const element = array[index];
            assert(orderbook[i] <= orderbook[i+1])
        }
    })

    it("should check that token is supported", async()=>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()

        truffleAssert.revert(
            tokenMapping[ticker].tokenAddress = address(0));

    })

    it("should check that User balance is correct after completed order", async()=>{
        let dex = await Dex.deployed()
        let link = await Link.deployed()

        await dex.depositEth({value:100});
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 40,10 );
        let ethBalance = await dex.balances(accounts[0], web3.utils.fromUtf8("ETH"));
        assert.equal(ethBalance.toNumber(),60)
       
        await link.approve( dex.address , 100);
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 40,10 );
        let linkBalance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"));
        assert.equal(linkBalance.toNumber(),60);

    })

})
1 Like

Hi @Karla

const element = array[index];

With the command above, you are assigning the content of array[index] to a constant called element.

Does this code run correctly?

 for (let i = 0; i < orderbook.length - 1; i++) {
            const element = array[index];
            assert(orderbook[i] >= orderbook[i+1])
         }

You do not have any array called array and you donā€™t have any index called index.
Also you do not use element anywhere.

Arrays are a fundamental concept in programming so make sure to be comfortable using them before continuing the Solidity course.

A quick example of how arrays work and how you can iterate though them:

const array = [1,2,3,4,5,6,7,8,9,10];

for (let i = 0 ; i < array.length; i++) {
  console.log(`The index ${i} contains the number: ${array[i]}`);
}

Here I am console.log the content, in your case you are assigning it to a constant.

Cheers,
Dani

2 Likes

Hi @dan-i,

Thank you for making the effort to explain Arrays and how to iterate through them.
I was actually referring to Filipā€™s solution code as I didnā€™t understand why he is using

  const element = array[index];

even though he is not using it in his example. Sorry I should have explained myself better I was assuming we all look at the same, my bad.