Assignment - Limit Order Test

hey @william, very sorry about that think i just had it the oppsoite way in my code when i did this project yeah you can just switch them in the tests that my bad. No worries g anytime never be afraid to post in the forms thats whats great about this academy there is bundles of people willing to help. now onto the market order!!!

Evan

Yo, @mcgrane5 and anyone interested:
I managed to get my testā€™s call to ā€œreturnBalanceā€ to finally return an actual number when console.logā€™ed. This is one solution:

await console.log(parseInt(await dex.returnBalance(accounts[0],"LINK")))

It just needed an extra, nested ā€˜awaitā€™ to reslove a promise (Is that even how you say that?)

Mind blown? Yeah, mine too.


Edit: and as it turns out, it works without the initial ā€œawaitā€.

1 Like

Here is my code:
dextest.js

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 have a positive ETH balance before placing a BUY limit order", async () => {
        /**
         * Limit Order function header - createLimitOrder(Side side, bytes32 ticker, uint256 amount, uint256 price)
        */
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        //expect to fail - low ETH balance
        await truffleAssert.reverts(  
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 2)
        );
        //deposit ETH - positive ETH balance
        await dex.depositEth({value: 100});
        //expect to pass 
        await truffleAssert.passes(  
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 2)
        );
    });
    //The user must have enough tokens deposited such that token balance >= sell order amount
    it("should have a positive token balance before placing a SELL limit order", async () => {
        /**
         * Limit Order function header - createLimitOrder(Side side, bytes32 ticker, uint256 amount, uint256 price)
         */
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        //expect to fail - low token balance
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 10)
        );
        //approve token spend
        await link.approve( dex.address, 500 );
        //add token to account record
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]});
        //deposit token - positive token balance
        await dex.deposit( 100, web3.utils.fromUtf8("LINK") );
        //expect to pass
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 5, 10)
        );
    });
    //The BUY order book should be ordered on price from the highest to lowest starting at index 0
    it("should sort the BUY orders from highest price to lowest price", async () => {
        /**
         * Orderbook function header - getOrderBook(bytes32 ticker, Side side)
         */
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        //approve spending
        await link.approve(dex.address, 500);
        //deposit ETH - positive ETH balance
        await dex.depositEth({value:100});
        //place BUY orders
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 5);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 2);
        await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 3);
        //get orderbook and sort by highest price to lowest price
        let orderbook = await dex.orderbook(web3.utils.fromUtf8("LINK"), 0);
        for(let i = 0; i < orderbook.length -1; i++) {
            assert(orderbook[i].price >= orderbook[i +1].price,);
        }
    });
    //The SELL order book should be ordered on price from the lowest to highest starting at index 0
    it("should sort the SELL orders from lowest price to highest price", async () => {
        /**
         * Orderbook function header - getOrderBook(bytes32 ticker, Side side)
         */
        let dex = await Dex.deployed();
        let link = await Link.deployed();
        //aprove spening
        await link.approve(dex.address, 500);
        //add token to account record
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]});
        //deposit token - positive token balance
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));
        //place SELL orders
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 15);
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 10);
        await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 8);
        //get orderbook and sort by lowest price to highest price
        let orderbook = await dex.orderbook(web3.utils.fromUtf8("LINK"), 1);
        for(let i = 0; i > orderbook.length -1; i++){
            assert(orderbook[i].price <= orderbook[i +1].price);
        }
    });
});```

Can anyone explain the cause of this phenemenon ā€¦

When I console.log() the following within my test script:

console.log(parseInt(await dex.returnBalance(accounts[0],"LINK")))
console.log(await dex.returnBalance(accounts[0],"LINK"))

the corresponding output to the console is:

1
BN { negative: 0, words: [ 1, <1 empty item> ], length: 1, red: null }

So as you can see, the very first console readout (a proper number/integer) is correct, and what I had been trying to work out all along. But without the ā€œparseInt()ā€ as part of the code, the output becomes a BN object?

Here is the returnBalance() function:

function returnBalance(address accountAddy, string memory ticker) public view returns(uint){
        return balances[accountAddy][ticker];
    }

Whatā€™s going on here?

1 Like

Hey @Melshman the reason this happens is because of the whole BN thing. There is a comversion that you can do i cant remeber off the top of my head its like .toNumber() of something. Ill have a look on google now for you see if i can find an awnser for you.

EDIT ok so what you may ne able to do is this im on my phone so i cant see an option to use the code snippet bear with me write the following

Let bal = await dex.returnBalance(accounts[ 0 ], ā€œLINKā€)

theb rub this command (enter)

Then say console.log(web3.utils.fromWei(bal.toNumber(), ā€œetherā€)

And run rhis

Now not sure if this will work especially since the output from you code above says NEGATIVE: 0, which leads me to believe you have no balance of link for that account. Have you definitely deposited link into it. If the command from above does not work hit up @dan-i im limited to what i can do wont have access to my computer for the day

Hi @Melshman

Truffle does indeed use BigNumber instead of normal numbers.
Javascript number handling is quite bad actually when the amount is too large:
image

For testing purposes, you should use bignumber and avoid the conversion to numbers (you can check the Chai package and use its bignumber methods).

Cheers,
Dani

1 Like
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const Ether = artifacts.require("Ether");

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

contract("Dex", (accounts) => {
	it("should not be possible to spend more ether by buying than user has deposited", async () => {
		let dex = await Dex.deployed();
		let link = await Link.deployed();
		let ether = await Ether.deployed();

		await truffleAssert.reverts(
			dex.createLimitOrder(0, web3.utils.fromUtf8(link.symbol()), 10, 10)
		);
		await dex.addToken(web3.utils.fromUtf8(ether.symbol()), ether.address);
		await ether.approve(dex.address, 100);
		await dex.deposit(100, web3.utils.fromUtf8(ether.symbol()));
		await truffleAssert.passes(
			dex.createLimitOrder(0, web3.utils.fromUtf8(link.symbol()), 10, 10)
		);
	});

	it("should not be possible to spend more tokens by selling than user has deposited", async () => {
		let dex = await Dex.deployed();
		let link = await Link.deployed();
		let ether = await Ether.deployed();

		await truffleAssert.reverts(
			dex.createLimitOrder(1, web3.utils.fromUtf8(link.symbol()), 10, 10)
		);
		await dex.addToken(web3.utils.fromUtf8(ether.symbol()), ether.address);
		await ether.approve(dex.address, 100);
		await dex.deposit(100, web3.utils.fromUtf8(ether.symbol()));
		await truffleAssert.passes(
			dex.createLimitOrder(0, web3.utils.fromUtf8(link.symbol()), 10, 10)
		);
		await truffleAssert.passes(
			dex.createLimitOrder(1, web3.utils.fromUtf8(link.symbol()), 10, 10)
		);
	});

	it("should throw an error if BUY orderbook items are not sorted from highest to lowest", async () => {
		let dex = await Dex.deployed();
		let link = await Link.deployed();
		let ether = await Ether.deployed();

		await dex.addToken(web3.utils.fromUtf8(ether.symbol()), ether.address);
		await ether.approve(dex.address, 100);
		await dex.deposit(100, web3.utils.fromUtf8(ether.symbol()));
		await dex.createLimitOrder(
			0,
			web3.utils.fromUtf8(link.symbol()),
			1,
			10
		);
		await dex.createLimitOrder(
			0,
			web3.utils.fromUtf8(link.symbol()),
			1,
			30
		);
		await dex.createLimitOrder(
			0,
			web3.utils.fromUtf8(link.symbol()),
			1,
			20
		);
		let buyOrderbook = await dex.orderBook(
			web3.utils.fromUtf8(link.symbol()),
			0
		);
		assert(buyOrderbook[0].price == 30);
		assert(buyOrderbook[1].price == 20);
		assert(buyOrderbook[2].price == 10);
	});

	it("should throw an error if SELL orderbook items are not sorted from lowest to highest", async () => {
		let dex = await Dex.deployed();
		let link = await Link.deployed();
		let ether = await Ether.deployed();

		await dex.addToken(web3.utils.fromUtf8(ether.symbol()), ether.address);
		await ether.approve(dex.address, 100);
		await dex.deposit(100, web3.utils.fromUtf8(ether.symbol()));
		await dex.createLimitOrder(
			1,
			web3.utils.fromUtf8(link.symbol()),
			1,
			10
		);
		await dex.createLimitOrder(
			1,
			web3.utils.fromUtf8(link.symbol()),
			1,
			30
		);
		await dex.createLimitOrder(
			1,
			web3.utils.fromUtf8(link.symbol()),
			1,
			20
		);
		let buyOrderbook = await dex.orderBook(
			web3.utils.fromUtf8(link.symbol()),
			1
		);
		assert(buyOrderbook[0].price == 10);
		assert(buyOrderbook[1].price == 20);
		assert(buyOrderbook[2].price == 30);
	});
});

1 Like

Is there a way to use ā€œlet dex = await DEX.deployed()ā€ and ā€œlet link = await LINK.deployed()ā€ so I only have to set them once for the entire test contract? It would clean up the code considerably :slight_smile:

const DEX = artifacts.require("DEX");
const ETHWallet = artifacts.require("ETHWallet");
const LINK = artifacts.require("LINK");
const truffleAssert = require('truffle-assertions');


contract("DEX", accounts => {
  let linksymbol = web3.utils.keccak256("LINK");
  let ethsymbol = web3.utils.keccak256("ETH");

  before(async function () {
    let dex = await DEX.deployed();
    let link = await LINK.deployed();

    await link.approve(dex.address, 20);
    await dex.addToken(linksymbol, link.address, {from: accounts[0]});
    await dex.deposit(20, linksymbol);
    await dex.depositETH({value: web3.utils.toWei('10', 'ether')});
  })

  it("should only be possible for owner to add tokens", async () => {
    let dex = await DEX.deployed();
    let link = await LINK.deployed();

    await truffleAssert.passes(
      dex.addToken(linksymbol, link.address, {from: accounts[0]})
    )

    await truffleAssert.reverts(
    dex.addToken(linksymbol, link.address, {from: accounts[1]})
    )
  })

  it("Should handle deposits correctly", async () => {
    let dex = await DEX.deployed();

    let linkBalance = await dex.balances(accounts[0], linksymbol);
    let ethBalance = await dex.balances(accounts[0], ethsymbol);

    await truffleAssert.passes(linkBalance == 10);
    await truffleAssert.passes(ethBalance == 10);
  })

  it("Should handle withdrawals correctly", async () => {
    let dex = await DEX.deployed();
    let link = await LINK.deployed();
    let linkBalance0 = link.balanceOf(accounts[0]);
    let ethBalance0 = web3.eth.getBalance(accounts[0]);

    await dex.withdraw(20, linksymbol);
    await dex.withdraw(web3.utils.toWei('10'), ethsymbol);

    await truffleAssert.passes(link.balanceOf(accounts[0]) > linkBalance0);
    await truffleAssert.passes(web3.eth.getBalance(accounts[0]) > ethBalance0);
    await truffleAssert.passes(dex.balances(accounts[0], linksymbol) == 0);
    await truffleAssert.passes(dex.balances(accounts[0], ethsymbol) == 0);
  })

  it("Must have ETH deposited such that deposited eth >= buy order value", async () => {
    let dex = await DEX.deployed();
    let link = await LINK.deployed();

    await link.approve(dex.address, 20);
    await dex.deposit(20, linksymbol);
    await dex.depositETH({value: web3.utils.toWei('10', 'ether')});

    await dex.createLimitOrder(0, linksymbol, 2, web3.utils.toWei('3'))

    await truffleAssert.passes(
          dex.balances(accounts[0], ethsymbol) >= dex.getOrderPrice(linksymbol, 0, 0))

    await truffleAssert.reverts(
      dex.createLimitOrder(0, linksymbol, 5, web3.utils.toWei('10'))
    )
  })


  it("Must have enough tokens deposited such that token balance >= sell order amount", async () => {
    let dex = await DEX.deployed();

    await dex.createLimitOrder(1, linksymbol, 2, web3.utils.toWei('3'))

    await truffleAssert.passes(
      dex.balances(accounts[0], linksymbol) >= dex.getOrderAmount(linksymbol, 1, 0)
    )

    await truffleAssert.reverts(
      dex.createLimitOrder(1, linksymbol, 30, web3.utils.toWei('150', 'finney'))
    )
  })


  it("First order ([0]) in the BUY order book should have the highest price", async () => {
    let dex = await DEX.deployed();

    await dex.createLimitOrder(0, linksymbol, 2, 2)
    await dex.createLimitOrder(0, linksymbol, 2, 3)
    await dex.createLimitOrder(0, linksymbol, 2, 4)

    let orderBook = dex.getOrderBook(linksymbol, 0);
    let order0 = dex.getLimitPrice(linksymbol, 0, 0);

    for (i = orderBook.length - 1; i >= 1; i--) {
      await truffleAssert.passes( order0 >= orderBook[i] )
    }
  })


  it("First order ([0]) in the SELL order book should have the lowest price", async () => {
    let dex = await DEX.deployed();

    await dex.createLimitOrder(1, linksymbol, 2, 4)
    await dex.createLimitOrder(1, linksymbol, 2, 3)
    await dex.createLimitOrder(1, linksymbol, 2, 2)

    let orderBook = dex.getOrderBook(linksymbol, 1);
    let order0 = dex.getLimitPrice(linksymbol, 1, 0);

    for (i = orderBook.length - 1; i >= 1; i--) {
      await truffleAssert.passes( order0 <= orderBook[i] )
    }
  })
})

Hi @CryptoPhoenix

Yes and thatā€™s the way you should go :slight_smile:

contract("DEX", accounts => {

 let dex
 let link
 let linksymbol = web3.utils.keccak256("LINK");
 let ethsymbol = web3.utils.keccak256("ETH");

 before(async function () {
   dex = await DEX.deployed();
   link = await LINK.deployed();

And here you go!

Cheers,
Dani

Here is my test code for the Dex I made for my cat Dave.

The only extra tests I added were to simply check that the limit orders were being added to the order book. This is because I realized that the tests that check the sort order of the book would pass if the order books remained empty.

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

// The buyer mast have ETH deposited such that deposited eth >= buy order value
// the seller must have enough tokens deposited such that token balance > sell order amount
// the first order ([0]) in the BUY order book should have the highest price
// the last order ([0]) in the BUY order book should have the lowest price

contract('DaveDex', accounts => {
  let dex, link;
  const LINK = web3.utils.fromUtf8('LINK');
  const BUY = 0;
  const SELL= 1;

  before(async ()=> {
    dex = await DaveDex.deployed();
    link = await Link.deployed();
    // Add LINK token to DEX
    await dex.addToken(LINK, link.address, { from: accounts[0] });

    // Approve LINK deposits
    await link.approve(dex.address, 1000, { from: accounts[0] });
  });


  it('should throw an error if ETH balance is too low when creating a BUY order', async () => {
    await truffleAssert.reverts(

      // Create BUY order for 5 LINK @ 2 ETH/LINK
      dex.createLimitOrder(LINK, BUY, 5, 2, { from: accounts[0] })
    );

    // Deposit 10 ETH to DEX
    await dex.depositEth({ from: accounts[0], value: 10 });
    await truffleAssert.passes(
      // Create BUY order for 5 LINK @ 2 ETH/LINK
      dex.createLimitOrder(LINK, BUY, 5, 2, { from: accounts[0] })
    );

  });

  it('should throw an error if TOKEN balance is too low when creating a SELL order', async () => {
    // Create SELL order without any LINK deposited
    await truffleAssert.reverts(
      dex.createLimitOrder(LINK, SELL, 100, 1)
    );

    // Deposit 1000 LINK
    await dex.deposit(1000, LINK, { from: accounts[0] });

    // Create SELL order for 5 LINK @ 2 ETH/LINK
    await truffleAssert.passes(
      dex.createLimitOrder(LINK, SELL, 5, 2, { from: accounts[0] })
    )
  });

  it('the BUY order book should contain limit orders created by the createLimitOrder function', async () => {
    await dex.depositEth({ from: accounts[1], value: 10 })
    await truffleAssert.passes(
      dex.createLimitOrder(LINK, BUY, 10, 1, { from: accounts[1] })
    )
    let buyOrderBook = await dex.getOrderBook(LINK, BUY)
    await expect(buyOrderBook.length).to.be.at.least(1)
  })

  it('the SELL order book should contain limit orders created by the createLimitOrder function', async () => {
    await truffleAssert.passes(
      dex.createLimitOrder(LINK, SELL, 10, 10, { from: accounts[0] })
    )
    let sellOrderBook = await dex.getOrderBook(LINK, SELL)
    await expect(sellOrderBook.length).to.be.at.least(1)
  })

  it('the BUY order book should be ordered on price from highest to lowest starting at index 0', async () => {
    
    // Deposit ETH to DEX
    await dex.depositEth({ from: accounts[0], value: 10 });
    await dex.depositEth({ from: accounts[1], value: 10 });
    await dex.depositEth({ from: accounts[2], value: 10 });

    // Create BUY orders
    await dex.createLimitOrder(LINK, BUY, 1, 3, { from: accounts[2] });
    await dex.createLimitOrder(LINK, BUY, 1, 1, { from: accounts[0] });
    await dex.createLimitOrder(LINK, BUY, 1, 2, { from: accounts[1] });

    // BUY order book for LINK
    let orderBook = await dex.getOrderBook(LINK, BUY);

    for(let i=0; i<orderBook.length-1; i++) {
      await expect(Number(orderBook[i].price)).to.be.gte(Number(orderBook[i+1].price))
    }
  });

  it('the SELL order book should be ordered on price from lowest to highest starting at index 0', async () => {

    // Create SELL orders
    await dex.createLimitOrder(LINK, SELL, 1, 10, { from: accounts[0] });
    await dex.createLimitOrder(LINK, SELL, 1, 8, { from: accounts[0] });
    await dex.createLimitOrder(LINK, SELL, 1, 9, { from: accounts[0] });


    // SELL order book for LINK
    let orderBook = await dex.getOrderBook(LINK, SELL);
    for(let i=0; i<orderBook.length-1; i++) {
      await expect(Number(orderBook[i].price)).to.be.lte(Number(orderBook[i+1].price))
    }
  });
});
1 Like

Here are my tests. That was quite a challenge! Cool!

// 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 User should not be able to create limit orders for not supported tokens

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

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

const LINKB32 = web3.utils.fromUtf8("LINK");
const ETH10 = web3.utils.toWei('10', 'ether');

contract("Dex", accounts => {

    let dex;
    let link;

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

    it("The user must have ETH deposited such that deposited eth >= buy order value", async ()=>{
        let ethBalance = parseInt(dex.getFunds(accounts[0])); // Get the current ETH balance for accounts[0]
        ethBalance++;

        await truffleAssert.reverts ( 
            // Then create an Order for one more ETH than available, expect to fail.
            await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, ethBalance, 1)
        );
        
        //top up and check if it passes
        await dex.addFunds({value: ETH10});
        await truffleAssert.passes ( 
            // Try same create, this time it should pass.
            await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), Side.BUY, ethBalance, 1)
        );    
    } );

    it("The user must have enough tokens deposited such that token balance >= sell order amount", async ()=>{
         // Get the current LINK balance for accounts[0], if 0 deposit 10
         let linkBalance = parseInt(dex.balances(accounts[0], LINKB32));
         if(linkBalance) { linkBalance++; }
         else {
             await dex.deposit (10, LINKB32);
             linkBalance = 11;
         }

        await truffleAssert.reverts (
            // Then create limit order for one more ETH than available, expect to fail.
            await dex.createLimitOrder(LINKB32, Side.SELL, linkBalance, 1)
        );  
        
        await dex.deposit (10, LINKB32); //deposit 10 more

        await truffleAssert.passes (
            // This should pass now
            await dex.createLimitOrder(LINKB32, Side.SELL, linkBalance, 1)
        ); 

    } );

    it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async ()=>{
        const buyOrders = [1,5,3,2,6]; // order prices to create
        await link.approve(dex.address, 1000); // approve dex for deposit

        await dex.addFunds({value: ETH10});

        for (i=0; i < buyOrders.length; i++){ // create some BUY orders
            await dex.createLimitOrder(LINKB32, Side.BUY, 0.1, buyOrders[i]);
        }
        
        const orderBook = dex.getOrderBook();

        // assert that each buy order in the array is smaller than the one before
        for (i=0; i < orderBook.length - 1; i++){
            const firstOrder = orderBook[i];
            const nextOrder = orderBook[i+1];
            assert(firstOrder.price > nextOrder.price);
        }
    } );

    it("The SELL  order book should be ordered on price from lowest to highest starting at index 0", async ()=>{
        const sellOrders = [1,5,3,2,6]; // order prices to create

        await dex.deposit(10, LINKB32); // deposit 10 Tokens so we can create sell orders

        for (i=0; i < sellOrders.length; i++){ // create some BUY orders
            await dex.createLimitOrder(LINKB32, Side.SELL, 0.1, sellOrders[i]);
        }
        
        const orderBook = dex.getOrderBook();

        // assert that each buy order in the array is smaller than the one before
        for (i=0; i < orderBook.length - 1; i++){
            const firstOrder = orderBook[i];
            const nextOrder = orderBook[i+1];
            assert(firstOrder.price < nextOrder.price);
        }
    } );

    it("The User should not be able to create limit orders for not supported tokens", async ()=>{
        const AAVEB32 = web3.utils.fromUtf8("AAVE");
        truffleAssert.reverts( //create BUY order of unlisted token should fail
            dex.createLimitOrder(AAVEB32, Side.BUY, 0.1, 1)
        );
        truffleAssert.reverts( //create SELL order of unlisted token should fail
            dex.createLimitOrder(AAVEB32, Side.SELL, 0.1, 1)
        );

        //now use LINK
        truffleAssert.passes( //create BUY order should succeed
            dex.createLimitOrder(LINKB32, Side.BUY, 0.1, 1)
        );
        truffleAssert.passes( //create SELL order of unlisted token should fail
            dex.createLimitOrder(LINKB32, Side.SELL, 0.1, 1)
        );
    } );

} );

1 Like

My answer is in 2 partsā€¦

first is header for createLimitOrder PLUS requirementsā€¦

    //create limit order to buy/sell ticker in the amount of 'amount' at 'price'
    function createLimitOrder(bytes32 ticker, Side side,uint amount, uint price) external tokenExist(ticker) onlyOwner {
        if (side == Side.BUY) {
            //check if users has enough ETH for buy order
            uint cost = amount * price; //in ether
            require(msg.sender.balance >= cost ether,"Insufficient ETH Balance!");
        }
        else { //sell order
        
            //check if users has enough tokens to sell
            require(balances[msg.sender][ticker] >= amount, "Insufficient Token Balance!");

        }

     }

Here is the JS code for testing, i added on to previous code so that LINK is already added as Token and accounts[0] has a LINK balance (in my case 500). Scroll down to see new testsā€¦

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

contract("Dex",accounts => {
    it("should only be possible for owners to add tokens", async () => {
  
        //creating instance of wallet contract
        let dex = await Dex.deployed();
      
        //creating instance of LINK token contract
        let link = await Link.deployed();
        
        //adding token ticker and address to wallet
        //await link.approve(dex.address,500);
        await truffleAssert.passes(dex.addToken(web3.utils.fromUtf8("LINK"),link.address, {from: accounts[0]}));

        //making sure token cannot be added twice
        await truffleAssert.reverts(dex.addToken(web3.utils.fromUtf8("LINK"),link.address, {from: accounts[0]}));
    })
    it("should handle deposits correctly", async () => {
  
        //creating instance of wallet contract
        let dex = await Dex.deployed();
      
        //creating instance of LINK token contract
        let link = await Link.deployed();

        //depositing 100 link into dex wallet
        await link.approve(dex.address,500);
        await dex.deposit(500,web3.utils.fromUtf8("LINK"));
        
        let balance = await dex.balances(accounts[0],web3.utils.fromUtf8("LINK"));

        //adding token ticker and address to wallet
        assert.equal(balance.toNumber(),500);
        
    })
    it("user should have enough ETH for limit buy order in account", async () => {
  
        //creating instance of wallet contract
        let dex = await Dex.deployed();
      
        //creating instance of LINK token contract
        let link = await Link.deployed();

        //checking to make sure too large order fails
        await truffleAssert.reverts(createLimitOrder(web3.utils.fromUtf8("LINK"),Dex.Side.BUY,500000000,2));
       
        //checking to make sure smaller order passes
        await truffleAssert.passes(createLimitOrder(web3.utils.fromUtf8("LINK"),Dex.Side.BUY,1,2));
       
    })
    it("user should have enough Tokens for limit sell order in account", async () => {
  
        //creating instance of wallet contract
        let dex = await Dex.deployed();
      
        //creating instance of LINK token contract
        let link = await Link.deployed();

        //checking to make sure too large order fails
        await truffleAssert.reverts(createLimitOrder(web3.utils.fromUtf8("LINK"),Dex.Side.SELL,5000000,2));
       
        //checking to make sure smaller order passes
        await truffleAssert.passes(createLimitOrder(web3.utils.fromUtf8("LINK"),Dex.Side.SELL,100,2));
       
    })

    it("make sure [0] in BUY is highest value", async () => {
  
        //creating instance of wallet contract
        let dex = await Dex.deployed();
      
        //creating instance of LINK token contract
        let link = await Link.deployed();

        //obtaining BUY order book for LINK
        let buy_order[] = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.BUY);

        //looking up [0] value
        let highest = await buy_order[0].price;

        let isHighest = true;

        //looking through buy_order book to determine if [0] is highest value;
        for (let i = 1;i < buy_order.length;i++) {
            if (buy_order[i].price > highest) {
                isHighest = false;
                break;
            }
        }

       assert.isTrue(isHighest);
    
    })
    it("make sure final price in SELL is lowest value", async () => {
  
        //creating instance of wallet contract
        let dex = await Dex.deployed();
      
        //creating instance of LINK token contract
        let link = await Link.deployed();

        //obtaining BUY order book for LINK
        let sell_order[] = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.sell);

        //looking up [0] value
        let last = await sell_order.length - 1;
        let lowest = await sell_order[last].price;

        let isLowest = true;
        
        //looking through buy_order book to determine if length - 1 is highest value;
        for (let i = last - 1;i > 0;i -= 1) {
            if (buy_order[i].price < lowest) {
                isLowest = false;
                break;
            }
        }     

       assert.isTrue(isLowest);
    
    })
})
2 Likes

Hereā€™s what I gotā€¦ >?<

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

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

contract("Dex - Order Book",async accounts =>{
  it("deposited ETH must be more than the buy order value", async ()=>{
    let dex = await Dex.deployed();

    await truffleAssert.reverts(dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"),10,2,{from: accounts[0]}));
    await dex.depositETH({value: 90});
    await truffleAssert.passes(dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"),10,2,{from: accounts[0]}));

    let estimatedCost = 10*2;
    let estimatedEther = dex.ethBalance(accounts[0]).then(y => y.toString()); 

    assert.isAtLeast(estimatedEther , estimatedCost,"Got sufficient ETH to buy");
  });

  it("should have enough tokens deposited for sell",async () =>{
    let dex = await Dex.deployed();
    let link = await Link.deployed();

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

    const balance = dex.balances(accounts[0],web3.utils.fromUtf8("LINK")).then(x => x.toNumber());

    await truffleAssert.passes(dex.createLimitOrder(1,web3.utils.fromUtf8("LINK"),7,2,{from: accounts[0]}));
    assert.isAtLeast(balance,7,"Got sufficient LINK to sell");
  });

  it("buy order book should be in descending order by price",async ()=>{
       let dex = await Dex.deployed();

       await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"),1, 13);
       await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"),1, 10);
       await dex.createLimitOrder(0,web3.utils.fromUtf8("LINK"),1, 9);

       const buyList = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),0);

        buyList.every(function (x, i) {
            assert(i === 0 || x.price <= buyList[i - 1].price,"Buy order book not in correct order");
        });
  });

  it("sell order book should be in ascending order by price",async ()=>{
    let dex = await Dex.deployed();

    await dex.createLimitOrder(1,web3.utils.fromUtf8("LINK"),1, 9);
    await dex.createLimitOrder(1,web3.utils.fromUtf8("LINK"),1, 10);
    await dex.createLimitOrder(1,web3.utils.fromUtf8("LINK"),1, 13);

    const sellList = dex.getOrderBook(web3.utils.fromUtf8("LINK"),1);

    sellList.every(function (x, i) {
        assert(i === 0 || x.price >= sellList[i - 1].price,"Sell order book not in correct order");
    });
});
});
2 Likes

Here are my first Dex tests:

const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")

const truffleAssert = require("truffle-assertions")

contract("Dex", accounts => {

    it("revert a BUY order request when ETH balance is not sufficient", async () => {
        let dex = await Dex.deployed()
        
        dex.depositETH({value: web3.utils.toWei("1", "ether")})

        let ticker = web3.utils.fromUtf8("LINK")
        let amount = 100  // LINK to buy
        let price = 20    // LINK/ETH price

        await truffleAssert.reverts(
            dex.submitOrder(Side.BUY, ticker, amount, price) // 5 ETH Order
        )
    })


    it("has expected ETH balance after a BUY order is placed", async () => {
        let dex = await Dex.deployed()
        let ticker = web3.utils.fromUtf8("LINK")

        let amount = 100  // LINK to buy
        let price = 20    // LINK/ETH price

        let ethBefore = dex.availableEth()
        await dex.submitOrder(Side.BUY, ticker, amount, price)
        let ethAfter = dex.availableEth()

        let orderValue = amount.div(price); // 5 ETH
        assert.equals(ethAfter, ethBefore - orderValue, "Incorrect ETH balance after placing buy order")
    })


    it("revert a SELL order request when the token balance is not sufficient", async () => {
        let dex = await Dex.deployed()

        let ticker = web3.utils.fromUtf8("LINK")
        let depositAmount = 20 
        let sellAmount = 100
        let price = 20 

        dex.deposit(ticker, depositAmount)
        
        await truffleAssert.reverts(
            dex.submitOrder(Side.SELL, ticker, sellAmount, price)
        )
    })

    it("has expected token balance after a SELL order is placed", async () => {
        let dex = await Dex.deployed()
        let ticker = web3.utils.fromUtf8("LINK")

        let sellAmount = 100  // LINK to sell
        let price = 20    // LINK/ETH price
        
        let tokensBefore = dex.tokenAvailableBalance(ticker)
        await dex.submitOrder(Side.SELL, ticker, sellAmount, price)
        let tokensAfter = dex.tokenAvailableBalance(ticker)

        assert.equals(tokensAfter, tokensBefore - sellAmount, "Incorrect token balance after placing sell order")
    })


    it("has BUY orderbook for a token ordered by descending prices", async () => {
        let dex = await Dex.deployed()
        let ticker = web3.utils.fromUtf8("LINK")
        let amount = 10

        dex.submitOrder(Side.BUY, ticker, amount, 20) 
        dex.submitOrder(Side.BUY, ticker, amount, 40) 
        dex.submitOrder(Side.BUY, ticker, amount, 10) 
        dex.submitOrder(Side.BUY, ticker, amount, 15) 

        let orders = dex.getOrderBook(ticker, Side.BUY)
        let orderPrices = orders.map(order => order.price)
        let expectedPrices = [40, 20, 15, 10]

        assert.equals(orderPrices, expectedPrices, "Incorrect ordering of buy orders in orderbook")
    })

    it("has SELL orderbook for a token ordered by ascending prices", async () => {
        let dex = await Dex.deployed()
        let ticker = web3.utils.fromUtf8("LINK")
        let amount = 10

        dex.submitOrder(Side.SELL, ticker, amount, 20) 
        dex.submitOrder(Side.SELL, ticker, amount, 40) 
        dex.submitOrder(Side.SELL, ticker, amount, 10) 
        dex.submitOrder(Side.SELL, ticker, amount, 15) 

        let orders = dex.getOrderBook(ticker, Side.BUY)
        let orderPrices = orders.map(order => order.price)
        let expectedPrices = [10, 15, 20, 40]

        assert.equals(orderPrices, expectedPrices, "Incorrect ordering of sell orders in orderbook")
    })
    
})
1 Like

Test Solution:

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

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

    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()
        dex.addTokens(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]}) 
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
        await link.approve(dex.address, 500);
        await dex.deposit(10, web3.utils.fromUtf8("LINK"));
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })

    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: 2000})
        await link.approve(dex.address, 600);   
        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 = 1; i < orderbook.length; i++) {           
            assert(orderbook[i].price >= orderbook[i+1].price)         
        }
    })

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

Hi @dan-i,
I was thinking that submitting limit orders should reduce the ETH balance (for buy orders) or token balance (for sell orders) held by the dex and ā€œreserveā€ those amounts for execution of the limit orders.
At the moment, its possible for the same user to issue multiple limit orders, as long as each of them is within the eth/tooken balance, but some of these orders might not be possible to fill because the account balance changed between when the limit order was submitted and when it was filled.

Is it a good idea to separate the account balance into 2, an available balance and a reserved balance?
When a limit order is received, the available balance should be reduced and the reserved balance should be increased, When an order is cancelled the opposite should happen.

What do you think?

Skipped 4 wallettest.js for limiting testing to dextext.js - The issue I get is that two dextexts donā€™t fail as expected but I canā€™t find out why.

  1. assure enough ETH is deposited for creating buy limit order
  2. assure token balance is sufficient for creating a sell limit order
const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = require("truffle-assertions")


contract("Dex", accounts => {
    
    // The user must have sufficient ETH deposited to create a limit order
    it("should throw an error if ETH balance is too low when creating a 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)
        )
    })

    // The user must have enough tokens deposited such that token balance >= sell order amount (enough LINK tokens to sell)
    it("should throw an error if token balance is too low when creating a SELL limit order", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        await truffleAssert.reverts(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
        await link.approve(dex.address, 500)
        await dex.deposit(10, web3.utils.fromUtf8("LINK"))
        await truffleAssert.passes(
            dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
        )
    })

The error message I get in ganache-cli:
Screenshot 2021-07-23 at 10.02.54

Full code available at: https://github.com/sneicode/DEX

Thanks for providing some hints for me to resolve this as independently as possible !
yestome

Hey @yestome, hope you are ok.

The contract function is not failing because there is no code on it, although is being executed anyway.
image

Carlos Z

Here is my Limit Order Test solution:

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

contract("Dex", accounts => {
    it("should have enough ETH to execute the buy order", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        
        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 23, 1)
        )
        
        dex.depositEth({value: 300});
        
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"),0, 23, 1)
        )
    })

    it("should not be able to place a limit sell order if account doesnt have enough tokens", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        
        await truffleAssert.reverts(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"),1, 23, 1)
        )
        
        await dex.addToken(web3.utils.fromUtf8("LINK"), link.address);

        await link.approve(dex.address, 450);
        
        await dex.deposit(50, web3.utils.fromUtf8("LINK"));
        
        await truffleAssert.passes(
            dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 10, 1)
        )
    })
    
    it("the fisrt buy order in the orderbook should have the highest price", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        
        await link.approve(dex.address, 500);
        
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 35);
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 40);
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 23);

        let orderbook = await dex.getOrderBook(0, web3.utils.fromUtf8("LINK"));
        assert(orderbook[0].price == 40);
    })
 
    it("the first sell order in the orderbook should have the lowest price", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        
        await link.approve(dex.address, 500);
        
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 50);
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 25);
        await dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 10);

        let orderbook = await dex.getOrderBook(1, web3.utils.fromUtf8("LINK"));
        assert(orderbook[0].price == 10);
    })
})

Are these two lines of code the same? Both deposits eth?
Just I donā€™t understand where is the function depositEth() in the 1st line? How this line can deposit eth? In 2nd line the function deposit() we created in Wallet contract, but where is depositEth(), how we can use it when we nowhere declare it?

1. await dex.depositEth({value: 300}) 

2. await dex.deposit({value: web3.utils.toWei('300', 'ether')})
1 Like