Project - Building a DEX

Hello Everyone,

I’ve been having an issue with creating an instance of my Wallet contract from within the truffle develop environment. I have no issue with the line:

truffle(develop)> let link = await Link.deployed()

And once that’s set, I’m able to call functions with link.functionName().

BUT!, when I try to declare an instance of my Wallet contract:

truffle(develop)>let wallet = await Wallet.deployed()

I receive this reply:

Uncaught TypeError: Wallet.deployed is not a function
    at evalmachine.<anonymous>:1:16

I suspect one of you savvy devs/fellow students can help me be more savvy too by helping me crack this case.

Below are my pertinent Migration files followed by the Link and Wallet contracts:

//filenae 2_wallet_migration.js
const Wallet = artifacts.require("Wallet");

module.exports = function (deployer) {
  deployer.deploy(Wallet);
};
//filename 3_token_migration.js
const Link = artifacts.require("Link");

module.exports = function (deployer) {
  deployer.deploy(Link);
};

Wallet Contract:

pragma solidity ^0.8.0;

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;
    }

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

    mapping(bytes32 => Token) public tokenMapping;
    
    bytes32[] public tokenList;

    mapping(address => mapping(bytes32 => uint256)) public balances;

    event deposit_successful(address toAccount, bytes32 ticker, uint amount);

    function addToken(bytes32 ticker, address tokenAddress) onlyOwner external{
        tokenMapping[ticker] = Token(ticker, tokenAddress);
        tokenList.push(ticker);
    }

    function deposit(uint amount, bytes32 ticker) tokenExists(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) tokenExists(ticker) external{
        require(balances[msg.sender][ticker] >= amount, "balance not sufficient");
        
        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
        //calls the .transfer() function of the contract at the address 
        //given by TokenMapping...
    }
}

Token Contract

pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/erc20/ERC20.sol";

contract Link is ERC20{
    constructor() ERC20("Chainlink", "LINK") public{ //constructors assumed public or internal
        _mint(msg.sender, 1000);
    }
}

Thanks!
David

1 Like

hey @Melshman, I took your code and compiled it in a sperate folder. Funnily enough everything compiles perfectly for me. So im unsure what the error could be. If we read the message it says that wallet.deployed() is not a function so we go to the migrations file and check and your migratons file is fine. So what could it be? A few times when i have been coding if ive been using truffle develop for an exteneded period i run into weird errors every so often that should not happen. I could be wrong but i thinjk if you keep truffle develop open for extened periods and are constantly making changes to your code and redeploying and redeploying end on end your more likley to run into those weird errors. I wouold suggest maybe that you completely exit the console and start again with a new session and see if that works. There was one time when restarting truffle did work for me . I think its good to do after a long session because your more sure that all your files are up to date.

(This could be totally wrong but i feel like sometimes when your making lots of changes to multiple files and forget to save some files but not others and then when you migrate truffle is compiling old versions with new versions of your files and here errros can come where they reallly dont exist its just that you havent saved /updated your files properly. I dont know if this is correct but i feel like it may play a factor making sure your files are always up to date and saved before migration. If you or anyone else has any insight into this that would be great cos im curious how truffle works under the hood because sometimes you do get such bazar errors out of nowhere and then you wait a minute migrate one more time and its gone or you dont know wat you did to fix it but it works… so yeah. thats my rant done but back to topic)

Im quite sure this is not the awnser you are looking for but i just wanted to suggest the the simple exit and restart truffle option.Its very strange though that this will not compile on your machine but will on mine.

1 Like

Wouldn’t you know it… Your suggestion worked. Thanks, @mcgrane5

1 Like

Hey @dan-i

The approve function is in this 3_token_migration.js file

const Link = artifacts.require("Link");
const Wallet = artifacts.require("Wallet");

module.exports = async function (deployer) {
    await deployer.deploy(Link);
    let wallet = await Wallet.deployed()
    let link = await Link.deployed()
    await link.approve(wallet.address, 500)
    wallet.addToken(web3.utils.fromUtf8("LINK"), link.address)
    await wallet.deposit(100, web3.utils.fromUtf8("LINK"))
};
1 Like

Hi @kresna_sucandra

In your Wallet.sol

.transferFrom(tokenMapping[ticker].tokenAddress, address(this), amount);

This is not correct, let’s check the TF function:

function transferFrom(address sender, address recipient, uint256 amount)

In your case, you are passing the token address as spender, which is not correct as that is msg.sender.

Should be:

.transferFrom(msg.sender, address(this), amount);

You can paste this and migrate :slight_smile:

const Link = artifacts.require("Link");
const Wallet = artifacts.require("Wallet");

module.exports = async function (deployer, n, accounts) {
    await deployer.deploy(Link);
    await deployer.deploy(Wallet);
    const wallet = await Wallet.deployed()
    const link = await Link.deployed()


    // Check user balance
    let userBalance = await link.balanceOf(accounts[0]);
    console.log(`The user balance is ${userBalance}`);

    // Increase allowance and check
    await link.approve(wallet.address, 500, {from: accounts[0]})
    const allowance = await link.allowance(accounts[0], wallet.address)
    console.log(`The allowance granted to wallet is ${allowance}`);



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


    // Check user balance and wallet balance
    // user bal should be 900 / wallet 100

    userBalance = await link.balanceOf(accounts[0]);
    console.log(`The user balance is ${userBalance}`);

    const contractErcBalance = await link.balanceOf(wallet.address);
    console.log(`The user balance is ${contractErcBalance}`);


};

Cheers,
Dani

1 Like

Hi @dan-i
Thank you. It works now. I just wondering if we need to update the video since I got those code from Filip video. Cheers

1 Like

Regarding Better Migration:

I’ve been trying to run the code the way Filip has it in the example. I’ve been keeping with him character-for-character:

//filename 3_token_migration.js
const Link = artifacts.require("Link");

module.exports = async function(deployer, network, accounts) {
  await deployer.deploy(Link);
  let wallet = await Wallet.deployed()
  let link = await Link.deployed()
  await link.approve(wallet.address, 500)
  wallet.addToken(web3.utils.fromUtf8("LINK"), link.address)
  await wallet.deposit(100, web3.utils.fromUtf8("LINK"))
  let balanceOfLink = await wallet.balances(accounts[0], web3.utils.fromUtf8("LINK"));
  console.log(balanceOfLink);
};

Everytime I run migrate/migrate --reset I receive this error:

ReferenceError: Wallet is not defined
    at module.exports (C:\Users\davem\Coding\IvanAcademy\DEX\migrations\3_token_migration.js:5:16)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)

etc…

If I comment out most of the code so we get the original migration file back, but including the command to instantiate the Link contract:

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

module.exports = async function(deployer, network, accounts) {
  await deployer.deploy(Link);
  //let wallet = await Wallet.deployed()
  let link = await Link.deployed()
  //await link.approve(wallet.address, 500)
  //wallet.addToken(web3.utils.fromUtf8("LINK"), link.address)
  //await wallet.deposit(100, web3.utils.fromUtf8("LINK"))
  //let balanceOfLink = await wallet.balances(accounts[0], web3.utils.fromUtf8("LINK"));
  //console.log(balanceOfLink);
};

the terminal doesn’t give me any errors. But as soon as I try including the statement to instantiate the Wallet contract, I get the error.

I’ve tried exiting/restarting the truffle development environment, deleting my build folder and remigrating, and goofing with the migration file for the Wallet contract, which looks like this currently:

//filename 2_wallet_migration.js
const Wallet = artifacts.require("Wallet");

module.exports = function (deployer) {
  deployer.deploy(Wallet);
};

When I exclude the Wallet instantiation from the 3_token_migration.js file and just execute the command from the terminal after migration, it works as expected.

Any idea what I’m doing wrong?

Thanks,
David

1 Like

Hey @Melshman. I pulled your code into my machine again and did indeed get the same error. You need to include

const Wallet = artifacts.require("Wallet")

in your token migrations file. Otherwise if you call deployer.deploy(wallet) you wont be able to get at your wallets contracts since your not giving any reference to it. Should work if you include that let me know

EDIT**
jusst following up with this your token migrations file should be

///filename 3_token_migration.js
const Link = artifacts.require("Link");
const Wallet = artifacts.require("Wallet");

module.exports = async function(deployer, network, accounts) {
  await deployer.deploy(Link);
  let wallet = await Wallet.deployed()
  let link = await Link.deployed()
  await link.approve(wallet.address, 500)
  wallet.addToken(web3.utils.fromUtf8("LINK"), link.address)
  await wallet.deposit(100, web3.utils.fromUtf8("LINK"))
  let balanceOfLink = await wallet.balances(accounts[0], web3.utils.fromUtf8("LINK"));
  console.log(balanceOfLink);
};
2 Likes

Thanks, @mcgrane5. That structure is starting to make sense.

1 Like

@Melshman. No worries man. up the girnd

Here are my tests, I did not get the last one and I am sure these tests could be improved or are even wrong as I have learned that one can lead yourself into believing one’s test are working…

contract("Dex2", accounts => {
  //==============MARKET ORDERS=========================================

  // when creating a SELL market order, the sellar needs to have enough tokens for the trade
  it ("should throw an error if insuficient amount of tokens when creating a SELL market order", 
  async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    await truffleAssert.reverts(
      dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 1000, 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.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 10, 1)
    )
  }); 

  // when creating a BUY market order, the buyer needs to have enough ETH for the trade
  it ("should have enough ETH for the trade when creating a BUY marker order",
  async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    await truffleAssert.reverts(
      dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 3100, 1)
    )
    await dex.depositEth({value: 10});
    await truffleAssert.passes(
      dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
    )
  });

  // Market orders can be submitted even if the order book is empty
  it ("should submit orders even if the order book is empty",
  async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

    let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 0)
    assert(orderBook.length === 0);
    console.log(orderBook);

    await dex.depositEth({value: 1000});
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)
    
  });

  // Market orders should be filled until the order book is empty or the market order is 100% filled
  it ("should fill Market orders until order book is empty or market order is 100% filled",
  async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

    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 dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
    await dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 1, 100)
    await dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 1, 200)
    
    await dex.depositEth({value: 1000});
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 100)
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 200)

    let orderBook = await dex.getOrderBook(web3.utils.fromUtf8("LINK"), 1)

    for(let i = 0; i < orderBook.length; i++) {
      assert(orderBook.length === 0, "Orders not filled");
    }
    console.log(orderBook);
    
  });
  // The ETH balance of the buyer should decrease with the filled amounts.
  it ("should decrease the buyer's ETH balance from the filled amounts", 
  async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

    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 dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)

    await dex.depositEth({value: 1000})
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)
    
    await dex.withdrawEth({value: 300})
    let balance = await dex.balances(accounts[0], web3.utils.fromUtf8("ETH"))

    assert.isBelow(balance.toNumber(), 3010); // total eth deposited

  });
  // The token balances of the limit order sellers should decrease with the filled amounts.
  it("should decrease seller's limit orders token balances once filled", 
  async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()

    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 dex.createMarketOrder(1, web3.utils.fromUtf8("LINK"), 1, 300)
    
    await dex.createMarketOrder(0, web3.utils.fromUtf8("LINK"), 1, 300)

    assert(link.balanceOf(dex.address < 10));

  })
  // Filled limit orders should be removed from the orderbook

});
2 Likes

Hi guys can anyone help?
I’m on the video finishing wallet and when trying to run

truffle(develop)> let link = await Link.deployed()

I get the following error:

Uncaught ReferenceError: Link is not defined
    at evalmachine.<anonymous>:1:9
truffle(develop)> link.balanceOf (accounts[0])
evalmachine.<anonymous>:0
link.balanceOf (accounts[0])
^

Uncaught ReferenceError: link is not defined
    at evalmachine.<anonymous>:0:1
    at sigintHandlersWrap (vm.js:273:12)
    at Script.runInContext (vm.js:140:14)
    at runScript (C:\Users\dell\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\core\lib\console.js:270:1)
    at Console.interpret (C:\Users\dell\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\core\lib\console.js:285:1)
    at bound (domain.js:413:15)
    at REPLServer.runBound [as eval] (domain.js:424:12)
    at REPLServer.onLine (repl.js:817:10)
    at REPLServer.emit (events.js:315:20)
    at REPLServer.EventEmitter.emit (domain.js:467:12)

code as follows: 3_token_migration.js

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

module.exports = function (deployer) {
  deployer.deploy(Link);
};

tokens.sol

pragma solidity 0.8.4;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Link is ERC20 {
    constructor() ERC20("Chainlink", "LINK") public {
        _mint(msg.sender, 1000);
    }
}

Thanks guys :slight_smile:

1 Like

hey @Keith2 brought your code into my machine and from what youve shown in your post it works. Judging by your errors the problem most likley lies in some typo in either your token.sol or migrations file. make sure your declarations of “Link” are consistent through all of your files. This is probably whats throwig the error. You probably have a typo somewhere in other parts of your code that i cant see because you havent posted. So i am going to do a walkthrough of how to fix using your code snippets above. Because the below code deploys fine on my machine.

1: Token.sol

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../node_modules/@openzeppelin/contracts/token/erc20/ERC20.sol";

contract Link is ERC20{
    constructor() ERC20("Chainlink", "LINK") public{ 
        _mint(msg.sender, 1000);
    }
}

yours is the same a this so thats fine. theres no error here.

2 Wallet.sol

// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "../node_modules/@openzeppelin//contracts/access/Ownable.sol";

contract Wallet is Ownable {

    //we need a token struct to keep track of common token attrivutes such as
    //the token address and its ticker or shorthand identifier
    struct Token {
        string ticker;
        address tokenAddress;
    }
    
    
    
    
    event deposited(address depositedFrom, address depositedTo, uint256 amount, string ticker);
    //we can now create a Token log array to keep track of all our token instaces
    mapping(string => Token) public tokenMapping;
    string[] public tokenList;
    
    //we will begin with a double mapping that maps an address token ticker or symbol
    // which maps to the balance of that token. We can have balances of many types of
    //tokens /eth so we need a mapping to keep track of this
    mapping(address => mapping(string => uint256)) public balances;

    //now we will create the functions we need 
    //function to add tokens
    function getOwner() public view virtual returns(address) {
        return msg.sender;
    }
    function addToken(string memory ticker, address tokenAddress) external onlyOwner {

        //create new token
        tokenMapping[ticker] = Token(ticker, tokenAddress);
        //add the new token to the token list
        tokenList.push(ticker);
    }

    //deposit function
    function deposit(uint amount, string memory ticker) external returns(bool _success) {
        require(tokenMapping[ticker].tokenAddress != address(0));
        IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);
        balances[msg.sender][ticker] += amount;  
        _success = true;
        //emit deposited(msg.sender, address(this), amount, ticker);

        return _success;
    }

    //withdrawal function
    function withdraw(uint amount, string memory ticker) external {
        require(tokenMapping[ticker].tokenAddress != address(0));
        require(balances[msg.sender][ticker] >= amount);
        balances[msg.sender][ticker] += amount;
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);


    }
}


you haven tposted your wallet file but im pretty sure its fine. The erroris not in the wallet contratc just posting this so you can copy and paste to get working. If theres no changes then you do not need this.

3 Token Migrations
again your token migrations file is fine to me but again just psoting this because this works works on my machine

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

module.exports = function (deployer) {
  deployer.deploy(Link);
};

Walllet migrations
I cant speak for your wallet migrations but it should be

//filename 2_wallet_migration.js
const Wallet = artifacts.require("Wallet");

module.exports = function (deployer) {
  deployer.deploy(Wallet);
};

Which you probably already have because thats whats in the tutorial. Again the error is probably a inconsistency in your “Link” declaration. but ig you use the code above it will deploy. Let me know because im curious to see if it is somthing other than a typo error if the above does not compile for you then that will be the case.

Evan

1 Like

Hi @mcgrane5 appreciate you coming back to me. I’ve checked for inconsistencies in the word Link and i’m struggling still to find a typo. I am getting a yellow warning message in the console saying the following which i’m not sure whether it has anything to do with it:

Visibility for constructor is ignored. If you want the contract to be non-deployable, making it "abstract" is sufficient.

Wallet migration as you say worked ok.

I’m struggling where to look next :frowning:

1 Like

All sorted now @mcgrane5, switched off vscode & back on again :slight_smile:

Cheers for your help

1 Like

he @keith, can you try and run the code i posted above. Its a combination of what you provided in the last post and also the correct wallet code which makes it work. If cannot deploy and use it let me know and ill try see what the error is. im just trying to establish some base ground for us both. That way its easier to pinpoint the error

hey @Keith2 brilliant. happy days.

Hi,

Can you help me please? I just did a part with createLimitOrder but my test goes wrong. I can’t figure out why. And 4th test does not even run. I also implemented SafeMath even though I didn’t have to. Here is my depository on github:

https://github.com/tommahawk25/DEX

And those are errors:
image
image

1 Like

Hey @Tomahawk. I had a quick look at your code just there. The main problem is not your tests. The issue is in your bubble sort.

//problem in the block of code below
if(side == Side.BUY) {
            for (uint256 i=orders.length-1; i<orders.length; i--) {
                if (orders[i].price < orders[i-1].price) {
                    break;
                }  
                Order memory right = orders[i];
                orders[i] = orders[i-1];
                orders[i-1] = right;
            }   
        }
        else if(side == Side.SELL) {
            for (uint256 i=0; i>orders.length; i++) {
                if (orders[i].price < orders[i+1].price) {
                    break;
                } 
                Order memory left = orders[i];
                orders[i] = orders[i+1];
                orders[i+1] = left;    
            } 
        }

I did a little debugging and your code was not capable of creating limit orders the function kept reverting so the issue was not your tests it is the actual function itself. Once i commented out your bubble sort algorithm the function was able to execute. the sorting a few issues with your indexing, the following changes to your sorting algorithm fix the function and allow it to run

//Bubble sort
        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 right = orders[i - 1];
                orders[i - 1] = orders[i-1];
                orders[i] = right;
                i--;
            }   
        }
        else if(side == Side.SELL) {
            while (i > 0) {
                if (orders[i - 1].price < orders[i].price) {
                    break;
                } 
                Order memory left = orders[i];
                orders[i - 1] = orders[i];
                orders[i] = left;  
                i--;  
            } 
        }

However the changes above only allow the first test to pass. There are issues with your other tests. I dont have time now to investigate this further as its late here but have a look yourself im sure they are easy fixes. I got your first test to work with the commenting out of the sort and then it also works using the modified code above.I

One piece of advice that helped me with this section you should manually test your functions in the console before writing full blown tests in a js file it makes it easier to catch errors. TruffleAssert only tells you if your test passed or failed and at most gives a vague reason why it doesnt show you exactly what causes the problem. There would have been no way to know that the problem lied withting your createLimitOrder( ) function without doing the manual tests in the console. I will debug this more tomorrow when i get up. Hope that cleared a few things for you though

2 Likes

Hi guys,

I just finished building the wallet (video 3 and 4) and was trying to test it. However, either by testing the wallet in console or by adding code in the migration file I got the same error msg: TypeError: param.substring is not a function

image

2_wallet_migration.js

const Wallet = artifacts.require("Wallet");

module.exports = function (deployer) {
  deployer.deploy(Wallet);
};

3_token_migration.js

const Link = artifacts.require("Link");
const Wallet = artifacts.require("Wallet");
module.exports = async function (deployer, network, accounts) {
  await deployer.deploy(Link);
  let wallet = await Wallet.deployed()
  let link = await Link.deployed()
  await link.approve(wallet.address, 500)
  wallet.addToken(web3.utils.fromUtf8("LINK"), link.address)
  await wallet.deposit(100, web3.utils.fromUtf8("LINK"))
  let balanceOfLink = await wallet.balances(accounts[0], web3.utils.fromUtf8("LINK"))
  console.log(balanceOfLink)
};

wallet.sol

pragma solidity 0.8.0;

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; //call token contracts in order to make transfer calls
    }
    mapping(bytes32 => Token) public tokenMapping; //in order to quickily search and update
    bytes32[] public tokenList; // in order to iterate

    mapping(address => mapping(bytes32 => uint256)) balances; // address=> tokenSymbol=> balances

    modifier isTokenExists(bytes32 ticker) {
        require(tokenMapping[ticker].tokenAddress != address(0));
        _;
    }

    function addToken(bytes32 ticker, address tokenAddress) external onlyOwner {
        tokenMapping[ticker] = Token(ticker, tokenAddress);
        tokenList.push(ticker);
    }

    function deposit(bytes32 ticker, uint256 amount)
        external
        isTokenExists(ticker)
    {
        IERC20(tokenMapping[ticker].tokenAddress).transferFrom(
            msg.sender,
            address(this),
            amount
        );
        balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
    }

    function withdraw(bytes32 ticker, uint256 amount)
        external
        isTokenExists(ticker)
    {
        // from this contract to msg.sender
        // we just held the tokens for owners in this DEX

        // this function is interacting with actual token contract. in order to
        // trasfer tokens between the user's ownership and our contract's ownership
        // we need to interact with ERC token contract
        // to interact with another contract -- we need interface and address
        // --> what the contract looks like and where the contract is
        require( //check
            balances[msg.sender][ticker] >= amount,
            "insufficnient balance"
        );

        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount); //effect
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount); //interact
        //we're interacting with the real token contract, transfer token fron that token contract
        //to the msg.sender who is the right owner
        //we are the DEX!!!
    }
}

tokens.sol (for testing purpose)

pragma solidity 0.8.0;

import "../node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Link is ERC20 {
    constructor() ERC20("ChainLink", "Link") {
        _mint(msg.sender, 1000);
    }
}

I couldn’t figure out why. Can anyone kindly give me a hint?
I also put the files on GitHub: https://github.com/Yanjun7/Solidity201/tree/main/DEX

Thanks in advance!!

Best,
Yanjun