07 Ethereum Smart Contracts
07 Ethereum Smart Contracts
1
”Contract” is used as a short form of “smart contract”.
07 Ethereum Smart Contracts © sebis 3
Source Code
Smart Contract
code is written in Compiler takes the
Solidity (.sol file) Solidity code and
produces EVM The hex encoded
bytecode bytecode is sent
The bytecode is
as transaction to
put into a block and
the network
mined. The contract
can now be used!
These steps are running on a private These steps are executed and stored in the
machine and are therefore not available to Blockchain and therefore available for
the public. everyone.
struct Tutor {
string firstName;
State variables
string lastName; § State variables are permanently stored in
}
mapping (address => Tutor) tutors; the contract’s storage.
address professor; § Changing the state requires a transactions
modifier onlyProfessor { and therefore costs ether.
require(msg.sender == professor);
_; § Reading the state of a contract is free and
} does not require a transaction.
constructor() public {
professor = msg.sender;
}
}
07 Ethereum Smart Contracts © sebis 6
Anatomy of a Solidity Smart Contract File (cont.)
File: BBSE.sol
contract BBSE {
struct Tutor {
string firstName;
Function modifiers
string lastName; § Function modifiers are a convenient way to
}
mapping (address => Tutor) tutors; reuse pieces of code.
address professor; § Changes the behavior of a function.
modifier onlyProfessor { § Can execute code either before and/or
require(msg.sender == professor);
_; after the actual function execution.
} § The low dash _ indicates where the actual
constructor() public { function code is injected.
professor = msg.sender;
}
§ Often used for authentication.
function getProfessor() view returns (address) {
return professor;
}
}
07 Ethereum Smart Contracts © sebis 7
Anatomy of a Solidity Smart Contract File (cont.)
File: BBSE.sol
contract BBSE {
struct Tutor {
string firstName;
Constructor
string lastName; § The constructor function is executed once
}
mapping (address => Tutor) tutors; when the contract is created through a
address professor; transaction.
modifier onlyProfessor { § The function cannot be called after the
require(msg.sender == professor);
_; creation of the contract.
} § Usually used to initialize the state of a
constructor() public { contract.
professor = msg.sender;
}
§ Execution costs gas and more complex
constructors lead to higher deployment
function getProfessor() view returns (address) {
return professor; costs.
}
}
07 Ethereum Smart Contracts © sebis 8
Anatomy of a Solidity Smart Contract File (cont.)
File: BBSE.sol
contract BBSE {
struct Tutor {
string firstName;
Functions
string lastName; § Functions are used to change the state of
}
mapping (address => Tutor) tutors; a contract.
address professor; § Can also be used to read the state of the
modifier onlyProfessor { contract.
require(msg.sender == professor);
_; § Consist of a name, a signature, a visibility,
} a type, a list of modifiers, and a return
constructor() public { type.
professor = msg.sender;
}
Formal definition:
function getProfessor() view returns (address) {
return professor; function (<parameter types>)
} {internal|external|public|private}
// This function adds a new tutor
[pure|constant|view|payable]
function addTutor(address tutorAddress, [(modifiers)]
string firstName, string lastName) onlyProfessor {
Tutor tutor = tutors[tutorAddress];
[returns (<return types>)]
tutor.firstName = firstName;
tutor.lastName = lastName;
}
}
07 Ethereum Smart Contracts © sebis 9
Language Features Overview
Solidity is inspired by JavaScript and comes with a very similar syntax. Furthermore, it implements the
standard set of features for high-level (object-oriented) programming languages. Compared to the
dynamically-typed JavaScript, Solidity uses static types.
Built-in functions
Error handling: assert(), require(), revert()
Math & Crypto: addmod(), mulmod(), sha3(), keccak256(), sha256(), ripemd160(), ecrecover()
Information: gasleft(), blockhash()
Contract related: selfdestruct()
A set of literals
Solidity comes with some Ethereum specific literals (like eth for units, e.g., int a = 5 eth)
Flow control
if, else, do, while, break, continue, for, return, ? … : … (ternary operator)
07 Ethereum Smart Contracts © sebis 10
Function and Variable Visibility
External
External methods can be called by other contracts and via transactions issued by a certain wallet.
Methods declared as external are always publicly visible and can’t be called directly by the
contract itself.
Public
Public can be called internally by the contract itself but also externally by other contracts and via
transactions. State variable which are defined as public will by default have getter method created
automatically by the compiler.
Internal
Internal methods can only be accessed by the contract itself or by any contract derived from it. They
are not callable from other contracts nor via transactions.
Private
Private methods can only be called internally by the contract who owns the method. Derived
contracts cannot access a private method of their parent contract.
07 Ethereum Smart Contracts © sebis 11
Data Storage in EVM
EVM can store data in three different places; storage, memory, and the stack.
Storage Memory Stack
§ The storage is comparable to a hard drive. § The memory is comparable to a § Because the EVM is a stack machine
It keeps data between function calls and is computer's RAM. It is a temporary storage rather than a register machine, all
persistent for each smart contract. This location for data. During execution, the computations are done on a data region
way, every execution on the contract has contract can use any amount of memory, called the stack. It has a maximum of
access to the data previously saved on the but once the execution is complete, the 1024 elements and comprises 256-bit
storage area. memory is cleaned for the next execution. words.
§ Reading the storage is expensive. § Memory is linear and can be addressed at § Moving stack items to storage or memory
Initializing and modifying storage is even the byte level, however, reads are limited to provide deeper access to the stack is
more expensive. Thus, you should limit the to 256 bits wide, but writing can be 8 bits possible.
amount of data you keep in persistent or 256 bits wide.
storage to what the contract requires.
Output of
numbers:
Output of [1,2]. In this case,
numbers: changing the
[0,2] and not [1,2]. value of myArray
does not affect
the value in the
numbers array.
myArray numbers
Solidity provides two special function type declaration besides the default one.
View function
Functions which are declared as view are read only, i.e. they do not modify any state variable nor alter
the state of the blockchain. However, they can read from state variables.
uint state = 5;
function add(uint a, uint b) public view returns (uint sum) { return a + b + state }
Pure function
Pure functions can be seen as a subset of view functions which don’t modify the state but also don’t
read from the state.
function add(uint a, uint b) public pure returns (uint sum) { return a +b }
Fallback function
A contract can have one unnamed fallback function. The fallback function is called when no other
function matches the function call (e.g., when Ether is sent to a contract without a receive function). A
special feature of this function is that it can’t have any parameters and doesn’t return anything.
function() { /* … */ }
Example
function buyInICO() public payable { /* … */ }
§ The keyword payable is also required for declaring constructors and addresses that can receive
Ether (e.g., constructor payable { /* … */ }, function withdraw (address payable _to) public { /* … */ }).
§ While implicit conversions are allowed from address payable to address, a casting function called
payable (<address>) must be used for conversions from address to address payable.
Sometimes it is required to check whether a certain condition is true or false before executing a
function. For instance, an authentication mechanism prior to the function call. Writing code twice
makes it harder to maintain and prone to security vulnerabilities. Therefore, Solidity implements the
concept of modifiers which are basically a reusable piece of code.
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_;
}
Internally, the actual function body is injected where _ is placed in the modifier.
It is possible to apply multiple modifiers to a function. The modifiers will be resolved sequentially,
starting from left to right. In the example below, a user can only call the kill function if he/she is the
owner of the contract and has an account balance with more than 1337 ETH.
contract owned {
address public owner;
constructor() public {
owner = msg.sender;
}
modifier onlyOwner {
require(msg.sender == owner);
_; // Actual function code is injected here
}
modifier isRich {
require(msg.sender.balance > 1337 ether);
_; // Actual function code is injected here
}
Solidity allows to overload functions, i.e. to define the same function twice with a different signature.
This can be helpful if a method needs to be adapted to certain situations.
Example
function sendEther(uint amount) {
require(this.balance >= amount);
payable(msg.sender).transfer(amount);
}
If sendEther() is called without the address argument, the Ether will be sent to the caller.
Otherwise, it will be sent to the address passed as parameter to the function.
Usually, function parameters are passed by their defined signature order. Solidity supports the
concept of named calls. The named calls principle allows to pass function parameters explicitly
via a dictionary.
function fourPlusTwo() returns (uint result) { function fourPlusTwo() returns (uint result) {
return myAddFunction(4, 2); return myAddFunction({b: 2, a:4});
} }
The order of the parameters is defined by the The function fourPlusTwo passes a dictionary with
function’s signature. keys that match the signature of myAddFunction. The
order within the dictionary does not matter.
Solidity also supports multiple inheritance for a contract. In this case, the compiler just copies all
parent contracts together and creates a single contract that is compiled to bytecode and deployed to
the blockchain. Once a contract is deployed, it is not possible to detect from the bytecode whether a
contract made use of inheritance or not.
If a parent contract contains a function that is also present in the sub contract, then the functions are
overloaded. In case both functions have the same signature, the sub contract’s function will override
the parent’s function. However, the parent function can still be explicitly accessed using the super
keyword.
Use Cases:
SafeMath, Authentification
Solidity uses, similar to Python, the C3 superclass linearization algorithm to define the order
of the inherited functions. There is no implicit order for parent classes, the order is defined by
the developer.
A B C
Y inherits from X D E
F
07 Ethereum Smart Contracts © sebis 22
Multiple Inheritance (cont.)
In Solidity, contracts can inherit from other contracts by using the keyword is.
Example
contract A {}
contract B {}
contract C {}
contract D is A, B {}
contract E is B, C {}
contract F is D, E {}
The function resolution order (FRO) of the example above would be:
F, D, E, A, B, C
The keyword super always references the next contract in the FRO. If super would be called in F, it would
reference to D and super in D would reference to E and so on.
Example
contract CarInsurance {
function payMonthlyFee() returns (boolean result);
}
Abstract contracts cannot be compiled to bytecode. A contract that inherits from an abstract
contract must implement and override all methods from the base contract to be compliable.
Abstract contracts offer a way to decouple the definition of a contract from its actual implementation.
This provides better extensibility and maintainability, in particular for larger contracts.
Example
interface CarInsurance {
function payMonthlyFee() returns (boolean result);
}
A contract can implement multiple interfaces at once. According to the Ethereum foundation, some
of the restrictions for interfaces might be lifted or changed in the future.
However, the real-world adoption of contracts still is negligible. Currently, the Do you need a
largest dApp1 using an Ethereum smart contract has less than ~11000 blockchain?
transactions per day (https://dappradar.com/rankings/protocol/ethereum).
1 Decentralized application.
07 Ethereum Smart Contracts © sebis 28
Model the Business Process
As in traditional software engineering, the first step for creating an application is to model the business process.
Identification
§ Identify the involved parties, systems, and their relationship.
§ Identify the necessary interactions between the parties and systems.
§ Identify the information that is exchanged between the parties and systems.
§ Identify the system boundaries.
§ Diagrams can help to get the big picture.
Modelling
§ Derive concrete models from the identified parties and systems.
§ Define concrete messages that are exchanged between the systems and parties.
§ Define a concrete data model used by the system.
§ Derive concrete interfaces for interaction with the systems and parties.
§ The overall architecture is usually modeled at a high abstraction level using architecture diagrams.
§ The concrete software is modeled at a lower abstraction level using class diagrams.
EOAs
Donor – Person who donates blood identified by their unique wallet address
DRK – Institution that runs blood donation events
Laboratory – Laboratory that takes the blood donation and creates blood products out of it
Hospital – The institution that transfuses the blood of the donor to a patient
Patient – The patient who gets a blood transfusion
Contracts
Blood Donation – Contract owned by the DRK, it tracks the time and date of the donation. Additionally, it records
whenever the donation is passed to another party, e.g., from the DRK to the laboratory.
In the Ethereum ecosystem transactions and messages are the only way for entities to interact.
Interactions
DRK à CONTRACT_CREATION:
Creates a blood donation (BD) and deploys it on the blockchain. The address of the donor is passed via
constructor and unchangeable.
DRK: 0x91A0639dDe409c126f058e33D743b1253738C8b9
PK: 0x3aae751e36ddffd4f7d5ff4bee409583a54df823111a30f780c18cd73ebb02f8
Laboratory: 0x3aDDBa6E0C56EE1357Bb9796b20480880cA37E81
PK: 0xdbe7d4d5460f6a6e086579a0acf071b652b6ed5ae0374d704a949cbb0b740a65
Hospital: 0x582FFFacdBFDaF1936672886035ea561FF669a44
PK: 0x8ae80121c7bc29a51eb4401754928051063a8dace9d35496dd26d0c4a1a0640c
Patient: 0x0780aFf9177d78E86Fc03158D504652f88c4D1bc
PK: 0x0a18f4e53a62e97b613ed94d0f411de327e0d1a0d5533c685042cae420aacbfb
Donor: 0x39bc67dBb1f5203AF048699233b29Dec903389A4
PK: 0x5aed62bff0a98533345482fb91ac80388869e6bdd5ad53c19b54a37468a5cb2d
Write set
(state changes)
Coinbase Coinbase
Contract Difficulty Difficulty
Gaslimit Gaslimit
Timestamp Timestamp
Block: n-2 Block: n-1 Block: n EVM
TX TX TX Block: n+1 Block: n+1
TX TX TX TX
past now
The origin of each contract function call is always a transaction by an externally owned account (EOA).
In more complex systems, multiple contracts communicate with each other. For example, when a contract uses
an oracle contract to get information from the outside world. In such cases, the issuer of the function call must
provide enough gas that also the oracle request can be fulfilled.
Whenever a contract issues a message to another contract, the gas from the origin transaction is just forwarded.
However, sometimes this is not intended, e.g., when only Ether should be transferred. Therefore, the Solidity
address class implements functions specifically for that use case.
An address can be directly defined via a valid 20 byte hex code representation.
address a = 0xd5e7726990fD197005Aae8b3f973e7f2A65b4c18
An address that can receive Ether must either be defined as address payable or it should be cast with
payable (<address>) function while sending Ether to it.
A a = A(0xd5e7726990fD197005Aae8b3f973e7f2A65b4c18)
contract A {
function f() {}
}
contract B {
function g() {
A a = new A();
address contract_a = address(a);
address self = address(this);
// B b = B(self) would work
// B b = B(contract_a) would fail
}
}
<address>.transfer(uint256 value)
Transfers the amount passed as value in Wei to the <address>. The function throws on failure. Forwards 2300 gas
to <address>. (NOTE: Must keep in mind that the called smart contract can quickly run out of gas and make the
transfer impossible)
<address>.send(uint256 value)
Same as <address>.transfer(uint256 value) but returns false on failure
<address>.call(…)
A Low-level function that can be used to invoke functions but also to send Ether. The function returns false on
failure and, by default, forwards all gas to <address> (NOTE: The called contract can execute complex operations
that can spend all of the forwarded gas, causing more cost to the caller). If there is no receive function defined in
the called contract (i.e., if the fallback gets triggered upon Ether received), then, only 2300 gas is forwarded.
<address>.delegatecall(…)
A low-level function that can be used to call a function at <address> in the context/state of the current contract (i.e.,
caller contract delegates the use of its storage to the receiving contract). This function returns false on failure.
(NOTE: Caller contract needs to trust the receiving contract)
§ The re-entrancy attack is one of the most damaging attacks to a Solidity smart contract. When a function makes
an external call to another untrusted contract, it becomes vulnerable to a re-entrancy attack.
§ The untrusted contract can place recursive calls back to the original function, in order to drain all the funds
in the calling contract.
§ This would work if the original function updates the balance of the receiving contract, after transferring the coins.
A B
9 Ether 1 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
A B
8 Ether 2 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
A B
8 Ether 2 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
A B
8 Ether 2 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
A B
8 Ether 2 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
A B
8 Ether 2 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
5. Now, A has 8 Ether and B has 2 Ether, while the balance of contract B in
A is still 1 Ether. B calls withdraw() again. A B
8 Ether 2 Ether
B: 1 Ether
fallback(){
withdraw(){ A.withdraw()
5
check balance>0 }
send Ether attack(){
balance=0 A.withdraw()
} }
5. Now, A has 8 Ether and B has 2 Ether, while the balance of contract B in A is
still 1 Ether. B calls withdraw() again. A B
6. withdraw() checks the balance of B and since it is still 1, A sends 8 Ether
B: 1 Ether
2 Ether
5. Now, A has 8 Ether and B has 2 Ether, while the balance of contract B in A is
still 1 Ether. B calls withdraw() again. A B
6. withdraw() checks the balance of B and since it is still 1, A sends another 8 Ether
B: 1 Ether
2 Ether
Ether to B fallback(){
withdraw(){ A.withdraw()
7. This process between withdraw() and fallback() continues since the check balance>0
5
}
send Ether attack(){
balance reset line in withdraw() cannot be reached. As long as this is balance=0
6
A.withdraw()
the case, B can keep on withdrawing from A }
.
}
.
.
07 Ethereum Smart Contracts © sebis 50
Prevention Measures Against the Re-entrancy Attack
§ Ensure all state changes happen before calling external contracts (update balances or code internally
before calling external code)
https://solidity-by-example.org/hacks/re-entrancy/
07 Ethereum Smart Contracts © sebis 51
Message Object
Some contracts may require information about the caller of a function, e.g. for authentication
purposes. Solidity provides the global msg object that contains information about the caller. It does not
matter whether the caller of the function was an externally owned account or another contract.
The object refers to the last account that was responsible for invoking the function. This can either be
a contract or an externally owned account.
msg.sender
The account address of the function’s caller, which has type address (NOTE: Needs to be cast to
address payable when calling transfer, send, or call).
msg.data
The complete payload of the message/transaction
msg.sig
The function’s hash signature so that the EVM knows which function is called
msg.value
The amount of Wei that is sent with the message
Since the message object always refers to the last sender, it requires some special attention when used in
combination with this in a contract.
contract A {
function f() public returns (address a) {
return msg.sender;
}
Some contracts may require information about the latest mined block, e.g. when a specific function
should be time locked. Solidity provides a global variable called block to access the most recent block
of the blockchain.
block.coinbase
The account address of the current block’s miner
block.difficulty
The current mining difficulty as unsigned integer
block.gaslimit
The current block’s gaslimit (by the miner)
block.timestamp
The UNIX timestamp of the block (in theory, can by manipulated by the miner)
The global tx is similar to the msg object and provides information about the transaction that triggered
the function call.
The main difference is that tx always refers to a transaction, i.e., its source is always an externally
owned account.
tx.origin
The issuer of the transaction. This is always an externally owned account.
DO NOT USE FOR AUTHENTICATION!
tx.gasprice
Information about the gas price that was used by the issuer of the transaction
Example
§ As of February 2022, the most common event on the Ethereum blockchain is the Transfer event. It is emitted
when transferring tokens:
§ The emit keyword is used to emit the event parameters, which are declared inside the contract code.
§ The transfer event logs who sent the transfer (from), who it was transmitted to (to), and how many tokens
were sent (value).
https://ethereum.org/de/developers/tutorials/logging-events-smart-contracts/
07 Ethereum Smart Contracts © sebis 56
Example: Defining and emitting an event signaling a change of a counter