Solidity Basics

I copy the text the is shown in the video, but have errors. Filip has onlyOwner in the addToken function, but when I do the same thing, I get an error. Also there are errors for other things that are just copying what is shown in the video.

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

//We can import a file (IERC20.sol) which is used as an interface for ERC20 tokens, 
//with the function headers, etc. that we can use with our dex. 
//We need this interface. We can use it with the information in our struct, 
//to interact with token contracts, and we can use it for functions like withdraw()
//(It is located in @OpenZeppelin\contracts\token\ERC20\IERC20.sol)
import "../node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol";
//We also import SafeMath
import "../node_modules/@openzeppelin/contracts/utils/math/SafeMath.sol";
//By adding the Ownable file, there is a limit on what users can do with the contract,
//for example, only the contract owner can add new tokens to the contract. 
import "../node_modules/@openzeppelin/contracts/access/Ownable.sol";


contract WalletOct2022 {
    using SafeMath for uint256;
//We make a struct to store information about the tokens used in our DEX.
//For tokens traded/bought in the DEX, we need token addresses, 
//to call the token contracts, to do transfer calls.
    struct Token {
        bytes32 ticker;
        address tokenAddress;
    }

//We save the token address & tickers in a combined array & mapping

//bytes32[] is an array
//Here are all the tickers of the tokens bought or traded in this DEX,
//which are used as token IDs in this DEX, 
//and they need to be unique.
//These bytes32 ticker items are put into a mapping, in which the ticker points to the struct called "Token",
//which contains the ticker and the token address. 
//With this data storage structure, we have an array that we can use to loop through, 
//for example to get information about a particular item in a group of items,
//and with the mapping, we have a way to quickly get information, and also to update an item, 
//by using a particular key and its connected value.
//With this structure, we cannot delete items, but a delete structure can be added.
//This struck & mapping has information about tokens, the mapping below has information about balances.
    mapping(bytes32 => Token) public tokenMapping;
    bytes32[] public tokenList;


//double mapping for multiple balances of ETH and ERC20 tokens.
//bytes32 is a data type used for the crypto ticker.
//The address points to another mapping, 
//of the ticker/token symbol (bytes32) that points to an integer (uint256)
    mapping(address => mapping(bytes32 => uint256)) public balances;


//This function adds token information to our storage
//It is external, since we do not need to run it from within here, also, this way we can save gas expenses
//we save it to our tokenMapping, for the ticker, connected with the struct with the name "Token", 
//which has a ticker symbol & a token contract address
//and we add it to the tokenList array, to which we only add / push the ticker (not the token)
//for a list of all of the IDs. 
//CHECK IF I WROTE "TICKER" IN A PLACE WHERE IT SHOLD BE "TOKEN NAME" !!
//the onlyOwner modifier limits the act of adding a token to the contract owner.
//to do this, the teacher put onlyOwner in the heading, before external, but this was shown as an error,
//which could stop compiling of the contract.
    function addToken(bytes32 ticker, address tokenAddress) onlyOwner external {
    tokenMapping[ticker] = Token(ticker, tokenAddress);
    tokenList.push(ticker);

    }

//DEPOSIT 
//In this function, there is a deposit from user into this dex contract, 
//calling the token contract, to transfer from msg.sender to this dex contract
//with ERC20 tokens, there is a tranderFrom function
//Something in the deposit function that is different from the withdraw function is that we ask
//the ERC20 contract to transferFrom msg.sender to address(this) which is us / the dex contract address
//the user has already stated an amount to transfer, before using this contract, and
//this the IERC20(tokens)... instruction will throw an error if the amount of the transfer is not real.
// the require(tokenMapping[ticker]... instruction checks if the token in the deposit exists in our dex.
//this instruction is used mnultiple time, and can be replaced by a modifier:
//  modifier tokenExist(bytes32 ticker){
    //  require(tokenMapping[ticker].tokenAddress != address(0), "Token does not exist"); REQUIRE + ERROR MESSAGE
    //  _;  }
    // Then we can use the modifier tokenExist(ticker) in funcion headers, before the visibility keyword
    function deposit(uint amount, bytes32 ticker) tokenExist(ticker) external {
        require(tokenMapping[ticker].tokenAddress != address(0));
        IERC20(tokenMapping[ticker].tokenAddress).transferFrom(msg.sender, address(this), amount);
        balances[msg.sender][ticker] = balances[msg.sender][ticker].add(amount);
    }

//withdraw function has interaction with the token contract, 
//to transfer token ownership between users and our contract, 
//we need to interact with the ERC20 token contract
//we use the the IERC20 interface instruction, and the IERC20.sol file, to interact with the token contract.
//in the IERC20 instruction, we need to input the address, using tokenMapping for the ticker, 
// .tokenAddress, 
//and we do a transfer call with .transfer() this is a transfer from us, 
//so that's why we can do the transfer call,
//sent to the msg.sender, from this contract to msg.sender, which is the owner of the tokens 
//The dex just keeps/stores the tokens owned by users, and they can withdraw the tokens that they own
//we send tokens back to the owners, we transfer from us to them, with the amount
//We also need to change the balance of the msg.sender, and the ticker, we need to reduce the amount of this
//we use Safemath for this, to be safe.
// "sub" means "subtract"
//we subtract the amount, with .sub(amount) 
// There was a problem with using .sub, 
//until I imported SafeMath and added instruction: using SafeMath for uint256; 
//If I get rid of SafeMath, I might need to change the .sub instruction.
//Before withdraw, we check that the user has the avaiable amount to withdraw.
//We do this with require that balances[msg.sender][ticker] >= amount.
//If not, we throw an error: "Balance not sufficient".
//We also need to check that the token is a real token in this dex, which we do with require
//tokenMapping for the ticker . tokenAddress is not equal to the zero address, which is address(0).
//We check with the zero address because 
//if this mapping for a particular ticker points to a struct which has not been made,
//the values will be zero, including both the ticker and the address
//When an address is zero or not made/started in the dex, 
//it is a zero address, "0x0000000000000000000000" which is also written "address(0)".
//Frst we check that the token is a real one on the dex, then we check about amount.
    function withdraw(uint amount, bytes32 ticker) external {
        require(tokenMapping[ticker].tokenAddress != address(0));
        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);
    }

}


I might need to see which are the errors.

The code you provided, its lacking of some syntax, have you seen the inheritance part?

The contract is not inheriting the Ownable contract, so the onlyOwner modifier is not visible to it.

Carlos Z

Thanks Carlos. I added the inheritance of the Ownable smart contract to the Wallet smart contract, and it solved the problems. Now I have a challenge with testing the contracts. There are 4 tests. 2 of the tests work, but the other 2 do not work.

Here is the error message:

Compiled successfully using:

  • solc: 0.8.17+commit.8df45f5f.Emscripten.clang

Contract: Dex
:heavy_check_mark: should only be possible for owner to add tokens (962ms)
:heavy_check_mark: should handle wrong withdraw correctly (195ms)
1) should handle correct withdraw correctly
> No events were emitted

2 passing (2s)
1 failing

  1. Contract: Dex
    should handle correct withdraw correctly:
    AssertionError: Failed with Error: Returned error: VM Exception while processing transaction: revert Balance not sufficient – Reason given: Balance not sufficient.
    at passes (node_modules\truffle-assertions\index.js:142:11)
    at Context. (test\wallettest.js:87:5)

Here is the code of the testing software:

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

//In the first section, we call the contract function, 
//with the contract name, Dex, as an argument.
//The second argument is a function which receives an argument of the accounts array, 
//which is sent into this function.
contract("Dex", accounts => {
    //in here, we can make tests 
    //everything within this contract function will be run with 
    //a single set of deployed contracts,
    //and for each contract statement/instruction , 
    //it will deploy our contract again another time
    //and all the tests will be run
    //we make a new test by wtiting "it", and in parenthese and quotes 
    //there is information about it/the test, which should be like a sentece.
    //The second argument is a function without arguments
    it("should only be possible for owner to add tokens", async () => {
      //in here we write our test, and we can add a lot of code from our migration file,
      //for example from 3_token_migration.js, the first 5 lines of instructions
      //in the module.exports function
  
    let dex = await Dex.deployed()
    let link = await Link.deployed()
         //in the lesson, the next line does not have "await"
         //We add the LINK token to the contract
         //It should only be possible for the owner to add tokens
         //so that means that we want to try it with the zero account
         //If this test is correct, the next line of code should work,
         //we need to use normal Mocha statements to show if it is true
         //to do this, we use a library called Truffle Assertions
    await truffleAssert.passes(
        dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[0]})
    )

 
//When we call this as a non-owner, it should fail
//We can call from account 1, which is not the owner, and it should fail
//reverts replaces asserts, and account 1 replaces account 0
       await truffleAssert.reverts(
        dex.addToken(web3.utils.fromUtf8("LINK"), link.address, {from: accounts[1]})
)
 }) 

 //We can make another test
 it("should handle wrong withdraw correctly", async () => {
  //in here we write our test, and we can add a lot of code from our migration file,
  //for example from 3_token_migration.js, the first 5 lines of instructions
  //in the module.exports function

let dex = await Dex.deployed()
let link = await Link.deployed()

     //in the lesson, the next line does not have "await"
     //We add the LINK token to the contract
     //It should only be possible for the owner to add tokens
     //so that means that we want to try it with the zero account
     //If this test is correct, the next line of code should work,
     //we need to use normal Mocha statements to show if it is true
     //to do this, we use a library called Truffle Assertions

     //assert comes with the Truffle library, we can use the normal Mocha assert
     //this compares the 2 arguments
    await truffleAssert.reverts(dex.withdraw(2000, web3.utils.fromUtf8("LINK")))
 })

 //We can make another test
 it("should handle correct withdraw correctly", async () => {
  //in here we write our test, and we can add a lot of code from our migration file,
  //for example from 3_token_migration.js, the first 5 lines of instructions
  //in the module.exports function

let dex = await Dex.deployed()
let link = await Link.deployed()

     //in the lesson, the next line does not have "await"
     //We add the LINK token to the contract
     //It should only be possible for the owner to add tokens
     //so that means that we want to try it with the zero account
     //If this test is correct, the next line of code should work,
     //we need to use normal Mocha statements to show if it is true
     //to do this, we use a library called Truffle Assertions

     //assert comes with the Truffle library, we can use the normal Mocha assert
     //this compares the 2 arguments
    await truffleAssert.passes(dex.withdraw(1, web3.utils.fromUtf8("LINK")))

 })


}) 

In your should handle correct withdraw correctly: unit test, you are trying to withdraw and get an error Balance not sufficient – Reason given: Balance not sufficient., probably beacuse you have not made any deposit to the contract, so there is no balance to withdraw.

Carlos Z

1 Like

I’ve just watched “view and pure” video and I would like to ask, isn’t “view” pretty much works the same as getter in Java or standard OOP language?

if that’s the case, in solidity, is there any “private” variables?
Also can someone give me an example of when “pure” is needed? I kind of get what it does, the way I understand it is, you set a function as a pure such that it only performs the code inside the function, and it doesn’t interact (read or write) with other variables and function inside the contract, but what is the point of having “pure”?

The pure function is used on a function that does not read nor modify the state of the contract.

Mainly used when you have a function that do any kind of calculation and return a result, but this result does not affect nor rely on any of the states of the contract.

You can read more about it here :nerd_face:

https://medium.com/cryptodevopsacademy/solidity-the-pure-getter-and-view-functions-281bff8fde23

Carlos Z

2 Likes

Can you tell me which course you have done for solidity and how it improve your level

Best answer possible. OMG.

10 months later, I can think of a simple solution for my problem. Since the smart contracts must have a front end. There might be a simple way to transform values in kilograms with commas " , " by using react or another js framework on the client’s front end. The user will never know that while he is enter he/she weights 80,5kg, the smart contract will receive a number with no " , " . I ll let you know how this develops.

Hello everyone! I am starting the Smart Contract 101 programming course project and I am a bit confused and lost as to what I should do. Even though I do not exactly know where to start I have written down some code and wanted to see how you guys would fix it or move on from what I have so far? I tried to get down some of the owner functionality but I need to focus more on the signature functionality and again I am a bit lost haha.

//SPDX-License-Identifier: Unlicensed

pragma solidity 0.7.5;

contract MultiSig{

address private owner; // the address of the owner of the wallet that they user inputs. Has to be private as the funds and account cannot be seen by the public

uint8 constant min_signatures = 2; //the minimum amount of singatures required to sing the transaction. In this case it is 2 as in 2 people need to sign the wallet

mapping(address => uint8) private _owners;

constructor ( address _owner){
    owner = _owner;
}

modifier onlyOwner() {
    require(msg.sender == owner);
    _;
}


function getOwner( address _TheOwner) public view returns(address){
    return _TheOwner;
}


function setOwner() public {
    owner = _TheOwner;
}



    struct Transaction{
    uint amount; //the amount of Ether being sent
    address to; //the address of the recipient and where the funds are being sent to
    address from; //address of the sender that is sending the funds
    uint8 signatureCount; //the amount of signatures that are needed to conduct the transaction
}

}

Hi @Ernesto_Guidos

The current error which I see is that you missed including address _TheOwner param on the setOwner function.

Updating it like this should fix the error.

function setOwner(address _TheOwner) public {
    owner = _TheOwner;
}

Let us know if you see any other errors.