Project - Building a DEX

Ohh so it is adding them together? I didn’t realize it was adding them together and that was precisely the problem I was tyring to explain. So in this formula… uint cost = filled.mul(orders[i].price) where is the addition part? Its the quantity filled times the price at each level but where is it adding them all together? I am sure there is something i am missing or not taking into consideration probably.
thanks again for your help btw. appreciate you trying to explain this.

Or wait… this forumula is happening inside the loop right? So is it looping again and adding to the cost that way? So each time it loops it basically adds each level (quantity x price)? Like in our example, the first loop is 5 *10 so the cost is $50. The second loop/iteration is 6x20 which is $120 so now cost is ($50 + $120) = $170, and so on? Is that how its adding each iteration together in the cost formual? via the loop?

1 Like

Can anyone tell me how to properly set the Ropsten network with a .env file using Moralis Nodes? I believe the problematic line here is… provider: () => new HDWalletProvider(mnemonic, https://speedy-nodes-nyc.moralis.io/${process.env.ROPSTEN_MORALIS_PROJECT_ID}/eth/ropsten), I don’t believe i am formatting this correcty. Anyone know how to correctly format this and set this up?

const dotenv = require("dotenv");
dotenv.config();

const HDWalletProvider = require("@truffle/hdwallet-provider");
const mnemonic = process.env.ROPSTEN_MNEMONIC;

module.exports = {

  networks: {
    development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 8545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
    },

    ropsten: {
      provider: () => new HDWalletProvider(mnemonic, `https://speedy-nodes-nyc.moralis.io/${process.env.ROPSTEN_MORALIS_PROJECT_ID}/eth/ropsten`),
      network_id: 3,       // Ropsten's id
      gas: 5500000,        // Ropsten has a lower block limit than mainnet
      confirmations: 2,    // # of confs to wait between deployments. (default: 0)
      timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
      skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },
  },
1 Like

hey @Bitborn it shoud work the way you have it set up there yes. what is the actualy error that your getting when trying to deploy?

I ended up using Infura this go around. Next project i will try Moralis nodes again and if i run into the same error i will copy and paste the error message. Thanks.

1 Like

no wories. i wish u the est of luck. and yes please do come here if youve any errors ill be here

hi
after reviewing the course smart contract 201, as a side project, I’m trying to create a coin that has a 5% buy fee and 10% sell fee, I found this solidity code, which I adapt but I could not implement the by fee ??
need some help please:

/**
 //SPDX-License-Identifier: UNLICENSED
 
*/

pragma solidity ^0.8.4;

abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }
}

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");
        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;
        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        return c;
    }

}

contract Ownable is Context {
    address private _owner;
    address private _previousOwner;
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    constructor () {
        address msgSender = _msgSender();
        _owner = msgSender;
        emit OwnershipTransferred(address(0), msgSender);
    }

    function owner() public view returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(_owner == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    function renounceOwnership() public virtual onlyOwner {
        emit OwnershipTransferred(_owner, address(0));
        _owner = address(0);
    }

}  

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
}

interface IUniswapV2Router02 {
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function factory() external pure returns (address);
    function WETH() external pure returns (address);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
}

contract Test is Context, IERC20, Ownable {
    using SafeMath for uint256;
    mapping (address => uint256) private _rOwned;
    mapping (address => uint256) private _tOwned;
    mapping (address => mapping (address => uint256)) private _allowances;
    mapping (address => bool) private _isExcludedFromFee;
    mapping (address => bool) private bots;
    mapping (address => uint) private cooldown;
    uint256 private constant MAX = ~uint256(0);
    uint256 private constant _tTotal = 1000000 * 10**9;
    uint256 private _rTotal = (MAX - (MAX % _tTotal));
    uint256 private _tFeeTotal;
    
    uint256 private _feeAddr1;
    uint256 private _feeAddr2;
    address payable private _feeAddrWallet1;
    address payable private _feeAddrWallet2;
    
    string private constant _name = "Testcoin";
    string private constant _symbol = "Test";
    uint8 private constant _decimals = 9;
    
    IUniswapV2Router02 private uniswapV2Router;
    address private uniswapV2Pair;
    bool private tradingOpen;
    bool private inSwap = false;
    bool private swapEnabled = false;
    bool private cooldownEnabled = false;
    uint256 private _maxTxAmount = _tTotal;
    event MaxTxAmountUpdated(uint _maxTxAmount);
    modifier lockTheSwap {
        inSwap = true;
        _;
        inSwap = false;
    }
    constructor () {
        _feeAddrWallet1 = payable(0xE7bd7Ad246abD89964732A0783bbe1f629f557Ad);
        _feeAddrWallet2 = payable(0xE7bd7Ad246abD89964732A0783bbe1f629f557Ad);
        _rOwned[_msgSender()] = _rTotal;
        _isExcludedFromFee[owner()] = true;
        _isExcludedFromFee[address(this)] = true;
        _isExcludedFromFee[_feeAddrWallet1] = true;
        _isExcludedFromFee[_feeAddrWallet2] = true;
        emit Transfer(address(0xA2b5b4b462E5d4707EEF54F4B6A7eA1a5c9C0A77), _msgSender(), _tTotal);
    }

    function name() public pure returns (string memory) {
        return _name;
    }

    function symbol() public pure returns (string memory) {
        return _symbol;
    }

    function decimals() public pure returns (uint8) {
        return _decimals;
    }

    function totalSupply() public pure override returns (uint256) {
        return _tTotal;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return tokenFromReflection(_rOwned[account]);
    }

    function transfer(address recipient, uint256 amount) public override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function setCooldownEnabled(bool onoff) external onlyOwner() {
        cooldownEnabled = onoff;
    }

    function tokenFromReflection(uint256 rAmount) private view returns(uint256) {
        require(rAmount <= _rTotal, "Amount must be less than total reflections");
        uint256 currentRate =  _getRate();
        return rAmount.div(currentRate);
    }

    function _approve(address owner, address spender, uint256 amount) private {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _transfer(address from, address to, uint256 amount) private {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "Transfer amount must be greater than zero");
        _feeAddr1 = 2;
        _feeAddr2 = 3;
        if (from != owner() && to != owner()) {
            require(!bots[from] && !bots[to]);
            if (from == uniswapV2Pair && to != address(uniswapV2Router) && ! _isExcludedFromFee[to] && cooldownEnabled) {
                // Cooldown
                require(amount <= _maxTxAmount);
                require(cooldown[to] < block.timestamp);
                cooldown[to] = block.timestamp + (30 seconds);
            }
            
            
            if (to == uniswapV2Pair && from != address(uniswapV2Router) && ! _isExcludedFromFee[from]) {
                _feeAddr1 = 1;
                _feeAddr2 = 9;
            }
            uint256 contractTokenBalance = balanceOf(address(this));
            if (!inSwap && from != uniswapV2Pair && swapEnabled) {
                swapTokensForEth(contractTokenBalance);
                uint256 contractETHBalance = address(this).balance;
                if(contractETHBalance > 0) {
                    sendETHToFee(address(this).balance);
                }
            }
        }
		
        _tokenTransfer(from,to,amount);
    }

    function swapTokensForEth(uint256 tokenAmount) private lockTheSwap {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();
        _approve(address(this), address(uniswapV2Router), tokenAmount);
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0,
            path,
            address(this),
            block.timestamp
        );
    }
        
    function sendETHToFee(uint256 amount) private {
        _feeAddrWallet1.transfer(amount.div(2));
        _feeAddrWallet2.transfer(amount.div(2));
    }
    
    function openTrading() external onlyOwner() {
        require(!tradingOpen,"trading is already open");
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        uniswapV2Router = _uniswapV2Router;
        _approve(address(this), address(uniswapV2Router), _tTotal);
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
        uniswapV2Router.addLiquidityETH{value: address(this).balance}(address(this),balanceOf(address(this)),0,0,owner(),block.timestamp);
        swapEnabled = true;
        cooldownEnabled = true;
        _maxTxAmount = 50000000000000000 * 10**9;
        tradingOpen = true;
        IERC20(uniswapV2Pair).approve(address(uniswapV2Router), type(uint).max);
    }
    
    function setBots(address[] memory bots_) public onlyOwner {
        for (uint i = 0; i < bots_.length; i++) {
            bots[bots_[i]] = true;
        }
    }
    
    function delBot(address notbot) public onlyOwner {
        bots[notbot] = false;
    }
        
    function _tokenTransfer(address sender, address recipient, uint256 amount) private {
        _transferStandard(sender, recipient, amount);
    }

    function _transferStandard(address sender, address recipient, uint256 tAmount) private {
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tTeam) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rOwned[recipient] = _rOwned[recipient].add(rTransferAmount); 
        _takeTeam(tTeam);
        _reflectFee(rFee, tFee);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _takeTeam(uint256 tTeam) private {
        uint256 currentRate =  _getRate();
        uint256 rTeam = tTeam.mul(currentRate);
        _rOwned[address(this)] = _rOwned[address(this)].add(rTeam);
    }

    function _reflectFee(uint256 rFee, uint256 tFee) private {
        _rTotal = _rTotal.sub(rFee);
        _tFeeTotal = _tFeeTotal.add(tFee);
    }

    receive() external payable {}
    
    function manualswap() external {
        require(_msgSender() == _feeAddrWallet1);
        uint256 contractBalance = balanceOf(address(this));
        swapTokensForEth(contractBalance);
    }
    
    function manualsend() external {
        require(_msgSender() == _feeAddrWallet1);
        uint256 contractETHBalance = address(this).balance;
        sendETHToFee(contractETHBalance);
    }
    

    function _getValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256, uint256, uint256) {
        (uint256 tTransferAmount, uint256 tFee, uint256 tTeam) = _getTValues(tAmount, _feeAddr1, _feeAddr2);
        uint256 currentRate =  _getRate();
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee) = _getRValues(tAmount, tFee, tTeam, currentRate);
        return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tTeam);
    }

    function _getTValues(uint256 tAmount, uint256 taxFee, uint256 TeamFee) private pure returns (uint256, uint256, uint256) {
        uint256 tFee = tAmount.mul(taxFee).div(100);
        uint256 tTeam = tAmount.mul(TeamFee).div(100);
        uint256 tTransferAmount = tAmount.sub(tFee).sub(tTeam);
        return (tTransferAmount, tFee, tTeam);
    }

    function _getRValues(uint256 tAmount, uint256 tFee, uint256 tTeam, uint256 currentRate) private pure returns (uint256, uint256, uint256) {
        uint256 rAmount = tAmount.mul(currentRate);
        uint256 rFee = tFee.mul(currentRate);
        uint256 rTeam = tTeam.mul(currentRate);
        uint256 rTransferAmount = rAmount.sub(rFee).sub(rTeam);
        return (rAmount, rTransferAmount, rFee);
    }

	function _getRate() private view returns(uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply.div(tSupply);
    }

    function _getCurrentSupply() private view returns(uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;      
        if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal);
        return (rSupply, tSupply);
    }
}

1 Like

this code uses uniswap to execute trades. your idea is much simpler and does not require this. the best way to go about yours is to make a simple erc20 token. and then in your Vendor contract hardcode the buy and sell fee as global constants. create your buy and sell function and using these constants you can make set the amount of eth/token and token/eth and have simple require statements to make sure these requirements are met when a user buys or sells.

something like this could get you started. of course you could also add a sell function to reverse the pair order. we can only buy with ETH here tho

contract Vendor {

  // Our Token Contract
  YourToken yourToken;

  // token price for ETH
  uint256 public tokensPerEth = 100;

  // Event that log buy operation
  event BuyTokens(address buyer, uint256 amountOfETH, uint256 amountOfTokens);

  constructor(address tokenAddress) {
    yourToken = YourToken(tokenAddress);
  }

  
  function buyTokens() public payable returns (uint256 tokenAmount) {
    require(msg.value > 0, "Send ETH to buy some tokens");

    uint256 amountToBuy = msg.value * tokensPerEth;

    // check if the Vendor Contract has enough amount of tokens for the transaction
    uint256 vendorBalance = yourToken.balanceOf(address(this));
    require(vendorBalance >= amountToBuy, "Vendor contract has not enough tokens in its balance");

    // Transfer token to the msg.sender
    (bool sent) = yourToken.transfer(msg.sender, amountToBuy);
    require(sent, "Failed to transfer token to user");

    // emit the event
    emit BuyTokens(msg.sender, msg.value, amountToBuy);

    return amountToBuy;
  }

 
  function withdraw() public onlyOwner {
    uint256 ownerBalance = address(this).balance;
    require(ownerBalance > 0, "Owner has not balance to withdraw");

    (bool sent,) = msg.sender.call{value: address(this).balance}("");
    require(sent, "Failed to send user balance back to the owner");
  }
}

the best way to do the fee is to abstract the buying and selling of the token into this verndor contract and apply the fee on buy and sell functions here.

Hoping someone can help with a general question regarding the depositEth and withdrawEth functions in wallet.sol. Why don’t both of these functions call IERC20 interface with the transfer/transferFrom functions like the regular deposit and withdraw functions? Don’t we need to call transfer via the interface in these functions too? Thanks in advance for anyone who replies with an explanation.

1 Like

hey @Bitborn. no for the withdraw and depositETH functions we dont need to call transfer through the interface. this is because these functions are for directly depositing ether which is a “native” asset. ERC20 tokens are not “native” assets, they are smart contracts them selves whose logic just mimic that of a currency. the reason we need to call the transferFrom function or transfer function with the IERC20 interface for tokens is because we need a way to access, the transfer/transferFrom functions for these contracts.

the way an interface works is by making a connection to the function we want to call, with the address were passing in. for example if we have an interface like

interface IERC20 {
  
  
    function transfer(address to, uint256 amount) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

then we can access the transfer/transferFrom funtion from any tokenContract as long as we know the address. so in this scenario, from your regular deposit function. (the deposit function specifically for tokens) we can access that tokens transferFrom for example, by something like

IERC20(myTokenAddress).transferFrom(addr1, addr2, amount);

this now begs the question, well why cant we just import the normal ERC20 token contract to achieve the same thing. well the awnser is we can of course do it thisway. for example the same would work for doing it with the ERC20

import "ERC20.sol"

contract Foo {

    function withdrawToken() public {
         ERC20(tokenAddress).transferFrom(addr1, addr2, amount);
    }
}

so why bother with interfaces at all. well if you look at the interface above again see that im only importing two function signatures. the advantage of interfaces is that you only need to import the functions you know or think you will want to call in your contract. whereas if we import the ERC20.sol contract, then were importing potentially lodes of unwanted function that we wont need.

So interfaces are extremely handly when you only want access to one or two functions from a contract. they are also handy when you dont know the entire contract but maybe you know only the function you want to use, that way you can just define the function heading and passing in its address to your interface will work. this is why intrfaces are very powerful.

Now to awnser your question ETH is a “native” asset, and not a contract, ETH the currency doesnt have an address, tp transfer or deposit eth we dposit eth via making functions payable and passing in the amount we want to deposit via ,sg.value, and we withdraw eth via the built in solididyt function .transfer or .call

Thanks for the explanation man. This clarified a lot. Appreciate it.

1 Like

Hey guys, I’m getting an error:

I’m currently in the video Better Migration

TypeError: wallet.balances is not a function

My Wallet contract

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

    modifier tokenExist(bytes32 ticker){
        require(tokenMapping[ticker].tokenAddress != address(0), "Token doesnt exist ");
        _;
    }

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

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

    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 wiithdraw(uint amount, bytes32 ticker) tokenExist(ticker) external{
        require(balances[msg.sender][ticker] >= amount, "Man's broke");
        balances[msg.sender][ticker] = balances[msg.sender][ticker] - amount;
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
    }
}

My Token contract

// 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);
    }
}```
2 Likes

You are trying to access the balance mapping directly, but you need a get function for it, just have to create a function like this:

function getBalance(address account) public view returns(uint256){
return balances[account];
}

Carlos Z

2 Likes

Help!
I keep getting these errors.

Here is my code:

Wallet:

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

    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), "No such token");
        _;
    }

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

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

    function withdraw(uint256 amount, bytes32 ticker) external tokenExist(ticker) {
        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);
    }

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

    function withdrawEth(uint amount) external {
        require(balances[msg.sender][bytes32("ETH")] >= amount,'Insuffient balance'); 
        balances[msg.sender][bytes32("ETH")] = balances[msg.sender][bytes32("ETH")].sub(amount);
        msg.sender.call{value:amount}("");
    }
}

Dex:

pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "./Wallet.sol";

contract DEX is Wallet {
    using SafeMath for uint256;

    enum Side { BUY, SELL }

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

    uint public nextOrderId;

    mapping(bytes32 => mapping(uint => Order[])) public 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));
        }
        else if(side == Side.SELL) {
            require(balances[msg.sender][ticker] <= amount.mul(price));
        }

        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 c = orders[i-1];
                orders[i-1] = orders[i];
                orders[i] = c;
                i--;
            }

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

        nextOrderId++;
    }
}

Dex Test:

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: 10})
        await truffleAssert.passes(
            dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 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.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.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);
        for (let i = 0; i < orderbook.length - 1; i++) {
            //const element = array[index];
            assert(orderbook[i] >= orderbook[i+1])
        }
    })
    //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);
        for (let i = 0; i < orderbook.length - 1; i++) {
            //const element = array[index];
            assert(orderbook[i] <= orderbook[i+1])
        }
    })
})
1 Like

hmmm for the first and third test it seems like you might have a logical or runtime error in your code because its saying the transaction has reverted. if we take the example of your frst test and navigate to it your really only calling the createLimit order funcion here so i would imagine that the revert error is most likley in your createLimit order function.

to be absolutley sure of this you can comment out everything in your firt test upto this line

 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 dex.createLimitOrder(0, web3.utils.fromUtf8("LINK"), 10, 1)
    })

if this fails then you know the error is in your createLimitOrder function and then you can go to this code and debug it line by line to try locate the error.

if you try this come back to me and we can try solve the problem.

the second test is failing not because of any runtime or logical error but because your code isnt failing. meaning its working, but its just not working as its intended to. if we navigate to the createLimit order function we can see why

else if(side == Side.SELL) {
            require(balances[msg.sender][ticker] <= amount.mul(price));
        }

your reversion has the wrong comparison operator. it should be the opposite way around like this

else if(side == Side.SELL) {
            require(balances[msg.sender][ticker] >= amount.mul(price));
        }

fo the third test i again recommend you follow the same apprach to debug as i recommended for the firsttest. comment out your test line by line until you fine the line that throws the error and go from there. one thing i am also noticing is that your arent adding th elink token. your just deploying it and approving it. remember that in order to createLimit oders, the token must exist or be registred. this is what the tokenExists modifier is for. so remember to call the addToken function before depositing or creating limitOrders

Hey,
I have 2 questions regarding the deposit function we had to build.

  1. Why is the function not payable as it receives Ether (or funds from another token contract) ?

  2. Why do we need to use transferFrom and just transfer is not sufficient ?
    IERC20(tokenMapping[ticker].tokenAddress).transfer(address(this),amount);
    You said that the user should give us allowance before doing the deposit, is this request done automatically by an external wallet like Metamask when we buy a new token ?

the deposit function you are referencing is not payable because its dealing with ERC20 token transfers. ERC0 tokens are themselves just smart contracts with logic and constrants built in to mimic that of a digital currency, however ERC20 tokens are not the same as ETHER. eth is the native currency of the ethereum blockchain whereas ERC20 toens are just smart contracts. you only need to use the payable keyword when sending someone ether.

secondly if you want tosend funds from a smart contract to a wallet like metamask then you use transfer howowver if you want to go the other way around and send tokens from a wallet to a smart contract, you need to use the transferFrom function. the reason you need to approve a contract before sending funds to it is beause when you send funds to a smart contract, your essentially giving that contract permission to use your funds. so the approve function prevents against attack vectors, where smart contracts can be used to drain users funds in certain situations. so this is always why you approve or give permission to a contract to use your funds before doing the actual transfer

3 Likes

I have problems going through the truffle test. May I know how to fix it please? It said

truffle(develop)> truffle test
Using network 'develop'.


Compiling your contracts...
===========================
> Compiling .\contracts\Migrations.sol
> Compiling .\contracts\dex.sol
> Compiling .\contracts\tokens.sol
> Compiling .\contracts\tokens.sol
> Compiling .\contracts\wallet.sol
> Artifacts written to C:\Users\ccy_s\AppData\Local\Temp\test--19012-BAvuMK8Y2iRM
> Compiled successfully using:
   - solc: 0.8.16+commit.07a7930e.Emscripten.clang
Error: Returned error: VM Exception while processing transaction: revert
    at module.exports (C:\Users\ccy_s\OneDrive\Documents\ethereum-201\ETH201_DEX\migrations\3_token_migration.js:10:13)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at Migration._deploy (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\Migration.js:72:1)
    at Migration._load (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\Migration.js:54:1)
    at Migration.run (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\Migration.js:202:1)
    at Object.runMigrations (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\index.js:142:1)
    at Object.runFrom (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\index.js:107:1)
    at Object.runAll (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\index.js:111:1)
    at Object.run (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\migrate\index.js:84:1)
    at Object.run (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\core\lib\testing\Test.js:114:1)
    at Object.module.exports [as run] (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\core\lib\commands\test\run.js:115:1)
    at runCommand (C:\Users\ccy_s\AppData\Roaming\npm\node_modules\truffle\build\webpack:\packages\core\lib\command-utils.js:190:1)

Below are my codes:

Migrations.sol:

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

contract Migrations {
  address public owner = msg.sender;
  uint public last_completed_migration;

  modifier restricted() {
    require(
      msg.sender == owner,
      "This function is restricted to the contract's owner"
    );
    _;
  }

  function setCompleted(uint completed) public restricted {
    last_completed_migration = completed;
  }
}

dex.sol:

pragma solidity 0.8.16;
//SPDX-License-Identifier: UNLICENSED
pragma experimental ABIEncoderV2;

import "./wallet.sol";

//market order always take out order from orderbook
//limit order is always added into orderbook

contract Dex is Wallet {

    enum Side {
        BUY,
        SELL
    }

    struct Order {
        uint id;
        address trader;
        bool buyOrder;
        bytes32 ticker;
        uint amount;
        uint price;
    }

    mapping(bytes32 => mapping(uint => Order[])) public orderBook;  //bytes32 is the asset

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

   // getOrderBook(bytes32("LINK"), Side.BUY)

   //function createLimitOrder() {

   //}

}

tokens.sol:

pragma solidity 0.8.16;
//SPDX-License-Identifier: UNLICENSED

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

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

wallet.sol:

pragma solidity 0.8.16;
//SPDX-License-Identifier: UNLICENSED

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;

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

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

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

    function withdraw(uint amount, bytes32 ticker) tokenExist(ticker) external {
        require(balances[msg.sender][ticker] >= amount, "Balances not sufficient");
        

        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
    }
}

wallettest.js

const Dex = artifacts.require("Dex")
const Link = artifacts.require("Link")
const truffleAssert = requrie('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]})
        )
    })   
})

@mcgrane5 @thecil Anyone able to help please? I would appreciate your advice!

1 Like

wow very sorry completely missed this. yeah i think i see the problem. your code is compiling fine, so theres no bugs at compile time, can u send me a link to your github im going to hace to take a look just to be sure,

your test looks ok to on first glance. yeah can u link your git repo and ill take a look immediately for you

2 Likes

Thanks a lot for your reply! Here’s my GitHub files for this truffle test:
https://github.com/cyl2chan/leon-chan.github.io/tree/main/ETH201%20-%20building%20DEX

I would appreciate your help!

1 Like