Solidity Basics

Thank you for the explanation. It was very good. :grinning:

1 Like

That is a very good explanation. Thanks!

@thecil I tried the contracts with the bank and government, and they did not work. In fact, the bank could not even send transactions. I copied the code from what Filip put on Github, and it still did not work. If even the code given from the teacher as a solution does not work, that can be a difficult situation.

//SPDX-License-Identifier: MIT

pragma solidity 0.7.5;

import "./Ownable.sol";

contract Bank is Ownable {

   

    mapping(address => uint) balance;

   

    event depositDone(uint amount, address indexed depositedTo);

   

    function deposit() public payable returns (uint)  {

        balance[msg.sender] += msg.value;

        emit depositDone(msg.value, msg.sender);

        return balance[msg.sender];

    }

   

    function withdraw(uint amount) public onlyOwner returns (uint){

        require(balance[msg.sender] >= amount);

        msg.sender.transfer(amount);

        return balance[msg.sender];

    }

   

    function getBalance() public view returns (uint){

        return balance[msg.sender];

    }

   

    function transfer(address recipient, uint amount) public {

        require(balance[msg.sender] >= amount, "Balance not sufficient");

        require(msg.sender != recipient, "Don't transfer money to yourself");

       

        uint previousSenderBalance = balance[msg.sender];

       

        _transfer(msg.sender, recipient, amount);

               

        assert(balance[msg.sender] == previousSenderBalance - amount);

    }

   

    function _transfer(address from, address to, uint amount) private {

        balance[from] -= amount;

        balance[to] += amount;

    }

   

}
type or paste code here
```![LIKE THE TEACHER'S CODE, BUT I GOT AN ERROR 2 SEPTEMBER 2022|690x332](upload://opMrhoEfXuR1EyT8r5ebmFl9wiz.png)
1 Like

hey @ARK, your contract compiles fine for me. Im wondering could it be your Ownable/sol contract. this is the only thing i can verify. can you share your Ownable.sol contract. but i can confirm the code above its fine and works. Are you sure also that the compiler vsrsion your using in Remix matches the version your using for your codtract?

Remix sometimes adjusts the version of Solidity, in response to the contract. I seems to match or approximately match the contract version. I did notice that the teacher’s Ownable contract is actually called Owner. I can try to to change the contract name, and see if that solves the problem.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Owner {

    address private owner;

    // event for EVM logging
    event OwnerSet(address indexed oldOwner, address indexed newOwner);

    // modifier to check if caller is owner
    modifier isOwner() {
        // If the first argument of 'require' evaluates to 'false', execution terminates and all
        // changes to the state and to Ether balances are reverted.
        // This used to consume all gas in old EVM versions, but not anymore.
        // It is often a good idea to use 'require' to check if functions are called correctly.
        // As a second argument, you can also provide an explanation about what went wrong.
        require(msg.sender == owner, "Caller is not owner");
        _;
    }

    /**
     * @dev Set contract deployer as owner
     */
    constructor() {
        console.log("Owner contract deployed by:", msg.sender);
        owner = msg.sender; // 'msg.sender' is sender of current call, contract deployer for a constructor
        emit OwnerSet(address(0), owner);
    }

    /**
     * @dev Change owner
     * @param newOwner address of new owner
     */
    function changeOwner(address newOwner) public isOwner {
        emit OwnerSet(owner, newOwner);
        owner = newOwner;
    }

    /**
     * @dev Return owner address 
     * @return address of owner
     */
    function getOwner() external view returns (address) {
        return owner;
    }

@mcgrane5 I solved the problem with the compiler and the interface (to the function in the Government contract). I needed to add and deploy the Government contract to Remix! But now the compiler is stopping the Bank contract about something else!

(upload://CGr7u04rMQ5Te0xMpMRC25JjMx.png)

Hello Filip,

Is it normal that in the Remix environment that I don’t see Javascript VM option. However, my first Solidity Hello World went through?

Thank you.

mcgrane5 Also, the compiler stops things when I add the interface from the Government contract!

:man_facepalming:

I think you are missing a } at the end of your contract to close the contract body.

If you are still using this contract you shared:

Carlos Z

1 Like

OK. It is not the last } of the contract. Maybe I just didn’t copy fully, when I sent it to the forum. Today, I tried adding }, and it did not fix the problem. I think that there was an error because of a bad connectoin with the inherited contract, Owner.sol. I think that there is maybe a problem because the inherited contract is named Owner or Ownable in various versions or in various lessons. Anyway I changed the name of the inherited contract, or something similar, and then got rid of the function with onlyOwner (a modifier, I believe), and I was finally able to deploy the contract. There seems to be an issue with the inherited contract.

1 Like

@thecil @mcgrane5 @

Hi,

In Remix, we compiled smart contracts, then we deployed them.

In Truffle, we compile smart contracts, then we migrate them.

“Migrate” normally means “to move or copy data to a new storage location, or with a new format.”

In Truffle, does the word “migrate” mean “deploy”?

migrate keyword means to execute all task related to deployment of contracts that have a migration file.

So you can think of migrate command has a deployment for contracts.

Carlos Z

1 Like

Thank you Carlos! I appreciate your answer.

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