Skip to main content
Change page

Anatomy of smart contracts

A smart contract is a program that runs at an address on Ethereum. They're made up of data and functions that can execute upon receiving a transaction. Here's an overview of what makes up a smart contract.

Prerequisites

Make sure you've read about smart contracts first. This document assumes you're already familiar with programming languages such as JavaScript or Python.

Data

Any contract data must be assigned to a location: either to storage or memory. It's costly to modify storage in a smart contract so you need to consider where your data should live.

Storage

Persistent data is referred to as storage and is represented by state variables. These values get stored permanently on the blockchain. You need to declare the type so that the contract can keep track of how much storage on the blockchain it needs when it compiles.

1// Solidity example
2contractSimpleStorage{
3uint storedData;// State variable
4// ...
5}
1# Vyper example
2storedData: int128

If you've already programmed object-oriented languages, you'll likely be familiar with most types. However, address should be new to you if you're new to Ethereum development.

An address type can hold an Ethereum address which equates to 20 bytes or 160 bits. It returns in hexadecimal notation with a leading 0x.

Other types include:

  • boolean
  • integer
  • fixed point numbers
  • fixed-size byte arrays
  • dynamically sized byte arrays
  • rational and integer literals
  • string literals
  • hexadecimal literals
  • enums

For more explanation, take a look at the docs:

Memory

Values that are only stored for the lifetime of a contract function's execution are called memory variables. Since these are not stored permanently on the blockchain, they are much cheaper to use.

Learn more about how the EVM stores data (Storage, Memory, and the Stack) in the Solidity docsopens in a new tab.

Environment variables

In addition to the variables you define on your contract, there are some special global variables. They are primarily used to provide information about the blockchain or current transaction.

Examples:

PropState variableDescription
block.timestampuint256Current block epoch timestamp
msg.senderaddressSender of the message (current call)

Functions

In the most simplistic terms, functions can get information or set information in response to incoming transactions.

There are two types of function calls:

  • internal – these don't create an EVM call
    • Internal functions and state variables can only be accessed internally (i.e., from within the current contract or contracts deriving from it)
  • external – these do create an EVM call
    • External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e., f() does not work, but this.f() works).

They can also be public or private

  • public functions can be called internally from within the contract or externally via messages
  • private functions are only visible for the contract they are defined in and not in derived contracts

Both functions and state variables can be made public or private

Here's a function for updating a state variable on a contract:

1// Solidity example
2functionupdate_name(string value)public{
3 dapp_name = value;
4}
  • The parameter value of type string is passed into the function: update_name
  • It's declared public, meaning anyone can access it
  • It's not declared view, so it can modify the contract state

View functions

These functions promise not to modify the state of the contract's data. Common examples are "getter" functions – you might use this to receive a user's balance for example.

1// Solidity example
2functionbalanceOf(address _owner)publicviewreturns(uint256 _balance){
3return ownerPizzaCount[_owner];
4}
1dappName: public(string)
2
3@view
4@public
5defreadName()-> string:
6return dappName

What is considered modifying state:

  1. Writing to state variables.
  2. Emitting eventsopens in a new tab.
  3. Creating other contractsopens in a new tab.
  4. Using selfdestruct.
  5. Sending ether via calls.
  6. Calling any function not marked view or pure.
  7. Using low-level calls.
  8. Using inline assembly that contains certain opcodes.

Constructor functions

constructor functions are only executed once when the contract is first deployed. Like constructor in many class-based programming languages, these functions often initialize state variables to their specified values.

1// Solidity example
2// Initializes the contract's data, setting the `owner`
3// to the address of the contract creator.
4constructor()public{
5// All smart contracts rely on external transactions to trigger its functions.
6// `msg` is a global variable that includes relevant data on the given transaction,
7// such as the address of the sender and the ETH value included in the transaction.
8// Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
9 owner = msg.sender;
10}
Show all
1# Vyper example
2
3@external
4def__init__(_beneficiary: address, _bidding_time: uint256):
5 self.beneficiary = _beneficiary
6 self.auctionStart = block.timestamp
7 self.auctionEnd = self.auctionStart + _bidding_time

Built-in functions

In addition to the variables and functions you define on your contract, there are some special built-in functions. The most obvious example is:

  • address.send() – Solidity
  • send(address) – Vyper

These allow contracts to send ETH to other accounts.

Writing functions

Your function needs:

  • parameter variable and type (if it accepts parameters)
  • declaration of internal/external
  • declaration of pure/view/payable
  • returns type (if it returns a value)
1pragmasolidity>=0.4.0<=0.6.0;
2
3contractExampleDapp{
4string dapp_name;// state variable
5
6// Called when the contract is deployed and initializes the value
7constructor()public{
8 dapp_name ="My Example dapp";
9}
10
11// Get Function
12functionread_name()publicviewreturns(string){
13return dapp_name;
14}
15
16// Set Function
17functionupdate_name(string value)public{
18 dapp_name = value;
19}
20}
Show all

A complete contract might look something like this. Here the constructor function provides an initial value for the dapp_name variable.

Events and logs

Events enable your smart contract to communicate with your frontend or other subscribing applications. Once a transaction is validated and added to a block, smart contracts can emit events and log information, which the frontend can then process and utilize.

Annotated examples

These are some examples written in Solidity. If you'd like to play with the code, you can interact with them in Remixopens in a new tab.

Hello world

1// Specifies the version of Solidity, using semantic versioning.
2// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
3pragmasolidity^0.5.10;
4
5// Defines a contract named `HelloWorld`.
6// A contract is a collection of functions and data (its state).
7// Once deployed, a contract resides at a specific address on the Ethereum blockchain.
8// Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
9contractHelloWorld{
10
11// Declares a state variable `message` of type `string`.
12// State variables are variables whose values are permanently stored in contract storage.
13// The keyword `public` makes variables accessible from outside a contract
14// and creates a function that other contracts or clients can call to access the value.
15stringpublic message;
16
17// Similar to many class-based object-oriented languages, a constructor is
18// a special function that is only executed upon contract creation.
19// Constructors are used to initialize the contract's data.
20// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
21constructor(stringmemory initMessage)public{
22// Accepts a string argument `initMessage` and sets the value
23// into the contract's `message` storage variable).
24 message = initMessage;
25}
26
27// A public function that accepts a string argument
28// and updates the `message` storage variable.
29functionupdate(stringmemory newMessage)public{
30 message = newMessage;
31}
32}
Show all

Token

1pragmasolidity^0.5.10;
2
3contractToken{
4// An `address` is comparable to an email address - it's used to identify an account on Ethereum.
5// Addresses can represent a smart contract or an external (user) accounts.
6// Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#address
7addresspublic owner;
8
9// A `mapping` is essentially a hash table data structure.
10// This `mapping` assigns an unsigned integer (the token balance) to an address (the token holder).
11// Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#mapping-types
12mapping(address=>uint)public balances;
13
14// Events allow for logging of activity on the blockchain.
15// Ethereum clients can listen for events in order to react to contract state changes.
16// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#events
17eventTransfer(addressfrom,address to,uint amount);
18
19// Initializes the contract's data, setting the `owner`
20// to the address of the contract creator.
21constructor()public{
22// All smart contracts rely on external transactions to trigger its functions.
23// `msg` is a global variable that includes relevant data on the given transaction,
24// such as the address of the sender and the ETH value included in the transaction.
25// Learn more: https://solidity.readthedocs.io/en/v0.5.10/units-and-global-variables.html#block-and-transaction-properties
26 owner = msg.sender;
27}
28
29// Creates an amount of new tokens and sends them to an address.
30functionmint(address receiver,uint amount)public{
31// `require` is a control structure used to enforce certain conditions.
32// If a `require` statement evaluates to `false`, an exception is triggered,
33// which reverts all changes made to the state during the current call.
34// Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
35
36// Only the contract owner can call this function
37require(msg.sender == owner,"You are not the owner.");
38
39// Enforces a maximum amount of tokens
40require(amount <1e60,"Maximum issuance exceeded");
41
42// Increases the balance of `receiver` by `amount`
43 balances[receiver]+= amount;
44}
45
46// Sends an amount of existing tokens from any caller to an address.
47functiontransfer(address receiver,uint amount)public{
48// The sender must have enough tokens to send
49require(amount <= balances[msg.sender],"Insufficient balance.");
50
51// Adjusts token balances of the two addresses
52 balances[msg.sender]-= amount;
53 balances[receiver]+= amount;
54
55// Emits the event defined earlier
56emitTransfer(msg.sender, receiver, amount);
57}
58}
Show all

Unique digital asset

1pragmasolidity^0.5.10;
2
3// Imports symbols from other files into the current contract.
4// In this case, a series of helper contracts from OpenZeppelin.
5// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#importing-other-source-files
6
7import"../node_modules/@openzeppelin/contracts/token/ERC721/IERC721.sol";
8import"../node_modules/@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
9import"../node_modules/@openzeppelin/contracts/introspection/ERC165.sol";
10import"../node_modules/@openzeppelin/contracts/math/SafeMath.sol";
11
12// The `is` keyword is used to inherit functions and keywords from external contracts.
13// In this case, `CryptoPizza` inherits from the `IERC721` and `ERC165` contracts.
14// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#inheritance
15contractCryptoPizzais IERC721, ERC165 {
16// Uses OpenZeppelin's SafeMath library to perform arithmetic operations safely.
17// Learn more: https://docs.openzeppelin.com/contracts/2.x/api/math#SafeMath
18usingSafeMathforuint256;
19
20// Constant state variables in Solidity are similar to other languages
21// but you must assign from an expression which is constant at compile time.
22// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constant-state-variables
23uint256constant dnaDigits =10;
24uint256constant dnaModulus =10** dnaDigits;
25bytes4privateconstant _ERC721_RECEIVED =0x150b7a02;
26
27// Struct types let you define your own type
28// Learn more: https://solidity.readthedocs.io/en/v0.5.10/types.html#structs
29structPizza{
30string name;
31uint256 dna;
32}
33
34// Creates an empty array of Pizza structs
35 Pizza[]public pizzas;
36
37// Mapping from pizza ID to its owner's address
38mapping(uint256=>address)public pizzaToOwner;
39
40// Mapping from owner's address to number of owned token
41mapping(address=>uint256)public ownerPizzaCount;
42
43// Mapping from token ID to approved address
44mapping(uint256=>address) pizzaApprovals;
45
46// You can nest mappings, this example maps owner to operator approvals
47mapping(address=>mapping(address=>bool))private operatorApprovals;
48
49// Internal function to create a random Pizza from string (name) and DNA
50function_createPizza(stringmemory _name,uint256 _dna)
51// The `internal` keyword means this function is only visible
52// within this contract and contracts that derive this contract
53// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#visibility-and-getters
54internal
55// `isUnique` is a function modifier that checks if the pizza already exists
56// Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html#function-modifiers
57isUnique(_name, _dna)
58{
59// Adds Pizza to array of Pizzas and get id
60uint256 id = SafeMath.sub(pizzas.push(Pizza(_name, _dna)),1);
61
62// Checks that Pizza owner is the same as current user
63// Learn more: https://solidity.readthedocs.io/en/v0.5.10/control-structures.html#error-handling-assert-require-revert-and-exceptions
64
65// note that address(0) is the zero address,
66// indicating that pizza[id] is not yet allocated to a particular user.
67
68assert(pizzaToOwner[id]==address(0));
69
70// Maps the Pizza to the owner
71 pizzaToOwner[id]= msg.sender;
72 ownerPizzaCount[msg.sender]= SafeMath.add(
73 ownerPizzaCount[msg.sender],
741
75);
76}
77
78// Creates a random Pizza from string (name)
79functioncreateRandomPizza(stringmemory _name)public{
80uint256 randDna =generateRandomDna(_name, msg.sender);
81_createPizza(_name, randDna);
82}
83
84// Generates random DNA from string (name) and address of the owner (creator)
85functiongenerateRandomDna(stringmemory _str,address _owner)
86public
87// Functions marked as `pure` promise not to read from or modify the state
88// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#pure-functions
89pure
90returns(uint256)
91{
92// Generates random uint from string (name) + address (owner)
93uint256 rand =uint256(keccak256(abi.encodePacked(_str)))+
94uint256(_owner);
95 rand = rand % dnaModulus;
96return rand;
97}
98
99// Returns array of Pizzas found by owner
100functiongetPizzasByOwner(address _owner)
101public
102// Functions marked as `view` promise not to modify state
103// Learn more: https://solidity.readthedocs.io/en/v0.5.10/contracts.html#view-functions
104view
105returns(uint256[]memory)
106{
107// Uses the `memory` storage location to store values only for the
108// lifecycle of this function call.
109// Learn more: https://solidity.readthedocs.io/en/v0.5.10/introduction-to-smart-contracts.html#storage-memory-and-the-stack
110uint256[]memory result =newuint256[](ownerPizzaCount[_owner]);
111uint256 counter =0;
112for(uint256 i =0; i < pizzas.length; i++){
113if(pizzaToOwner[i]== _owner){
114 result[counter]= i;
115 counter++;
116}
117}
118return result;
119}
120
121// Transfers Pizza and ownership to other address
122functiontransferFrom(address _from,address _to,uint256 _pizzaId)public{
123require(_from !=address(0)&& _to !=address(0),"Invalid address.");
124require(_exists(_pizzaId),"Pizza does not exist.");
125require(_from != _to,"Cannot transfer to the same address.");
126require(_isApprovedOrOwner(msg.sender, _pizzaId),"Address is not approved.");
127
128 ownerPizzaCount[_to]= SafeMath.add(ownerPizzaCount[_to],1);
129 ownerPizzaCount[_from]= SafeMath.sub(ownerPizzaCount[_from],1);
130 pizzaToOwner[_pizzaId]= _to;
131
132// Emits event defined in the imported IERC721 contract
133emitTransfer(_from, _to, _pizzaId);
134_clearApproval(_to, _pizzaId);
135}
136
137/**
138 * Safely transfers the ownership of a given token ID to another address
139 * If the target address is a contract, it must implement `onERC721Received`,
140 * which is called upon a safe transfer, and return the magic value
141 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;
142 * otherwise, the transfer is reverted.
143 */
144functionsafeTransferFrom(addressfrom,address to,uint256 pizzaId)
145public
146{
147// solium-disable-next-line arg-overflow
148this.safeTransferFrom(from, to, pizzaId,"");
149}
150
151/**
152 * Safely transfers the ownership of a given token ID to another address
153 * If the target address is a contract, it must implement `onERC721Received`,
154 * which is called upon a safe transfer, and return the magic value
155 * `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`;
156 * otherwise, the transfer is reverted.
157 */
158functionsafeTransferFrom(
159addressfrom,
160address to,
161uint256 pizzaId,
162bytesmemory _data
163)public{
164this.transferFrom(from, to, pizzaId);
165require(_checkOnERC721Received(from, to, pizzaId, _data),"Must implement onERC721Received.");
166}
167
168/**
169 * Internal function to invoke `onERC721Received` on a target address
170 * The call is not executed if the target address is not a contract
171 */
172function_checkOnERC721Received(
173addressfrom,
174address to,
175uint256 pizzaId,
176bytesmemory _data
177)internalreturns(bool){
178if(!isContract(to)){
179returntrue;
180}
181
182bytes4 retval =IERC721Receiver(to).onERC721Received(
183 msg.sender,
184from,
185 pizzaId,
186 _data
187);
188return(retval == _ERC721_RECEIVED);
189}
190
191// Burns a Pizza - destroys Token completely
192// The `external` function modifier means this function is
193// part of the contract interface and other contracts can call it
194functionburn(uint256 _pizzaId)external{
195require(msg.sender !=address(0),"Invalid address.");
196require(_exists(_pizzaId),"Pizza does not exist.");
197require(_isApprovedOrOwner(msg.sender, _pizzaId),"Address is not approved.");
198
199 ownerPizzaCount[msg.sender]= SafeMath.sub(
200 ownerPizzaCount[msg.sender],
2011
202);
203 pizzaToOwner[_pizzaId]=address(0);
204}
205
206// Returns count of Pizzas by address
207functionbalanceOf(address _owner)publicviewreturns(uint256 _balance){
208return ownerPizzaCount[_owner];
209}
210
211// Returns owner of the Pizza found by id
212functionownerOf(uint256 _pizzaId)publicviewreturns(address _owner){
213address owner = pizzaToOwner[_pizzaId];
214require(owner !=address(0),"Invalid Pizza ID.");
215return owner;
216}
217
218// Approves other address to transfer ownership of Pizza
219functionapprove(address _to,uint256 _pizzaId)public{
220require(msg.sender == pizzaToOwner[_pizzaId],"Must be the Pizza owner.");
221 pizzaApprovals[_pizzaId]= _to;
222emitApproval(msg.sender, _to, _pizzaId);
223}
224
225// Returns approved address for specific Pizza
226functiongetApproved(uint256 _pizzaId)
227public
228view
229returns(address operator)
230{
231require(_exists(_pizzaId),"Pizza does not exist.");
232return pizzaApprovals[_pizzaId];
233}
234
235/**
236 * Private function to clear current approval of a given token ID
237 * Reverts if the given address is not indeed the owner of the token
238 */
239function_clearApproval(address owner,uint256 _pizzaId)private{
240require(pizzaToOwner[_pizzaId]== owner,"Must be pizza owner.");
241require(_exists(_pizzaId),"Pizza does not exist.");
242if(pizzaApprovals[_pizzaId]!=address(0)){
243 pizzaApprovals[_pizzaId]=address(0);
244}
245}
246
247/*
248 * Sets or unsets the approval of a given operator
249 * An operator is allowed to transfer all tokens of the sender on their behalf
250 */
251functionsetApprovalForAll(address to,bool approved)public{
252require(to != msg.sender,"Cannot approve own address");
253 operatorApprovals[msg.sender][to]= approved;
254emitApprovalForAll(msg.sender, to, approved);
255}
256
257// Tells whether an operator is approved by a given owner
258functionisApprovedForAll(address owner,address operator)
259public
260view
261returns(bool)
262{
263return operatorApprovals[owner][operator];
264}
265
266// Takes ownership of Pizza - only for approved users
267functiontakeOwnership(uint256 _pizzaId)public{
268require(_isApprovedOrOwner(msg.sender, _pizzaId),"Address is not approved.");
269address owner =this.ownerOf(_pizzaId);
270this.transferFrom(owner, msg.sender, _pizzaId);
271}
272
273// Checks if Pizza exists
274function_exists(uint256 pizzaId)internalviewreturns(bool){
275address owner = pizzaToOwner[pizzaId];
276return owner !=address(0);
277}
278
279// Checks if address is owner or is approved to transfer Pizza
280function_isApprovedOrOwner(address spender,uint256 pizzaId)
281internal
282view
283returns(bool)
284{
285address owner = pizzaToOwner[pizzaId];
286// Disable solium check because of
287// https://github.com/duaraghav8/Solium/issues/175
288// solium-disable-next-line operator-whitespace
289return(spender == owner ||
290this.getApproved(pizzaId)== spender ||
291this.isApprovedForAll(owner, spender));
292}
293
294// Check if Pizza is unique and doesn't exist yet
295modifierisUnique(stringmemory _name,uint256 _dna){
296bool result =true;
297for(uint256 i =0; i < pizzas.length; i++){
298if(
299keccak256(abi.encodePacked(pizzas[i].name))==
300keccak256(abi.encodePacked(_name))&&
301 pizzas[i].dna == _dna
302){
303 result =false;
304}
305}
306require(result,"Pizza with such name already exists.");
307_;
308}
309
310// Returns whether the target address is a contract
311functionisContract(address account)internalviewreturns(bool){
312uint256 size;
313// Currently there is no better way to check if there is a contract in an address
314// than to check the size of the code at that address.
315// See https://ethereum.stackexchange.com/a/14016/36603
316// for more details about how this works.
317// TODO Check this again before the Serenity release, because all addresses will be
318// contracts then.
319// solium-disable-next-line security/no-inline-assembly
320assembly{
321 size :=extcodesize(account)
322}
323return size >0;
324}
325}
Show all

Further reading

Check out Solidity and Vyper's documentation for a more complete overview of smart contracts:

Was this article helpful?

AltStyle によって変換されたページ (->オリジナル) /