Project - Building a DEX

Hey @CaliCrypto22, hope you are well.

The IERC20 is related to the standard interface functions for a ERC20 contract.
Check here: https://docs.openzeppelin.com/contracts/4.x/api/token/erc20#IERC20

So basically, using tokenMapping (which is the mapping to store the address of each token, like USDT, based on its bytes32 value of the ticker), filip invoke the transfer function using the interface and the address of a existing contract (a token, like usdt or dai for example).

So what the withdraw function does with that code line at the end, is to transfer an amount of the tokenMapping[ticker].tokenAddress contract (using the IERC20) from that contract to msg.sender.

Like transferring USDT from me to you, but using the IERC20, a contract is capable of interact with other ERC20 contracts. So basically with Interfaces you allow external contracts to interact with yours.

(If you for example want to add Uniswap functionality to your DEX, you will have to learn how to use their interface :nerd_face: )

Hope i explained my self clear, if not, let me know

Carlos Z

1 Like

Thanks! I think I understand better now.

image

I am having this error many times when I compile on this and previous assignments. Solved it by modifying ā€œuncheckedā€, but this time I probably should leave it there since it is a bigger assignment. Any idea how to solve this ā€œuncheckedā€ thing?

1 Like

u seem to be using an older version of the safemath contract

1 Like

thanks, changing compiler version and all pragma solidity versions in all files to 0.8.10 solved the issue.

1 Like

I tried playing around with the ethereum boiler plate, and Iā€™m looking for a way to add avalanche to one of the supported chains in the DEX.

1 Like

do you mean the dex code here from this course

nope not that. I tried using the ethereum boiler plate by moralis. it has the dex component implemented in it.

Iā€™m having the same doubt. I guess would be better to create an unique ID in our contract for every token, but probably will be more cost expensive.

Hi!

I was wondering how you guys approach debugging. I have written the code for bubble sort but when I run the test I get the " Error: Returned error: VM Exception while processing transaction: revert". How would you approach narrowing down the error?

Thank you!

1 Like

hey @sbelka1703 could you share you code i would start off by commenting things out one by one to isolate the last working line of code for your algorithm the un comment lines one by one until you find the place it breaks

Actually, a few of my tests were failing, so I decided to create a separate branch in git, from when at least two tests were passing and rebuild from there.

So I am at the point where I am done with the bubble sort. All of my tests pass, except for the first one. I am getting " AssertionError: Failed with Error: Returned error: VM Exception while processing transaction: out of gas" error."

I think there is something wrong here, but I canā€™t figure out what it is:

function createLimitOrder(Side side, bytes32 ticker, uint amount, uint price) public {
        if(side == Side.BUY){
            require(balances[msg.sender]["ETH"] >= amount.mul(price), "Balance too low");
            
        }

I am not sure if the ETH is actually being deposited or not, maybe itā€™s failing at that stage

Here is my code:

dex.sol

pragma solidity >=0.4.22 <0.9.0;
pragma experimental ABIEncoderV2;

import "./wallet.sol";

contract Dex is Wallet {
    using SafeMath for uint256; 
    enum Side{
        BUY,
        SELL
    }

    struct Order{
       uint id;
       address trader;
       Side side;
       bytes32 ticker;
       uint amount;
       uint price;
    }

    uint public nextOrderId;
    
    //A ticker that points to an enum(buy/sell 0 or 1), that pooints to an order book 
    mapping(bytes32 => mapping(uint => Order[])) orderBook;  

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

    function createLimitOrder(Side side, bytes32 ticker, uint amount, uint price) public {
        if(side == Side.BUY){
            require(balances[msg.sender]["ETH"] >= amount.mul(price), "Balance too low");
            
        }

        else if(side == Side.SELL){
            require(balances[msg.sender][ticker] >= amount);
        }
       
        Order[] storage orders = orderBook[ticker][uint(side)];
        
        orders.push(
            Order(nextOrderId, msg.sender, side, ticker, amount, price)
            );

        
        uint i = orders.length > 0 ? orders.length - 1 : 0;

        if(side == Side.BUY){
            while(i > 0){
                if(orders[i - 1].price > orders[i].price) {
                    break;   
                }
                Order memory orderToMove = orders[i - 1];
                orders[i - 1] = orders[i];
                orders[i] = orderToMove;
                i--;
            }
        }
        else if (side == Side.SELL){
            while(i > 0){
                if(orders[i - 1].price < orders[i].price) {
                    break;   
                }
                Order memory orderToMove = orders[i - 1];
                orders[i - 1] = orders[i];
                orders[i] = orderToMove;
                i--;
            }
        }
        nextOrderId++;
    }
}

dextest.js

//The first order ([0]) in the BUY order book should have the highest price

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

contract("Dex", accounts => {
  //The user must have ETH deposited such that deposited eth >= buy order value
  it("should throw an error if ETH balance is too low when creating BUY limit order", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    await truffleAssert.reverts(
        dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
    )
    dex.depositEth({value: 100})
    await truffleAssert.passes(
        dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 1, 1)
    )
})
//The user must have enough tokens deposited such that token balance >= sell order amount
it("should throw an error if token balance is too low when creating SELL limit order", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    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)
    )
})
//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.depositEth({value: 3000})
    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)
    }
})
//The SELL order book should be ordered on price from lowest to highest starting at index 0
it("The SELL order book should be ordered on price from lowest to highest starting at index 0", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    await link.approve(dex.address, 500);
    await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
    await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
    await dex.createLimitOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)

    let orderbook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1);
    assert(orderbook.length > 0);
    for (let i = 0; i < orderbook.length - 1; i++) {
        assert(orderbook[i].price <= orderbook[i+1].price)
    }
})  
})


wallet.sol

pragma solidity 0.8.9;

import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";

contract Wallet is Ownable {

    using SafeMath for uint256;
    struct Token {
        bytes32 ticker;
        address tokenAddress;
    }

    mapping(bytes32 => Token) public tokenMapping;
    bytes32[] public tokenList;
    //this mapping is using an address that points to another mapping, which has a bytes32 ticker key that points the balance of that ticker
    mapping(address => mapping(bytes32 => uint256)) public balances;


    /* 
    addToken uses ticker and tokenAddress as arguments. The ticker is used as key in tokens mapping.
    Tokens mapping uses a bytes32 key for ticker to point to the Token struct.
    We set the key in toekens mapping equal to a new token that will be added as the Token struct
    We are also pushing the ticker into the tokenList, which contains only the tiker symbols  
    */
    function addToken(bytes32 ticker,address tokenAddress) onlyOwner external {
        tokenMapping[ticker] = Token(ticker, tokenAddress);
        tokenList.push(ticker);
    
    }
    /* 
    The deposit function uses an IERC20 interface. Specificaly the function: 
    
    transferFrom(
        address sender,
        address recipient,
        uint256 amount)

    Tokens mapping is also used, that points to the struct Token, where we can access Token.tokenAddress 
    by using bytes32 ticker as key 
    */


    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].add(amount);
        
    }

    

    function withdraw(uint amount, bytes32 ticker) tokenExist(ticker) external {
        require(
            balances[msg.sender][ticker] >= amount,
            'balance too low'
        );
        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
    }

    function depositEth() payable external {
        balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")].add(msg.value);
    }

    modifier tokenExist(bytes32 ticker) {
        require(
            tokenMapping[ticker].tokenAddress != address(0),
            'this token does not exist'
        );
        _;
    }
}

Iā€™m a bit unclear about the deposit because the senders in truffle donā€™t have tokens in their wallets, they only have ether. The only tokens the owner owns, are in the contract itself. The only tokens that exist in the entire world, are balances in this contract so how is the sender supposed to add tokens from an external world? All he can do is transfer tokens from one balance to the other or somehow call a mint function to create more tokens.

I actually implemented the address(this) function thinking, the contract owns ALL the tokens but that didnā€™t make sense either so I removed it and looked up the answer to understand what was wanted here.

P.S. Ok, so deposit is actually loading up the smart contract with tokens not owned by external users, but by the contract itself. Ok, this like a Harry Potter wallet where I can add coins from heaven whenever I want.

Hey @sheilashehu, hope you are well.

Could you please share your migration file in the following way? :nerd_face:
https://academy.moralis.io/lessons/how-to-post-code-in-the-forum

Carlos Z

Hello, I am having problems with my test file. Compilation is successful but it generates 0 tests passing like below.

This is my wallettest.js file.

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

contract("Dex", accounts => {
    it("should only be possible for owner to add tokens", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.passes(
            dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        )
        await truffleAssert.reverts(
            dex.addToken(web3.utils.fromUtf8("AAVE"), link.address, {from: accounts[1]})
            )
    })
    it("should handle deposists corrrecty", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address, 500);
        await dex.deposit(100, web3.utils.fromUtf8("LINK"));
        let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("LINK"))
        assert.equal( balance.toNumber(), 100 )
    })
    it("should handle faulty withdrawls correctly", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.reverts(dex.withdraw(500, web3.utils.fromUtf8("LINK")))
    })
    it("should handle correct withdrawls correctly", async () => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await truffleAssert.passes(dex.withdraw(100, web3.utils.fromUtf8("LINK")))
    })
})
1 Like

@sheilashehu is your test folder specificaly called ā€œtestā€

can you push your code to github ill have a look at your repo seems like something with truffle assertions thi can happen sometimes if something isnt set up correctly.

you are definitely initing the tests with truffle test also?

I just pushed my code in github.

https://github.com/sheilashehu/DexSmartContract/blob/main/wallettest.js

yes, truffle test.

Thank you.

1 Like

hey @sheilashehu. can you psuh up ypur entire folder. uploading them individually is not the best way to do it because it doesnt keep the format of your project folder. can u upload via git push from the terminal. this way i can clone it and see if i can reporduce your issue on my machine that way i will be able to help you. sorry for the touble