Go to course navigation

3 - Ethereum Tooling II.

Ethereum smart contracts

A smart contract is an automatic computer program that executes and stores data. It is one of the features that make Ethereum different from the Bitcoin platform. Ethereum smart contracts are executed in the EVM and coded (mostly) in the Solidity programming language. They are used in various sectors: banking, insurance or logistics.

Self-enforcing

Standard contracts bind the parties involved according to their legal system and are enforceable by the law and its institutions. Smart contracts, on the other hand, are self-enforcing contracts. They are written into the lines of code that is distributed across the decentralized blockchain network. They need no central authority to validate them.

Unstoppable

Smart contracts should be irreversible and irrevocable. So for example the accounts to which tokens will be transferred can’t be changed or deleted. But exceptions apply, such as TransparentUpgradeableProxy. Ethereum Smart Contracts can’t be re-deployed on the same address.

Secure

Many Smart Contracts have bugs that make it easy for hackers to operate with tokens to their advantage.

Ethereum transactions

Atomicity

Transactions are atomic operations, they can’t be divided or interrupted. Transaction is either done, or not.

Accounts

There are two types of accounts. First, it’s the externally owned accounts (EOA). Those are the accounts of the users that have a balance, are autonomous and controlled by a private key. Second, it’s the contract accounts that contain the EVM code.

Deployment transaction

Contract creation is a transaction. Each contract has its own storage and code. During the initial transaction, a new irreversible code is created.

Calls vs. transactions

There are two ways to communicate with a smart contract. The first way is transactions, which change the network state. Transactions consume resources of the network and, of course, costs some gas. After you confirm the transaction, it becomes listed among the pending transactions and waits for a miner to validate it. The second way is the Smart Contract Call that calls the storage and returns its content. SSCs are simple RPC calls that you can run using e.g. geth requests. SSCs cost no gas and you get the response immediately. Calls don’t change the network state.

Hands-on excercise

Tutorial objectives

We will learn how to compile and deploy smart contracts.

Tutorial pre-requirements

Caution:

Computers in the laboratory don’t allow to install programs and save their state between login sessions. Please bring your own hardware for these tutorials.

Compile a contract

Caution:

License should be defined (or you get a compiler warning).

Once the contract enters the network, you can’t find out anything about its license, unless it’s specified in the body of the contract. Also always define compatible versions of solidity compilers.

Caution:

Don’t use floating pragma, fix your contract to a (tested) compiler version. A good practice is to use compiler versions 3-6 months behind the latest. See https://swcregistry.io/docs/SWC-103.

  • Compile a simple contract.
// SPDX-License-Identifier: GPL-3.0
// pragma solidity ^0.8.9;
pragma solidity 0.8.9;

contract Name {
    string myName;

    function getMyName() public view returns (string memory) {
        return myName;
    }

    function changeMyName(string memory _newName) public {
        myName = _newName;
    }
}
  • Check the compiler version
    • Directly $ solc --version
    • Using Docker image $ docker run ethereum/solc:stable --version
  • Check the compiler version 0.8.9
    • Using Docker image $ docker run ethereum/solc:0.8.9 --version
  • Test the compiler. You should get this response: “Compiler run successful, no output requested.”
    • Using the binary $ solc name_contract.sol
    • Or using Docker image $ docker run -v $(pwd):/sources --platform linux/amd64 ethereum/solc:0.8.9 -o /sources/output /sources/name_contract.sol
  • Define the output as a binary and ABI to the folder target
    • $ solc -o target --bin --abi name_contract.sol
    • Or docker run -v $(pwd):/sources --platform linux/amd64 ethereum/solc:0.8.9 -o /sources/output --bin --abi /sources/name_contract.sol
    • Check the outputs in the target folder, inspect .abi file

Deploy the contract (using geth)

Initialise again a new local chain with a a new genesis block. See chaged chain parameters.

{
  "config": {
    "chainId": 15,
    "byzantiumBlock": 0,
    "constantinopleBlock": 0,
    "petersburgBlock": 0,
    "istanbulBlock": 0,
    "homesteadBlock": 0,
    "eip150Block": 0,
    "eip155Block": 0,
    "eip158Block": 0
  },
  "difficulty": "0x40000",
  "gasLimit": "2100000",
  "alloc": {
    "7df9a875a174b3bc565e6424a0050ebc1b2d1d82": { "balance": "30000000000" },
    "f41c74c9ae680c1aa78f42e5647a62f353b7bdde": { "balance": "40000000000" },
    "0332a7cb94a12d0980f44b2b751f11a4db8cf17e": { "balance": "50000000000" }
  }
}
  • Initialise the local chain
    • Delete previous local chain, re-use local accounts from last tutorial
    • $ geth init genesis.json
    • Start the local chain $ geth console --nodiscover
  • Get some Ether (quick tutorial 2 recap)
    • personal.unlockAccount(eth.accounts[0])
    • miner.setEtherbase(eth.accounts[0])
    • miner.start()
    • miner.stop()
  • Define the contract
    • Add the contents of target/name_contract.bin to a property. Prefix it with “0x” to signal it’s a hexadecimal code.
    • nameHex = "0x…​"
    • Add the contents of target/name_contract.abi to a property
    • nameAbi = …​
    • Create local web3 representation of the contract
    • nameInterface = eth.contract(nameAbi)

Deploy the contract. Which is, as we mentioned earlier, another type of transaction apart from the transaction used to send tokens.

  • Deploy the contract
    • Create transaction for contract deployment, together with parameters, such as the account you want to use, data format and gas
    • nameTx = nameInterface.new({from: eth.accounts[0], data: nameHex, gas: 1000000})
    • See the transaction status
    • eth.pendingTransactions
    • Mine a block with the transaction
    • miner.start() miner.stop()
    • Get information about the contract
    • eth.getTransactionReceipt(nameTx.transactionHash)
    • Save contract address
    • nameAddress = nameTx.address
  • Call the contract
    • Call function getMyName nameInterface.at(nameAddress).getMyName.call()
    • Send transaction with ChangeMyName ChangeTx = nameInterface.at(nameAddress).changeMyName.sendTransaction("karel", {from: eth.accounts[0], gas: 1000000})
    • Mine the transaction
    • Call function getMyName nameInterface.at(nameAddress).getMyName.call()

[Deprecated] Deploy the contract (using Truffle)

The default template, created using the command truffle unbox, is MetaCoin. Learn more about Truffle Boxes at https://www.trufflesuite.com/boxes.

  • Create a new project with a default template
    • $ truffle init
    • Check truffle and compiler versions `$ truffle version
  • Compile the contract
    • Copy your contract to your Truffle project
    • $ cp ../name_contract.sol contracts/
    • Change the contract Solidity version to match the Truffle compiler version
    • Compile the contract
    • $ truffle compile
    • Check the build result in build/contracts/Name.json
const Migrations = artifacts.require("Migrations");
const Name = artifacts.require("Name");

module.exports = function (deployer) {
 deployer.deploy(Migrations);
 deployer.deploy(Name);
};
  • Deploy the contract
    • Define contracts you want to deploy in the migrations/1_initial_migrations.js file
    • Open Ganache (Quick Start)
    • Go to Settings
    • Add Project: truffle-config.js
    • Press Save & Restart
    • In the tab Contracts, you can see your contracts
    • Deploy them $ truffle deploy
  • Call the contract
    • Start Truffle console $ truffle console
    • Check the configuration truffle(ganache)> global
    • Get an instance of a deployed smart contract
    • truffle(ganache)> const instance = await Name.deployed()
    • Call getMyName() function
    • truffle(ganache)> instance.getMyName()
    • Call changeMyName() that will trigger a transaction
    • truffle(ganache)> instance.changeMyName("karlos")
    • Check transactions in Ganache, profit

Deploy the contract (using Remix)

Visit https://remix.ethereum.org/.

Build and deploy a contract using Hardhat

Deploy name_contract.sol using Hardhat https://hardhat.org/hardhat-runner/docs/getting-started