// The user must have ETH deposited such that deposited eth >= buy order value
// The user must have enough tokens deposited such that token balance >= sell order amount
// The BUY order book should be ordered on price from highest to lowest starting at index 0
// The SELL order book should be ordered on price from lowest to highest starting at index 0
const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = require('truffle-assertions')
contract("Dex", accounts => {
it("should only be possible to create a BUY limit order that the user can afford", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await truffleAssert.reverts(
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 3, 1)
);
await dex.depositETH({value: 3});
await truffleAssert.passes(
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 3, 1)
);
})
it("should only be possible to create a SELL limit order that the user can afford", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await truffleAssert.reverts(
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 5, 1)
);
await dex.addtoken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await link.approve(dex.address, 5)
await dex.deposit(5, web3.utils.fromUtf8("LINK"));
await truffleAssert.passes(
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 5, 1)
);
})
it("should order the BUY order book highest to lowest", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await link.approve(dex.address, 500);
await dex.depositETH({value: 3000});
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 300)
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 100)
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 0, 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price >= orderbook[i+1].price, "not right order in buy book")
}
})
it("should order the SELL order book lowest to highest", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await link.approve(dex.address, 500);
await dex.depositETH({value: 3000});
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 300)
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 100)
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price <= orderbook[i+1].price, "not right order in sell book")
}
})
})
const Dex = artifacts.require("Dex");
const Token = artifacts.require("Token");
const truffleAssert = require("truffle-assertions");
contract("Dex", accounts => {
it("can't create an ETH order the user can't afford.", async () => {
let dex = await Dex.deployed();
// Deposit 100 ETH.
await dex.depositETH({value: 100});
// Make a valid order with 100 ETH, and an invallid order with 500 ETH.
await truffleAssert.passes( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("LINK")));
await truffleAssert.reverts( dex.createLimitOrder(1, 500, 500, web3.utils.fromUtf8("LINK")));
assert( dex.getOrderBook().length > 0 );
});
it("can't create a token order the user can't afford", async () => {
let dex = await Dex.deployed();
let token = await Token.deployed();
// Deposite 100 tokens.
await token.approve(dex.address, 100);
await dex.deposit(100, web3.utils.fromUtf8("LINK"));
// Make a valid order of 100 tokens, then make an invallid order with 500 tokens.
await truffleAssert.passes( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("LINK")));
await truffleAssert.reverts( dex.createLimitOrder(1, 500, 500, web3.utils.fromUtf8("LINK")));
assert( dex.getOrderBook().length > 0 );
});
it("can't to create orders for unsupported tokens", async () => {
let dex = await Dex.deployed();
let token = await Token.deployed();
// Deposite 100 tokens.
await token.approve(dex.address, 100);
await dex.deposit(100, web3.utils.fromUtf8("LINK"));
// Make orders to LINK and UNSPORTED.
await truffleAssert.passes( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("LINK")));
await truffleAssert.reverts( dex.createLimitOrder(1, 100, 100, web3.utils.fromUtf8("UNSPORTED")));
});
it("should order the BUY book in descending order.", async () => {
let dex = await Dex.deployed();
let token = await Token.deployed();
// Deposite 100 tokens.
await token.approve(dex.address, 100);
await dex.deposit(100, web3.utils.fromUtf8("LINK"));
// Submit some orders.
await dex.createLimitOrder(1, 10, 30, web3.utils.fromUtf8("LINK"));
await dex.createLimitOrder(1, 10, 10, web3.utils.fromUtf8("LINK"));
await dex.createLimitOrder(1, 10, 20, web3.utils.fromUtf8("LINK"));
// Retrieve orderbook and check if ascending.
let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
for(let i = 1; i < orderBook.length; i++) {
assert(orderBook[i].price <= orderBook[i - 1].price);
}
});
it("should order the SELL book in ascending order.", async () => {
let dex = await Dex.deployed();
let token = await Token.deployed();
// Deposite 100 tokens.
await token.approve(dex.address, 100);
await dex.deposit(100, web3.utils.fromUtf8("LINK"));
// Submit some orders.
await dex.createLimitOrder(1, 10, 30, web3.utils.fromUtf8("LINK"));
await dex.createLimitOrder(1, 10, 10, web3.utils.fromUtf8("LINK"));
await dex.createLimitOrder(1, 10, 20, web3.utils.fromUtf8("LINK"));
// Retrieve orderbook and check if ascending.
let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
for(let i = 1; i < orderBook.length; i++) {
assert(orderBook[i].price >= orderBook[i - 1].price);
}
});
});
not tested but here is the try
const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = require('truffle-assertions');
contract("Dex", accounts => {
it("User´s deposited eth is larger or equal to buy order amount", async => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await truffleAssert.reverts(
dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"), 10,1)
)
await dex.deposit(100, 0)
await truffleAssert.passes(
dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"),10,1)
)
})
it('should throw an error if token balance is too low when creating a SELL order', async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await truffleAssert.reverts(
dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),10,1)
);
await dex.deposit(100, web3.utils.fromUtf8("LINK"))
// Create SELL order for 5 LINK @ 2 ETH/LINK
await truffleAssert.passes(
dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),10,1)
)
})
it("ordering the BUY book in descending order.", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.deposit(100, web3.utils.fromUtf8("LINK"))
// Submit some orders.
await dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"),10,1)
await dex.createLimitOrder(BUY, web3.utils.fromUtf8("LINK"),20,1)
// Retrieve orderbook and check if ascending.
let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
for(let i = 1; i < orderBook.length; i++) {
assert(orderBook[i].price <= orderBook[i - 1].price)
}
})
it("ordering the sell book in ascending order.", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.deposit(100, web3.utils.fromUtf8("LINK"))
await dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),10,1)
await dex.createLimitOrder(SELL, web3.utils.fromUtf8("LINK"),20,1)
let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
for(let i = 1; i < orderBook.length; i++) {
assert(orderBook[i].price >= orderBook[i - 1].price)
}
})
})
Here is my updated code: Fixed errors and cleanup with grouping.
dextest.js
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link")
const truffleAssert = require('truffle-assertions')
const LINK = web3.utils.fromUtf8("LINK")
const BUY = 0
const SELL = 1
contract("Dex Contract Limit Order Test", accounts => {
let dex
let link
before(async () => {
dex = await Dex.deployed()
link = await Link.deployed()
await dex.addToken(LINK, link.address, {from: accounts[0]})
await link.approve(dex.address, 500)
})
describe('Initial state', () => {
it("should assert true when dex deployed", async function () {
return assert.isTrue(true);
});
})
describe('Limit Buying', () => {
it("should throw an error when creating a BUY limit order without a positive ETH balance", async () => {
await truffleAssert.reverts(
dex.createLimitOrder(BUY, LINK, 10, 2)
)
});
it("should allow creating a BUY limit order with a positive ETH balance", async () => {
await dex.depositEth({value: 100})
await truffleAssert.passes(
dex.createLimitOrder(BUY, LINK, 50, 2)
)
});
})
describe('Limit Selling', () => {
it("should throw an error when creating a SELL limit order without a positive Token balance", async () => {
await truffleAssert.reverts(
dex.createLimitOrder(SELL, LINK, 5, 10)
)
});
it("should allow creating a SELL limit order with a positive Token balance", async () => {
await dex.deposit(100, LINK)
await truffleAssert.passes(
dex.createLimitOrder(SELL, LINK, 5, 10)
)
});
})
describe('Orderbook Sorting', () => {
it("should sort the BUY orders from highest price to lowest price starting at index 0", async () => {
await dex.depositEth({value: 1000})
//BUY side limit orders
await dex.createLimitOrder(BUY, LINK, 1, 35)
await dex.createLimitOrder(BUY, LINK, 2, 25)
await dex.createLimitOrder(BUY, LINK, 4, 40)
await dex.createLimitOrder(BUY, LINK, 1, 50)
await dex.createLimitOrder(BUY, LINK, 1, 75)
let orderbook = await dex.getOrderBook(LINK, BUY)
assert(orderbook.length > 0)
console.log(orderbook);
//check orderbook sorted correctly
for(let i = 0; i < orderbook.length -1; i++) {
assert(orderbook[i].price >= orderbook[i +1].price)
}
});
it("should sort the SELL orders from lowest price to highest price starting at index 0", async () => {
await dex.deposit(100, LINK)
//SELL side limit orders
await dex.createLimitOrder(SELL, LINK, 10, 75)
await dex.createLimitOrder(SELL, LINK, 15, 35)
await dex.createLimitOrder(SELL, LINK, 20, 50)
await dex.createLimitOrder(SELL, LINK, 40, 25)
await dex.createLimitOrder(SELL, LINK, 15, 40)
let orderbook = await dex.getOrderBook(LINK, SELL)
assert(orderbook.length > 0)
console.log(orderbook);
//check orderbook sorted correctly
for(let i = 0; i < orderbook.length -1; i++) {
assert(orderbook[i].price <= orderbook[i +1].price)
}
});
})
});
Limit Order Test
This is my solution to the assignment.
Limit Order Assignment
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const truffleAssert = artifacts.require("truffle-assertions");
contract("Dex", accounts => {
//the user must have ETH deposited such that deposited Eth >= buy order value
it("should only be possible to deposit ETH into a BUY order if owner has more than or equal to that amount", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
dex.depositEth({value: 5}) //ETH deposited
await truffleAssert.passes(
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 1)
)
await truffleAssert.reverts(
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 5, 1)
)
})
//the user must have enough tokens deposited such that token balance > sell order amount
it("Should only be possible to place sell order with more than enough tokens", async () => {
let dex = await Dex.deployed();
let link = await Link.deployed();
await link.approve(dex.address, 500);
await dex.deposit(200, web3.utils.fromUtf8("LINK"));
await truffleAssert.passes(
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 100, web3.utils.toWei("2", "ether"))
)
await truffleAssert.reverts(
dex.createLimitOrder(web3.utils.fromUtf8("LINK"), 1, 300, web3.utils.toWei("2", "ether"))
)
})
//the first order ([0]) in the BUY order book should have the highest price
it("BUY orderbook should be ordered on price from highest to lowest starting at index 0", async () => {
let dex = await Dex.deployed();
let link = await Link.deployed();
await link.approve(dex.address, 1000);
//add some orders in unordered price sequence
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
assert(orderbook.length > 0);
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price >= orderbook[i+1].price, "Order book not correctly sorted!");
}
})
//the first order ([0]) in the SELL order book should have the lowest price
it("The SELL order book should be ordered on price from lowest to highest starting at index 0", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await link.approve(dex.address, 1000);
await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
assert(orderbook.length > 0);
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price <= orderbook[i+1].price, "Order book not correctly sorted!")
}
})
})
Let me know if anything needs modifying. Thanks
const Dex = artifacts.require("dex")
const Link = artifacts.require("link")
const truffleAssertions = require('truffle-assertions');
const TA = require('truffle-assertions');
contract("Dex",accounts => {
it("User has eth >= buy order", async () =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.addToken(web3.utils.fromUtf8("ETH"),eth.address);
let eth_balance = await dex.balances[msg.sender][web3.utils.fromUtf8("ETH")]
await TA.revert(
dex.openBuyOrder(web3.utils.fromUtf8("ETH"),web3.utils.fromUtf8("LINK"),100)
)
await eth.approve(dex.address,100);
await dex.deposit(web3.utils.fromUtf8("ETH"),10);
await TA.passes(
dex.openBuyOrder(web3.utils.fromUtf8("ETH"),web3.utils.fromUtf8("LINK"),100)
)
})
it("User has token >= sell order", async () =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.addToken(web3.utils.fromUtf8("LINK"),eth.address)
await TA.revert(
dex.openSellOrder(web3.utils.fromUtf8("LINK"),web3.utils.fromUtf8("ETH"),100)
)
await link.approve(dex.address,100);
await dex.deposit(web3.utils.fromUtf8("LINK"),100);
await TA.passes(
dex.openSellOrder(web3.utils.fromUtf8("LINK"),web3.utils.fromUtf8("ETH"),100)
)
})
it("order book in sequence", async () =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let orderBook = await dex.GetOrderBook
dex.openLimitOrder("LINK",100,10)
for (let i = 0; 1<dex.orderBook.length-1; i++){
if (orderBook[web3.utils.fromUtf8("ETH")][i].price < orderBook[web3.utils.fromUtf8("ETH")][i+1].price){
let fail = 'false';
}
}
assert.AssertString.isEqual(fail,'false')
})
})
Iâm not extremely sure but letâs go
it("should have enough eth", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await truffleAssert.reverts(0, dex.limitOrder(0, web3.utils.fromUtf8("ETH"), 10, 500))
await dex.deposit(10, web3.utils.fromUtf8("ETH"))
await truffleAssert.passes(0, dex.limitOrder(0, web3.utils.fromUtf8("ETH"), 5, 500))
})
it("should have enough token", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
dex.addToken(web3.utils.fromUtf8("LINK"), link.address, { from: accounts[0] })
await truffleAssert.reverts(dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 500))
await dex.deposit(10, web3.utils.fromUtf8("LINK"))
await truffleAssert.passes(dex.limitOrder(BUY, web3.utils.fromUtf8("LINK"), 5, 500))
})
it("should first in the order book have the highest price", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 500)
await dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 200)
await dex.limitOrder(0, web3.utils.fromUtf8("LINK"), 5, 100)
for(let i = 0; i < orderBook[web3.utils.fromUtf8("ETH")][0].length; i++){
assert(orderBook[web3.utils.fromUtf8("ETH")][0][i] > orderBook[web3.utils.fromUtf8("ETH")][0][i+1]);
}
})
Hi guys, Iâm the only one that during the limitOrder test have the error âdex.depositEth is not a functionâ ?
(I donât have this function in Wallet.sol)
I need to be sure, is this all correct:
function depositEth() external payable {
balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(
msg.value
);
}
Improved after watching Limit Order Test Solution video
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const truffleAssert = require('truffle-assertions');
contract("Dex", async accounts => {
describe("orderBook Tests", () => {
it("Should only allow valid BUY limit orders", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//reverts when trader's eth balance <= buy order tokenAmount
await truffleAssert.reverts(
//params: side, ticker, tokenAmount, price
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
)
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//assert trader ETH balance >= tokenAmount*ETH price
await truffleAssert.passes(
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
)
})
it("Should only allow valid SELL limit orders", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//reverts when trader's token balance <= tokenAmount * 1/price
await truffleAssert.reverts(
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
)
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//assert trader token balance >= tokenAmount * 1/price
await truffleAssert.passes(
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
)
})
it("Should sort Buy Orderbook from highest to lowest price", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//create a series of buy limit orders, not sorted
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
//console.log(orderbook), for debugging
//assert Buy orderBook[0] is the highest price
for (let i = 0; i < orderbook.length - 1; i++) { //orderbook.length - 1 since last item will have no item to compare against
assert(orderbook[i].price >= orderbook[i+1].price, 'BuyOrderBook: orders not sorted correctly')
}
})
it("Should sort Sell Orderbook from lowest to highest price", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//create a series of sell limit orders, not sorted
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
//console.log(orderbook), for debugging
//assert SELL orderBook[0] is the lowest price
for (let i = 0; i < orderbook.length - 1; i++) { //orderbook.length - 1 since last item will have no item to compare against
assert(orderbook[i].price <= orderbook[i+1].price, 'SellOrderBook: orders not sorted correctly')
}
})
it("Should prevent limit orders for unsupported markets", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//reverts when token not found
await truffleAssert.reverts(
dex.createLimitOrder(1, web3.utils.fromUtf8("Aave"), 10, 1)
)
})
//Should be able to update an order
it("Should allow user to update a limit order", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//create a series of buy and sell orders
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400) //sell order
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
//update params: orderID, amount, price
dex.updateLimitOrder(1, 2, 200)
//check that order buy and sell order books are sorted correctly after update
//check order fills when conditions are true
})
//Should be able to delete an order
it("Should allow user to delete a limit order", async() => {
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//create a series of buy and sell orders
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400) //sell order
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
//delete params: orderID, trader (msg.sender)
await truffleAssert.passes(
dex.deleteLimitOrder(1)
//check that the orderID is associated with the trader
//check that order buy and sell order books are sorted correctly after update
)
})
it("Should fill limit order when BUY price >= SELL price", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
//test setup
dex.depositEth({value: 3000}) //deposit ETH into the contract
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK")) //deposit into msg.sender balance
//create a series of buy and sell orders
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 400) //sell order
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
await truffleAssert.passes(
//TODO: ?? Fill should fire only when BUY price >= SELL price at the completion of order creation or update (limit or market)
//What about partial fills??
dex.fillLimitOrder(TODO)
//Buyer ETH balance is decreased by order amount, token balance is increased by order amount
//Seller ETH balance is increased by order amount, token balance is decreased by order amount
//The buyer and sellers orders are removed from the orderBook
//The BUY orderBook is updated to show the next highest price first
//The SELL orderBook is updated to show the next lowest price first
//The transaction is recorded for historical reference
)
})
})
})
I think you need to pass a bytes32 converted âETHâ as the param - e.g.,
function depositETH() payable external {
require(msg.value > 0, "DEX: ETH transfer should be greater than 0");
balances[msg.sender][bytes32("ETH")]= balances[msg.sender][bytes32("ETH")].add(msg.value);
yeah, I correct it, thank you very much.
Unfortunately this isnât the origin of the error that is stopping me aah.
In the first test I have an âout of gasâ error and I donât understand whyyyy?
This error accour after the Limit order solution error, after that i write the lines:
Order[] storage orders = orderBook[ticker][uint256(side)];
orders.push(
Order(nextOrderId, msg.sender, side, ticker, amount, price)
);
if you share your repo code i can have a look for you
Thanks man,
itâs just a matter of few lines:
Order[] storage orders = orderBook[ticker][uint256(side)]; orders.push( Order(nextOrderId, msg.sender, side, ticker, amount, price) );
With these I have the error:
AssertionError: Failed with Error: Returned error: VM Exception while processing transaction: out of gas"
Without it goes smooth. Then there are the rest of my code but it doesnât matter, i think.
Dex.sol
//SPDX-License-Identifier: MIT
pragma solidity >=0.6.0 <0.9.0;
import "./Wallet.sol";
contract Dex is Wallet {
using SafeMath for uint256;
enum Side {
BUY,
SELL
}
struct Order {
uint256 id;
address trader;
Side side;
bytes32 ticker;
uint256 amount;
uint256 price;
}
uint256 public nextOrderId = 0;
mapping(bytes32 => mapping(uint256 => Order[])) public orderBook;
function getOrderBook(bytes32 ticker, Side side)
public
view
returns (Order[] memory)
{
return orderBook[ticker][uint256(side)];
}
function createLimitOrder(
Side side,
bytes32 ticker,
uint256 amount,
uint256 price
) public {
if (side == Side.BUY) {
require(balances[msg.sender]["ETH"] >= amount.mul(price));
} else if (side == Side.SELL) {
require(balances[msg.sender][ticker] >= amount);
}
Order[] storage orders = orderBook[ticker][uint256(side)];
orders.push(
Order(nextOrderId, msg.sender, side, ticker, amount, price)
);
/*require(orders.length > 0);
if (side == Side.BUY) {
for (uint256 i = orders.length - 1; i <= 0; i--) {
if (orders[i].price < orders[i - 1].price) {
Order memory ordertoMove = orders[i - 1];
orders[i - 1] = orders[i];
orders[i] = ordertoMove;
} else {
break;
}
}
} else if (side == Side.SELL) {
for (uint256 i = orders.length - 1; i >= 0; i--) {
if (orders[i].price > orders[i - 1].price) {
Order memory ordertoMove = orders[i - 1];
orders[i - 1] = orders[i];
orders[i] = ordertoMove;
} else {
break;
}
}
}*/
nextOrderId++;
}
dextest.js
it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await link.approve(dex.address, 500);
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0);
assert(orderbook.length > 0);
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price >= orderbook[i + 1].price, "not in the right order in sell book")
}
})
I was checking online and there they talk about changing something about gas parameters or stuff like that in the truffle-config, but i donât find anything that work for me for now.
Thank you mate.
Assignment for test:
const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = require("truffle-assertions")
contract("Dex", accounts => {
let dex, link;
before(() => {
dex = await Dex.deployed()
link = await Link.deployed()
await dex.addtoken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
})
it("should have enough ETH deposited to place a buy order", async() => {
await truffleAssert.reverts(
// Params order: id, isBuyOrder, ticker, amt, price
dex.createLimitOrder(1001, 1, web3.utils.fromUtf8("LINK"), 100, 1)
)
await dex.depositETH({from: accounts[0], value: web3.utils.fromWei(5, "Ether")})
await truffleAssert.passes(
// Params order: id, isBuyOrder, ticker, amt, price
dex.createLimitOrder(1001, 1, web3.utils.fromUtf8("LINK"), 100, 1)
)
})
it("should have enough tokens deposited for sell order", async() => {
await truffleAssert.reverts(
dex.createLimitOrder(1002, 0, web3.utils.fromUtf8("LINK"), 100, 1.2)
)
await link.approve(dex.address, 10000)
await dex.deposit(100, web3.utils.fromUtf8("LINK"));
await truffleAssert.passes(
dex.createLimitOrder(1002, 0, web3.utils.fromUtf8("LINK"), 100, 1.2)
)
})
it("should have Buy orders arranged in descending order according to price", async() => {
await dex.createLimitOrder(1003, 1, web3.utils.fromUtf8("LINK"), 100, 1.2)
await dex.createLimitOrder(1004, 1, web3.utils.fromUtf8("LINK"), 100, 1.3)
let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1)
for(let i = 0; i < book.length; i++) {
assert(book[i].price > book[i+1].price, "Not in correct order!")
}
})
it("should have Sell orders arranged in ascending order according to price", async() => {
await dex.createLimitOrder(1005, 0, web3.utils.fromUtf8("LINK"), 100, 1)
await dex.createLimitOrder(1006, 0, web3.utils.fromUtf8("LINK"), 100, 1.3)
let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
for(let i = 0; i < book.length; i++) {
assert(book[i].price < book[i+1].price, "Not in correct order!")
}
})
it("should remove order from orderbook after order is completed", async() => {
// Buy order
await dex.createLimitOrder(1010, 1, web3.utils.fromUtf8("LINK"), 100, 1.2)
await dex.createLimitOrder(1011, 1, web3.utils.fromUtf8("LINK"), 100, 1.3)
// Sell order
await dex.createLimitOrder(1012, 0, web3.utils.fromUtf8("LINK"), 100, 1.3)
let book = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1)
let highest_price = book[0].price
// order with highest price of 1.3 is removed from order book
assert.equal(highest_price , 1.2)
})
})
Probably you need to activate the optimizer on the compiler settings, here is an example:
// Configure your compilers
compilers: {
solc: {
version: ">=0.6.0 <0.8.0", // Fetch exact version from solc-bin (default: truffle's version)
// docker: true, // Use "0.5.1" you've installed locally with docker (default: false)
settings: { // See the solidity docs for advice about optimization and evmVersion
optimizer: {
enabled: false,
runs: 200
},
evmVersion: "byzantium"
}
}
},
Carlos Z
This is my code for limit order test, I struggled for a while because I kept getting âout of gasâ error, after lot of reading, staring and debugging, it turned out I was missing await in one of the statements.
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link")
const truffleAssert = require("truffle-assertions");
contract("Dex", accounts => {
it("Should throw an error if ETH balance is not > limit BUY order value", async () => {
// The user must have ETH deposited such that deposited eth >= buy order value
let dex = await Dex.deployed() // Get wallet instance
let link = await Link.deployed() // Get link instance
await truffleAssert.reverts(
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
)
await dex.depositEth({value: 3000})
await truffleAssert.passes(
dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
)
})
it("should throw error if token balance is not >= limit SELL order", async () => {
// Sell order for a token: The user must have enough tokens deposited such that token balance > sell order amount
let dex = await Dex.deployed() // Get wallet instance
let link = await Link.deployed() // Get link instance
await truffleAssert.reverts(
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
)
await link.approve(dex.address, 500)
await dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
await dex.deposit(10, web3.utils.fromUtf8("LINK"))
await truffleAssert.passes(
dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
)
})
it("should always have orders from from highest to lowest price in BUY order book.", async () => {
// The first order ([0]) in the BUY order book should have the highest price
let dex = await Dex.deployed() // Get wallet instance
let link = await Link.deployed() // Get link instance
await link.approve(dex.address, 500);
await dex.depositEth({value: 3000});
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
await dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
assert(orderbook.length > 0)
assert(orderbook[0].price == 300)
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price >= orderbook[i+1].price, "Not right order in orderBook")
}
})
it("should always have orders from from lowest to highest price in SELL order book.", async () => {
// The orders should always be from lowest to highest price in SELL order book .
// The first order ([0]) in the SELL order book should have the lowest price
let dex = await Dex.deployed() // Get wallet instance
let link = await Link.deployed() // Get link instance
await link.approve(dex.address, 500);
await dex.depositEth({value: 3000});
// 1 was added in previous test
await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)
let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1)
assert(orderbook.length > 0)
assert(orderbook[1].price == 100)
for (let i = 0; i < orderbook.length - 1; i++) {
assert(orderbook[i].price <= orderbook[i+1].price, "Not right order in orderBook")
}
})
})
@thecil thank you for your support man,even though
I had already changed it. @Onemilestone saved my life ahah. I was making the same stupid mistake. Now i can push forward.
Thank you guys.
@thecil
As a learning exercise, I am trying to add Update and Delete functionality to limit orders.
In the spirit of TDD (I love how Filip pushes for this), I wrote my tests first .
I am working on the Delete function. My test is breaking because getTraderOrderBook() is returning an AssertionError(expected 0 to equal 3).
In order to get the BUY orderbook filtered for only the particular trader (address), I wrapped the orderBook mapping inside another mapping. Not sure if that works
Iâm not sure whether my error is in my function or in my 3-level mapping.
Note about commented lines: I am using Hardhat. I tried debugging using console.log but hardhat returned TypeError: TypeError: Member âlogâ instead of an empty array.
Would be grateful for any hints or suggestions. I mean, delete and update, I should be able to do this!
Here is my code for getting an order book specific to the trader:
//map ticker,side => Order
mapping(bytes32 => mapping(uint256 => Order[])) public orderBook;
//map trader,ticker,side => Order
mapping(address => mapping(bytes32 => mapping(uint256 =>Order[]))) public traderOrderBook;
function getOrderBook(bytes32 ticker, Side side) public view returns (Order[] memory){
return orderBook[ticker][uint256(side)];
}
function getTraderOrderBook(address trader, bytes32 ticker, Side side) public view returns (Order[] memory){
//Order[] memory traderOrders = traderOrderBook[trader][ticker][uint256(side)];
//console.log(traderOrders);
//return traderOrders;
return traderOrderBook[trader][ticker][uint256(side)];
}
Here are my tests. I am using hardhat:
const { ethers } = require('hardhat');
const { expect } = require('chai');
const Side = {
BUY: 0,
SELL: 1,
};
const Tokens = {
LINK: ethers.utils.formatBytes32String('LINK'),
};
let Dex;
let dex;
let Link;
let link;
let owner;
let trader1;
let trader2;
describe("Limit order tests", function () {
beforeEach(async function () {
Dex = await ethers.getContractFactory('Dex');
dex = await Dex.deploy();
Link = await ethers.getContractFactory('Link');
link = await Link.deploy();
//USAGE NOTE: connect with signer object, pass param with signer.address property
[owner, trader1, trader2] = await ethers.getSigners();
});
//other tests here, not shown
describe("Update and delete limit order tests", function () {
it("Should allow trader to delete a BUY order and the BUY order book should resort correctly", async () => {
//Given owner with balances
await dex.connect(owner).depositETH({ value: 10000});
await dex.connect(owner).addToken(Tokens.LINK, link.address);
await link.connect(owner).approve(dex.address, 1000);
await dex.connect(owner).depositToken(300, Tokens.LINK);
//And trader1 with balances
await link.connect(owner).transfer(trader1.address, 300);
await link.connect(trader1).approve(dex.address, 300);
await dex.connect(trader1).depositToken(300, Tokens.LINK);
await dex.connect(trader1).depositETH({ value: 10000});
//And trader2 with balances
await link.connect(owner).transfer(trader2.address, 300);
await link.connect(trader2).approve(dex.address, 300);
await dex.connect(trader2).depositToken(300, Tokens.LINK);
await dex.connect(trader2).depositETH({ value: 10000});
//And a BUY order book filled with orders from more than one trader
await dex.connect(owner).createLimitOrder(Side.BUY, Tokens.LINK, 5, 1);
await dex.connect(owner).createLimitOrder(Side.BUY, Tokens.LINK, 10, 7);
await dex.connect(owner).createLimitOrder(Side.BUY, Tokens.LINK, 15, 4);
await dex.connect(trader1).createLimitOrder(Side.BUY, Tokens.LINK, 5, 2);
await dex.connect(trader1).createLimitOrder(Side.BUY, Tokens.LINK, 10, 8);
await dex.connect(trader1).createLimitOrder(Side.BUY, Tokens.LINK, 15, 5);
await dex.connect(trader2).createLimitOrder(Side.BUY, Tokens.LINK, 5, 3);
await dex.connect(trader2).createLimitOrder(Side.BUY, Tokens.LINK, 10, 9);
await dex.connect(trader2).createLimitOrder(Side.BUY, Tokens.LINK, 15, 6);
let orderbook = await dex.getOrderBook(Tokens.LINK, Side.BUY);
expect(orderbook.length).to.be.equal(9);
//When trader1 deletes one of his buy orders
let beforeTrader1BuyLimitOrders = await dex.getTraderOrderBook(trader1.address, Tokens.LINK, Side.BUY);
expect(beforeTrader1BuyLimitOrders.length).to.be.equal(3); //<<--TEST BREAKS HERE, returns(0)
let orderId = beforeTrader1BuyLimitOrders[1].OrderID; //pick one of the 3 trader1 orders to delete
await dex.connect(trader1).deleteLimitOrder(orderId); //and delete the order
//then the order is removed from the order book
let afterTrader1BuyLimitOrders = await dex.getTraderOrderBook(trader1.address, Tokens.LINK, Side.BUY);
expect(afterTrader1BuyLimitOrders.length).to.be.equal(2);
expect(orderbook.length).to.be.equal(8);
//and the BUY order book is resorted from highest price to lowest price
for (let i = 0; i < orderbook.length - 1; i++) {
expect(orderbook[i].price).to.be.lte(orderbook[i+1].price);
}
});
it.skip("Should allow trader to delete a SELL order and the SELL order book should resort correctly", async () => {
// Given an orderbook filled with orders from more than one trader
// And a trader with several orders
// When the trader deletes an order
// Then all the following are true:
// a trader may only delete orders that belong to the trader
// the order is removed from the orderBook
// the order book is resorted correctly
});
it.skip("Should allow trader to update and order and the order book should resort correctly", async () => {
// Given an orderbook filled with orders from more than one trader
// And a trader with several orders
// When the trader updates an order
// Then all the following are true:
// a trader may only update orders that belong to the trader
// the order is updated
// the order book is resorted correctly
// BUY order in middle of order book, order price is reduced >> bubble sort compares to right
// BUY order in middle of order book, order price is raised >> bubble sort compares to left
// BUY order is top of order book, order price is raised >> no need to sorted
// BUY order is bottom of order book, order price is raised >> bubble sort compares to left
// and so on
// opposite for SELL orders
});
});
});
hey @pivot. great that your tyring to do extra things here with the delete and update function. The problem with deletion here is that the order of the orderbook matters. Since this is the case we cannot simply just do the usual method whereby we take the element we want to delete, move it to the last index and call .pop().Doing this will cause the orderbook to be come unsorted which is not what we want. If you really wanted to do it this way you could just resort after calling .pop() but there is a better way that more efficent
Instead what you can do to delete orders from the book is to use a similar approach. We first identify the index we want to delete. Then we run a loop going from this index to 1 minus the length of the array and we shift every eleemnt by 1. then we call .pop(). Ill write a dummy function so you can see what i mean
function deleteOrders(uint indexToDelete, uint side, bytes32 ticker) public onlyOwners {
Order[] storage orders = traderOrderBook[ticker][uint256(side)];
for (uint j = indexToDelete; j < orders.length-1; j++){
orders[j] = orders[j+1];
}
orders.pop();
Ill explain what this code does with an example. Say we have an array with 5 eleemtns ordered from 1 to 5 as shown below
[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ]
Now say we want to delete the third index. Byut we need to keep the array sorted. If we pass 3 as the âindexToDeleteâ var into the above function then we start our loop at that index and for each element including 3 up until the second last index in the array we swap them so the new array will look like
=> [ 1 ] [ 2 ] [ 4 ] [ 5 ] [ 5 ]
But now we need to call .pop() to get rid of the last unwanted element. and after this we have successfully removed the required eleemnt and the new resulting array is
=> [ 1 ] [ 2 ] [ 3 ] [ 4 ]
So thats for deleteion. If you want to update an order such as the price or maybe the amount its more complicated. This is because there are two scenaros.
- Update the amount => the orders position wont change (easy scenario)
- Update the price => The orders position will change (hard scenario)
I suggest you write a function called updateOrder(). Before you even enter the body you need to require that the order is not filled or partially filled at all. If this is the case then the function should revert as the user should not be able to update an order if its partially filled. The you need to handle both scenarios. For the first its easy just pass in the index of the order you want to modify and change the amount you could do this like order[indexToUpdate].amount = newAmount
In the other scenario you will need to resort after you update the price. To do this update the price as normal like order[indexToUpdate].amount = newAmount
. Then after this resort the ordrbook. The most efficent way to do this would be to segregate your bubble sort logic into its own function called sortOrder()
or something and then after you update the price you just need to call sort0rder()
. also this goes with out saying in your market order function replace the bubble sort logic with sortOrder()
too.
My last tip is you should not implement this and try to run your tests straight away they will most likley fail. You should instead launch up your terminal and manually do the tests yourself this way its easier to see where thing are going wrong and make fixes quicker rather than waiting like 30 seconds or more for your test script to finish each time you make a change to the contract code. If you need anymore help especially on the update order function share you code, i will clone it and write it out for you, but you should give it a good stab yourself first.
Happy coding,
Evan