Project - Building a DEX

I have a question about testing contracts.
I found the idea of Mock contracts, but that leads me to wanting to control which contracts are deployed to which networks. Its obvious that you don’t want to deploy your testing contracts to mainnet.
Is there a way to specify that Mock contracts do not get deployed to mainnet and save the gas to deploy them?
I’ve already created a base Mock contract that adds a modifier for disabling the functions on mainnet, but that doesn’t solve the gas issue.
Please let me know if this has been solved in another way already.

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

contract Mock {
    enum EthereumNetwork {
        // Ethereum's mainnet ID is 1
        Unknown, MainNet
    }

    modifier disableOnMainnet() {
        require(getChainId() != uint256(EthereumNetwork.MainNet), "calling test functions on Ethereum's MainNet is not allowed");
        _;
    }

    function getChainId() private view returns (uint256 chainId) {
        assembly {
            chainId := chainid()
        }
    }
}

1 Like

hey @walter_w, thats actually a really good question, something i never even thought about. Perhaps if you tested and tested and tested and local chain or even on ropsten or kovan testnet so much that you knew your code was secure you could just remove the tests and deploy to mainnet. Or perhaps deploying to mainet with the mocha tests doesnt make a difference in the grand scheme of things i don’t know. A curious question though.

I was able to answer the question myself. The deploy script is the place for it.
I don’t like the idea of manually removing the tests before deploying since its a manual step that breaks the devops rules.
I think its really important to be as defensive as possible in all of the code that you write just in case someone else deploys your code and doesn’t know what they are doing.

const web3 = require("web3");
const Dex = artifacts.require("Dex");
const DexMock = artifacts.require("DexMock");
const MAINNET = 1;

module.exports = function (deployer) {
  deployer.deploy(web3.version.network == MAINNET ? Dex : DexMock);
};

Hey walter. Thats great insight thanks for that. This will play a role in the old solidity 201 course where we have to make a coinflip dapp on our own and deploy to testnet. Thanks for that code snippet am going to use that myself when i finish my dapp.

Hi dani,

I have now updated the dex.sol with some improvement to the sorting logic and have also implemented the sort related tests. However, I am now seeing a compilation error and have no clues about it.

image

My codebase is now fully updated in github

https://github.com/rays70/DEX

Please, can you help ?

Cheers

hey @rays it means one of your import statements is the wrong path or incorrect. Double check our openzeppelin imports or any other imports you are using

1 Like

Hi @rays

This is most likely an issue with Node or Truffle, proceed as follow and let me know:

Keep me posted,
Dani

1 Like

Hi Dani and Mcgrane5,

Thanks a lot for your feedback. I have now moved the project to a brand new directory and done npm install of truffle fresh from the scratch. Now the compilation errors are gone.

Cheers!

1 Like

Hey everyone,

Having a weird issue and not sure how to address. There doesn’t seem to be much online about my problem either.

I am implementing a truffle test and when I run the test the console just streams characters.

Here is the wallet code as I am testing the first test in the course for add token for owner.

pragma solidity >=0.6.0 <0.8.4;

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

contract Wallet{

    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 tokenExists(bytes32 ticker){
        require(tokenMapping[ticker].tokenAddress != address(0));
        _;
    }
    function addToken(bytes32 ticker, address tokenAddress) external {
        tokenMapping[ticker] = Token(ticker, tokenAddress);
        tokenList.push(ticker);
    }

    function deposit(uint amount, bytes32 ticker) tokenExists(ticker) external{
        
        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);

    }

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


}

Here is the test code

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]})
        
        )
    })
})

and here are my migrations

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

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

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

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

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

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

};

Here is a sample of the output I get once it appears the start running. It just keeps going like this

IACgCACECQdz/yABBADYCACACQQFGBEAQASEBEAAaAkAgACgCDCIARQ0AIAAgACgCBCICQQFrNgIEIAINACAAIAAoAgAoAggRAAAgABB+CyABEAUACyAAIAEpAiA3AhwLIQAgAEGwiSY2AgAgACwAD0F/TARAIAAoAgQQewsgABB7C70BAQF/IAAgASgCBDYCACAAIAEtAAg6AAQgACABKAIMN

If any has some idea of what is going wrong I would really appreciate it.

Thanks

Hi @Michael_Gerst

Create a repo on GitHub with the full project and share the link, I am going to clone it and reproduce the issue.

Regards,
Dani

Hey @dan-i ,

Here is the link

Michael Gerst Dex Project

Thanks for offering to help I really appreciate.

Hey @Michael_Gerst

Some errors in the migration and also keep in mind that all the interactions with a smart contract are async!
Remove the 2_ migration file and use this one:

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

module.exports = async function (deployer, network, accounts) {

  // Deployment
  await deployer.deploy(Link);
  await deployer.deploy(Wallet);
  let wallet = await Wallet.deployed();
  let link = await Link.deployed();


  await link.approve(wallet.address, 500); // this is async
  await 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")); // this is async
  console.log(`Link Balance: ${Number(balanceOfLink)}`);

};

Screenshot 2021-06-03 at 09.46.36

1 Like

Thanks @dan-i

This helped tremendously you rock.

1 Like

Hello I have a question about my dextest.js. When using truffleAssert.passes on the deposit function can I duplicate the truffleAssert.passes on the same function? I ran the truffle testAssert.passes the first time and it ran correctly but when I duplicated it it didn’t run and this is the error I received:

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

contract("Wallet",accounts => {
    it("Should only be for owners to add tokens", async() => {
        let wallet = await Wallet.deployed()
        let link = await Link.deployed()
        await truffleAssert.passes(
            wallet.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
        )
    })
})

contract("Wallet", accounts => {
    it("should only be owners to add tokens", async() => {
        let wallet = await Wallet.deployed()
        let link = await Link.deployed()
        await truffleAssert.reverts(
            wallet.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[1]})
        )
    })
})

contract("Dex", accounts => {
    it("should only be the 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("LINK"), link.address, {from: accounts[1]})
        )
    })

    it("should handle deposits correctly", async() => {
        let dex = await Dex.deployed()
        let link = await Link.deployed()
        await link.approve(dex.address, 500);
        await truffleAssert.passes(
            dex.deposit(100, web3.utils.fromUtf8("LINK"))
        )
    })

    contract ("Dex", accounts => {
        it("should handle deposits correctly", async() => {
            let dex = await Dex.deployed()
            let link = await Link.deployed()
            await link.approve(dex.address, 500);
            await truffleAssert.passes(
                dex.deposit(100, web3.utils.fromUtf8("LINK"))
            )
        })

    })
})

Capture

The error message is related to a require statement in your contract whose condition is not satisfied.
You also have the error message, therefore you can easily find out which require statement fails and why (“This token ticker does not exists”).

1 Like

Hi guys,

I’m struggling a lot with a very basic concept. I just don’t understand what it means to set the ‘contract instance’ in Truffle (let instance = await Helloworld.deployed() ) and why we have to do it. Filip explains it but it is still not clear to me. He also speaks about an async function which I don’t understand. Why do we have to run ‘await’ after setting the instance? A screenshot down below for further explanation:

Schermafbeelding 2021-06-09 om 12.25.30

My next question is what the purpose is to run:
“let dex = await Dex.deployed()”

and
"await link.approve(dex.address, 500);

I’m specificly curious on the terms “await” and “let”.

Schermafbeelding 2021-06-09 om 12.24.35

I want to say thank you already!!

hey @thomascarl. so basically we can use the truffle console to test our smart contract by running our functions and seeing if they behave as we expect them to. You creating a contract instance as a way to initialise our contract so that we can call all of the functions within it.

Say we have a contract with three functions, funcA(), funcB() and func() and we want to use the truffle console to call these functions and run them. We vto do this we need to deploy our contract and create this “instance of it”. so we do so with

let instance = await contract.deployed()

what this does is it stores the contract in the “instance” variable so that we can use it. Now that we have the contratc instance stored in the instance var we can now individually call each of our functions by saying

//Calls functionA
await instance.funcA()

//Calls functionB
await instance.funcB()

//Calls functionC
await instance.funcC()

So next what is the purpose of the let keyword. Well i javascript we can declare variables in a few ways. Two main ways are using the “let” and “var” keywords. The main difference between them is to do with scoping. Like global and local vars etc. Variables declared by the “var” keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by “{ }” . A good example to explain this is say we have a function that has a if statment.

Function A() {
    var a = 1;
    var b = 2;
    if (b > a) { 
        let message = "b is greater than a"
        console.log(a);
        console.log(b)
    }

   console.log(message)

}

In this function we declare the variables “a” and “b” at the top of the function with the var keyoword. So because we used the var jeyword we wil be have access to these variables throughout the entire function meaning that if we can use them within the entire function body. Howerver when we enter theif statment we declare the message variable with the let keyword. Since we use let, then the scope of this variable is restricted to the enclosing block, ie. the if statment. So we can use the message variable only withtin the if statment. However if we try to console.log it outside of the if statment we will get an error. I hope this makes sense its all to do with scope.

So why the await keyword. So before we look at this its important to realise that in the truffle console we are actually working with javascript. And in java script we can have regular function and asynchronous functions. In javascript if we call a regular function depending on the circumstances, the function will not become resolved instantly. Usually when we call a function then its result comes as a “promise”. The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. So an async function is a function declared with the async keyword, and the await keyword is permitted within them. The async and await keywords enable asynchronous promise-based behavi or to be written in a cleaner style, avoiding the need to explicitly configure promise chains. Also in async functions await expressions make promise-returning functions behave as though they’re synchronous by suspending execution until the returned promise is fulfilled or rejected. The resolved value of the promise is treated as the return value of the await expression. We can think of it like this, the use of async and await enables the use of ordinary try / catch blocks around asynchronous code.

I hope i awnsered your question let me know if anything is still confusing

2 Likes

that one is a variable with name dex to save the intance of the deployed contract.

The let term is used to create a variable that is accessible only to the block within the declariation (can not be called outside the block).

await is used to wait for a result, for an asynchronous functions, approve is the function from the link contract to allow the dex address to spent an amount of links on its name.

Carlos Z

1 Like

Hello, I have a problem when running a test.

Error: Returned error: VM Exception while processing transaction: revert Ownable: caller is not the owner – Reason given: Ownable: caller is not the owner.
image

My wallet_test.js code:

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(
			await dex.addToken(
				web3.utils.fromUtf8(link.symbol()),
				link.address,
				{
					from: accounts[0],
				}
			)
		);
		await truffleAssert.reverts(
			await dex.addToken(
				web3.utils.fromUtf8(link.symbol()),
				link.address,
				{
					from: accounts[1],
				}
			),
			"should only be possible for owner to add tokens"
		);
	});
});

Hey @benlive

It looks like you are calling the function addToken with a user that is not the contract owner.
Are you deploying the contract using accounts[0]?

Make sure accounts[0] is the owner and try again.

Keep us posted!