Deploy a first smart contract. The base

For rapid prototyping use Remix and select “Javascript VM”.


Simplest commented Contract example

pragma solidity ^0.4.24; // Specify compiler version

contract ExampleOne { // Contract definition
  uint256 myVar; // storage varible. Can only store positive integer with a max value of 256 bits
  
  function setMyVar(uint256 _myVar) public { // Public method than can be called externally and a parameter has to be passed
    myVar = _myVar;
  }

}

Compile, deploy and interact with the Smart Contract still via Remix.


Second commented Contract example

pragma solidity ^0.4.24; // Specify compiler version

contract MyContract  { // Contract definition
  
  uint256 public myVar; // storage varible. Can only store positive integer with a max value of 256 bits. The public keywork automatically generates the get Method.
  
  constructor(uint256 _myVar) public { // Cannot be pure or view. As a normal constructor, it is automatically called. Not prefixed by function keywork. Called during contract deployment.
    myVar = _myVar; // Used to pass a variable during the deployment
  }

  function setMyVar(uint256 _myVar) public { // Public method than can be called externally and a parameter has to be passed
    myVar = _myVar;
  }

  // For information
  //function getMyVar() public pure { // Will not interact with other part of the smart contract excepted other pure methods. An example could be a method that multiples 2 numbers.
  //  return myVar;
  //}

  // Optional
  function getMyVar() view public returns(uint256) { // It is a pure reading funciton (Free on Ethereum). It is a "call" not a "transact"
    return myVar;
  }

  function() public { // Anonymous function. Used when we interact with a Smart Contract but we do not specify any function. Often used to transfer Eth. This anonymous function can also be explicitely called. It is a fallback function. We should keen it as simple as possible (for gas).
    myVar = 2;
  }

}

Other variable types

pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract  { // Contract definition

  uint8  myUint8;
  int8 myInt8;

  uint myUint256; // = uint256 

  function divideInteger() public pure returns(uint8) { // The remainer of 5/2 (0.5) will be discard. The return type uint8 rounds the return value. 
    uint five = 5;
    uint two = 2;
    return five/two;
  }

  function wrapAround() public pure returns(uint8) { // The remainer of 5/2 (0.5) will be discard. The return type uint8 rounds the return value. 
    uint8 zero = 0;
    zero--;
    return zero; //The result won't be -1 here since the return type is uint8. The value will be 255.
  }

  function getBalance() public view returns(uint) { // Returns the balance in Wei.
    // 1 Ether 
    //  = 10^18 Wei
    //  = 10^15 KWei
    //  = 10^12 MWei
    //  = 10^9 GWei
    //  = 10^6 Szabo
    //  = 10^3 Finney
    return address(this).balance;
  }

  function() public payable { //Payable is mandatory if we want to pass a Ether value to the smart contract after deployment
    uint balance = msg.value;
  }

  function withdrawEverything() public { //Send back money received in Smart Contract
    msg.sender.transfer(getBalance());
  }
}

Modifiers

pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract  { // Contract definition
  uint public myVarOne;
  uint public myVarTwo;
  bool writable;
  
  modifier mustBeWritable() { // Modifiers can be used to avoid the require() in functions like below
    require(writable);
    _;
  }

  function setWritable(bool _writeable) {
    writeable = _writeable;
  }

  //function updateMyVarOne(uint _myVar) public {
  //  require(writable);
  //  myVarOne = _myVar;
  //}

  //function updateMyVarTwo(uint _myVar) public {
  //  require(writable);
  //  myVarTwo = _myVar;
  //}
  
  function updateMyVarOne(uint _myVar) public mustBeWritable {
    myVarOne = _myVar;
  }

  function updateMyVarTwo(uint _myVar) public mustBeWritable {
    myVarTwo = _myVar;
  }
   
}

Mappings

pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract { // Contract definition
  mapping(uint => bool) public myMapping; // key value pairs. All values are false by default. (true for all 256 values)

  function writeSomethingInTheMapping(uint _myInt) public {
    myMapping[_myInt] = true
  }

}

Structs

With structs we can define our own data types.

pragma solidity ^0.4.24; // Specify compiler version 

contract MyContract { // Contract definition
  
  struct MyStruct {
    uint timestamp;
    uint counter;
  }

  mapping(address => MyStruct) public myMapping;

  function myFunction() public {
    myMapping[msg.sender].timestamp = now;
    myMapping[msg.sender].counter++;
  }

}

Notary app example code

Create a file called notary.sol.

pragma solidity ^0.4.24; // Specify compiler version 

contract Notary { // Contract definition
  struct MyNotaryDocument {
    bytes32 checkSum; // Hash of the file
    string comments;
    string fileName;
    bool isSet;
    address setBy;
    uint timestamp;
  }

  mapping(bytes32 => myNotaryDocument) public docMapping;

  event NewDocument(bytes32 _checksum, string fileName, address indexed _setBy); // indexed so that we can search by _setBy later (only 3)

  function addDocument(bytes32 _checksum, string _fileName, string comments) public {
    require(!docMapping[_checksum].isSet);

    docMapping[_checksum].isSet = true;
    docMapping[_checksum].fileName = _fileName;
    docMapping[_checksum].timestamp = now;
    docMapping[_checksum].comments = _comments;
    docMapping[_checksum].setBy = msg.sender;

    emit NewDocument(_checksum, _fileName, msg.sender);
  }

  function documentSet(bytes32 _checksum) public view returns(string, uint, string, address) {
    require(docMapping[_checksum].isSet);

    return (docMapping[_checksum].fileName, docMapping[_checksum].timestamp, docMapping[_checksum].comments, docMapping[_checksum].setBy);
  }
}

Notary smart contract unit test in JS example

Under test directory create a new file called notaryTest.js and add the following content:

var NotaryArtifact = artifacts.require("./notary.sol");

contract("NotaryContract", function(accounts) {
  it("This is my first test", function() {
    return NotaryArtifact.deployed().then(function(instance) {
      console.log(instance);
    })
  });

  it("should not be able to find a hash for an unexisting document", async function() {
    return NotaryArtifact.deployed().then(async function(instance) {
      try {
        await instance.documentSet(0x1111111111111111111111111111111111111111111111111111111111111111);
        assert.fail(true, true, "Exception expected while calling documentSet with non existing hash")
      } catch(error) {
        if (error.message.search("revert") >= 0) {
          assert.equal(error.message.search("revert") >= 0, true, "Error Message does not reflect expected Exception Message");
        } else {
          throw error;
        }
      }
    })
  });

  it("should not be able to create the read a document", async () => {
    let instance = await NotaryArtifact.deployed();
    await instance.addDocument(0x2222222222222222222222222222222222222222222222222222222222222222, "test", "test");
    let entry = await instance.documentSet(0x2222222222222222222222222222222222222222222222222222222222222222);
    //console.log(entry)
    assert.equal(entry[0], "test", "Filename should be equal to test");
    assert.equal(entry[1].toNumber() >= 1, true, "Timestamp should be bigger than 1");
    assert.equal(entry[2], "test", "Comment should be equal to test");
    assert.equal(entry[3, accounts[0], "The transaction should have been passed by account 0");

  });
});

Then execute truffle test.


Notary smart contract unit test in Solidity example

Under test directory create a new file called notaryTest.sol and add the following content:

pragma solidity ^0.4.23;

import "truffle/Assert.sol";
import "truffle/DeployedAddresses.sol";
import "../contracts/Notary.sol";

contract NotaryTest {
  function testAddAndRead() public {
    Notary notaryContract = Notary(DeployedAddresses.Notary());
    notaryContract.addDocument(0x3333333333333333333333333333333333333333333333333333333333333333, "test", "test");
    string memory fileName;
    uint timestamp;
    string memory comment;
    address sender;
    (fileName, timestamp, comment, sender) = notaryContract.documentSet(0x3333333333333333333333333333333333333333333333333333333333333333);
    Assert.equal(fileName, "test", "Filename should be equal to test");
    Assert.equal(sender, address(this), "The caller and the address of the smart contract creator should be the same")

  }

  function testException() public {
    address notaryAddress = address(DeployedAddresses.Notary());
    bool transactionOk = notaryAddress.call(bytes4(keccak256("documentSet(bytes32)")) "0x4444444444444444444444444444444444444444444444444444444444444444");
    Assert.equal(transactionOk, false, "Transaction should fail");
  }
}

Then execute truffle test.


Truffle Framework

Truffle is useful when you work with a blockchain development team.

Installation

# Install NVM
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash
echo "export NVM_NODEJS_ORG_MIRROR=http://nodejs.org/dist" >> ~/.bashrc
source ~/.bashrc
nvm ls-remote

# Install NodeJS
nvm install v12.22.0
nvm use v12.22.0
nvm use default v12.22.0

# Install truffle
npm i -g truffle

# Create an empty folder and create an empty project
truffle init

# Other Truffle commands
truffle compile
truffle migrate
truffle test

# Install Ganache
git clone --depth=1 https://github.com/trufflesuite/ganache.git && cd ganache
npm install
npm start

Truffle project configuration

Edit the truffle-config.js and add the Ganache development network. So inside module.exports = {networks:{}} add the following lines of code:

    development: {
      host: "127.0.0.1",
      port: 8545,
      network_id: "*", // match any network
      websockets: true
    },

Then run truffle migrate to deploy the basic smart contract to your dev blockchain.