Project - Building a DEX

Here you can ask questions related to this specific course section.

3 Likes

@Filip How come you did this order in the deposit function of the wallet?

    function deposit(uint amount, bytes32 ticker) external tokenExist(ticker){
    
        IERC20 remote = IERC20(tokenMapping[ticker].tokenAddress);
        require(remote.balanceOf(msg.sender) >= amount, "Balance not sufficient");    
        remote.transferFrom(msg.sender, address(this), amount);
        balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
    }

In your code you did the transfer first and then the effect (add balance locally).
Is it not better to do this:

   function deposit(uint amount, bytes32 ticker) external tokenExist(ticker){
    
        IERC20 remote = IERC20(tokenMapping[ticker].tokenAddress);
        require(remote.balanceOf(msg.sender) >= amount, "Balance not sufficient"); 
        balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);   
        remote.transferFrom(msg.sender, address(this), amount);
    }

Or is the Checks / Effects / Interactions patten not applicable here?

2 Likes

@filip It seems that the introduction video for test driven development is missing in the section.
Video 6 ends with: “In the next video we are going to see what test driven development is…”.
Video 7 starts with: “Now that you know about test driven development…”

5 Likes

When trying to compile the multisig wallet from the previous course using truffle,
I’ve made a new folder
Run truffle init in the folder
Made multisig.sol in contracts folder and 2_multisig_deploy.js in migrations folder
Edited compiler version to 0.7.5 in truffle-config.js

And when I run migrate from truffle develop, I get the following:

Error: TypeError: soljson.Pointer_stringify is not a function
at Object.compile (/usr/local/lib/node_modules/truffle/build/webpack:/packages/workflow-compile/legacy/index.js:72:1)

Is this a problem with truffle version? I am still using the truffle installation used with ganache from the old 201 course.

Here is my code as well for the .js deploy file as well as my multisig wallet. I also tried compiling using your uploaded code for your multisig wallet solution and received the same error.

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

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

pragma solidity 0.7.5;
pragma abicoder v2;

contract Wallet {
    address[] public owners;
    uint limit;

    struct Transfer{
        uint amount;
        address payable receiver;
        uint approvals;
        bool hasBeenSent;
        uint id;
    }

    event TransferRequestCreated(uint _id, uint _amount, address _initiator, address _receiver);
    event ApprovalReceived(uint _id, uint _approvals, address _approver);
    event TransferApproved(uint _id);

    Transfer[] transferRequests;

    mapping(address => mapping(uint => bool)) approvals;

    //Should only allow people in the owners list to continue the execution.
    modifier onlyOwners(){
        bool owner = false;
        for(uint i=0; i<owners.length;i++){
            if(owners[i] == msg.sender){
                owner = true;
            }
        }
        require(owner == true);
        _;
    }
    //Should initialize the owners list and the limit
    constructor(address[] memory _owners, uint _limit) {
        owners = _owners;
        limit = _limit;
    }

    //Empty function
    function deposit() public payable {}

    //Create an instance of the Transfer struct and add it to the transferRequests array
    function createTransfer(uint _amount, address payable _receiver) public onlyOwners {
        emit TransferRequestCreated(transferRequests.length, _amount, msg.sender, _receiver);
        transferRequests.push(
            Transfer(_amount, _receiver, 0, false, transferRequests.length)
        );

    }

    //Set your approval for one of the transfer requests.
    //Need to update the Transfer object.
    //Need to update the mapping to record the approval for the msg.sender.
    //When the amount of approvals for a transfer has reached the limit, this function should send the transfer to the recipient.
    //An owner should not be able to vote twice.
    //An owner should not be able to vote on a tranfer request that has already been sent.
    function approve(uint _id) public onlyOwners {
        require(approvals[msg.sender][_id] == false);
        require(transferRequests[_id].hasBeenSent == false);

        approvals[msg.sender][_id] = true;
        transferRequests[_id].approvals++;

        emit ApprovalReceived(_id, transferRequests[_id].approvals, msg.sender);

        if(transferRequests[_id].approvals >= limit){
            transferRequests[_id].hasBeenSent = true;
            transferRequests[_id].receiver.transfer(transferRequests[_id].amount);
            emit TransferApproved(_id);
        }
    }

    //Should return all transfer requests
    function getTransferRequests() public view returns (Transfer[] memory){
        return transferRequests;
    }


}

How should I proceed and thanks for the help!

Hello! I have a couple of questions about this final project that are a little confusing to me. I’d appreciate any reply.

  1. When building the wallet for the DEX the array for tokenLists is made up of the token’s symbol(ticker). What happens if you need to add 2 or more tokens with the same ticker? I tried changing the mappings and tokenLists array but that seemed like it would not let the user interact using the token’s symbol.

  2. While developing the code for transfers and withdraws in the wallet for the DEX, I noticed there was no implement to guard against reentrency in the code nor a implementation of OpenZeppelin’s ReEntrency Guard extension. Was this not included to save time for video, or is the reentrency automatically prevented from the ERC20 interface functions? Hope my question is clear.

Thanks for any help

2 Likes

I am not sure but maybe it could be related to either the version of truffle or solidity that you are using.
I found this link that seems to point in that direction:
https://github.com/trufflesuite/truffle/issues/2702

Can you try running:

npm uninstall -g truffle
npm install -g truffe

This way you get the latest version.
If it does not work you can always uninstall and reinstall the old version.

Hi @Capplequoppe

Here he is not sending ether from his contract to another contract.
He is moving funds into his contract.

Still good if you follow the pattern :slight_smile:

Cheers,
Dani

2 Likes

Good evening @dan-i

Trying to wrap my head around calling IERC20(address). Seems like this concept came out of nowhere.
When you do this, are you “interacting” with the token’s contract?
I added another require statement that checks if the depositors balance is sufficient in the token’s contract. Does that make sense? Or am I doing it wrong?

function deposit(uint amount, bytes32 ticker) external {
        require(tokenMapping[ticker].tokenAddress != address(0));
        require(IERC20(tokenMapping[ticker].tokenAddress).balanceOf(msg.sender) >= amount);

        balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
        IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);
    }

Hey @wilsonlee123

Let me clarify your doubts about ERC tokens.
This concept is the same for all ERC (20/721/1155 etc…).
A token is just a number in a mapping.

When you own Tether tokens (or any other ERC), you simply have a number higher than 0 in that _balances mapping of that specific contract.

Look at the standard erc20 openzeppelin contract HERE

mapping (address => uint256) private _balances;

When you transfer Tether from your address to my address, what happens in the background is that a function transfer is called in the Tether contract, and that function decreases you balance in the _balances mapping and increases mine.

In order to move an ERC token you always call the contract of that token.

For tether you call the tether contract.
For LINK you call the chainlink contract etc…

This is the reason why behind this call:

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

you are basically saying to Solidity: look, there is a contract that has this address (tokenMapping[ticker].tokenAddress) and that contract has function transferFrom than requires the following parameters address sender, address recipient, uint256 amount.

If you want to move Tether, tokenMapping[ticker].tokenAddress will be the tether contract address, if you want to move LINK tokens, tokenMapping[ticker].tokenAddress will be the chainlink token address.

Regarding your require statement require(IERC20(tokenMapping[ticker].tokenAddress).balanceOf(msg.sender) >= amount); this is not necessary.

The function transferFrom already check that for you:

function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);

      .....
    }
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
       .....
        uint256 senderBalance = _balances[sender];
        require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
       ....
    }

Hopefully I clarified your doubts.

Let me know if you still have questions.

Cheers,
Dani

5 Likes

Thanks for clearing that up, @dan-i !
That makes sense, the whole concept of withdrawing and depositing is kind of an illusion lol.

One more thing I’m kind of confused about. I’m looking at transfer() from ERC20:

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

_msgSender() is already passed automatically, so when we use:

    function withdraw(uint amount, bytes32 ticker) external {
        require(tokenMapping[ticker].tokenAddress != address(0));
        
        balances[msg.sender][ticker] = balances[msg.sender][ticker].sub(amount);
        IERC20(tokenMapping[ticker].tokenAddress).transfer(msg.sender, amount);
    }
}

To me it looks like withdraw() is subtracting from msg.sender and adding to msg.sender too. That _msgSender() parameter is msg.sender, isn’t it?

Here are my market order tests:

Check for sufficient ETH / Token balance
  it("should check that seller has enough tokens for selling", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
    await link.approve(dex.address, 500)
    await dex.deposit(10, web3.utils.fromUtf8("LINK"))
    await truffleAssert.passes(
      dex.createMarketOrder(10, web3.utils.fromUtf8("LINK"), 1)
    )
    await truffleAssert.reverts(
        dex.createMarketOrder(100000, web3.utils.fromUtf8("LINK"), 1)
    )
  })
  //When creating a BUY Order the trader needs enough ETH
  it("should check that BUYER has enough ETH for buying", async () => {
    let dex = await Dex.deployed()
    let link = await Link.deployed()
    await dex.depositEth({value: 3000});
    await truffleAssert.passes(
      dex.createMarketOrder(10, web3.utils.fromUtf8("LINK"), 0)
    )
    await truffleAssert.reverts(
        dex.createMarketOrder(100000, web3.utils.fromUtf8("LINK"), 0)
    )
  })

I only got so far today, but the first two tests work.

1 Like

Hi @wilsonlee123

The method _msgSender() returns the msg.sender.
With that clarification let’s check the transfer functions.

Consider the transfer() function:

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

Consider the _transfer() function:

function _transfer(address sender, address recipient, uint256 amount) internal virtual {

The first parameter is the sender (_msg.sender());
The 2nd parameter is the recipient;
The 3rd parameter is the amount;

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
       ...
        _balances[sender] = senderBalance - amount;
        _balances[recipient] += amount;

        emit Transfer(sender, recipient, amount);
    }

Should make sense now :slight_smile:

Happy learning,
Dani

@dan-i Please look at withdraw() in the wallet contract - it’s passing in msg.sender, amount. So what would show up in the _transfer would be (_msgSender, msg.sender, amount)? Is that right?

Hey @wilsonlee123

Keep in mind that you are calling an external contract.

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

sender = your contract address (msg.sender is not the user but your contract as you are calling an external contract).

recipient = the user address

I wrote for you a simple example hoping to clarify your ideas:

A.sol

pragma solidity 0.8.0;

interface B {
    function r() external view returns (address);    
}

contract Test {
    
    function go (address _contractB) public returns(address){
        address result = B(_contractB).r();
        return result;
    }
}

B.sol

pragma solidity 0.8.0;

contract Test {
    
    function r () public returns(address){
        return msg.sender;
    }
}
  • Deploy B and copy its address;
  • Deploy Al
  • Call go() by giving the contract B address as param
  • Check the returned value.

Cheers,
Dani

2 Likes

Great example! I think it finally makes sense to me now.
The contract calling the external contract is the _msgSender, and then in the contract itself msg.sender is the address of the user.
thanks again, @dan-i!

Good day @dan-i,

I’ve written out the basic tests now and about to start on the limit order test assignment. It doesn’t want to compile my contracts though. I keep getting this:

image

In truffle-config.js the compiler is set up like this:

// Configure your compilers
  compilers: {
    solc: {
       version: "0.7.0",    // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      // settings: {          // See the solidity docs for advice about optimization and evmVersion
      //  optimizer: {
      //    enabled: false,
      //    runs: 200
      //  },
      //  evmVersion: "byzantium"
      // }

I have a feeling it has to do with my compile, but I’m not sure, what would you recommend checking?

Hey @wilsonlee123

This looks like a node issue tbh.
Follow this faq and let me know: FAQ - How to downgrade Node.Js

@dan-i I downgraded and it’s still giving me the error. I started a new folder with just wallet.sol and it wouldn’t even let me compile that. So definitely something weird with the compiler. What would you recommend trying from here on?
image

Hey @wilsonlee123

Seems to be an issue with truffle https://github.com/trufflesuite/truffle/issues/2702
Update truffle to the latest version and retry sudo npm i -g truffle

cheers,
Dani

Thanks, that did it! I can move on now.

1 Like