• It is a piece of code that lives on the blockchain
  • A self operating computer program that automatically executes whenc ertain conditions are met
  • Smart contracts have an account address, like wallet accounts, they can hold Ether as well, they have their own storage

Smart Contract Lifecycle

  • Develop – Write the contract and compile.
  • Deploy – Once deployed, the smart contract is un-editable. After deploying on any network (e.g. mainnet, testnet), you get an address specific to where contract was deployed.
  • Invoke – Use the address created after the smart contract is deployed to invoke contract to call functions on it.
  • Destroy – Once a contract has finished its purpose, it is important to destroy it so no one can call functions on it anymore. Remember that smart contracts cannot be edited once deployed. Also, often smart contracts are tied to funds, so keeping it around if it is no longer used puts these funds at risk.

-> Presentation of remix, already done multiple times

Remix offers these 3 environment options when deploying a contract:

  • Javascript VM (an in-browser ethereum network, used for testing)
  • Injected web3 (used in conjunction with Metamask to deploy to public networks)
  • Web3 provider (used to connect to a locally running node)

Memory management in solidity

  • Memory – Used for temporary storage of data, information is lost after function execution
  • Storage – Used for variables in storage, stay with the contract, and the data persists
  • CallData – Like a stack. Used for EVM call execution

Elementary Data types in Solidity

  • uint – Use for unsigned integers of various sizes ( 0 – 1 – 2 – 3…)
  • int – Use for signed integers of various sizes (-1, -2, 10 , 5..)
  • bool – The possible values are constants true and false
  • address – Represents address of externally owned account or contract account. Holds a 20 byte hex string of an Ethereum address. It is a Value Type.
pragma solidity ^0.4.25;

contract BasicDataTypesContract {
    uint8 a = 255; //1 bytes unsigned integer
    address public owner; //Address types variable, called owner
    bool public flag = true;
    uint ownerInitialBalance; //uint type variable
    //Function takes in an address, and rtetunrs the balance of this address
    function test (address addr) public returns (uint) {
    
    owner = addr;
    ownerInitialBalance = owner.balance;
    
    if (1 > 0) { // this will work because expression evaluates to bool 
    //do something
    }
    return ownerInitialBalance;
    }



}

Type conversion in solidity

Implicit Conversions

“If an operator is applied to different types, the compiler tries to implicitly convert one of the operands to the type of the other (the same is true for assignments). In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost: uint8 is convertible to uint16 and int128 to int256, but int8 is not convertible to uint256 (because uint256 cannot hold e.g. -1).” (Source: Solidity Documentation v. 0.5.3)

Explicit Conversions

“If the compiler does not allow implicit conversion but you know what you are doing, explicit type conversion is sometimes possible. Note that this may give you some unexpected behavior and allows you to bypass some security features of the compiler, so be sure to test that the result is what you want!” (Source: Solidity Documentation v. 0.5.3)

pragma solidity >=0.4.24;

contract TypesConversion {
    uint8 a = 255;              // a is 1 bytes unsigned integer
    uint ownerInitialBalance;   // uint256

    function conversion ( ) public {
        int  b;        // b is 32 bytes signed integer...256 bits
        b = a;         // Compilation successful, since a 8bytes type can easily fit in a 32bytes type
        // a = b;      // Fails compilation as the maximum value of int256 cannot be accomodated in uint8
        a = uint8(b);  // Explicit type coversion...converting a 32bytes to 1bytes
    }
}

Complex Data Types in Solidity

  • Arrays
pragma solidity >=0.4.24;

contract ArraysContract {

    // Elementary type array in storage
    // Static Array with size 3 of type int
    int[3]     staticIntArray = [1,2];    // Last element set to 0
    // Dynamic array of type int8
    int8[]     dynamicIntArray;
    // Dynamic array of type bool
    bool[]     dynamicBoolArray;

    function testArray() public {
        // Allocate memory for 8 elements to the dynamic bool storage array
        dynamicBoolArray = new bool[](8);
        // Allocate memory and initialize elements in the int array
        // Explicit conversion is needed from uint8 to int8
        dynamicIntArray = [int8(1),2,3];
        // This will work fine, since we are inside a function
        uint8[] memory memoryArray; // A Dynamic memory array
        // Allocation with assignment not allowed for dynamic memory arrays
        // memoryArray = [1,2,3];
        uint8[] memory dynamicMemoryArray;  // A Dynamic memory array
        // Assignment of dynamic NOT allowed
        //dynamicMemoryArray = [uint8(1),2];
        memoryArray = new uint8[](8);
        // push() not allowed for memory array
        // memoryArray.push(1);
        // memoryArray.length=6;

        /** Examples of array initialization with assignment below */

        // Static storage array assignment is OK
        // Compile time size check is carried out
        // so assignment to [1,2,3,4] will fail below
        staticIntArray = [1,2,3];


        // Static memory array
        uint[2] memory  staticMemoryArray;

        // This is  allowed - make sure the type is matching
        // staticMemoryArray is of type uint
        staticMemoryArray = [uint(1),2];

        // This is allowed
        staticMemoryArray[0] = 0;
        staticMemoryArray[1] = 1;

        dynamicMemoryArray[0] = 1;
        dynamicMemoryArray[1] = 2;
    }
}

We cannot push on memory arrays in Solidity.

Assignment of dynamic memory arrays is NOT allowed in Solidity.

pragma solidity >=0.4.24;

contract StringBytes {
    // Static byte arrays, Both declarations will create array with 3 byte elements
    byte[3] fixedByteArray; // The type byte[] is an array of bytes, but due to padding rules, 
                            //it wastes 31 bytes of space for each element (except in storage). 
                            //It is better to use the bytes type instead.
    bytes3 bytes3Array;
    // Dynamic bytes arrays
    byte[] dynamicByteArray;
    bytes bytesArray;
    // String variable
    string    string1 = "testing";

    // Converts the bytes type to string type
    function conversionTest() public pure returns(string memory) {
        bytes   memory string2 = "Udacity"; // dynamic memory bytes type
        string  memory converted = string(string2);
        return converted;
    }

    // Retrieves the element at specified index
    // Cannot do with strings, hence converting to bytes
    function  getElementAt(uint index) public view returns(byte) {
        // Convert string to bytes
        bytes  memory bytesData = bytes(string1);
        // Get the element at the specified index
        byte   element = bytesData[index];
        return element;
    }

    function  testing() internal pure {
        // uint8 need to be explicitly converted to byte type
        // Converting to byte type, since fixedByteArray
        // is a byte type array
        // Assignment NOT allowed as bytes3 Array is a static Array
        // is readonly
        // bytes3Array[0] = 1;
        // Memory dynamic bytes Array
        bytes memory memoryBytes; // dynamic memory array
        memoryBytes = new bytes(20); //allocating memory
        memoryBytes[0] = "a";
        // Push will give compiler error as push() allowed for storage only
        //memoryBytes.push('c');
    }

    function stringExamples() public pure returns(string memory) {
        string memory string3 = "abcde";  // string array in memory
        return string3;
    }
}
  • Structs

Structs are another user-defined type you will be using often. Struct types can be used inside mappings and arrays and they can itself contain mappings and arrays. Let’s see some examples of this type.

pragma solidity >=0.4.24;

contract StructsContract {

    // Family
    struct Family {
        bytes32 lastName;
        uint8 houseNo;
        uint16 age;
    }

    // Creating an array of type family..which is a struct
    Family[] myFamily;

    // Getting a lastName, and returning complete
    // family details
    // We cannot compare 2 strings in solidity...
    function getName(bytes32 name) public view returns (bytes32, uint8, uint16) {
        // Search the array
        for(uint8 i = 0; i < myFamily.length; i++){
            if(name == myFamily[i].lastName) {
                return (myFamily[i].lastName,uint8(myFamily[i].houseNo), myFamily[i].age);
            }
        }
    }

    // Structs Cannot be passed as argument so we are passing all elements/attributes of struct as args
    function addName(bytes32 _lastName, uint8 _value, uint16 _age) public returns (bool) {
        // Declare the struct variable in memory...
        Family memory newFamily;
        //  use the . notation to access members of a struct
        newFamily.lastName = _lastName;
        newFamily.houseNo = _value;
        newFamily.age = _age;
        // Push the newFamily struct...into our myFamily array
        myFamily.push(newFamily);
        return true;
    }
}
  • Mapping : Key -> Value table (not iterable, length cannot be retrieved as well)
pragma solidity >=0.4.24;

contract MappingsContract {
    // Creates in Storage
    mapping(string => string) relations;

    // Add a relation
    function addRelation(string memory name, string memory relation) public {
        // Store the relation
        relations[name] = relation;
    }

    // Returns a Relation
    function getRelation(string memory name) public view returns (string memory){
        return relations[name];
    }

    // Remove the key value pair from the mapping
    function removeRelation(string memory name) public {
        delete(relations[name]);
    }
}
  • Strings
  • Bytes
  • Enum

Enums are a way to create a user-defined type in Solidity and you will use them quite often in upcoming smart contracts in your projects, so let’s explore some of their key properties.Enum values can be explicitly converted to/from uint value.

pragma solidity >=0.4.24;

contract EnumsContract {

    // Create an Enumeration
    enum names {Joe, Brandy, Rachna, Jessica}

    // get the value at specified index
    function getNames(uint8 arg) public pure returns (string memory){
        if(arg == uint8(names.Joe)) return "Joe";
        if(arg == uint8(names.Brandy)) return "Brandy";
        if(arg == uint8(names.Rachna)) return "Rachna";
        if(arg == uint8(names.Jessica)) return "Jessica";
    }
}

Solidity Global Variables and Ether Units

Solidity offers several global variables that will be vital as you continue developing smart contracts that retrieve and post information in the blockchain.

  • Block Global Variable (.number, .coinbase, .timestamp, .difficulty, .gaslimit, .blockhash)
  • Tx Global Variable (.gasprice, .origin)
  • Msg Global Variable (.data, .sender, .sig, .value)
  • Ether Unit conversions using Global prefixes (wei, finney..)
  • Time Unit conversions using Global prefixes (now)
pragma solidity >=0.4.24;

contract GlobalVariables {

    string  public lastCaller = "not-set";

    // Demonstrates the use of the ether subdenominations
    function etherUnitsTest() public pure returns(bool) {
        // True
        bool value = (1 ether == 1000 finney);
        return value;
    }

    // Demonstrates the use of the time units
    function  timeUnits() public view returns (uint) {
        uint timeNow = now; //storing current time using now
        //returns block time in seconds since 1970
        if (timeNow == 1000 days) { // converting 1000 literal to days, using the suffix days
            return timeNow;
        }
    }

    // Demonstrates the use of block object
    function  getBlockInformation() public view returns (uint number, bytes32 hash, address coinbase, uint difficulty) {
        number = block.number; // Previous block
        hash = blockhash(number - 1); // -1 because excluding current...same as block.blockhash()
        // Current block
        coinbase = block.coinbase;
        difficulty = block.difficulty;
    }

    // Demonstrates the use of the msg object
    function getMsgInformation() public view returns (bytes memory data, bytes4 sig, address sender) {
        data = msg.data;
        sig = msg.sig;
        sender = msg.sender;
    }

}

Functions in Solidity

Function syntax :

Function types :

Additionnal visibility :

Internal : Only accessed internally, within the contract or contracts derived from it

External : part of the contract interface, can be called from other contracts and functions, cannot be called internally though

by default a function is public

if you create a public storage variable, an automatic GETTER function is created for you:

string public ownerName;
-> getter function is created and you can call it using ownerName()

If they are multiple return values, they will be returned in an array

Fallback Functions

Fallback functions provide a safeguard. They are automatically triggered if the function signature does not match any of the function signatures in the smart contract. A contract can have exactly one fallback function. These fallback functions are unnamed.

Function Modifiers

“Modifiers can be used to easily change the behavior of functions. For example, they can automatically check a condition prior to executing the function. Modifiers are inheritable properties of contracts and may be overridden by derived contracts.” 

Constructor functions :

Is called automatically when the contract is instantiated. A good place to initialize storage variables

Function modifiers : Modifiers can be used to easily change the behaviour of functions. For example, they can automatically check a condition prior to executing the function. Modifiers are inheritable properties of contracts and may be overridden by derived contracts.

Error Handling in Solidity

  • revert ( )
  • require ( )
  • assert ( )

the developer of the contract can decide how such errors are communicated to the caller of the contract :

  • Return a value to communicate the error
  • Emit events
  • Throw an exception

No try – catch mechanism.

Once an exception is thrown :

  • It is just passed over to the caller
  • All changes to the contract are rolled back, so no contract / state changes happen
  • No ethers are sent from a function call
  • All sent Ethers are sent back to the caller
  • Gas is used (the unused gas is sent back though unless throw)
  • The transaction still gets recorded on the blockchain

Using require : all used gas is sent back, used for throwing exceptions to the caller

Using assert : No gas is sent back, used mostly for internal testing

Require : if the expression evaluates to be true, then the code inside the function is executed normally, a great way to make sure that some requirement condition is met, before some code is executed : require ( msg.send == owner), require (msg.value >= 0.1ether)

pragma solidity >=0.4.24;

contract ExceptionsContract {

    // Public variable lastCaller, with a value of none
    string  public lastCaller = "none";

    // Function revertBehavior that takes in a name as an argument,
    //and sets the lastCaller variable to this argument received
    function  revertBehavior(string memory name) public returns (bool) {
        lastCaller = name;

        // Check if length of the string is zero
        // If the argument received has zero length, it throws an exception...
        // Once an exception is thrown, the variable lastCaller rolls back to its initial value
        if (bytes(name).length == 0) {
            revert("The length of the string is zero");
        }
        // This is the same thing...just using require to check the length of the input string.
        // The code will only be exceuted if the length is greater than 0
        // The above lines of code may be replaced with this
        require(bytes(name).length > 0, "The length of the string is zero");

        return true;
    }

}

// So, what is going on is that in the function revertBehavior
// lastCaller is being changed to the input argument
// and then an exception is thrown, and the lastCaller reverts back
// to its orginial value, thus nullifying the effect

Inheritance in Solidity

solidity supports multiple inheritance.

pragma solidity >=0.4.24;

contract MainContract {

    uint internal value;

    constructor (uint amount) public {
        value = amount;
    }

    function deposit (uint amount) public {
        value += amount;
    }

    function withdraw (uint amount) public {
        value -= amount;
    }
}
pragma solidity >=0.4.24;

import "./MainContract.sol";

// We have an ContractInterface, that has a function
// sendmoney...but there is no function body
interface ContractInterface {
    function sendMoney (uint amount, address _address) external returns (bool);
}

// This is a BaseContract, that has its constructor, and deposit and withdraw functions...
contract BaseContract {

    uint public value;

    // Anytime base contract has a constructor, we will need to initialize this using
    // the derived contracts constructor function

    constructor (uint amount) public {
        value = amount;
    }

    function deposit (uint amount) public {
        value += amount;
    }

    function withdraw (uint amount) public {
        value -= amount;
    }
}

// This shows multiple inheritance

// This will give an error...since baseContract has a constructor that we need to initialize
// contract myContract is baseContract, interfaceContract {

contract InheritanceContract is BaseContract(100), ContractInterface, MainContract(100) {

    string public contractName;

    constructor (string memory _n) public {
        contractName = _n;
    }
    function getValue () public view returns (uint) {
        return value;
    }

    // Function that allows you to convert an address into a payable address
    function _make_payable(address x) internal pure returns (address payable) {
        return address(uint160(x));
    }

    // This function has to be implemented, since it is unimplemented in the interfaceContract
    function sendMoney (uint amount, address _address) public returns (bool) {
        _make_payable(_address).transfer(amount);
    }
}

Events in solidity

Solidity events are similar to Javascript events that can be set to trigger when something (i.e. an event) happens. This is a very powerful feature that will play a huge part when we create DApps (decentralized application). DApps, websites, or anything connected to Ethereum JSON-RPC API, can listen to these events and act accordingly. For example, we could create a banking DApp that listens for the user to click the deposit button. Once the button is clicked, the deposit event fires which will deposit the appropriate value into the assigned wallet address:

event Deposit(address from, uint value);
pragma solidity >=0.4.24;

contract EventsContract {
    // Represents the time when the bidding will end
    uint biddingEnds = now + 5 days;

    struct HighBidder {
        address bidder;
        string bidderName;
        uint bid;
    }

    // Variable of type HighBidder
    HighBidder public highBidder;

    // Events emitted by contract
    // Whenever a high bid is received
    event NewHighBid(address indexed who, string name, uint howmuch);

    // High bid preceded by this event
    event BidFailed(address indexed who, string name, uint howmuch);

    // Ensures that bid can be received i.e, auction not ended
    modifier timed {
        if(now < biddingEnds){
            _;
        } else {
            /**throw an exception */
            revert("Throw an exception");
        }
    }

    constructor() public {
        // Starts the bidding at 1 ether
        highBidder.bid = 1 ether;
    }

    
    // Payable since ether should be coming along
    // Timed, we need to end this bidding in 5 days
    function bid(string memory bidderName) public payable timed {
        if(msg.value > highBidder.bid) {
            highBidder.bidder = msg.sender;
            highBidder.bidderName = bidderName;
            highBidder.bid = msg.value;
            // Received a high bid - emit event
            emit NewHighBid(msg.sender, bidderName, msg.value);
        } else {
            // Received bid less than high bid emit event
            emit BidFailed(msg.sender, bidderName, msg.value);
            // throwing exception would return the ethers
            revert("Throw an exception");
        }
    }
}

 Events generated by the Solidity contract are stored in all the ethereum nodes and events are part of the ABI.

Tokens

Token contracts are just smart contracts that contain a mapping (address => balance). Balances can represent anything : a monetary value, a license, a physical object..

  • Smart contracts are used to create Tokens. These smart contracts also facilitate transactions of tokens and record balances of tokens in an account.
  • After a token has been created, it can be traded, spent, or given to someone else.

ERC = Ethereum Request for Comment : technical documents used by smart contract developers. Defines a set of rules to implement tokens on ethereum ecosystem. Makes standardization easy.

Every development project improves overtime and Ethereum is no exception. Similar to Bitcoin Improvement Proposals (BIPs), Ethereum has Ethereum Improvement Proposals (EIPs). Ethereum Improvement Proposals (EIPs) describe standards for the Ethereum platform, including core protocol specifications, client APIs, and contract standards.

Included under EIPs are ERCs, Etherum Requests for Comments (a full list can be found here). Some common ERCs resulted interfaces developers can use to create token. For example,

  • ERC-20 – A standard interface for fungible tokens. Most common standard.
  • ERC-721 – A standard interface for non-fungible tokens, also known as deeds.
Fungible Token

A Fungible Token is a token, where all tokens are exactly the same.

  • They have the same value.
  • For example, any 1 dollar bill is equal to another 1 dollar bill.
  • All the widely used ERC-20 tokens are fungible tokens.
Non-Fungible Token

A Non-Fungible Token is a special type of cryptographic token which represents something unique.

  • Non-Fungible tokens are not interchangeable, because they all have a different value.
  • For example, if we represented real estate parcels in tokens, not all parcels would be equal in value, hence all these tokens will also not be equal in value.
  • ERC-721 is a non-fungible token standard.
  • The famous CryptoKitties Token is an ERC-721 non-fungible token.

ERC-20 Walk-through

ERC-20 Token Interface

The ERC-20 Token interface has 3 optional fields, 2 events, and 6 mandatory functions:

  • Optional Fields
    • Name of the token
    • Symbol of the token (ticker)
    • Decimals values in tokens (how divisible a token can be, from 0 to 18, not divisible = 0, very divisible = 18)
  • Events
    • transfer event : each time a transfer happens this event is triggered
    • approve event : each time an addresss approves another address to spend a specific amount of tokens
  • Mandatory Functions
    • totalSupply ( ) function : returns the total supply of the ERC-20 token
    • balanceOf ( ) function : returns the number of tokens held by a specific address
    • transfer ( ) function : send a given amount of tokens to an address -> no checks made on the recipient, make sure that the receiver is valid
    • transferFrom ( ) function : allows a smart contract to automate a transfer process and book a transfer on behalf of an address, a bit like delegate the payment of a bill. You approve a contract to send your tokens.
    • approve ( ) function : owner give another address (smart contract) approval to transfer a certain amount of token
    • allowance ( ) function : provides the number of remaining tokens allowed to be spent for an address by another address
pragma solidity >=0.4.24;

contract ERC20Interface {

    string public constant name = "Udacity Token";
    string public constant symbol = "UDC";
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);

    function totalSupply() public constant returns (uint);
    function balanceOf(address tokenOwner) public constant returns (uint balance);
    function transfer(address to, uint tokens) public returns (bool success);
    function transferFrom(address from, address to, uint tokens) public returns (bool success);
    function approve(address spender, uint tokens) public returns (bool success);
    function allowance(address tokenOwner, address spender) public constant returns (uint remaining);

}

And now a first version of a contract to be compiled and then deployed on remix using the javascript VM

pragma solidity >=0.4.24;

contract myToken {

    string public constant name = "Udacity Token";
    string public constant symbol = "UDC";
    uint8 public constant decimals = 18;  // 18 is the most common number of decimal places
    uint _totalSupply;

    // Balances for each account stored using a mapping
    mapping(address => uint256) balances;

    // Owner of the account approves the allowance of another account
    // Create an allowance mapping
    // The first key is the owner of the tokens
    // In the 2nd mapping, its says who can spend on your behalf, and how many
    // So, we are creating a mapping, where the kep is an address,
    // The value is further a mapping of address to amount
    mapping(address => mapping (address => uint256)) allowance;

    event Transfer(address indexed from, address indexed to, uint tokens);
    event Approval(address indexed tokenOwner, address indexed spender, uint tokens);


    // Called automatically when contract is initiated
    // Sets to total initial _totalSupply, as per the input argument
    // Also gives the initial supply to msg.sender...who creates the contract
    constructor(uint amount) public {
        _totalSupply = amount;
         balances[msg.sender] = amount;
    }

    // Returns the total supply of tokens
    function totalSupply() public view returns (uint256) {
        return _totalSupply;
    }

    // Get the token balance for account `tokenOwner`
    // Anyone can query and find the balance of an address
    function balanceOf(address tokenOwner) public view returns (uint balance) {
        return balances[tokenOwner];
    }

    // Transfer the balance from owner's account to another account
    // Decreases the balance of "from" account
    // Increases the balance of "to" account
    // Emits Transfer event
    function transfer(address to, uint tokens) public returns (bool success) {
        if(tokens < 1){
            revert("Not enough Ether provided.");
        }
        require(tokens <= balances[msg.sender]);
        balances[msg.sender] = balances[msg.sender] - tokens;
        balances[to] = balances[to] + tokens;
        emit Transfer(msg.sender, to, tokens);
        return true;
    }

    // Send amount of tokens from address `from` to address `to`
    // The transferFrom method is used to allow contracts to spend
    // tokens on your behalf
    // Decreases the balance of "from" account
    // Decreases the allowance of "msg.sender"
    // Increases the balance of "to" account
    // Emits Transfer event
    function transferFrom(address from, address to, uint tokens) public returns (bool success) {
        balances[from] = balances[from] - tokens;
        allowance[from][msg.sender] = allowance[from][msg.sender] - tokens;
        balances[to] = balances[to] + tokens;
        emit Transfer(from, to, tokens);
        return true;
    }

    // Approves the `spender` to withdraw from your account, multiple times, up to the `tokens` amount.
    // So the msg.sender is approving the spender to spend these many tokens
    // from msg.sender's account
    // Setting up allowance mapping accordingly
    // Emits approval event
    function approve(address spender, uint tokens) public returns (bool success) {
        allowance[msg.sender][spender] = tokens;
        emit Approval(msg.sender, spender, tokens);
        return true;
    }
}

it works :

With error handling :

function transfer(address to, uint tokens) public returns (bool success) { require(tokens <= balances[msg.sender]); balances[msg.sender] = balances[msg.sender] - tokens; balances[to] = balances[to] + tokens; emit Transfer(msg.sender, to, tokens); return true; }

Creating ERC-20 Using OpenZeppelin

We are going to do so via truffle, so :

1) Verify you have the Truffle (v5.0.2) latest installed if not use the command 

npm install -g truffle

2)  Create a directory

mkdir SampleToken
cd SampleToken

4) Run the command to initialize a truffle project.

truffle init

5) Run command used to set up the provider to connect to the Infura Node

npm install --save truffle-hdwallet-provider

6) Run 

npm install openzeppelin-solidity

7) Go into your contracts folder, and create your token smart contract file SampleToken.sol

pragma solidity >=0.4.24;

import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/ERC20.sol";
import "../node_modules/openzeppelin-solidity/contracts/token/ERC20/ERC20Detailed.sol";

contract SampleToken is ERC20Detailed, ERC20 {

    constructor(string memory _name, string memory _symbol, uint8 _decimals, uint _initialSupply) 
    ERC20Detailed(_name, _symbol, _decimals) public {
        require(_initialSupply > 0, "INITIAL_SUPPLY has to be greater than 0");
        _mint(msg.sender, _initialSupply);
    }
}

You Actually import the ERC20.sol token out of the openzeppelin package you have downloaded, it can be found here :

Had to install a previous version to make it work with the code in the course :

  • Remove the node_modules folder (or simply delete node_modules folder in your project folder):

rm -rf node_modules

  • Install a compatible version for this exercise

npm install openzeppelin-solidity@2.4

Then we need to fill the codefor the truffle-config.js file set up:

Of course you have to change the infuraKey to the network you want to use and you need to put the mnemonic phrase of your metamask if you want to deploy. I also put version 0.5.5 for the compiler..

const HDWalletProvider = require('truffle-hdwallet-provider');
const infuraKey = "https://rinkeby.infura.io/v3/XXXXXXXXXX";
//
// const fs = require('fs');
const mnemonic = "<METAMASK SEED>";

module.exports = {

  networks: {
    development: {
      host: "127.0.0.1",     // Localhost (default: none)
      port: 9545,            // Standard Ethereum port (default: none)
      network_id: "*",       // Any network (default: none)
     },
    // Useful for deploying to a public network.
    // NB: It's important to wrap the provider as a function.
    rinkeby: {
      provider: () => new HDWalletProvider(mnemonic, infuraKey),
        network_id: 4,       // rinkeby's id
        gas: 4500000,        // rinkeby has a lower block limit than mainnet
        gasPrice: 10000000000
    },
  },

  // Set default mocha options here, use special reporters etc.
  mocha: {
    // timeout: 100000
  },

  // Configure your compilers
  compilers: {
    solc: {
      version: "0.5.5",    // 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"
      // }
    }
  }
}

And then you need to put in a new file called 2_initial_migration.js file in the migrations folder :

var SampleToken = artifacts.require("SampleToken");

module.exports = function(deployer) {
  deployer.deploy(SampleToken, "UdacityExampleToken", "UET", 18, 1000);
};

Finally, let’s deploy it locally :

1) Open a Terminal window, and make sure you are inside your project directory

2) Run the command `truffle develop` (to run a local ethereum network)

3) Use the command `compile` (to compile your solidity contract files)

4) Use the command `migrate --reset` (to deploy your contract to the locally running ethereum network)

Deploy Token Contract on Rinkeby

Now we are going to deploy to the smart contract to the Rinkeby testnet. Get some fake ethers from a faucet. I used : https://faucet.rinkeby.io/

You have to post on a social network, I did it on twitter, and then paster the URL of your post in the field on the faucet and done !

Now that we have enough ethers our Rinkeby account, let’s deploy our token contract to the Rinkeby network! Once deployed, we can use the contract to find our contract on Etherscan.

You have to expose your Mnemonic phrase in the code to be able to deploy the smart contract. Do not put your real mnemonic from your real main Metamask account, rather create a new metamask account from scratch in a different browser and use it for all your tests and all your experimentations. I have my main metamask in one broswer and my test metamask in my second browser.

Some interesting details found on stackexchange :

Seed words are used as a seed to the master key of a BIP39-compliant deterministic wallet.

BIP39 added the support for mnemonics to HD wallets, with the heirarchical deterministic wallet standard itself being BIP44 (previously BIP32).

BIP44 allows you to generate private keys – which then equate to accounts – in a tree structure, with parent keys being able to generate child keys, down to an inifite depth.

Your set of words therefore seed a set of accounts deterministically. Accounts in your metamask extension in one browswer are members of this set, and doesn’t have their own mnemonic. They share the same mnemonic. It isn’t only for the first account, it’s for all of them. If you now click “Create Account” in Metamask, it’ll give you the exact same second account as you had on your first machine. (i.e. It’s deterministic.).

As such you have to use a different broswer if you want to generate a new seed phrase for a fake test metamask.

Alright, let’s deploy on rinkeby now :

truffle migrate --reset --network rinkeby

And if everything is right config wise :

And it is on rinkeby etherscan : https://rinkeby.etherscan.io/address/0x64F9034D81Ff2409ACbE9400df107314Ffc8AFFE

And then add it to the metamask, I obviously did a wrong config regarding the initial supply :

Indeed In my parameters I specified 1000 supply with 18 decimals.. not great, but it works !

var SampleToken = artifacts.require("SampleToken");

module.exports = function(deployer) {
  deployer.deploy(SampleToken, "UdacityExampleToken", "UET", 18, 1000);
};

And that’s the end of this section !


Brax

Dude in his 30s starting his digital notepad