Thank you @thecil. I appreciate the positive feedback and will look into the Ethereum Dapp Programming course. I will then try to build a functional frontend for this orderbook.
Huge man, congratulations. Did you have already front-end experience or made first some courses?
Here is my code, nothing really special. I don’t know front-end or stuff like that and I’m not so stimutaled to create some new functions in this contract. Maybe I will start create a new contract project on my own, in the meanwhile I’m hungry of knowledge, I want to studdyyyy.
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 filled;
}
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][bytes32("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, 0) // mettere 0 per filled?
);
// uint i = orders.length > 0 ? orders.length -1 : 0
uint256 i;
if (orders.length > 0) {
i = orders.length - 1;
} else {
i = orders.length;
}
if (side == Side.BUY) {
for (i; i > 0; i--) {
if (orders[i].price > orders[i - 1].price) {
Order memory swapTool = orders[i - 1];
orders[i - 1] = orders[i];
orders[i] = swapTool;
} else {
break;
}
}
} else if (side == Side.SELL) {
for (i; i > 0; i--) {
if (orders[i].price < orders[i - 1].price) {
Order memory swapTool = orders[i - 1] = orders[i - 1];
orders[i - 1] = orders[i];
orders[i] = swapTool;
} else {
break;
}
}
}
nextOrderId++;
}
function createMarketOrder(Side side, bytes32 ticker, uint256 amount) public {
if (side == Side.SELL) { //check if there are enough token
require( balances[msg.sender][ticker] >= amount,"Not enough balance");
}
uint256 orderBookSide;
if (side == Side.BUY) {
orderBookSide = 1;
} else {
orderBookSide = 0;
}
Order[] storage orders = orderBook[ticker][orderBookSide];
uint256 totalFilled = 0;
uint filled = 0;
for (uint256 i = 0; i < orders.length && totalFilled < amount; i++) {
uint leftToFill = amount.sub(totalFilled);
uint availableToFill = orders[i].amount.sub(orders[i].filled);
if (availableToFill > leftToFill) { //Ci sta dentro
filled = leftToFill;
}
else {// availableToFIll <= leftToFIll
filled = availableToFill;
}
totalFilled = totalFilled.add(filled);
orders[i].filled = orders[i].filled.add(filled);
uint256 cost = filled.mul(orders[i].price);
if (side == Side.BUY) {
require(balances[msg.sender]["ETH"] > cost);
balances[orders[i].trader][ticker] = balances[orders[i].trader][ticker].sub(filled); //seller balances
balances[orders[i].trader]["ETH"] = balances[orders[i].trader]["ETH"].add(cost);
balances[msg.sender][ticker] = balances[msg.sender][ticker].add(filled); // buyer balances
balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].sub(cost);
} else { // if SELLING
balances[orders[i].trader][ticker] = balances[orders[i].trader][ticker].add(filled); //seller balances
balances[orders[i].trader]["ETH"] = balances[orders[i].trader]["ETH"].sub(cost);
balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(filled); // buyer balances
balances[msg.sender]["ETH"] = balances[msg.sender]["ETH"].add(cost);
}
}
_cleanOrderbook(ticker, orderBookSide);
}
// Loop through the orderbook and remove 100% filled orders
function _cleanOrderbook(bytes32 ticker, uint256 orderBookSide) internal {
Order[] storage orders = orderBook[ticker][orderBookSide];
while(orders.length > 0 && orders[0].filled == orders[0].amount){
for(uint i = 0; i<orders.length - 1; i++){
orders[i] = orders[i+1];
}
orders.pop();
}
}
}
hey @giomiri ehh just a little im building it with react. react is much easier for front end once you get used to it. i do still struggle with css tho at times i just fillde with things until its right can take a while tho
Nice, I will check that course so, thanks mas
Pfeeew that was hard work,
But useful! It was also a good opportunity to learn how to use the truffle debugger, perform step by step execution and display transaction logs.
Some notes:
The createLimitOrder function sorts the limit orders in the order book so that best orders are at the end of the array.
This makes the createMarketOrder cheaper in terms of gas cost: because best sell orders (cheapest selling price) are filled first, they can be popped from the array without having to re-arrange all elements.
Dex code
Dex.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
import "./Wallet.sol";
contract Dex is Wallet
{
enum Side{
BUY,
SELL
}
struct Order{
uint id;
address trader;
Side side;
bytes32 ticker;
uint amount;
uint price;
bool filled;
uint partialFill; // Number of tokens that have been filled already (should be <= amount)
}
mapping(bytes32 => mapping(uint => Order[])) orderBook;
//This function returns a copy of the Order array. Not to be used internally (expensive)
function getOrderBook(bytes32 ticker, Side side) view external returns(Order[] memory order) {
return orderBook[ticker][uint(side)];
}
function createLimitOrder(bytes32 ticker, Side side, uint price, uint amount) tokenExists(ticker) public {
require(price > 0, "Price cannot be zero");
require(amount > 0, "Amount cannot be zero");
if(side == Side.SELL)
{
require(balances[msg.sender][ticker] >= amount, "Insufficient token balance");
Order[] storage orders = orderBook[ticker][uint(side)];
orders.push(Order(0,msg.sender, side, ticker, amount, price, false, 0));
uint i = orders.length-1;// point to last element
while(i>0)
{
// last order price is bigger than new one
if(orders[i-1].price >= orders[i].price)
{
//In order so nothing to do
break;
}else
{
// Swap orders
Order memory savedOrder = orders[i-1];
orders[i-1] = orders[i];
orders[i] = savedOrder;
}
i--;
}
}else{
require(balances[msg.sender][bytes32("ETH")] >= amount*price, "Insufficient Eth balance on Dex");
}
}
function updateBalances(bytes32 ticker, address trader, uint currentTransactionAmount, uint transactionPrice) private
{
//Transfer ETH from buyer to seller
// Check buyer has enough ETH
require(balances[msg.sender][bytes32("ETH")] >= transactionPrice, "Buyer has insufficient ETH balance");
// Debit the buyer's ETH balance
balances[msg.sender][bytes32("ETH")] -= transactionPrice;
// Credit the seller's token balance
balances[trader][bytes32("ETH")] += transactionPrice;
//Transfer tokens from seller to buyer
// Check seller has enough tokens
require(balances[trader][ticker] >= currentTransactionAmount, "Seller has insufficient token balance");
//Debit the seller's tokens balance
balances[trader][ticker] -= currentTransactionAmount;
//Credit the buyer's tokens balance
balances[msg.sender][ticker] += currentTransactionAmount;
}
function createMarketOrder(bytes32 ticker, Side side, uint buyAmount) tokenExists(ticker) public
{
if (side == Side.SELL){
require(balances[msg.sender][ticker] >= buyAmount, "Insufficient token balance on Dex");
}
else if (side == Side.BUY){
// Go through the SELL order book
Order[] storage sellOrders = orderBook[ticker][uint(Side.SELL)];
uint amountLeftToFill = buyAmount;
uint totalTransactionPrice = 0;
// Go through the sell orders, starting from the top (cheapest selling price)
for(uint i=sellOrders.length; i>0 && amountLeftToFill >0; --i)
{
Order storage currentSellOrder = sellOrders[i-1];
uint currentTransactionAmount = 0;
// can fill?
if (amountLeftToFill < (currentSellOrder.amount-currentSellOrder.partialFill)){
// Partial fill of limit order
currentTransactionAmount = amountLeftToFill;
currentSellOrder.partialFill+= currentTransactionAmount;
}
else{
// SELL order 100% filled
currentTransactionAmount = currentSellOrder.amount-currentSellOrder.partialFill;
currentSellOrder.filled = true;
currentSellOrder.partialFill = currentSellOrder.amount;
}
// Transaction price:
uint currentTransactionPrice = currentTransactionAmount * currentSellOrder.price;
totalTransactionPrice += currentTransactionPrice;
updateBalances(ticker, currentSellOrder.trader, currentTransactionAmount, currentTransactionPrice);
amountLeftToFill -= currentTransactionAmount;
}
// Clean-up filled orders
for(uint i=sellOrders.length ; i>0; --i){
if (sellOrders[i-1].filled)
sellOrders.pop();
}
}
}
}
Unit tests for createMarketOrder function
MarketOrderTests.js
const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const { link } = require("fs-extra")
const truffleAssert = require("truffle-assertions")
// Tests are structured in 3 contracts
// 1. empty order book
// 2. order book contains 1 limit order
// 3. order book contains 2 limit orders
// Only one side was implemented (BUY market order vs SELL limit orders)
// Edge cases
// A market order can be created with an empty order book
// Requirements
// Ticker must exist in DEX
// When creating a SELL market order, seller needs to have enough token for the trade
// When creating a BUY market order, buyer must have enough ETH
// Nominal Scenarios
// Market order should be filled until the order book is empty or market order 100% filled
// Effects
// ETH Balance of buyer should decrease with the filled amount
// Token balance of sellers should decrease with the filled amount
// Filled limit orders should be removed from the order book
// Partially filled limit orders should be updated in the order book
contract("DEX, createMarketOrder - order book empty", accounts => {
it("a market order can be created with an empty order book", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let ticker = web3.utils.utf8ToHex("LINK")
dex.addToken(ticker, link.address)
dex.depositEth({value: 2})
let orderBook = await dex.getOrderBook(ticker, Dex.Side.BUY)
assert(orderBook.length == 0, "This test assumes order book is empty")
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.BUY, 2))
await link.approve(dex.address, 1)
await dex.deposit(1, ticker)
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.SELL, 1))
})
it("user must have enough tokens for a sell market order", async() => {
let dex = await Dex.deployed()
let link = await Link.deployed()
let ticker = web3.utils.utf8ToHex("LINK")
await link.approve(dex.address, 1000)
await dex.deposit(100, ticker)
await truffleAssert.reverts(
dex.createMarketOrder(ticker, Dex.Side.SELL, 200)
)
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.SELL, 100)
)
})
})
contract("Dex, createMarketOrder - order book contains 1 SELL limit order", accounts =>{
// Test the following situations:
// market order fully filled by matching sell limit order
// market order partially filled with sell limit order fully met
// market order fully filled but sell limt order partially met
let ticker = web3.utils.utf8ToHex("LINK")
it("BUY order not filled because buyer has not enough ETH", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.addToken(ticker, link.address)
// Give away 10 LINK to account 1
await link.transfer(accounts[1], 10)
// Account 1 adds 10 LINK to the DEX
await link.approve(dex.address, 10, {from: accounts[1]})
await dex.deposit(10, ticker, {from: accounts[1]})
// Account 1 creates 1 limit order
await dex.createLimitOrder(ticker, Dex.Side.SELL, 3, 3, {from: accounts[1]})
// Account 0 executes market order
// Fails because not enough ETH
await truffleAssert.reverts(
dex.createMarketOrder(ticker, Dex.Side.BUY, 2)
)
})
it("BUY order filled successfully with enough ETH", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
// buyer deposits 9 wei on Dex, this should fix the test
await dex.depositEth({value: 9, from: accounts[0]});
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.BUY, 3)
)
})
it("Filled trades should be removed from the order book", async() =>{
let dex = await Dex.deployed();
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
// Following the previous test, the order book should now be empty
assert.equal(orders.length, 0, "Order book should be empty")
})
it("ETH and token balances of buyer and seller should be updated", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert.equal(orders.length, 0, "Order book should be empty")
// Account 1 to sell 1 LINK for 3 wei (Account 1 should have 2 LINK left at this stage)
await dex.createLimitOrder(ticker, Dex.Side.SELL, 3, 1, {from : accounts[1]})
await dex.depositEth({value: 15, from: accounts[0]})
buyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
sellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
sellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
buyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
await truffleAssert.passes(
// Create market order to buy 1 token - it should meet the limit order to sell 1 token
dex.createMarketOrder(ticker, Dex.Side.BUY, 1)
)
let newBuyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
let newSellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
let newSellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
let newBuyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
assert.equal( newBuyerEthBalance - buyerEthBalance, -3, "Buyer ETH balance not properly debited")
assert.equal( newSellerTokenBalance - sellerTokenBalance, -1, "Seller token balance not properly debited")
assert.equal( newSellerEthBalance - sellerEthBalance, 3, "Seller ETH balance not properly credited")
assert.equal( newBuyerTokenBalance - buyerTokenBalance, 1, "Buyer token balance not properly credited")
})
it("Limit SELL order can be partially filled: limit order should not be removed, balances should be correctly updated", async()=>{
let dex = await Dex.deployed()
// Check order book is empty
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert(orders.length == 0, "Order book should be empty at the start of the test")
// Account 1 creates 1 limit order
await dex.createLimitOrder(ticker, Dex.Side.SELL, 3, 3, {from: accounts[1]})
orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert.equal(orders.length, 1, "Order should have been added")
buyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
sellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
sellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
buyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.BUY, 2, {from: accounts[0]}))
assert.equal(orders.length, 1, "Partially filled limit order should not be removed from the order book")
let newBuyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
let newSellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
let newSellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
let newBuyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
assert.equal( newBuyerEthBalance - buyerEthBalance, -6, "Buyer ETH balance not properly debited")
assert.equal( newSellerTokenBalance - sellerTokenBalance, -2, "Seller token balance not properly debited")
assert.equal( newSellerEthBalance - sellerEthBalance, 6, "Seller ETH balance not properly credited")
assert.equal( newBuyerTokenBalance - buyerTokenBalance, 2, "Buyer token balance not properly credited")
})
it("Partially filled SELL limit order can be fully filled - it should be removed from the order book ", async()=>{
// This tests expects a partially filled SELL limit order to remain in the order book with 1 token left to be filled
let dex = await Dex.deployed()
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.BUY, 1, {from: accounts[0]}))
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert.equal(orders.length, 0, "Partially filled limit order was filled and should have been removed from the order book")
})
})
contract("Dex, createMarketOrder - 2 SELL limit orders in order book", accounts => {
let ticker = web3.utils.utf8ToHex("LINK")
it("Buyer should have enough ETH to meet all market limit orders in the order book", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.addToken(ticker, link.address)
// Give away LINK to account 1
await link.transfer(accounts[1], 20)
// Account 1 approves the Dex to withdraw LINK with the LINK smart contract
await link.approve(dex.address, 20, {from: accounts[1]})
// Account 1 deposit its LINK to the Dex
await dex.deposit(20, ticker, {from: accounts[1]})
// Account 1 creates 2 limit orders
await dex.createLimitOrder(ticker, Dex.Side.SELL, 3, 3, {from: accounts[1]})
await dex.createLimitOrder(ticker, Dex.Side.SELL, 4, 2, {from: accounts[1]})
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL)
assert.equal(orders.length, 2, "There should be 2 SELL orders in the order book")
// Account 0 has enough ETH for the first order only (3x3 = 9). Therefore the market order should fail
await dex.depositEth({value: 9})
await truffleAssert.reverts(//Not enough ETH to complete purchase
dex.createMarketOrder(ticker, Dex.Side.BUY, 5, {gas: 300000})
)
// Account 0 deposits more ETH (2x4 = 8) to meet the 2nd order
await dex.depositEth({value: 8})
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.BUY, 5, {gas: 300000})
)
})
it("Filled trades should be removed from the order book", async() =>{
let dex = await Dex.deployed();
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
// Following the previous test, the order book should now be empty
assert.equal(orders.length, 0, "Order book should be empty")
})
it("Buyer and seller ETH/token balances should be properly credited/debited", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert.equal(orders.length, 0, "Order book should be empty")
await dex.depositEth({value: 17, from: accounts[0]})
// Account 1 adds 2 limit orders for a total of 5 tokens
await dex.createLimitOrder(ticker, Dex.Side.SELL, 3, 3, {from: accounts[1]})
await dex.createLimitOrder(ticker, Dex.Side.SELL, 4, 2, {from: accounts[1]})
orders = await dex.getOrderBook(ticker, Dex.Side.SELL)
assert.equal(orders.length, 2, "There should be 2 SELL orders in the order book")
buyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
sellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
buyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
sellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
await truffleAssert.passes(
// Create market order to buy 5 tokens - it should meet both limit orders
dex.createMarketOrder(ticker, Dex.Side.BUY, 5)
)
let newBuyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
let newSellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
let newBuyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
let newSellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
assert.equal( newBuyerEthBalance - buyerEthBalance, -17, "Buyer ETH balance not properly debited")
assert.equal( newSellerTokenBalance - sellerTokenBalance, -5, "Seller token balance not properly debited")
assert.equal( newSellerEthBalance - sellerEthBalance, 17, "Seller ETH balance not properly credited")
assert.equal( newBuyerTokenBalance - buyerTokenBalance, 5, "Buyer token balance not properly credited")
})
it("Limit SELL order can be partially filled: limit order should not be removed, balances should be correctly updated", async()=>{
let dex = await Dex.deployed()
// Check order book is empty
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert(orders.length == 0, "Order book should be empty at the start of the test")
// Account 1 creates 1 limit order
await dex.createLimitOrder(ticker, Dex.Side.SELL, 3, 3, {from: accounts[1]})
await dex.createLimitOrder(ticker, Dex.Side.SELL, 4, 2, {from: accounts[1]})
orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert.equal(orders.length, 2, "Order should have been added")
// Account 0 will need 13 wei for this operation (3x3 + 1x4)
await dex.depositEth({value: 13, from: accounts[0]})
buyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
sellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
sellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
buyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
await truffleAssert.passes(
// Account 0 wants to BUY 4 tokens only, therefore one limit order will be partially filled
dex.createMarketOrder(ticker, Dex.Side.BUY, 4, {from: accounts[0]}))
assert.equal(orders.length, 2, "Partially filled limit order should not be removed from the order book")
let newBuyerEthBalance = await dex.getMyEthBalance({from: accounts[0]})
let newSellerEthBalance = await dex.getMyEthBalance({from: accounts[1]})
let newSellerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[1]})
let newBuyerTokenBalance = await dex.getMyTokenBalance(ticker, {from: accounts[0]})
assert.equal( newBuyerEthBalance - buyerEthBalance, -13, "Buyer ETH balance not properly debited")
assert.equal( newSellerTokenBalance - sellerTokenBalance, -4, "Seller token balance not properly debited")
assert.equal( newSellerEthBalance - sellerEthBalance, 13, "Seller ETH balance not properly credited")
assert.equal( newBuyerTokenBalance - buyerTokenBalance, 4, "Buyer token balance not properly credited")
})
it("Partially filled SELL limit order can be fully filled - it should be removed from the order book ", async()=>{
// This tests expects a partially filled SELL limit order to remain in the order book with 1 token left to be filled
let dex = await Dex.deployed()
// Account 0 will need 13 wei for this operation (1x4)
await dex.depositEth({value: 4, from: accounts[0]})
await truffleAssert.passes(
dex.createMarketOrder(ticker, Dex.Side.BUY, 1, {from: accounts[0]}))
let orders = await dex.getOrderBook(ticker, Dex.Side.SELL);
assert.equal(orders.length, 0, "Partially filled limit order was filled and should have been removed from the order book")
})
})
Unit tests for createLimitOrder function
DexTest.js
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const truffleAssert = require("truffle-assertions");
// Test ideas:
// ticker should be valid
// create limit order with price or amount zero should fail
// Token amount sufficient for sell
// ETH amount sufficient for buy
// sell order book ordered by ascending price
contract("DEX, createLimitOrder", accounts => {
it("should handle invalid ticker", async() =>{
let dex = await Dex.deployed()
await truffleAssert.reverts(
dex.createLimitOrder(web3.utils.utf8ToHex("INVA"), Dex.Side.BUY, 1, 1)
)
})
it("amount and price should not be zero", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
await dex.addToken(web3.utils.utf8ToHex("LINK"), link.address, {from: accounts[0]})
await dex.depositEth({value:1000});
let ethBalance = await dex.getMyEthBalance();
console.log("Dex Eth balance:" + ethBalance )
await truffleAssert.reverts(
dex.createLimitOrder(web3.utils.utf8ToHex("LINK"), Dex.Side.BUY, 1, 0)
)
await truffleAssert.reverts(
dex.createLimitOrder(web3.utils.utf8ToHex("LINK"), Dex.Side.BUY, 0, 1)
)
/*f
console.log("Dex address:" + dex.address)f
let balance = await web3.eth.getBalance(dex.address)
let balance2 = await dex.balances[accounts[0]][web3.utils.utf8ToHex("ETH")]
*/
await truffleAssert.passes(
dex.createLimitOrder(web3.utils.utf8ToHex("LINK"), Dex.Side.BUY, 1, 1)
)
})
it("user should have enough tokens for a sell order", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let linkTicker = web3.utils.utf8ToHex("LINK")
await dex.addToken(linkTicker, link.address)
await link.approve(dex.address, 500)
await dex.deposit(29, linkTicker)
await truffleAssert.reverts(
// Create a sell order
dex.createLimitOrder(linkTicker, Dex.Side.SELL, 2, 30)
)
await truffleAssert.passes(
// Create a sell order with sufficient token balance
dex.createLimitOrder(linkTicker, Dex.Side.SELL, 2, 29)
)
})
it("buyer should have enough ETH balance to execute the buy order", async()=>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let linkTicker = web3.utils.utf8ToHex("LINK")
await dex.addToken(linkTicker, link.address)
await dex.depositEth({value: 60})
let ethBalance = await dex.getMyEthBalance()
console.log("Eth balance " + ethBalance)
await truffleAssert.reverts(
// Create a sell order
dex.createLimitOrder(linkTicker, Dex.Side.BUY, 2, 1000)
)
await truffleAssert.passes(
// Create a sell order with sufficient token balance
dex.createLimitOrder(linkTicker, Dex.Side.BUY, 2, 30)
)
})
// cheapest price is at the top of the array because it will be the first one to be 'popped' if an order goes through
it("sell order book should be ordered by price descending", async() =>{
let dex = await Dex.deployed()
let link = await Link.deployed()
let linkTicker = web3.utils.utf8ToHex("LINK")
await dex.addToken(linkTicker, link.address)
await link.approve(dex.address, 500)
await dex.deposit(100, linkTicker)
await dex.createLimitOrder(linkTicker, Dex.Side.SELL, 3, 5)
await dex.createLimitOrder(linkTicker, Dex.Side.SELL, 2, 5)
await dex.createLimitOrder(linkTicker, Dex.Side.SELL, 6, 5)
let orderBook = await dex.getOrderBook(linkTicker, Dex.Side.SELL)
if (orderBook.length > 0)
{
for(let i=1; i<orderBook.length; i++){
assert(orderBook[i-1].price >= orderBook[i].price, "wrong ordering at element " + i + " price "+orderBook[i].price);
}
}
}
)
})
Here is my Dex Project it’s almost ready but there is something I cannot get past by : https://github.com/Riki0923/DEX
I am able to deposit Eth now to the contract address, works fine now with metamask but when I would like to withdraw eth and do some limit order I always get this error:
“Uncaught (in promise) Error: invalid number value (arg=“amount”, coderType=“uint256”, value=“0.1”)”
I’ve looked in google and the solution I found is that I need to change the uint toString(), but I do not know which part of the code I should do that? Can someone help me out?
Thank you!
niceeee great work @Riki. i think your problem is you pssing in a decmal into the create limit order function. try converting this to wei and pas the wei demonitated value in to the createlimit order instead
yeah this should be why i think. look a your code
let enum1 = $("#placedEnumLimitOrder").val();
console.log(enum1);
let token1 = web3.utils.fromUtf8($("#placedTokenLimitOrder").val());
console.log(token1);
let amount1 = $("#placedAmountLimitOrder").val();
console.log(amount1);
let price = $("#placedPriceLimitOrder").val();
console.log(price);
await dex.methods.createLimitOrder(enum1, token1, amount1, price).send();
alert("Limit Order Succesfully created!!");
reloadPage();
try change amount to
let amount1 = $("#placedAmountLimitOrder").val();
amount1 = web3.utils.toWei(amount1.toString(), "ether");
await dex.methods.createLimitOrder(enum1, token1, amount1, price).send();
Hello @mcgrane5 !
Thank you very much for looking to my query.
So I have tried this first on the WithdrawEth function because I am getting the same error there too.
Now I do not get anny error but the transaction is not going through.
As you can see I have deposited 0.2 there and then I would like to withdraw 0.1. Console.log says that the value is indeed 0.1 but the balance after the transaction is the same than before.
Code part here:
Any more idea you have? ( I am trying things out meanwhile )
Code part is here. I think if withdraw will work, so will the order functions. I have also tried with your suggestion in addition with the toString, but still the same thing.
Okay solution found for withdraw but there is one thing I dont get.
Now if I do the withdraw it works okay in the contract but it also withdraws the same amount from the metamask account too:
Example:
I have withdrawed 0.1 Eth but it has been withdrawned from both places
code is this
async function withdrawEth(){
let withdrawedEth = $("#wEth").val();
withdrawedEth = web3.utils.toWei(withdrawedEth, "ether");
console.log("withdrawed eth val is: " + withdrawedEth);
let address = ethereum.selectedAddress;
//console.log(address);
let balanceBefore = await dex.methods.balances(address, web3.utils.fromUtf8("ETH")).call();
console.log("Balance before transaction is: " + balanceBefore);
await dex.methods.withdrawETH().send({from: ethereum.selectedAddress, value: withdrawedEth});
let balanceAfter = await dex.methods.balances(address, web3.utils.fromUtf8("ETH")).call();
console.log("Balance after transaction is: " + balanceAfter);
//reloadPage();
//$("#theNumber").html(parseInt($("#theNumber").text()) - withdrawedEth );
}
yeah so withdraw isnt like deposit it doesnt need to be a payable function. only the adress that the ether is being sent to needs to be payable. Like you can keep the function payable if you like but i personall dont. I would use the notion
payable(msg.sender).transfer(amount) //etc
so the reason that its subtracting a balance from your metamask is that you are sending an ether value into your withdraw contract interaction. look here
await dex.methods.withdrawETH().send({from: ethereum.selectedAddress, value: withdrawedEth})
your sending eth into the contract with
value: withdrawedEth
the valuethat you specify should be in the withdraw eth part so it should be
await dex.methods.withdrawETH(amount).send({from: ethereum.selectedAddress})
if your really curious to see what i mean in action and you want to do a little proof check for yourself. keep the function as it is then make another function in your contract that returns the contract balance. can be achieved with #
function contractBalanace() public view returns (uint256) {
return balance[address(this)]
}
then in main.js write a function to call the above function. if you call your withdraw function as it is then call the getContractBalance function you will notice that the contract balance has increaseed by the amount you withdrawed because of the fact that your sending value into the contract in the
await dex.methods.withdrawETH().send({from: ethereum.selectedAddress, value: withdrawedEth})
line of code.
this again is optional but it will make sense to you if you take the time to do it.
Moral is for withdraw done make it payable only make the address you want to transfer to payable and the amount being sent in a withdraw function gets passed in in the function header not as a param in the .send() call
Okay I have checked these and really it does send the eth to the contract.
I have deleted the payable part from the function now and it works okay.
That’s great anything else just text hete. Great work very impressive
When your done try deploy ot on a testnet too. Andbthen youll be able to interact with testnet tokems like kovan chainlink and other things. And remeber theres a function in that dex called add token. So it will be very much possible for u to add a token by address for any other custom tokens u make. Keep up the gpod work. Excited to see it when its done ping a post in here when u do
Hi again @mcgrane5
Yes there would be another thing here.
So I decided that I will add an addToken number so anybody who uses this will be able to add any kind of token they wish.
During the course we made a link.sol file where we mint 1000 Link. I have tried to write something which should put this out in the balance section but no avail for now.
As you can see here the 10000 Link is minted.
my code here for printing to the webpage:
async function prtTokens(){
let tokenList = await dex.methods.getTokenListLength().call();
for(let i = 0; i < tokenList; i++){
let tokenVar = await dex.methods.tokenList(i).call();
let tokenBalance = await dex.methods.balances(ethereum.selectedAddress, tokenVar).call();
console.log("Token Balance of : " + web3.utils.toUtf8(tokenVar) + " " + tokenBalance);
$('<tr /> ').text(web3.utils.toUtf8(tokenVar) + ": " + tokenBalance).appendTo("#theNumber2");
}
}
but it is not showing anything. Link balance should be 10k now.
Anyway I think I will finish working on this for today and check back things tomorrow, but if you could lend another hand for that, that would be great help
Also I have updated the github repo so everything is up to date: https://github.com/Riki0923/DEX
Thanks for everything today.
Riki
ok when u mint link you ae minting someone tokens of that token contract so the balance will be in their metamask account. if you get the address of your link token and go to metamask. when in metamask click the assets tab where it shows your tokens. at the bottom there is an option to add a new custom token if you paste the link address then you will see your link 10k token balance. to get them into your contract u need to call th deposit function in the wallet contract you wrote which takes in the ticker and the amount. this will allow u to make erc20 deposits. u need a new button and function to do this
calling the deposit function will evoke the transferFrom function from your link token. You didnt code this transferFrom function yourself its inherited from the ERC20.sol contract. This function will transfer your link tokens frm yur metamask account into the dex smart contract for u to use to trade with. Note that initially calling the deposit function will fail when you first set it up with a button. this is because you are going to have to approve the dex contract to be allowed to spend your link tokens on your behalf. So when you make this deposit function for ERC20’s you will have to call link.approve and then dex.deposit. if you have any isuuses let me know
Okay I think it’s working now (at least with Chainlink)
So if you want to check I have pushed everything to Github here: https://github.com/Riki0923/DEX
Also added a Readme part, I hope it is simple enough
I have checked that the orders are working correctly now if I buy or sell Chainlink, but of course the metamask account has to have chainlink in order to send something there or withdraw from it.
I know it still can be upgraded for example to be able to use it with any other tokens, but the method is working now and I think I will leave this for a while and go back to the Kitty D’app project because I just can’t deal with it on the last part and come back to this some time.
I hope it is okay for a start.
Best Regards,
Riki
hey @Riki no worries. whenever u want to come back to it just post any issues here. your so close with it theres only a few thing su need to do but compared to what you have achieved so far its very small