hey @raphbaph. so ive currentlt taken solidity 101 201, 201[OLD] and the ethereum game programming course. I have actually stopped some of the course here as i am taking some time to learn frontend development and backend development like databases and APIs and all to better myself as a programmer before starting my dex frontend because i want to include live price feeds and live charts for ethereum for example etc. after that i plan to do SC security and chainlink, dapp programming etc and the EOS courses too
Very cool! I donât like to do front-end so much.
Will deep dive on Chainlink and try to run a Node myself.
After that I enrolled in the Consensys Bootcamp starting in September.
But will still do the dApp or Games course here at Ivan. Love the courses here.
So accessible.
Am having issue using web3.am getting below error message
con.js:13 Uncaught ReferenceError: Web3 is not defined
at con.js:13
are you properly importing web3 into your js file and declsaring your window.web3 object properly?
No i did npm install web3 on the part i am doing my project
I assume your working on some sort of front end. Not im not sure cos you have not provided ypur code. But you need to import web 3. Maybe this is why its not defined. So your initial main.js template could be something like
import Web3 from 'web3';
import youContractName from '../abis/YourContractName.json'
//this function is standard as per the metamask docs. you need to use this code to
//allow your browser to connect to the blockchain (it does this trhough metamask)
init = async () => {
loadWeb3()
loadBlockchainData()
}
loadWeb3() = async () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum)
await window.ethereum.enable()
console.log("hello");
}
else if(window.web3) {
window.web3 = new Web3(window.web3.currentProvider)
}
else {
window.alert("non-Ethereum browser detected.");
}
}
loadBlockchainData() = async () => {
const web3 = window.web3
// Load account
const accounts = await web3.eth.getAccounts()
// Network ID
const networkId = await web3.eth.net.getId()
const networkData = yourContractName.networks[networkId]
if(networkData) {
const contractInstance = new web3.eth.Contract(yourContractName.abi, networkData.address)
}
else {
alert("cannot find contract address for detected network");
}
}
init();
Ok so im not sure at all if this will help you or not as its only my guess that you have not imported web3 and thats what the error is but without seeing your code i could be completely wrong. To initialise your js file to connect to eth blockhain using metaMask and web3.js the above template is not bad
it has two main functions. loadWeb3() and loadBlockchainData(). LoadWeb3 is responsible for connecting your browser to the ethereum blockchain through metamask. Without this function you will not be able to use web3.js. This is adapted from the metamask docs.
The loadBlockchainData function is responsible for connecting our smart contratcs backend to our frontend. It does so by creating an instance of ouf our smart contratcs so that we can access its functions to read and write to the blockchain (call and send Txs). This function also gets the current account we are using the dApp in. If you use this template you should not get that web3 error.
Also the imports at the top are only nessecary for dynamically fetching the contract abi and address for creating our contract instance. Hope this makes sense again not sure if this is a solution to your problem but if you are making a front end this template is good to use
Hey @mcgrane5 i tried the import statement i got below error
con.js:7 Uncaught SyntaxError: Cannot use import statement outside a module
I used type module on the html i got below
localhost/:1 Uncaught TypeError: Failed to resolve module specifier "web3". Relative references must start with either "/", "./", or "../".
Below is my code
con.js
//var web3 = new Web3(Web3.givenProvider);
//web3.eth.net.getId()
//ethereum.enable();
///Check if Metamask is installed
import Web3 from 'web3';
if(typeof window.ethereum !== 'undefined'){
console.log('metaMast Is installed');
}else{
console.log('metaMast Is not installed');
}
var web3 = new Web3(Web3.givenProvider);
///Variable declearation
const etherButton = document.querySelector('.enableEthereumButton');
const showAccount = document.querySelector('.showAccount');
const sendEth = document.querySelector('.sendEth');
const showBal = document.querySelector('.showBal');
const AproveContract = document.querySelector('.AproveContract');
var instance;
var user;
var contractAddress ="0xF250AB1bf7207F493cB9c6451AF32D554cC6433c";
etherButton.addEventListener('click',()=>{
getAccounts();
});
AproveContract.addEventListener('click',()=>{
ApproveCon();
});
async function ApproveCon(){
console.log("Here");
//instance = new.web3.eth.Contract(abi,contractAddress, {from: ethereum.selectedAddress});
console.log(instance);
}
///Called once account is been change on metamask
ethereum.on('accountsChanged', function (accounts) {
// Time to reload your interface with accounts[0]!
console.log("Network: " + ethereum.networkVersion);
console.log("Selected: " + ethereum.selectedAddress);
showAccount.innerHTML = ethereum.selectedAddress;
});
async function getAccounts() {
console.log('In');
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
const account = accounts[0];
showAccount.innerHTML = account;
const balance = await ethereum.request({
method:'eth_getBalance',
params: [account, "latest"]
});
const read = parseInt(balance) /10**18;
console.log(read.toFixed(5));
showBal.innerHTML = read.toFixed(5);
console.log("Network: " + ethereum.networkVersion);
console.log("Selected: " + ethereum.selectedAddress);
}
const ethereumButton = document.querySelector('.enableEthereumButton');
const sendEthButton = document.querySelector('.sendEthButton');
let accounts = [];
//Sending Ethereum to an address
sendEth.addEventListener('click', () => {
ethereum
.request({
method: 'eth_sendTransaction',
params: [
{
//from: accounts[0],
from: ethereum.selectedAddress,
to: '0x58B7D176ae545Ff8db3246b13e6c7f593f3e9E4b',
value: '100000000000000',
//gasPrice: '0x09184e72a000',
//gas: '0x2710',
},
],
})
.then((txHash) => console.log(txHash))
.catch((error) => console.error);
});
/*const transactionParameters = {
nonce: '0x00', // ignored by MetaMask
gasPrice: '0x09184e72a000', // customizable by user during MetaMask confirmation.
gas: '0x2710', // customizable by user during MetaMask confirmation.
to: '0x0000000000000000000000000000000000000000', // Required except during contract publications.
from: ethereum.selectedAddress, // must match user's active address.
value: '0x00', // Only required to send ether to the recipient from the initiating external account.
data:
'0x7f7465737432000000000000000000000000000000000000000000000000000000600057', // Optional, but used for defining smart contract creation and interaction.
chainId: '0x3', // Used to prevent transaction reuse across blockchains. Auto-filled by MetaMask.
};
// txHash is a hex string
// As with any RPC call, it may throw an error
const txHash = await ethereum.request({
method: 'eth_sendTransaction',
params: [transactionParameters],
});*/
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Page Title</title>
</head>
<body>
<button class="enableEthereumButton">Enable Ethereum</button>
<h2>Account: <span class="showAccount"></span></h2>
<button class="sendEth">Send Eth</button>
<h2>Balance: <span class="showBal"></span></h2>
<h2>Contract: <input id="AprCon" class="AprCon"/> </h2>
<button class="AproveContract">ApproveCon</button>
</body>
<script src="ABI.js"></script>
<script type="module" src="con.js"></script>
</html>
hey @chim4us, hmm i was just about to say try adding a module type to your con.js script in the html file as that seems to be a good fix just from doing a quick google there. However you have already done this. Ok lets try to add web3 without an import. You can also do it this way by
var web3 = new Web3(Web3.givenProvider);
in place of what used to be your import. And then you must also include we3.j sin your html file so include this script in your head section not the body like you currently have. Also just to be sure include you con.js file as a script in the head section also but change type to text/javascript
<script type="text/javascript" src="./web3.min.js"></script>
<script type="text/javascript" src="./con.js"></script>
This should work for you.
Here is my code belowâŚpassed all tests!
Question: I had to remove âonlyOwnerâ from my create limit order function in order to run tests. Should I put that back in now that tests have passed? I assume only the owner should be able to post limit orders!
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./wallet.sol";
contract Dex is Wallet {
enum Side {
BUY,
SELL
}
struct Order {
uint id;
address trader;
Side side;
bytes32 ticker;
uint amount;
uint price;
}
uint public nextOrderId = 0;
mapping(bytes32 => mapping(uint => Order[])) public orderBook;
//same input getOrderBook(bytes32("LINK"),Side.BUY)
function getOrderBook(bytes32 ticker, Side side) view public returns(Order[] memory) {
return orderBook[ticker][uint(side)];
}
//swapping elements in orderbook
function _swap(Order[] storage _orders, uint p1, uint p2) private {
Order memory first_order = _orders[p1];
//swapping objects in array
_orders[p1] = _orders[p2];
_orders[p2] = first_order;
}
//create limit order to buy/sell ticker in the amount of 'amount' at 'price'
function createLimitOrder(Side side, bytes32 ticker,uint amount, uint price) public tokenExist(ticker) {
if (side == Side.BUY) {
//check if users has enough ETH for buy order
uint cost = amount * price; //in ether
require(balances[msg.sender][bytes32("ETH")] >= cost,"Insufficient ETH Balance!");
}
else { //sell order
//check if users has enough tokens to sell
require(balances[msg.sender][ticker] >= amount, "Insufficient Token Balance!");
}
//creating pointer array to orders for ticker / side
Order[] storage orders = orderBook[ticker][uint(side)];
//[order1, order2]
//adding new order to order book
orders.push(Order(nextOrderId,msg.sender,side,ticker,amount,price));
//Sorting order book, placing new order in right place
if (side == Side.BUY){ //sorting highest to lowest price
for (uint i = orders.length - 1;i > 0;i--){
if (orders[i].price > orders[i-1].price) {
//orders unsorted, swapping elements
_swap(orders,i,i-1);
}
else break;
}
}
else if (side == Side.SELL) { //sorting lowest to highest price
for (uint i = orders.length - 1;i > 0;i--){
if (orders[i].price < orders[i-1].price) {
//orders unsorted, swapping elements
_swap(orders,i,i-1);
}
else break;
}
}
nextOrderId++;
}
//determine how many 'ticker' tokens in SELL or BUY book
function tokenAmount(Side side, bytes32 ticker) public view tokenExist(ticker) returns (uint) {
//creating pointer array to orders for ticker / side
Order[] storage _orders = orderBook[ticker][uint(side)];
uint size = _orders.length;
uint totalTokens = 0;
//looping to shift elements n units over
for (uint i=0;i < size;i++) totalTokens += _orders[i].amount;
return totalTokens;
}
//determine total BUY or SELL orderbook value in wei
function valueAmount(Side side, bytes32 ticker) public view tokenExist(ticker) returns (uint) {
//creating pointer array to orders for ticker / side
Order[] storage _orders = orderBook[ticker][uint(side)];
uint size = _orders.length;
uint totalValue = 0;
//looping to shift elements n units over
for (uint i=0;i < size;i++) totalValue += _orders[i].amount * _orders[i].price;
return totalValue;
}
//remove first n orders from orderbook while keeping orderbook sorted
function removeOrders(Side side, bytes32 ticker, uint n) private tokenExist(ticker) {
//creating pointer array to orders for ticker / side
Order[] storage _orders = orderBook[ticker][uint(side)];
uint size = _orders.length;
//looping to shift elements n units over
for (uint i=n;i < size;i++){
_orders[i-n] = _orders[i];
}
//popping last to pop last n elements from list (duplicates)
for (uint i=0;i < n;i++) _orders.pop();
}
//execute an order between a buyer and a seller of 'amount' ticker at 'price'
function executeOrder(address _buyer,address _seller, bytes32 tick, uint amount, uint price) private tokenExist(tick) {
//making sure _seller as enough ticker and _buyer has enough ETH
require(balances[_seller][tick] >= amount,"Insuffient seller token balance!");
require(balances[_buyer][bytes32("ETH")] >= amount * price,"Insuffient buyer ETH!");
//tranferring 'amount' of ticker tokens to _buyer's balance
balances[_seller][tick] -= amount;
balances[_buyer][tick] += amount;
//tranferring proceeds ( 'amount * price ' ) of sale to seller account
balances[_buyer][bytes32("ETH")] -= amount * price;
balances[_seller][bytes32("ETH")] += amount * price;
}
/****************************************************************** */
/****************************************************************** */
//create and execute market orders
/****************************************************************** */
/****************************************************************** */
function createMarketOrder(Side side, bytes32 ticker,uint amount) public tokenExist(ticker) onlyOwner returns (uint){
//FIRST CHECK - if SELL make sure seller has enough Tokens
if (side == Side.SELL) {
require(balances[msg.sender][ticker] >= amount,"Insufficient Token Balance!");
}
//if market order is SELL it will use BUY book, if market order is BUY it will use sell book
Side orderbookSide;
if (side == Side.SELL) orderbookSide = Side.BUY;
else orderbookSide = Side.SELL;
//creating pointer array to orders for ticker / side
Order[] storage orders = orderBook[ticker][uint(orderbookSide)];
//[order1, order2]
//check is at least 1 order in order book, otherwise operation complete
if (orders.length == 0) return 0;
//calculating total token amount and total value of orderbook
uint orderBookValue = valueAmount(orderbookSide, ticker);
uint orderBookTokens = tokenAmount(orderbookSide, ticker);
//counting remaining amount of ticker that needs to be sold or bought
uint remaining = amount;
//number of limit orders that needs to be COMPLETELY filled for this market order
uint orderFillCount = 0;
bool partialFill = false; //true if there is a partial fill of an limit order
uint orderValue = 0; //total order value based on limit order prices
//checking if there are enough tokens to fulfill market order
if (amount >= orderBookTokens){ //if true, need to empty out order book fullfill market order
orderFillCount = orders.length;
orderValue = orderBookValue;
remaining = 0;
}
else { //amount < orderBookTokens
//looping through order book to determine how many limit orders will
//be need to be filled and calc total order value
for (uint i = 0;i < orders.length;i++){
//determine if order will be filled by orders[i].amount
if (remaining > orders[i].amount) { //if true, current order is not enough to fill market order
orderFillCount++;
remaining -= orders[i].amount;
orderValue += orders[i].amount * orders[i].price;
}
else if (remaining == orders[i].amount){ //exactly enough to fill order
orderFillCount++;
orderValue += orders[i].amount * orders[i].price;
remaining = 0;
break;
}
else { // means remaining < order[i].amount => partial fill
partialFill = true;
orderValue += remaining * orders[i].price;
break;
}
} // end for (uint i = 0;i < size;i++)
} // close else for (amount > orderBookTokens)
//THIRD CHECK - now that we know order value, make sure if market buy order, they have enough ETH
if (side == Side.BUY) {
//check if users has enough ETH for buy order
require(balances[msg.sender][bytes32("ETH")] >= orderValue,"Insufficient ETH Balance!");
}
//looping through array to fulfill buy order
if (side == Side.BUY) {
for (uint i = 0;i < orderFillCount;i++){
//transfering ETH to EACH seller & transfering 'ticker' to buyer for each trade
executeOrder(msg.sender,orders[i].trader,ticker,orders[i].amount,orders[i].price);
}
//if there is partial order to fill, fill it now
if (partialFill){
//partial fill order will be at position +1 from where above loop ended
executeOrder(msg.sender,orders[orderFillCount].trader,ticker,remaining,orders[orderFillCount].price);
//updating remaining balance for order[orderFillCount].trader in orderbook
orders[orderFillCount].amount -= remaining;
}
} // end if (side == Side.BUY)
//looping through array to fulfill sell order
else if (side == Side.SELL) {
for (uint i = 0;i < orderFillCount;i++){
//transfering ETH to seller & transfering 'ticker' to EACH buyer
executeOrder(orders[i].trader,msg.sender,ticker,orders[i].amount,orders[i].price);
}
//if there is partial order to fill, fill it now
if (partialFill){
//partial fill order will be at position +1 from above loop
executeOrder(orders[orderFillCount].trader,msg.sender,ticker,remaining,orders[orderFillCount].price);
//updating remaining balance for order[orderFillCount].trader in orderbook
orders[orderFillCount].amount -= remaining;
}
} // end else if (side == Side.SELL)
//remove and clean up order book of fully executed orders
removeOrders(orderbookSide,ticker,orderFillCount);
//returning ordervalue
return orderValue;
}
}
Here is my first test harness. code for tests was LONGER than the dex.sol code lol!
// BUY (ask): user's ETH > buy order value
// SELL (bid): user must have enough tokens such that token amount is greater than sell order amount
//the first order [0] in the BUY book must have highest price
//the last order in SELL book must have lowest price ().length - 1
const Dex = artifacts.require("Dex");
const Link = artifacts.require("Link");
const truffleAssert = require('truffle-assertions');
contract.skip("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,2000);
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,2000);
await dex.deposit(2000,web3.utils.fromUtf8("LINK"));
let balance = await dex.balances(accounts[0],web3.utils.fromUtf8("LINK"));
//checking LINK balance is properly deposited
assert.equal(balance.toNumber(),2000);
})
it("should be able to submit market order with nothing in order book", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//submitting market order
await truffleAssert.passes(dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100));
})
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 order with no ETH does not go through
await truffleAssert.reverts(dex.createLimitOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100,2));
//depositing ETH
dex.depositEth({value: 100000},{from: accounts[0]});
//checking to make sure order now passes
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100,200));
})
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, current balance is 2000 LINK (see above)
await truffleAssert.reverts(dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20000,2));
//checking to make sure smaller order passes, current balance is 2000 LINK (see above)
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),100,200));
})
it("make sure BUY order list is sorted high to low", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//creating some test buy limit orders
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100,200));
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100,300));
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100,100));
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100,600));
//obtaining BUY order book for LINK
let buy_order = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.BUY);
assert(buy_order.length > 0);
//take a look at order book
//console.log(buy_order);
//looking through buy_order book to determine if list is sorted from highest to lowest
for (let i = 1;i < buy_order.length;i++) {
assert(buy_order[i-1].price >= buy_order[i].price);
}
})
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();
//creating some test limit sell orders
//creating some test buy limit orders
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),100,200));
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),100,300));
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),100,100));
await truffleAssert.passes(dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),200,500));
//obtaining BUY order book for LINK
let sell_order = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
assert(sell_order.length > 0);
//take a look at order book
//console.log(sell_order);
//looking through buy_order book to determine if list is sorted from lowest to highest
for (let i = 1;i < sell_order.length;i++) {
assert(sell_order[i-1].price <= sell_order[i].price);
}
})
})
Test harness for market order testsâŚ
// BUY (ask): user's ETH > buy order value
// SELL (bid): user must have enough tokens such that token amount is greater than sell order amount
//the first order [0] in the BUY book must have highest price
//the last order in SELL book must have lowest price ().length - 1
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,2000);
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 2000 link into dex wallet accounts[0]
await link.approve(dex.address,2000);
await dex.deposit(500,web3.utils.fromUtf8("LINK"));
let balance = await dex.balances(accounts[0],web3.utils.fromUtf8("LINK"));
//checking LINK balance is properly deposited
assert.equal(balance.toNumber(),500);
})
it("should be able to submit market order with nothing in order book", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//submitting market order
await truffleAssert.passes(dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),100));
})
it("market order should fill the correct number of orders in order book", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//depositing ETH for test
dex.depositEth({value: 100000});
//sending link to accounts 1,2,3
await link.transfer(accounts[1],100);
await link.transfer(accounts[2],100);
await link.transfer(accounts[3],100);
let balance = await link.balanceOf(accounts[1]);
//console.log(balance.toNumber());
//approve dex for accounts 1,2,3
await link.approve(dex.address,100,{from:accounts[1]});
await link.approve(dex.address,100,{from:accounts[2]});
await link.approve(dex.address,100,{from:accounts[3]});
//deposit LINK to dex wallet
await dex.deposit(100,web3.utils.fromUtf8("LINK"),{from:accounts[1]});
await dex.deposit(100,web3.utils.fromUtf8("LINK"),{from:accounts[2]});
await dex.deposit(100,web3.utils.fromUtf8("LINK"),{from:accounts[3]});
//adding limit orders
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,200,{from:accounts[1]});
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,300,{from:accounts[2]});
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,500,{from:accounts[3]});
//obtaining SELL order book for LINK
let _orders = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
let orderLength = _orders.length;
//submitting market buy order that clear out order book
await dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),40);
//obtaining NEW SELL order book for LINK
let new_orders = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
let new_orderLength = new_orders.length;
//console.log(new_orders);
console.log(new_orderLength);
//1 order should be left
assert(new_orderLength == 1);
})
it("big market order should fill all orders and empty out order book", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//obtaining SELL order book for LINK
let _orders = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
let orderLength = _orders.length;
//console.log(_orders);
console.log(orderLength);
//balance before
let balance = await dex.balances(accounts[0],web3.utils.fromUtf8("LINK"));
//submitting market buy order larger than sell limit orders left in order book
await dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),40);
//obtaining NEW SELL order book for LINK
let new_orders = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
let new_orderLength = new_orders.length;
//console.log(new_orders);
console.log(new_orderLength);
//balance after
let new_balance = await dex.balances(accounts[0],web3.utils.fromUtf8("LINK"));
//no order should be left
assert(new_orderLength == 0);
//new account[0] should have increased by 20 (since only 1 limit sell order of 20 was left in order book)
assert.equal(new_balance.toNumber(),balance.toNumber() + 20);
})
it("sell market order should have enough tokens for the sale", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//submitting market sell order that should pass
await truffleAssert.passes(dex.createMarketOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),30));
//submitting market sell order that should fail - over 2000 LINK
await truffleAssert.reverts(dex.createMarketOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),3000));
})
it("buy market order acount ETH balance drops by the amount of purchase", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
// adding small limit order
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),1,300,{from:accounts[2]});
//ETH balance before
let balance = await dex.balances(accounts[0],web3.utils.fromUtf8("ETH"));
//submitting market buy order that should pass
await dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),1);
//ETH balance after
let new_balance = await dex.balances(accounts[0],web3.utils.fromUtf8("ETH"));
//check balances
assert.equal(balance.toNumber()-300,new_balance.toNumber());
})
it("limit sell orders LINK should drop by amount of sell", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//sending link to accounts 1,2,3
await link.transfer(accounts[1],100);
await link.transfer(accounts[2],100);
//approve dex for accounts 1,2
await link.approve(dex.address,200,{from:accounts[1]});
await link.approve(dex.address,200,{from:accounts[2]});
//deposit LINK to dex wallet
await dex.deposit(100,web3.utils.fromUtf8("LINK"),{from:accounts[1]});
await dex.deposit(100,web3.utils.fromUtf8("LINK"),{from:accounts[2]});
//adding limit orders
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,200,{from:accounts[1]});
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,300,{from:accounts[2]});
//balance before
let balance_1 = await dex.balances(accounts[1],web3.utils.fromUtf8("LINK"));
let balance_2 = await dex.balances(accounts[2],web3.utils.fromUtf8("LINK"));
//submitting market buy order that should pass
await dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),40);
//balance after
let new_balance_1 = await dex.balances(accounts[1],web3.utils.fromUtf8("LINK"));
let new_balance_2 = await dex.balances(accounts[2],web3.utils.fromUtf8("LINK"));
//check balances
assert.equal(new_balance_1.toNumber(),balance_1.toNumber()-20);
assert.equal(new_balance_2.toNumber(),balance_2.toNumber()-20);
})
it("filled limit orders should be removed from order book", async () => {
//creating instance of wallet contract
let dex = await Dex.deployed();
//creating instance of LINK token contract
let link = await Link.deployed();
//adding limit orders
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,200,{from:accounts[1]});
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,300,{from:accounts[2]});
await dex.createLimitOrder(Dex.Side.SELL,web3.utils.fromUtf8("LINK"),20,300,{from:accounts[3]});
//obtaining BUY order book for LINK
let _orders = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
let orderLength = _orders.length;
//console.log(_orders);
console.log(orderLength);
//submitting market buy order that will remove 2 limit orders
await dex.createMarketOrder(Dex.Side.BUY,web3.utils.fromUtf8("LINK"),60);
//obtaining NEW BUY order book for LINK
let new_orders = await dex.getOrderBook(web3.utils.fromUtf8("LINK"),Dex.Side.SELL);
let new_orderLength = new_orders.length;
//console.log(new_orders);
console.log(new_orderLength);
assert.equal(new_orderLength,0);
})
})
you should not restrict the limit order function to onlyOwner. noone would be able to use the dex only the one address who deployed it otherwise. Anyone should be able to submit limit and market orders.
Hey Everyone,
This is my repo for Dex - DEX
Iâve written the code for createMarketOrder()
in a slightly different way.
Hope you find it interesting.
Thanks,
Tanu
Hi guys,
I almost finisht the Dex but I got an error message in my wallet.sol file. It is in the âfunction withdrawEthâ and has to do with the msg.sender.call function. I looked at @thecil his post about the call function but I still dont understand what the point is.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";
contract Wallet is Ownable{
struct Token{
bytes32 ticker;
address tokenAddress;
}
mapping(bytes32 => Token) public tokenMapping;
bytes32[] public tokenList;
mapping(address => mapping(bytes32 => uint256)) public balances;
modifier tokenExist(bytes32 ticker){
require(tokenMapping[ticker].tokenAddress != address(0), "Token does not exist");
_;
}
function addToken(bytes32 ticker, address tokenAddress) onlyOwner external{
tokenMapping[ticker] = Token(ticker, tokenAddress);
tokenList.push(ticker);
}
function deposit(uint amount, bytes32 ticker) tokenExist(ticker) external {
IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);
balances[msg.sender][ticker] = balances[msg.sender][ticker] + amount;
}
function withdraw(uint amount, bytes32 ticker) tokenExist(ticker) external {
require(balances[msg.sender][ticker] >= amount, "Balance not sufficient");
balances[msg.sender][ticker] = balances[msg.sender][ticker]- amount;
IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
}
function depositEth() payable external {
balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")] + msg.value;
}
function withdrawEth(uint _amount) external {
require(balances[msg.sender][bytes32("ETH")] >= _amount,'withdrawEth: Insuffient balance');
balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")]-(_amount);
msg.sender.call{value:_amount}("");
}
}
hey @thomascarl. replace
msg.sender.call{value:_amount}("");
with
msg.sender.transfer(amount)
sjould work now
Yes, here is a nice reply on the differences between them (also its explain why is dangerous to use .call()
.
Took this resource from: https://ethereum.stackexchange.com/questions/19341/address-send-vs-address-transfer-best-practice-usage.
- throws on failure
- forwards
2,300
gas stipend (not adjustable), safe against reentrancy - should be used in most cases as itâs the safest way to send ether
- returns
false
on failure - forwards
2,300
gas stipend (not adjustable), safe against reentrancy - should be used in rare cases when you want to handle failure in the contract
- returns
false
on failure - forwards all available gas (adjustable), not safe against reentrancy
- should be used when you need to control how much gas to forward when sending ether or to call a function of another contract
Detailed version below:
The relative tradeoffs between the use of someAddress.send()
, someAddress.transfer()
, and someAddress.call.value()()
:
-
someAddress.send()
andsomeAddress.transfer()
are considered safe against reentrancy. While these methods still trigger code execution, the called contract is only given a stipend of 2,300 gas which is currently only enough to log an event. -
x.transfer(y)
is equivalent torequire(x.send(y))
, it will automatically revert if the send fails. -
someAddress.call.value(y)()
will send the provided ether and trigger code execution. The executed code is given all available gas for execution making this type of value transfer unsafe against reentrancy.
Using send()
or transfer()
will prevent reentrancy but it does so at the cost of being incompatible with any contract whose fallback function requires more than 2,300 gas. It is also possible to use someAddress.call.value(ethAmount).gas(gasAmount)()
to forward a custom amount of gas.
One pattern that attempts to balance this trade-off is to implement both a push and pull mechanism, using send()
or transfer()
for the push component and call.value()()
for the pull component.
It is worth pointing out that exclusive use of send()
or transfer()
for value transfers does not itself make a contract safe against reentrancy but only makes those specific value transfers safe against reentrancy.
More details are here https://consensys.github.io/smart-contract-best-practices/recommendations/#be-aware-of-the-tradeoffs-between-send-transfer-and-callvalue
Reasons for adding transfer()
: https://github.com/ethereum/solidity/issues/610
call()
can also be used to issue a low-level CALL
opcode to make a message call to another contract:
if (!contractAddress.call(bytes4(keccak256("someFunc(bool, uint256)")), true, 3)) {
revert;
}
The forwarded value
and gas
can be customized:
contractAddress.call.gas(5000)
.value(1000)(bytes4(keccak256("someFunc(bool, uint256)")), true, 3);
This is equivalent to using a function call on a contract:
SomeContract(contractAddress).someFunc.gas(5000)
.value(1000)(true, 3);
Beware of the right padding of the input data in call()
https://github.com/ethereum/solidity/issues/2884
transfer()
, send()
and call()
functions are translated by the Solidity compiler into the CALL
opcode.
As explained on the Subtleties page in the Ethereumâs wiki:
CALL has a multi-part gas cost:
- 700 base
- 9000 additional if the value is nonzero
- 25000 additional if the destination account does not yet exist (note: there is a difference between zero-balance and nonexistent!)
The child message of a nonzero-value CALL operation (NOT the top-level message arising from a transaction!) gains an additional 2300 gas on top of the gas supplied by the calling account; this stipend can be considered to be paid out of the 9000 mandatory additional fee for nonzero-value calls. This ensures that a call recipient will always have enough gas to log that it received funds.
Carlos Z
This unit test works for me using msg.sender.transfer()
instead of the call()
function. You can try it also if interested @mcgrane5 @thomascarl
it("5. WITHDRAW ETH correctly", async function (){
await dexInstance.depositEth({from: owner, value:_qtyEth(1)})
await dexInstance.withdrawEth( _qtyEth(1), {from: owner});
await expectRevert(
dexInstance.withdrawEth( _qtyEth(1), {from: alfa}),
"withdrawEth: Insuffient balance"
);
});
let _qtyEth = function(_amount) {
return (web3.utils.toWei(_amount.toString(), "ether"))
}
Carlos Z
Thanks @thecil and @mcgrane5. It is more clear to me now.
I do have another question. It is about the testing part. When I run test I got 8 tests passing, one pending and also 8 failing. Maybe I just donât understand the output but when I compare it to Filip his test results I dont see that many failed test. Down below the screenshot of the failed test, I also pushed my final code to GitHub so maybe someone can check it out.
hey @thomascarl. i will show you a good trick for seeing why your tests fail. I will use the example of this test here
//The BUY order book should be ordered on price from highest to lowest starting at index 0
it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await 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);
truffleAssert(orderbook.length > 0);
console.log(orderbook);
for (let i = 0; i < orderbook.length - 1; i++){
truffleAssert(orderbook[i].price >= orderbook[i+1].price, "not right order in buy book")
}
})
Currently your saying this fails. What you should do is try to pinpoint the location or line of code that is causing the test to fail. This may originally seemm hard becayse the truffle-assertion error messages are not that useful. i would imagine its your assert statement. So what i would do is i would comment out everything down to your odernook function call. and console.log. like this
//The BUY order book should be ordered on price from highest to lowest starting at index 0
it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await 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);
truffleAssert(orderbook.length > 0);
console.log(orderbook);
})
run this i would imagine it works. If it does it will print the orderbook to your terminal screen and that way you will be able to judge if the orderbook is actually sorted by price. However if your test wont even run for this and it still fails. then go back and coment out the limit orders like so
//The BUY order book should be ordered on price from highest to lowest starting at index 0
it("The BUY order book should be ordered on price from highest to lowest starting at index 0", async () => {
let dex = await Dex.deployed()
let link = await Link.deployed()
await link.approve(dex.address, 500);
})
run this and if it runs then you now know the isue lies with your limit order function. its all about finding the line of code that throws the error. Once you know where the error is you will be able to debug more easily. This applies for all your tests so just repeat this process.
EDIT actually i think its fialing because your not depositing any link your only approving. The error is probably thrown when youc create limit orders as u have insuffixent funds
Evan
Hello everyone, finished my DEX project.
GitHub repo: https://github.com/BenasVolkovas/decentralized-exchange
I changed the create market order function part where the program needs to remove filled orders. Now it costs less ether to execute the function. I hope it helps someone.
@dan-i, @thecil. I have a question in regards to price feeds. I am looking to include live price charts into my dex frontend. Is there any good APIs for getting data of ethereum and other token prices for example. I know you can use chainlink to get the current price etc in your smart contract. But can you query chainlink to get the entire history of a ethereums price. I was reading into other things like âThe graphâ. What would yous reccomend. Or perhaps should i should just try to query an API in say like my main.js file.
My plan is to use something like plotly.js to graph the data. If yous know of any better plotting libraries mores suited for the dex application that would be brillaint also . I very adept with matplotlib in python for computational stuff but from what ive been reading online there is no set in stone way to use matplotlib with javascript (except for something called mpld3.js which i may look into but it might be more hassle than its worth)
Hey man, for charting you could just use the trading view widgets:
https://www.tradingview.com/widget/
And for easy price tracking you could use https://www.coingecko.com/en/api.
Off course you could also use chainlinks feeds but that will require a complex addition in the contract (you could check the chainlink course to learn how to )
https://data.chain.link/
Carlos Z