Introduction to Ethereum development

Luca Di Domenico
10 min readNov 28, 2021

Introduction

The scope of this article is to introduce Ethereum to developers who are not familiar with it.

First, the main concepts around Ethereum will be introduced:

  • what is Ethereum?
  • what is the EVM?
  • how cryptography is used in the Ethereum blockchain
  • what is web 3.0?
  • transactions and the gas fees

Finally, an example of a simple smart contract will be presented in order to introduce the Solidity language.

This article simply collects some of the concepts needed to understand how my first ERC20 token works. If you want to deeply understand the concepts around Ethereum, I’m learning from the following resources:

What is Ethereum?

The book “Mastering Ethereum” gives the following definition of Ethereum:

  • From a computer science perspective, Ethereum is a deterministic but practically unbounded state machine, consisting of a globally accessible singleton state and a virtual machine that applies changes to that state.
  • From a more practical perspective, Ethereum is an open source, globally decentralized computing infrastructure that executes programs called smart contracts. It uses a blockchain to synchronize and store the system’s state changes, along with a cryptocurrency called ether to meter and constrain execution resource costs.

Ethereum Virtual Machine

The Ethereum Virtual Machine (EVM) is the single-thread virtual machine that executes smart contract code on the Ethereum blockchain. It can be said that the EVM stays to Ethereum like the Java Virtual Machine (JVM) stays to the operating system.

So you write your smart contract in a high-level programming language (e.g. Solidity is the most used language), you compile it into bytecode then execute it on Ethereum via the EVM, just like you need to compile Java code into bytecode in order to execute it on the operating system via the JVM.

Ethereum’s language is Turing complete, meaning that Ethereum can straightforwardly function as a general-purpose computer.

Ether currency measurement

Many people refers to Ethereum as the currency, but in reality Ethereum is the name of the blockchain and “ether” is the currency to pay for use the Ethereum platform.

The ether currency (or ETH) is divided in smaller unit. The smallest unit of ETH is called “wei” and 1 ETH = 10¹⁸ wei. Internally, when there is a transaction, the blockchain always represents the amount of ETH as wei and this is represented as an unsigned integer. For example if you transact 1 ETH, the blockchain will encode 1000000000000000000 as the value of the transaction.

The following table shows the unit of measurement of ETH.

Cryptography in Ethereum

The first thing to consider when talking about cryptography in Ethereum is the difference between cryptography and encryption: encryption is a subset of cryptography, and means “secret writing”. In fact, cryptography can also be used to prove the ownership of a secret without showing the secret (i.e. with digital signatures) or to prove the authenticity of data (hashing aka digital fingerprint). This is extensively used in the Ethereum blockchain.

Instead, no part of the Ethereum protocol involves encryption; that is to say all communications with the Ethereum platform and between nodes (including transaction data) are unencrypted and can (necessarily) be read by anyone. This is so everyone can verify the correctness of state updates and consensus can be reached.

To use the Ethereum blockchain you need to have a pair of public and private key. The private key is never trasmitted in the blockchain. Accounts that have a pair of public and private key are called EOA (Externally Owned Account). There is another type of account in Ethereum and is the Smart Contract account, also called Contracts Accounts. The difference between Contracts Accounts account and EOA is that Contracts Accounts do not own a private key.

Each account is identified by its address (i.e. the address where transactions will be sent/received): for EOA, the address is derived from the public key, by using an hashing function, “Keccak256”, which is the original implementation of the SHA-3 algorithm.

URL: https://github.com/ethereumbook/ethereumbook/blob/develop/04keys-addresses.asciidoc#ethereums-cryptographic-hash-function-keccak-256

For Contracts Accounts, the address is derived from the contract creation transaction as a function of the creating account and nonce (more on this later).

URL: https://ethereum.stackexchange.com/questions/760/how-is-the-address-of-an-ethereum-contract-computed

URL: https://jeancvllr.medium.com/solidity-tutorial-all-about-addresses-ffcdf7efc4e7#:~:text=Each%20contract's%20address%20is%20derived,one%20of%20the%20contract's%20functions.

Web3

In Decentralized Web Application (Dapps) built on blockchain, web3 is a term to represent a new vision and focus for web applications: from centrally owned and managed applications (web 2.0), to applications built on decentralized protocols (web 3.0).

In order to interact with the Ethereum blockchain we use the web3.js JavaScript library, which bridges JavaScript applications that run in the browser with the Ethereum blockchain.

Transactions

Transactions are signed messages sent by EOA to another EOA or a smart contract that trigger a change of the global state on the blockchain. The change of state can be for example transfer of ether from one account to another, or execution of smart contract code that updates one of the global objects.

Transactions can only be initiated by EOA, not by contracts. Contracts never run on they own, but are always executed by EOA with transactions.

Transactions, which change the state of the EVM, need to be broadcast to the whole network. Any node can broadcast a request for a transaction to be executed on the EVM; after this happens, a miner will execute the transaction and propagate the resulting state change to the rest of the network.

A transaction is a serialized binary message that includes the following fields:

  • recipient: the destination Ethereum address
  • signature: the signature of the transaction generated by the EOA with his private key
  • maxFeePerGas: the price of gas the originator is willing to pay
  • maxPriorityFeePerGas: the maximum amount of gas to use as a tip for the miners
  • gasLimit: the maximum amount of gas the originator is willing to consume for this transaction
  • value: the amount of ether to send to the destination
  • data: the variable-length binary data payload

The main “payload” of a transaction is contained in the “value” and “data” fields. A transaction can have both, only the value or only the data, or nothing. Every transaction of the blockchain is signed by the sender EOA, which means that the payload can not be tampered by other nodes and you are sure that the sender has not been counterfeited.

A transaction can be a:

  • payment (of ether currency)
  • invocation (a function of a smart contract is called)

A transaction having only value is a payment. A transaction with only data is an invocation. A transaction with both value and data is both a payment and an invocation.

Another type of transaction is the “Contract deployment”: this transaction does not have a recipient field.

For example, in web3.js to send a transaction that is a payment we can use the following Javascript code:

var src = web3.eth.accounts[0];
var dst = web3.eth.accounts[1];
web3.eth.sendTransaction({from: src, to: dst, value: web3.toWei(0.01, "ether"), data: ""});

Note the “data” parameter is empty. To make this transaction an invocation, we can add value to “data”:

var src = web3.eth.accounts[0];
var dst = web3.eth.accounts[1];
web3.eth.sendTransaction({from: src, to: dst, value: web3.toWei(0.01, "ether"), data: "0x123456"});

Gas fees

In Ethereum, users pay for the computational resources needed in order to execute transactions.

Gas is the unit that measures the amount of computational effort required to execute specific operations on the Ethereum network.

Gas refers to the fee required to conduct a transaction on Ethereum successfully.

Gas fees are paid in ether (ETH). Gas price is denoted in gwei, each gwei is equal to 0.000000001 ETH.

The “gasPrice” field of a transaction specifies how much a user wants to pay for gas fees, while the “gasLimit” refers to the maximum amount of gas the user is willing to consume on a transaction.

In a simple transaction between two EOAs the “gasLimit” is fixed ad 21000 units, so gas fees are predictable.

When executing smart contracts, gas fees are much less predictable because execution of smart contracts may require more or less resources and time based on different factors (for example user input).

How gas fees are calculated

After the London update, which was released in date August 2021, the way transaction fees are calculated has changed in order to make them more predictable when calling smart contracts.

Before the London Update

To illustrate the way gas fees are calculated I’ll show an example:

Bob sends to Alice 1 ETH and the price of gas is 200 gwei. The gasLimit for sending ETH between EOA is fixed at 21000 units. The total fee payed will be 200*21000 = 4200000 gwei, i.e 0,0042 ETH. So after the transfer, Bob will be charged by 1,0042 ETH and Alice will receive 1 ETH.

The gas fees will be distributed to miners, so miners receive 0,0042 ETH.

After the London upgrade

Gas fees are now composed of two parts: base fee + tip.

Miners will no more be rewarded by gas fees for confirming a transaction. Instead the gas fees will be burned and miners will be rewarded by a percentage of the total gas fees spent by the initiator of the transaction. This percentage referred as “tip” and is specified by the “maxPriorityFeePerGas” transaction’s field. The tip is set by the wallet that initiates the transaction. The greater is the tip, the greater the priority for the transaction is and the faster the transaction will be executed by miners.

If we return to the previous example, suppose that the price of gas is 190 gwei now and the tip set by the wallet of Bob is 10 gwei. After the transaction is completed, Bob will be charged by 1,0042 ETH, Alice will receive 1 ETH, 0,00399 ETH will be burned and 0,00021 ETH will be distributed to the miners. As can be seen, the London update has made mining activities far less profitable.

The price of gas and tips can be found at the following URL: https://ethgasstation.info/

How smart contract works

We will examine the basic elements that compose a smart contract written in Solidity language by looking at the Faucet.sol contract from the “Mastering Ethereum” book. The code can be found in the book’s repository: https://github.com/ethereumbook/ethereumbook/.

Code:

// SPDX-License-Identifier: CC-BY-SA-4.0// Version of Solidity compiler this program was written for
pragma solidity 0.6.4;
// Our first contract is a faucet!
contract Faucet {
// Accept any incoming amount
receive() external payable {}
// Give out ether to anyone who asks
function withdraw(uint withdraw_amount) public {
// Limit withdrawal amount
require(withdraw_amount <= 100000000000000000);
// Send the amount to the address that requested it
msg.sender.transfer(withdraw_amount);
}
}

As can be seen in the following code:

pragma solidity 0.6.4;

the above example uses the compiler version 0.6.4, which is outdated but it is still suitable for an of introduction to smart contract development concepts.

contract Faucet {

This line declares a contract object, similar to a class declaration in other object oriented languages.

function withdraw(uint withdraw_amount) public {

The function is named “withdraw”, and it takes one unsigned integer (uint) argument named withdraw_amount. It is declared as a public function, meaning it can be called by other contracts. The function definition follows, between curly braces. The first part of the withdraw function sets a limit on withdrawals:

require(withdraw_amount <= 100000000000000000);

It uses the built-in Solidity function require to test a precondition, that the with draw_amount is less than or equal to 100,000,000,000,000,000 wei, which is the base unit of ether (put the table 2.1 here) and equivalent to 0.1 ether. If the withdraw function is called with a withdraw_amount greater than that amount, the require function here will cause contract execution to stop and fail with an exception.

The actual withdrawal is performed by calling the function msg.sender.transfer() at line 17:

msg.sender.transfer(withdraw_amount);

The msg object is one of the inputs that all contracts can access. It represents the transaction that triggered the execution of this contract. The attribute sender is the sender address of the transaction. The function transfer is a built-in function that transfers ether from the current contract to the address of the sender. Reading it backward, this means transfer to the sender of the msg that triggered this contract execution. The transfer function takes an amount as its only argument. We pass the withdraw_amount value that was the parameter to the withdraw function declared a few lines earlier.

Next, we we declare one more function:

function () public payable {}

This function is a so-called fallback or default function, which is called if the transaction that triggered the contract didn’t name any of the declared functions in the contract, or any function at all, or didn’t contain data. Contracts can have one such default function (without a name) and it is usually the one that receives ether. That’s why it is defined as a public and payable function, which means it can accept ether into the contract. It doesn’t do anything, other than accept the ether, as indicated by the empty definition in the curly braces ({}). If we make a transaction that sends ether to the contract address, as if it were a wallet, this function will handle it.

Error handling using revert(), require() and assert()

The require and assert functions work in the same way: they evaluate a boolean condition. If the condition is true, the execution of the contract proceeds, else the execution stops with an error. By convention the assert() function is used internally when the outcome is expected to be true, meaning that the function is used to test internal condition. require() is used to test input(such as transaction arguments or function parameters).

When the contract execution stops whit an error, all the state changes (i.e changes on the global state of the blockchain) are reverted. The revert() function can be called manually in a smart contract when a developer wants to revert all the state changes.

Contructors

The contructor of a contract will be called only once, when the contract is created on the blockchain.

See here for more information: https://docs.soliditylang.org/en/v0.8.10/contracts.html#constructors

payable functions

payable: the “payable” is a modifier that allows a smart contract to receive ether. Specifically, payable functions are able to handle receiving ethers in transactions. If you try to send ether to a smart contract calling a function that does not have the payable keyword, the transaction will be rejected.

See here for more information:

--

--

Luca Di Domenico

Web security and Crypto. I’m a Software Security consultant and Freelance Web3 Developer. Follow me on Twitter! @luca_dd7