Solidity
Solidity is an object-oriented, high-level language for implementing smart contracts. Smart contracts are programs which govern the behaviour of accounts within the Ethereum state.
Solidity is a curly-bracket language designed to target the Ethereum Virtual Machine (EVM). It is influenced by C++, Python and JavaScript. You can find more details about which languages Solidity has been inspired by in the language influences section.
Solidity is statically typed, supports inheritance, libraries and complex user-defined types among other features.
With Solidity you can create contracts for uses such as voting, crowdfunding, blind auctions, and multi-signature wallets.
When deploying contracts, you should use the latest released version of Solidity. Apart from exceptional cases, only the latest version receives security fixes. Furthermore, breaking changes as well as new features are introduced regularly. We currently use a 0.y.z version number to indicate this fast pace of change.
Warning
Solidity recently released the 0.8.x version that introduced a lot of breaking changes. Make sure you read the full list.
Ideas for improving Solidity or this documentation are always welcome, read our contributors guide for more details.
Hint
You can download this documentation as PDF, HTML or Epub by clicking on the versions flyout menu in the bottom-left corner and selecting the preferred download format.
Getting Started
1. Understand the Smart Contract Basics
If you are new to the concept of smart contracts we recommend you to get started by digging into the “Introduction to Smart Contracts” section, which covers:
A simple example smart contract written in Solidity.
2. Get to Know Solidity
Once you are accustomed to the basics, we recommend you read the “Solidity by Example” and “Language Description” sections to understand the core concepts of the language.
3. Install the Solidity Compiler
There are various ways to install the Solidity compiler, simply choose your preferred option and follow the steps outlined on the installation page.
Hint
You can try out code examples directly in your browser with the Remix IDE. Remix is a web browser based IDE that allows you to write, deploy and administer Solidity smart contracts, without the need to install Solidity locally.
Warning
As humans write software, it can have bugs. You should follow established software development best-practices when writing your smart contracts. This includes code review, testing, audits, and correctness proofs. Smart contract users are sometimes more confident with code than their authors, and blockchains and smart contracts have their own unique issues to watch out for, so before working on production code, make sure you read the Security Considerations section.
4. Learn More
If you want to learn more about building decentralized applications on Ethereum, the Ethereum Developer Resources can help you with further general documentation around Ethereum, and a wide selection of tutorials, tools and development frameworks.
If you have any questions, you can try searching for answers or asking on the Ethereum StackExchange, or our Gitter channel.
Translations
Community contributors help translate this documentation into several languages. Note that they have varying degrees of completeness and up-to-dateness. The English version stands as a reference.
You can switch between languages by clicking on the flyout menu in the bottom-left corner and selecting the preferred language.
Note
We recently set up a new GitHub organization and translation workflow to help streamline the community efforts. Please refer to the translation guide for information on how to start a new language or contribute to the community translations.
Contents
Introduction to Smart Contracts
A Simple Smart Contract
Let us begin with a basic example that sets the value of a variable and exposes it for other contracts to access. It is fine if you do not understand everything right now, we will go into more details later.
Storage Example
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}
The first line tells you that the source code is licensed under the GPL version 3.0. Machine-readable license specifiers are important in a setting where publishing the source code is the default.
The next line specifies that the source code is written for Solidity version 0.4.16, or a newer version of the language up to, but not including version 0.9.0. This is to ensure that the contract is not compilable with a new (breaking) compiler version, where it could behave differently. Pragmas are common instructions for compilers about how to treat the source code (e.g. pragma once).
A contract in the sense of Solidity is a collection of code (its functions) and
data (its state) that resides at a specific address on the Ethereum
blockchain. The line uint storedData;
declares a state variable called storedData
of
type uint
(unsigned integer of 256 bits). You can think of it as a single slot
in a database that you can query and alter by calling functions of the
code that manages the database. In this example, the contract defines the
functions set
and get
that can be used to modify
or retrieve the value of the variable.
To access a member (like a state variable) of the current contract, you do not typically add the this.
prefix,
you just access it directly via its name.
Unlike in some other languages, omitting it is not just a matter of style,
it results in a completely different way to access the member, but more on this later.
This contract does not do much yet apart from (due to the infrastructure
built by Ethereum) allowing anyone to store a single number that is accessible by
anyone in the world without a (feasible) way to prevent you from publishing
this number. Anyone could call set
again with a different value
and overwrite your number, but the number is still stored in the history
of the blockchain. Later, you will see how you can impose access restrictions
so that only you can alter the number.
Warning
Be careful with using Unicode text, as similar looking (or even identical) characters can have different code points and as such are encoded as a different byte array.
Note
All identifiers (contract names, function names and variable names) are restricted to the ASCII character set. It is possible to store UTF-8 encoded data in string variables.
Subcurrency Example
The following contract implements the simplest form of a cryptocurrency. The contract allows only its creator to create new coins (different issuance schemes are possible). Anyone can send coins to each other without a need for registering with a username and password, all you need is an Ethereum keypair.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
// The keyword "public" makes variables
// accessible from other contracts
address public minter;
mapping (address => uint) public balances;
// Events allow clients to react to specific
// contract changes you declare
event Sent(address from, address to, uint amount);
// Constructor code is only run when the contract
// is created
constructor() {
minter = msg.sender;
}
// Sends an amount of newly created coins to an address
// Can only be called by the contract creator
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
// Errors allow you to provide information about
// why an operation failed. They are returned
// to the caller of the function.
error InsufficientBalance(uint requested, uint available);
// Sends an amount of existing coins
// from any caller to an address
function send(address receiver, uint amount) public {
if (amount > balances[msg.sender])
revert InsufficientBalance({
requested: amount,
available: balances[msg.sender]
});
balances[msg.sender] -= amount;
balances[receiver] += amount;
emit Sent(msg.sender, receiver, amount);
}
}
This contract introduces some new concepts, let us go through them one by one.
The line address public minter;
declares a state variable of type address.
The address
type is a 160-bit value that does not allow any arithmetic operations.
It is suitable for storing addresses of contracts, or a hash of the public half
of a keypair belonging to external accounts.
The keyword public
automatically generates a function that allows you to access the current value of the state
variable from outside of the contract. Without this keyword, other contracts have no way to access the variable.
The code of the function generated by the compiler is equivalent
to the following (ignore external
and view
for now):
function minter() external view returns (address) { return minter; }
You could add a function like the above yourself, but you would have a function and state variable with the same name. You do not need to do this, the compiler figures it out for you.
The next line, mapping (address => uint) public balances;
also
creates a public state variable, but it is a more complex datatype.
The mapping type maps addresses to unsigned integers.
Mappings can be seen as hash tables which are virtually initialised such that every possible key exists from the start and is mapped to a value whose byte-representation is all zeros. However, it is neither possible to obtain a list of all keys of a mapping, nor a list of all values. Record what you added to the mapping, or use it in a context where this is not needed. Or even better, keep a list, or use a more suitable data type.
The getter function created by the public
keyword
is more complex in the case of a mapping. It looks like the
following:
function balances(address account) external view returns (uint) {
return balances[account];
}
You can use this function to query the balance of a single account.
The line event Sent(address from, address to, uint amount);
declares
an “event”, which is emitted in the last line of the function
send
. Ethereum clients such as web applications can
listen for these events emitted on the blockchain without much
cost. As soon as it is emitted, the listener receives the
arguments from
, to
and amount
, which makes it possible to track
transactions.
To listen for this event, you could use the following
JavaScript code, which uses web3.js to create the Coin
contract object,
and any user interface calls the automatically generated balances
function from above:
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
})
The constructor is a special function that is executed during the creation of the contract and
cannot be called afterwards. In this case, it permanently stores the address of the person creating the
contract. The msg
variable (together with tx
and block
) is a
special global variable that
contains properties which allow access to the blockchain. msg.sender
is
always the address where the current (external) function call came from.
The functions that make up the contract, and that users and contracts can call are mint
and send
.
The mint
function sends an amount of newly created coins to another address. The require function call defines conditions that reverts all changes if not met. In this
example, require(msg.sender == minter);
ensures that only the creator of the contract can call
mint
. In general, the creator can mint as many tokens as they like, but at some point, this will
lead to a phenomenon called “overflow”. Note that because of the default Checked arithmetic, the transaction would revert if the expression balances[receiver] += amount;
overflows, i.e., when balances[receiver] + amount
in arbitrary precision arithmetic is larger
than the maximum value of uint
(2**256 - 1
). This is also true for the statement
balances[receiver] += amount;
in the function send
.
Errors allow you to provide more information to the caller about
why a condition or operation failed. Errors are used together with the
revert statement. The revert
statement unconditionally
aborts and reverts all changes similar to the require
function, but it also
allows you to provide the name of an error and additional data which will be supplied to the caller
(and eventually to the front-end application or block explorer) so that
a failure can more easily be debugged or reacted upon.
The send
function can be used by anyone (who already
has some of these coins) to send coins to anyone else. If the sender does not have
enough coins to send, the if
condition evaluates to true. As a result, the revert
will cause the operation to fail
while providing the sender with error details using the InsufficientBalance
error.
Note
If you use this contract to send coins to an address, you will not see anything when you look at that address on a blockchain explorer, because the record that you sent coins and the changed balances are only stored in the data storage of this particular coin contract. By using events, you can create a “blockchain explorer” that tracks transactions and balances of your new coin, but you have to inspect the coin contract address and not the addresses of the coin owners.
Blockchain Basics
Blockchains as a concept are not too hard to understand for programmers. The reason is that most of the complications (mining, hashing, elliptic-curve cryptography, peer-to-peer networks, etc.) are just there to provide a certain set of features and promises for the platform. Once you accept these features as given, you do not have to worry about the underlying technology - or do you have to know how Amazon’s AWS works internally in order to use it?
Transactions
A blockchain is a globally shared, transactional database. This means that everyone can read entries in the database just by participating in the network. If you want to change something in the database, you have to create a so-called transaction which has to be accepted by all others. The word transaction implies that the change you want to make (assume you want to change two values at the same time) is either not done at all or completely applied. Furthermore, while your transaction is being applied to the database, no other transaction can alter it.
As an example, imagine a table that lists the balances of all accounts in an electronic currency. If a transfer from one account to another is requested, the transactional nature of the database ensures that if the amount is subtracted from one account, it is always added to the other account. If due to whatever reason, adding the amount to the target account is not possible, the source account is also not modified.
Furthermore, a transaction is always cryptographically signed by the sender (creator). This makes it straightforward to guard access to specific modifications of the database. In the example of the electronic currency, a simple check ensures that only the person holding the keys to the account can transfer money from it.
Blocks
One major obstacle to overcome is what (in Bitcoin terms) is called a “double-spend attack”: What happens if two transactions exist in the network that both want to empty an account? Only one of the transactions can be valid, typically the one that is accepted first. The problem is that “first” is not an objective term in a peer-to-peer network.
The abstract answer to this is that you do not have to care. A globally accepted order of the transactions will be selected for you, solving the conflict. The transactions will be bundled into what is called a “block” and then they will be executed and distributed among all participating nodes. If two transactions contradict each other, the one that ends up being second will be rejected and not become part of the block.
These blocks form a linear sequence in time and that is where the word “blockchain” derives from. Blocks are added to the chain in rather regular intervals - for Ethereum this is roughly every 17 seconds.
As part of the “order selection mechanism” (which is called “mining”) it may happen that blocks are reverted from time to time, but only at the “tip” of the chain. The more blocks are added on top of a particular block, the less likely this block will be reverted. So it might be that your transactions are reverted and even removed from the blockchain, but the longer you wait, the less likely it will be.
Note
Transactions are not guaranteed to be included in the next block or any specific future block, since it is not up to the submitter of a transaction, but up to the miners to determine in which block the transaction is included.
If you want to schedule future calls of your contract, you can use a smart contract automation tool or an oracle service.
The Ethereum Virtual Machine
Overview
The Ethereum Virtual Machine or EVM is the runtime environment for smart contracts in Ethereum. It is not only sandboxed but actually completely isolated, which means that code running inside the EVM has no access to network, filesystem or other processes. Smart contracts even have limited access to other smart contracts.
Accounts
There are two kinds of accounts in Ethereum which share the same address space: External accounts that are controlled by public-private key pairs (i.e. humans) and contract accounts which are controlled by the code stored together with the account.
The address of an external account is determined from the public key while the address of a contract is determined at the time the contract is created (it is derived from the creator address and the number of transactions sent from that address, the so-called “nonce”).
Regardless of whether or not the account stores code, the two types are treated equally by the EVM.
Every account has a persistent key-value store mapping 256-bit words to 256-bit words called storage.
Furthermore, every account has a balance in
Ether (in “Wei” to be exact, 1 ether
is 10**18 wei
) which can be modified by sending transactions that
include Ether.
Transactions
A transaction is a message that is sent from one account to another account (which might be the same or empty, see below). It can include binary data (which is called “payload”) and Ether.
If the target account contains code, that code is executed and the payload is provided as input data.
If the target account is not set (the transaction does not have
a recipient or the recipient is set to null
), the transaction
creates a new contract.
As already mentioned, the address of that contract is not
the zero address but an address derived from the sender and
its number of transactions sent (the “nonce”). The payload
of such a contract creation transaction is taken to be
EVM bytecode and executed. The output data of this execution is
permanently stored as the code of the contract.
This means that in order to create a contract, you do not
send the actual code of the contract, but in fact code that
returns that code when executed.
Note
While a contract is being created, its code is still empty. Because of that, you should not call back into the contract under construction until its constructor has finished executing.
Gas
Upon creation, each transaction is charged with a certain amount of gas
that has to be paid for by the originator of the transaction (tx.origin
).
While the EVM executes the
transaction, the gas is gradually depleted according to specific rules.
If the gas is used up at any point (i.e. it would be negative),
an out-of-gas exception is triggered, which ends execution and reverts all modifications
made to the state in the current call frame.
This mechanism incentivizes economical use of EVM execution time and also compensates EVM executors (i.e. miners / stakers) for their work. Since each block has a maximum amount of gas, it also limits the amount of work needed to validate a block.
The gas price is a value set by the originator of the transaction, who
has to pay gas_price * gas
up front to the EVM executor.
If some gas is left after execution, it is refunded to the transaction originator.
In case of an exception that reverts changes, already used up gas is not refunded.
Since EVM executors can choose to include a transaction or not, transaction senders cannot abuse the system by setting a low gas price.
Storage, Memory and the Stack
The Ethereum Virtual Machine has three areas where it can store data: storage, memory and the stack.
Each account has a data area called storage, which is persistent between function calls and transactions. Storage is a key-value store that maps 256-bit words to 256-bit words. It is not possible to enumerate storage from within a contract, it is comparatively costly to read, and even more to initialise and modify storage. Because of this cost, you should minimize what you store in persistent storage to what the contract needs to run. Store data like derived calculations, caching, and aggregates outside of the contract. A contract can neither read nor write to any storage apart from its own.
The second data area is called memory, of which a contract obtains a freshly cleared instance for each message call. Memory is linear and can be addressed at byte level, but reads are limited to a width of 256 bits, while writes can be either 8 bits or 256 bits wide. Memory is expanded by a word (256-bit), when accessing (either reading or writing) a previously untouched memory word (i.e. any offset within a word). At the time of expansion, the cost in gas must be paid. Memory is more costly the larger it grows (it scales quadratically).
The EVM is not a register machine but a stack machine, so all computations are performed on a data area called the stack. It has a maximum size of 1024 elements and contains words of 256 bits. Access to the stack is limited to the top end in the following way: It is possible to copy one of the topmost 16 elements to the top of the stack or swap the topmost element with one of the 16 elements below it. All other operations take the topmost two (or one, or more, depending on the operation) elements from the stack and push the result onto the stack. Of course it is possible to move stack elements to storage or memory in order to get deeper access to the stack, but it is not possible to just access arbitrary elements deeper in the stack without first removing the top of the stack.
Instruction Set
The instruction set of the EVM is kept minimal in order to avoid incorrect or inconsistent implementations which could cause consensus problems. All instructions operate on the basic data type, 256-bit words or on slices of memory (or other byte arrays). The usual arithmetic, bit, logical and comparison operations are present. Conditional and unconditional jumps are possible. Furthermore, contracts can access relevant properties of the current block like its number and timestamp.
For a complete list, please see the list of opcodes as part of the inline assembly documentation.
Message Calls
Contracts can call other contracts or send Ether to non-contract accounts by the means of message calls. Message calls are similar to transactions, in that they have a source, a target, data payload, Ether, gas and return data. In fact, every transaction consists of a top-level message call which in turn can create further message calls.
A contract can decide how much of its remaining gas should be sent with the inner message call and how much it wants to retain. If an out-of-gas exception happens in the inner call (or any other exception), this will be signaled by an error value put onto the stack. In this case, only the gas sent together with the call is used up. In Solidity, the calling contract causes a manual exception by default in such situations, so that exceptions “bubble up” the call stack.
As already said, the called contract (which can be the same as the caller) will receive a freshly cleared instance of memory and has access to the call payload - which will be provided in a separate area called the calldata. After it has finished execution, it can return data which will be stored at a location in the caller’s memory preallocated by the caller. All such calls are fully synchronous.
Calls are limited to a depth of 1024, which means that for more complex operations, loops should be preferred over recursive calls. Furthermore, only 63/64th of the gas can be forwarded in a message call, which causes a depth limit of a little less than 1000 in practice.
Delegatecall / Callcode and Libraries
There exists a special variant of a message call, named delegatecall
which is identical to a message call apart from the fact that
the code at the target address is executed in the context (i.e. at the address) of the calling
contract and msg.sender
and msg.value
do not change their values.
This means that a contract can dynamically load code from a different address at runtime. Storage, current address and balance still refer to the calling contract, only the code is taken from the called address.
This makes it possible to implement the “library” feature in Solidity: Reusable library code that can be applied to a contract’s storage, e.g. in order to implement a complex data structure.
Logs
It is possible to store data in a specially indexed data structure that maps all the way up to the block level. This feature called logs is used by Solidity in order to implement events. Contracts cannot access log data after it has been created, but they can be efficiently accessed from outside the blockchain. Since some part of the log data is stored in bloom filters, it is possible to search for this data in an efficient and cryptographically secure way, so network peers that do not download the whole blockchain (so-called “light clients”) can still find these logs.
Create
Contracts can even create other contracts using a special opcode (i.e. they do not simply call the zero address as a transaction would). The only difference between these create calls and normal message calls is that the payload data is executed and the result stored as code and the caller / creator receives the address of the new contract on the stack.
Deactivate and Self-destruct
The only way to remove code from the blockchain is when a contract at that
address performs the selfdestruct
operation. The remaining Ether stored
at that address is sent to a designated target and then the storage and code
is removed from the state. Removing the contract in theory sounds like a good
idea, but it is potentially dangerous, as if someone sends Ether to removed
contracts, the Ether is forever lost.
Warning
Even if a contract is removed by selfdestruct
, it is still part of the
history of the blockchain and probably retained by most Ethereum nodes.
So using selfdestruct
is not the same as deleting data from a hard disk.
Note
Even if a contract’s code does not contain a call to selfdestruct
,
it can still perform that operation using delegatecall
or callcode
.
If you want to deactivate your contracts, you should instead disable them by changing some internal state which causes all functions to revert. This makes it impossible to use the contract, as it returns Ether immediately.
Precompiled Contracts
There is a small set of contract addresses that are special:
The address range between 1
and (including) 8
contains
“precompiled contracts” that can be called as any other contract
but their behaviour (and their gas consumption) is not defined
by EVM code stored at that address (they do not contain code)
but instead is implemented in the EVM execution environment itself.
Different EVM-compatible chains might use a different set of
precompiled contracts. It might also be possible that new
precompiled contracts are added to the Ethereum main chain in the future,
but you can reasonably expect them to always be in the range between
1
and 0xffff
(inclusive).
Installing the Solidity Compiler
Versioning
Solidity versions follow Semantic Versioning. In addition, patch level releases with major release 0 (i.e. 0.x.y) will not contain breaking changes. That means code that compiles with version 0.x.y can be expected to compile with 0.x.z where z > y.
In addition to releases, we provide nightly development builds with the intention of making it easy for developers to try out upcoming features and provide early feedback. Note, however, that while the nightly builds are usually very stable, they contain bleeding-edge code from the development branch and are not guaranteed to be always working. Despite our best efforts, they might contain undocumented and/or broken changes that will not become a part of an actual release. They are not meant for production use.
When deploying contracts, you should use the latest released version of Solidity. This is because breaking changes, as well as new features and bug fixes are introduced regularly. We currently use a 0.x version number to indicate this fast pace of change.
Remix
We recommend Remix for small contracts and for quickly learning Solidity.
Access Remix online, you do not need to install anything.
If you want to use it without connection to the Internet, go to
https://github.com/ethereum/remix-live/tree/gh-pages and download the .zip
file as
explained on that page. Remix is also a convenient option for testing nightly builds
without installing multiple Solidity versions.
Further options on this page detail installing commandline Solidity compiler software on your computer. Choose a commandline compiler if you are working on a larger contract or if you require more compilation options.
npm / Node.js
Use npm
for a convenient and portable way to install solcjs
, a Solidity compiler. The
solcjs program has fewer features than the ways to access the compiler described
further down this page. The
Using the Commandline Compiler documentation assumes you are using
the full-featured compiler, solc
. The usage of solcjs
is documented inside its own
repository.
Note: The solc-js project is derived from the C++ solc by using Emscripten which means that both use the same compiler source code. solc-js can be used in JavaScript projects directly (such as Remix). Please refer to the solc-js repository for instructions.
npm install -g solc
Note
The commandline executable is named solcjs
.
The commandline options of solcjs
are not compatible with solc
and tools (such as geth
)
expecting the behaviour of solc
will not work with solcjs
.
Docker
Docker images of Solidity builds are available using the solc
image from the ethereum
organisation.
Use the stable
tag for the latest released version, and nightly
for potentially unstable changes in the develop branch.
The Docker image runs the compiler executable, so you can pass all compiler arguments to it.
For example, the command below pulls the stable version of the solc
image (if you do not have it already),
and runs it in a new container, passing the --help
argument.
docker run ethereum/solc:stable --help
You can also specify release build versions in the tag, for example, for the 0.5.4 release.
docker run ethereum/solc:0.5.4 --help
To use the Docker image to compile Solidity files on the host machine mount a local folder for input and output, and specify the contract to compile. For example.
docker run -v /local/path:/sources ethereum/solc:stable -o /sources/output --abi --bin /sources/Contract.sol
You can also use the standard JSON interface (which is recommended when using the compiler with tooling). When using this interface it is not necessary to mount any directories as long as the JSON input is self-contained (i.e. it does not refer to any external files that would have to be loaded by the import callback).
docker run ethereum/solc:stable --standard-json < input.json > output.json
Linux Packages
Binary packages of Solidity are available at solidity/releases.
We also have PPAs for Ubuntu, you can get the latest stable version using the following commands:
sudo add-apt-repository ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install solc
The nightly version can be installed using these commands:
sudo add-apt-repository ppa:ethereum/ethereum
sudo add-apt-repository ppa:ethereum/ethereum-dev
sudo apt-get update
sudo apt-get install solc
Furthermore, some Linux distributions provide their own packages. These packages are not directly maintained by us, but usually kept up-to-date by the respective package maintainers.
For example, Arch Linux has packages for the latest development version:
pacman -S solidity
There is also a snap package, however, it is currently unmaintained. It is installable in all the supported Linux distros. To install the latest stable version of solc:
sudo snap install solc
If you want to help testing the latest development version of Solidity with the most recent changes, please use the following:
sudo snap install solc --edge
Note
The solc
snap uses strict confinement. This is the most secure mode for snap packages
but it comes with limitations, like accessing only the files in your /home
and /media
directories.
For more information, go to Demystifying Snap Confinement.
macOS Packages
We distribute the Solidity compiler through Homebrew as a build-from-source version. Pre-built bottles are currently not supported.
brew update
brew upgrade
brew tap ethereum/ethereum
brew install solidity
To install the most recent 0.4.x / 0.5.x version of Solidity you can also use brew install solidity@4
and brew install solidity@5
, respectively.
If you need a specific version of Solidity you can install a Homebrew formula directly from Github.
View solidity.rb commits on Github.
Copy the commit hash of the version you want and check it out on your machine.
git clone https://github.com/ethereum/homebrew-ethereum.git
cd homebrew-ethereum
git checkout <your-hash-goes-here>
Install it using brew
:
brew unlink solidity
# eg. Install 0.4.8
brew install solidity.rb
Static Binaries
We maintain a repository containing static builds of past and current compiler versions for all supported platforms at solc-bin. This is also the location where you can find the nightly builds.
The repository is not only a quick and easy way for end users to get binaries ready to be used out-of-the-box but it is also meant to be friendly to third-party tools:
The content is mirrored to https://binaries.soliditylang.org where it can be easily downloaded over HTTPS without any authentication, rate limiting or the need to use git.
Content is served with correct Content-Type headers and lenient CORS configuration so that it can be directly loaded by tools running in the browser.
Binaries do not require installation or unpacking (with the exception of older Windows builds bundled with necessary DLLs).
We strive for a high level of backwards-compatibility. Files, once added, are not removed or moved without providing a symlink/redirect at the old location. They are also never modified in place and should always match the original checksum. The only exception would be broken or unusable files with a potential to cause more harm than good if left as is.
Files are served over both HTTP and HTTPS. As long as you obtain the file list in a secure way (via git, HTTPS, IPFS or just have it cached locally) and verify hashes of the binaries after downloading them, you do not have to use HTTPS for the binaries themselves.
The same binaries are in most cases available on the Solidity release page on Github. The
difference is that we do not generally update old releases on the Github release page. This means
that we do not rename them if the naming convention changes and we do not add builds for platforms
that were not supported at the time of release. This only happens in solc-bin
.
The solc-bin
repository contains several top-level directories, each representing a single platform.
Each one contains a list.json
file listing the available binaries. For example in
emscripten-wasm32/list.json
you will find the following information about version 0.7.4:
{
"path": "solc-emscripten-wasm32-v0.7.4+commit.3f05b770.js",
"version": "0.7.4",
"build": "commit.3f05b770",
"longVersion": "0.7.4+commit.3f05b770",
"keccak256": "0x300330ecd127756b824aa13e843cb1f43c473cb22eaf3750d5fb9c99279af8c3",
"sha256": "0x2b55ed5fec4d9625b6c7b3ab1abd2b7fb7dd2a9c68543bf0323db2c7e2d55af2",
"urls": [
"bzzr://16c5f09109c793db99fe35f037c6092b061bd39260ee7a677c8a97f18c955ab1",
"dweb:/ipfs/QmTLs5MuLEWXQkths41HiACoXDiH8zxyqBHGFDRSzVE5CS"
]
}
This means that:
You can find the binary in the same directory under the name solc-emscripten-wasm32-v0.7.4+commit.3f05b770.js. Note that the file might be a symlink, and you will need to resolve it yourself if you are not using git to download it or your file system does not support symlinks.
The binary is also mirrored at https://binaries.soliditylang.org/emscripten-wasm32/solc-emscripten-wasm32-v0.7.4+commit.3f05b770.js. In this case git is not necessary and symlinks are resolved transparently, either by serving a copy of the file or returning a HTTP redirect.
The file is also available on IPFS at QmTLs5MuLEWXQkths41HiACoXDiH8zxyqBHGFDRSzVE5CS.
The file might in future be available on Swarm at 16c5f09109c793db99fe35f037c6092b061bd39260ee7a677c8a97f18c955ab1.
You can verify the integrity of the binary by comparing its keccak256 hash to
0x300330ecd127756b824aa13e843cb1f43c473cb22eaf3750d5fb9c99279af8c3
. The hash can be computed on the command line usingkeccak256sum
utility provided by sha3sum or keccak256() function from ethereumjs-util in JavaScript.You can also verify the integrity of the binary by comparing its sha256 hash to
0x2b55ed5fec4d9625b6c7b3ab1abd2b7fb7dd2a9c68543bf0323db2c7e2d55af2
.
Warning
Due to the strong backwards compatibility requirement the repository contains some legacy elements but you should avoid using them when writing new tools:
Use
emscripten-wasm32/
(with a fallback toemscripten-asmjs/
) instead ofbin/
if you want the best performance. Until version 0.6.1 we only provided asm.js binaries. Starting with 0.6.2 we switched to WebAssembly builds with much better performance. We have rebuilt the older versions for wasm but the original asm.js files remain inbin/
. The new ones had to be placed in a separate directory to avoid name clashes.Use
emscripten-asmjs/
andemscripten-wasm32/
instead ofbin/
andwasm/
directories if you want to be sure whether you are downloading a wasm or an asm.js binary.Use
list.json
instead oflist.js
andlist.txt
. The JSON list format contains all the information from the old ones and more.Use https://binaries.soliditylang.org instead of https://solc-bin.ethereum.org. To keep things simple we moved almost everything related to the compiler under the new
soliditylang.org
domain and this applies tosolc-bin
too. While the new domain is recommended, the old one is still fully supported and guaranteed to point at the same location.
Warning
The binaries are also available at https://ethereum.github.io/solc-bin/ but this page stopped being updated just after the release of version 0.7.2, will not receive any new releases or nightly builds for any platform and does not serve the new directory structure, including non-emscripten builds.
If you are using it, please switch to https://binaries.soliditylang.org, which is a drop-in
replacement. This allows us to make changes to the underlying hosting in a transparent way and
minimize disruption. Unlike the ethereum.github.io
domain, which we do not have any control
over, binaries.soliditylang.org
is guaranteed to work and maintain the same URL structure
in the long-term.
Building from Source
Prerequisites - All Operating Systems
The following are dependencies for all builds of Solidity:
Software |
Notes |
---|---|
CMake (version 3.13+) |
Cross-platform build file generator. |
Boost (version 1.77+ on Windows, 1.65+ otherwise) |
C++ libraries. |
Command-line tool for retrieving source code. |
|
z3 (version 4.8+, Optional) |
For use with SMT checker. |
cvc4 (Optional) |
For use with SMT checker. |
Note
Solidity versions prior to 0.5.10 can fail to correctly link against Boost versions 1.70+.
A possible workaround is to temporarily rename <Boost install path>/lib/cmake/Boost-1.70.0
prior to running the cmake command to configure solidity.
Starting from 0.5.10 linking against Boost 1.70+ should work without manual intervention.
Note
The default build configuration requires a specific Z3 version (the latest one at the time the
code was last updated). Changes introduced between Z3 releases often result in slightly different
(but still valid) results being returned. Our SMT tests do not account for these differences and
will likely fail with a different version than the one they were written for. This does not mean
that a build using a different version is faulty. If you pass -DSTRICT_Z3_VERSION=OFF
option
to CMake, you can build with any version that satisfies the requirement given in the table above.
If you do this, however, please remember to pass the --no-smt
option to scripts/tests.sh
to skip the SMT tests.
Minimum Compiler Versions
The following C++ compilers and their minimum versions can build the Solidity codebase:
Prerequisites - macOS
For macOS builds, ensure that you have the latest version of Xcode installed. This contains the Clang C++ compiler, the Xcode IDE and other Apple development tools that are required for building C++ applications on OS X. If you are installing Xcode for the first time, or have just installed a new version then you will need to agree to the license before you can do command-line builds:
sudo xcodebuild -license accept
Our OS X build script uses the Homebrew package manager for installing external dependencies. Here’s how to uninstall Homebrew, if you ever want to start again from scratch.
Prerequisites - Windows
You need to install the following dependencies for Windows builds of Solidity:
Software |
Notes |
---|---|
C++ compiler |
|
Visual Studio 2019 (Optional) |
C++ compiler and dev environment. |
Boost (version 1.77+) |
C++ libraries. |
If you already have one IDE and only need the compiler and libraries, you could install Visual Studio 2019 Build Tools.
Visual Studio 2019 provides both IDE and necessary compiler and libraries. So if you have not got an IDE and prefer to develop Solidity, Visual Studio 2019 may be a choice for you to get everything setup easily.
Here is the list of components that should be installed in Visual Studio 2019 Build Tools or Visual Studio 2019:
Visual Studio C++ core features
VC++ 2019 v141 toolset (x86,x64)
Windows Universal CRT SDK
Windows 8.1 SDK
C++/CLI support
We have a helper script which you can use to install all required external dependencies:
scripts\install_deps.ps1
This will install boost
and cmake
to the deps
subdirectory.
Clone the Repository
To clone the source code, execute the following command:
git clone --recursive https://github.com/ethereum/solidity.git
cd solidity
If you want to help developing Solidity, you should fork Solidity and add your personal fork as a second remote:
git remote add personal git@github.com:[username]/solidity.git
Note
This method will result in a prerelease build leading to e.g. a flag being set in each bytecode produced by such a compiler. If you want to re-build a released Solidity compiler, then please use the source tarball on the github release page:
https://github.com/ethereum/solidity/releases/download/v0.X.Y/solidity_0.X.Y.tar.gz
(not the “Source code” provided by github).
Command-Line Build
Be sure to install External Dependencies (see above) before build.
Solidity project uses CMake to configure the build. You might want to install ccache to speed up repeated builds. CMake will pick it up automatically. Building Solidity is quite similar on Linux, macOS and other Unices:
mkdir build
cd build
cmake .. && make
or even easier on Linux and macOS, you can run:
#note: this will install binaries solc and soltest at usr/local/bin
./scripts/build.sh
Warning
BSD builds should work, but are untested by the Solidity team.
And for Windows:
mkdir build
cd build
cmake -G "Visual Studio 16 2019" ..
In case you want to use the version of boost installed by scripts\install_deps.ps1
, you will
additionally need to pass -DBoost_DIR="deps\boost\lib\cmake\Boost-*"
and -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded
as arguments to the call to cmake
.
This should result in the creation of solidity.sln in that build directory. Double-clicking on that file should result in Visual Studio firing up. We suggest building Release configuration, but all others work.
Alternatively, you can build for Windows on the command-line, like so:
cmake --build . --config Release
CMake Options
If you are interested what CMake options are available run cmake .. -LH
.
SMT Solvers
Solidity can be built against SMT solvers and will do so by default if they are found in the system. Each solver can be disabled by a cmake option.
Note: In some cases, this can also be a potential workaround for build failures.
Inside the build folder you can disable them, since they are enabled by default:
# disables only Z3 SMT Solver.
cmake .. -DUSE_Z3=OFF
# disables only CVC4 SMT Solver.
cmake .. -DUSE_CVC4=OFF
# disables both Z3 and CVC4
cmake .. -DUSE_CVC4=OFF -DUSE_Z3=OFF
The Version String in Detail
The Solidity version string contains four parts:
the version number
pre-release tag, usually set to
develop.YYYY.MM.DD
ornightly.YYYY.MM.DD
commit in the format of
commit.GITHASH
platform, which has an arbitrary number of items, containing details about the platform and compiler
If there are local modifications, the commit will be postfixed with .mod
.
These parts are combined as required by SemVer, where the Solidity pre-release tag equals to the SemVer pre-release and the Solidity commit and platform combined make up the SemVer build metadata.
A release example: 0.4.8+commit.60cc1668.Emscripten.clang
.
A pre-release example: 0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang
Important Information About Versioning
After a release is made, the patch version level is bumped, because we assume that only
patch level changes follow. When changes are merged, the version should be bumped according
to SemVer and the severity of the change. Finally, a release is always made with the version
of the current nightly build, but without the prerelease
specifier.
Example:
The 0.4.0 release is made.
The nightly build has a version of 0.4.1 from now on.
Non-breaking changes are introduced –> no change in version.
A breaking change is introduced –> version is bumped to 0.5.0.
The 0.5.0 release is made.
This behaviour works well with the version pragma.
Solidity by Example
Voting
The following contract is quite complex, but showcases a lot of Solidity’s features. It implements a voting contract. Of course, the main problems of electronic voting is how to assign voting rights to the correct persons and how to prevent manipulation. We will not solve all problems here, but at least we will show how delegated voting can be done so that vote counting is automatic and completely transparent at the same time.
The idea is to create one contract per ballot, providing a short name for each option. Then the creator of the contract who serves as chairperson will give the right to vote to each address individually.
The persons behind the addresses can then choose to either vote themselves or to delegate their vote to a person they trust.
At the end of the voting time, winningProposal()
will return the proposal with the largest number
of votes.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
/// @title Voting with delegation.
contract Ballot {
// This declares a new complex type which will
// be used for variables later.
// It will represent a single voter.
struct Voter {
uint weight; // weight is accumulated by delegation
bool voted; // if true, that person already voted
address delegate; // person delegated to
uint vote; // index of the voted proposal
}
// This is a type for a single proposal.
struct Proposal {
bytes32 name; // short name (up to 32 bytes)
uint voteCount; // number of accumulated votes
}
address public chairperson;
// This declares a state variable that
// stores a `Voter` struct for each possible address.
mapping(address => Voter) public voters;
// A dynamically-sized array of `Proposal` structs.
Proposal[] public proposals;
/// Create a new ballot to choose one of `proposalNames`.
constructor(bytes32[] memory proposalNames) {
chairperson = msg.sender;
voters[chairperson].weight = 1;
// For each of the provided proposal names,
// create a new proposal object and add it
// to the end of the array.
for (uint i = 0; i < proposalNames.length; i++) {
// `Proposal({...})` creates a temporary
// Proposal object and `proposals.push(...)`
// appends it to the end of `proposals`.
proposals.push(Proposal({
name: proposalNames[i],
voteCount: 0
}));
}
}
// Give `voter` the right to vote on this ballot.
// May only be called by `chairperson`.
function giveRightToVote(address voter) external {
// If the first argument of `require` evaluates
// to `false`, execution terminates and all
// changes to the state and to Ether balances
// are reverted.
// This used to consume all gas in old EVM versions, but
// not anymore.
// It is often a good idea to use `require` to check if
// functions are called correctly.
// As a second argument, you can also provide an
// explanation about what went wrong.
require(
msg.sender == chairperson,
"Only chairperson can give right to vote."
);
require(
!voters[voter].voted,
"The voter already voted."
);
require(voters[voter].weight == 0);
voters[voter].weight = 1;
}
/// Delegate your vote to the voter `to`.
function delegate(address to) external {
// assigns reference
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "You have no right to vote");
require(!sender.voted, "You already voted.");
require(to != msg.sender, "Self-delegation is disallowed.");
// Forward the delegation as long as
// `to` also delegated.
// In general, such loops are very dangerous,
// because if they run too long, they might
// need more gas than is available in a block.
// In this case, the delegation will not be executed,
// but in other situations, such loops might
// cause a contract to get "stuck" completely.
while (voters[to].delegate != address(0)) {
to = voters[to].delegate;
// We found a loop in the delegation, not allowed.
require(to != msg.sender, "Found loop in delegation.");
}
// Since `sender` is a reference, this
// modifies `voters[msg.sender].voted`
Voter storage delegate_ = voters[to];
// Voters cannot delegate to accounts that cannot vote.
require(delegate_.weight >= 1);
sender.voted = true;
sender.delegate = to;
if (delegate_.voted) {
// If the delegate already voted,
// directly add to the number of votes
proposals[delegate_.vote].voteCount += sender.weight;
} else {
// If the delegate did not vote yet,
// add to her weight.
delegate_.weight += sender.weight;
}
}
/// Give your vote (including votes delegated to you)
/// to proposal `proposals[proposal].name`.
function vote(uint proposal) external {
Voter storage sender = voters[msg.sender];
require(sender.weight != 0, "Has no right to vote");
require(!sender.voted, "Already voted.");
sender.voted = true;
sender.vote = proposal;
// If `proposal` is out of the range of the array,
// this will throw automatically and revert all
// changes.
proposals[proposal].voteCount += sender.weight;
}
/// @dev Computes the winning proposal taking all
/// previous votes into account.
function winningProposal() public view
returns (uint winningProposal_)
{
uint winningVoteCount = 0;
for (uint p = 0; p < proposals.length; p++) {
if (proposals[p].voteCount > winningVoteCount) {
winningVoteCount = proposals[p].voteCount;
winningProposal_ = p;
}
}
}
// Calls winningProposal() function to get the index
// of the winner contained in the proposals array and then
// returns the name of the winner
function winnerName() external view
returns (bytes32 winnerName_)
{
winnerName_ = proposals[winningProposal()].name;
}
}
Possible Improvements
Currently, many transactions are needed to assign the rights to vote to all participants. Can you think of a better way?
Blind Auction
In this section, we will show how easy it is to create a completely blind auction contract on Ethereum. We will start with an open auction where everyone can see the bids that are made and then extend this contract into a blind auction where it is not possible to see the actual bid until the bidding period ends.
Simple Open Auction
The general idea of the following simple auction contract is that everyone can send their bids during a bidding period. The bids already include sending money / Ether in order to bind the bidders to their bid. If the highest bid is raised, the previous highest bidder gets their money back. After the end of the bidding period, the contract has to be called manually for the beneficiary to receive their money - contracts cannot activate themselves.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract SimpleAuction {
// Parameters of the auction. Times are either
// absolute unix timestamps (seconds since 1970-01-01)
// or time periods in seconds.
address payable public beneficiary;
uint public auctionEndTime;
// Current state of the auction.
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
// Set to true at the end, disallows any change.
// By default initialized to `false`.
bool ended;
// Events that will be emitted on changes.
event HighestBidIncreased(address bidder, uint amount);
event AuctionEnded(address winner, uint amount);
// Errors that describe failures.
// The triple-slash comments are so-called natspec
// comments. They will be shown when the user
// is asked to confirm a transaction or
// when an error is displayed.
/// The auction has already ended.
error AuctionAlreadyEnded();
/// There is already a higher or equal bid.
error BidNotHighEnough(uint highestBid);
/// The auction has not ended yet.
error AuctionNotYetEnded();
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
/// Create a simple auction with `biddingTime`
/// seconds bidding time on behalf of the
/// beneficiary address `beneficiaryAddress`.
constructor(
uint biddingTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
auctionEndTime = block.timestamp + biddingTime;
}
/// Bid on the auction with the value sent
/// together with this transaction.
/// The value will only be refunded if the
/// auction is not won.
function bid() external payable {
// No arguments are necessary, all
// information is already part of
// the transaction. The keyword payable
// is required for the function to
// be able to receive Ether.
// Revert the call if the bidding
// period is over.
if (block.timestamp > auctionEndTime)
revert AuctionAlreadyEnded();
// If the bid is not higher, send the
// money back (the revert statement
// will revert all changes in this
// function execution including
// it having received the money).
if (msg.value <= highestBid)
revert BidNotHighEnough(highestBid);
if (highestBid != 0) {
// Sending back the money by simply using
// highestBidder.send(highestBid) is a security risk
// because it could execute an untrusted contract.
// It is always safer to let the recipients
// withdraw their money themselves.
pendingReturns[highestBidder] += highestBid;
}
highestBidder = msg.sender;
highestBid = msg.value;
emit HighestBidIncreased(msg.sender, msg.value);
}
/// Withdraw a bid that was overbid.
function withdraw() external returns (bool) {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `send` returns.
pendingReturns[msg.sender] = 0;
// msg.sender is not of type `address payable` and must be
// explicitly converted using `payable(msg.sender)` in order
// use the member function `send()`.
if (!payable(msg.sender).send(amount)) {
// No need to call throw here, just reset the amount owing
pendingReturns[msg.sender] = amount;
return false;
}
}
return true;
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd() external {
// It is a good guideline to structure functions that interact
// with other contracts (i.e. they call functions or send Ether)
// into three phases:
// 1. checking conditions
// 2. performing actions (potentially changing conditions)
// 3. interacting with other contracts
// If these phases are mixed up, the other contract could call
// back into the current contract and modify the state or cause
// effects (ether payout) to be performed multiple times.
// If functions called internally include interaction with external
// contracts, they also have to be considered interaction with
// external contracts.
// 1. Conditions
if (block.timestamp < auctionEndTime)
revert AuctionNotYetEnded();
if (ended)
revert AuctionEndAlreadyCalled();
// 2. Effects
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 3. Interaction
beneficiary.transfer(highestBid);
}
}
Blind Auction
The previous open auction is extended to a blind auction in the following. The advantage of a blind auction is that there is no time pressure towards the end of the bidding period. Creating a blind auction on a transparent computing platform might sound like a contradiction, but cryptography comes to the rescue.
During the bidding period, a bidder does not actually send their bid, but only a hashed version of it. Since it is currently considered practically impossible to find two (sufficiently long) values whose hash values are equal, the bidder commits to the bid by that. After the end of the bidding period, the bidders have to reveal their bids: They send their values unencrypted and the contract checks that the hash value is the same as the one provided during the bidding period.
Another challenge is how to make the auction binding and blind at the same time: The only way to prevent the bidder from just not sending the money after they won the auction is to make them send it together with the bid. Since value transfers cannot be blinded in Ethereum, anyone can see the value.
The following contract solves this problem by accepting any value that is larger than the highest bid. Since this can of course only be checked during the reveal phase, some bids might be invalid, and this is on purpose (it even provides an explicit flag to place invalid bids with high value transfers): Bidders can confuse competition by placing several high or low invalid bids.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address payable public beneficiary;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// Allowed withdrawals of previous bids
mapping(address => uint) pendingReturns;
event AuctionEnded(address winner, uint highestBid);
// Errors that describe failures.
/// The function has been called too early.
/// Try again at `time`.
error TooEarly(uint time);
/// The function has been called too late.
/// It cannot be called after `time`.
error TooLate(uint time);
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
// Modifiers are a convenient way to validate inputs to
// functions. `onlyBefore` is applied to `bid` below:
// The new function body is the modifier's body where
// `_` is replaced by the old function body.
modifier onlyBefore(uint time) {
if (block.timestamp >= time) revert TooLate(time);
_;
}
modifier onlyAfter(uint time) {
if (block.timestamp <= time) revert TooEarly(time);
_;
}
constructor(
uint biddingTime,
uint revealTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
biddingEnd = block.timestamp + biddingTime;
revealEnd = biddingEnd + revealTime;
}
/// Place a blinded bid with `blindedBid` =
/// keccak256(abi.encodePacked(value, fake, secret)).
/// The sent ether is only refunded if the bid is correctly
/// revealed in the revealing phase. The bid is valid if the
/// ether sent together with the bid is at least "value" and
/// "fake" is not true. Setting "fake" to true and sending
/// not the exact amount are ways to hide the real bid but
/// still make the required deposit. The same address can
/// place multiple bids.
function bid(bytes32 blindedBid)
external
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: blindedBid,
deposit: msg.value
}));
}
/// Reveal your blinded bids. You will get a refund for all
/// correctly blinded invalid bids and for all bids except for
/// the totally highest.
function reveal(
uint[] calldata values,
bool[] calldata fakes,
bytes32[] calldata secrets
)
external
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(values.length == length);
require(fakes.length == length);
require(secrets.length == length);
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bidToCheck = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(values[i], fakes[i], secrets[i]);
if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
// Bid was not actually revealed.
// Do not refund deposit.
continue;
}
refund += bidToCheck.deposit;
if (!fake && bidToCheck.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// Make it impossible for the sender to re-claim
// the same deposit.
bidToCheck.blindedBid = bytes32(0);
}
payable(msg.sender).transfer(refund);
}
/// Withdraw a bid that was overbid.
function withdraw() external {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// It is important to set this to zero because the recipient
// can call this function again as part of the receiving call
// before `transfer` returns (see the remark above about
// conditions -> effects -> interaction).
pendingReturns[msg.sender] = 0;
payable(msg.sender).transfer(amount);
}
}
/// End the auction and send the highest bid
/// to the beneficiary.
function auctionEnd()
external
onlyAfter(revealEnd)
{
if (ended) revert AuctionEndAlreadyCalled();
emit AuctionEnded(highestBidder, highestBid);
ended = true;
beneficiary.transfer(highestBid);
}
// This is an "internal" function which means that it
// can only be called from the contract itself (or from
// derived contracts).
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// Refund the previously highest bidder.
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
}
Safe Remote Purchase
Purchasing goods remotely currently requires multiple parties that need to trust each other. The simplest configuration involves a seller and a buyer. The buyer would like to receive an item from the seller and the seller would like to get money (or an equivalent) in return. The problematic part is the shipment here: There is no way to determine for sure that the item arrived at the buyer.
There are multiple ways to solve this problem, but all fall short in one or the other way. In the following example, both parties have to put twice the value of the item into the contract as escrow. As soon as this happened, the money will stay locked inside the contract until the buyer confirms that they received the item. After that, the buyer is returned the value (half of their deposit) and the seller gets three times the value (their deposit plus the value). The idea behind this is that both parties have an incentive to resolve the situation or otherwise their money is locked forever.
This contract of course does not solve the problem, but gives an overview of how you can use state machine-like constructs inside a contract.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Purchase {
uint public value;
address payable public seller;
address payable public buyer;
enum State { Created, Locked, Release, Inactive }
// The state variable has a default value of the first member, `State.created`
State public state;
modifier condition(bool condition_) {
require(condition_);
_;
}
/// Only the buyer can call this function.
error OnlyBuyer();
/// Only the seller can call this function.
error OnlySeller();
/// The function cannot be called at the current state.
error InvalidState();
/// The provided value has to be even.
error ValueNotEven();
modifier onlyBuyer() {
if (msg.sender != buyer)
revert OnlyBuyer();
_;
}
modifier onlySeller() {
if (msg.sender != seller)
revert OnlySeller();
_;
}
modifier inState(State state_) {
if (state != state_)
revert InvalidState();
_;
}
event Aborted();
event PurchaseConfirmed();
event ItemReceived();
event SellerRefunded();
// Ensure that `msg.value` is an even number.
// Division will truncate if it is an odd number.
// Check via multiplication that it wasn't an odd number.
constructor() payable {
seller = payable(msg.sender);
value = msg.value / 2;
if ((2 * value) != msg.value)
revert ValueNotEven();
}
/// Abort the purchase and reclaim the ether.
/// Can only be called by the seller before
/// the contract is locked.
function abort()
external
onlySeller
inState(State.Created)
{
emit Aborted();
state = State.Inactive;
// We use transfer here directly. It is
// reentrancy-safe, because it is the
// last call in this function and we
// already changed the state.
seller.transfer(address(this).balance);
}
/// Confirm the purchase as buyer.
/// Transaction has to include `2 * value` ether.
/// The ether will be locked until confirmReceived
/// is called.
function confirmPurchase()
external
inState(State.Created)
condition(msg.value == (2 * value))
payable
{
emit PurchaseConfirmed();
buyer = payable(msg.sender);
state = State.Locked;
}
/// Confirm that you (the buyer) received the item.
/// This will release the locked ether.
function confirmReceived()
external
onlyBuyer
inState(State.Locked)
{
emit ItemReceived();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
state = State.Release;
buyer.transfer(value);
}
/// This function refunds the seller, i.e.
/// pays back the locked funds of the seller.
function refundSeller()
external
onlySeller
inState(State.Release)
{
emit SellerRefunded();
// It is important to change the state first because
// otherwise, the contracts called using `send` below
// can call in again here.
state = State.Inactive;
seller.transfer(3 * value);
}
}
Micropayment Channel
In this section we will learn how to build an example implementation of a payment channel. It uses cryptographic signatures to make repeated transfers of Ether between the same parties secure, instantaneous, and without transaction fees. For the example, we need to understand how to sign and verify signatures, and setup the payment channel.
Creating and verifying signatures
Imagine Alice wants to send some Ether to Bob, i.e. Alice is the sender and Bob is the recipient.
Alice only needs to send cryptographically signed messages off-chain (e.g. via email) to Bob and it is similar to writing checks.
Alice and Bob use signatures to authorise transactions, which is possible with smart contracts on Ethereum. Alice will build a simple smart contract that lets her transmit Ether, but instead of calling a function herself to initiate a payment, she will let Bob do that, and therefore pay the transaction fee.
The contract will work as follows:
Alice deploys the
ReceiverPays
contract, attaching enough Ether to cover the payments that will be made.Alice authorises a payment by signing a message with her private key.
Alice sends the cryptographically signed message to Bob. The message does not need to be kept secret (explained later), and the mechanism for sending it does not matter.
Bob claims his payment by presenting the signed message to the smart contract, it verifies the authenticity of the message and then releases the funds.
Creating the signature
Alice does not need to interact with the Ethereum network to sign the transaction, the process is completely offline. In this tutorial, we will sign messages in the browser using web3.js and MetaMask, using the method described in EIP-712, as it provides a number of other security benefits.
/// Hashing first makes things easier
var hash = web3.utils.sha3("message to sign");
web3.eth.personal.sign(hash, web3.eth.defaultAccount, function () { console.log("Signed"); });
Note
The web3.eth.personal.sign
prepends the length of the
message to the signed data. Since we hash first, the message
will always be exactly 32 bytes long, and thus this length
prefix is always the same.
What to Sign
For a contract that fulfils payments, the signed message must include:
The recipient’s address.
The amount to be transferred.
Protection against replay attacks.
A replay attack is when a signed message is reused to claim authorization for a second action. To avoid replay attacks we use the same technique as in Ethereum transactions themselves, a so-called nonce, which is the number of transactions sent by an account. The smart contract checks if a nonce is used multiple times.
Another type of replay attack can occur when the owner
deploys a ReceiverPays
smart contract, makes some
payments, and then destroys the contract. Later, they decide
to deploy the RecipientPays
smart contract again, but the
new contract does not know the nonces used in the previous
deployment, so the attacker can use the old messages again.
Alice can protect against this attack by including the
contract’s address in the message, and only messages containing
the contract’s address itself will be accepted. You can find
an example of this in the first two lines of the claimPayment()
function of the full contract at the end of this section.
Packing arguments
Now that we have identified what information to include in the signed message,
we are ready to put the message together, hash it, and sign it. For simplicity,
we concatenate the data. The ethereumjs-abi
library provides a function called soliditySHA3
that mimics the behaviour of
Solidity’s keccak256
function applied to arguments encoded using abi.encodePacked
.
Here is a JavaScript function that creates the proper signature for the ReceiverPays
example:
// recipient is the address that should be paid.
// amount, in wei, specifies how much ether should be sent.
// nonce can be any unique number to prevent replay attacks
// contractAddress is used to prevent cross-contract replay attacks
function signPayment(recipient, amount, nonce, contractAddress, callback) {
var hash = "0x" + abi.soliditySHA3(
["address", "uint256", "uint256", "address"],
[recipient, amount, nonce, contractAddress]
).toString("hex");
web3.eth.personal.sign(hash, web3.eth.defaultAccount, callback);
}
Recovering the Message Signer in Solidity
In general, ECDSA signatures consist of two parameters,
r
and s
. Signatures in Ethereum include a third
parameter called v
, that you can use to verify which
account’s private key was used to sign the message, and
the transaction’s sender. Solidity provides a built-in
function ecrecover that
accepts a message along with the r
, s
and v
parameters
and returns the address that was used to sign the message.
Extracting the Signature Parameters
Signatures produced by web3.js are the concatenation of r
,
s
and v
, so the first step is to split these parameters
apart. You can do this on the client-side, but doing it inside
the smart contract means you only need to send one signature
parameter rather than three. Splitting apart a byte array into
its constituent parts is a mess, so we use
inline assembly to do the job in the splitSignature
function (the third function in the full contract at the end of this section).
Computing the Message Hash
The smart contract needs to know exactly what parameters were signed, and so it
must recreate the message from the parameters and use that for signature verification.
The functions prefixed
and recoverSigner
do this in the claimPayment
function.
The full contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract ReceiverPays {
address owner = msg.sender;
mapping(uint256 => bool) usedNonces;
constructor() payable {}
function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) external {
require(!usedNonces[nonce]);
usedNonces[nonce] = true;
// this recreates the message that was signed on the client
bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this)));
require(recoverSigner(message, signature) == owner);
payable(msg.sender).transfer(amount);
}
/// destroy the contract and reclaim the leftover funds.
function shutdown() external {
require(msg.sender == owner);
selfdestruct(payable(msg.sender));
}
/// signature methods.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);
assembly {
// first 32 bytes, after the length prefix.
r := mload(add(sig, 32))
// second 32 bytes.
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes).
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
Writing a Simple Payment Channel
Alice now builds a simple but complete implementation of a payment channel. Payment channels use cryptographic signatures to make repeated transfers of Ether securely, instantaneously, and without transaction fees.
What is a Payment Channel?
Payment channels allow participants to make repeated transfers of Ether without using transactions. This means that you can avoid the delays and fees associated with transactions. We are going to explore a simple unidirectional payment channel between two parties (Alice and Bob). It involves three steps:
Alice funds a smart contract with Ether. This “opens” the payment channel.
Alice signs messages that specify how much of that Ether is owed to the recipient. This step is repeated for each payment.
Bob “closes” the payment channel, withdrawing his portion of the Ether and sending the remainder back to the sender.
Note
Only steps 1 and 3 require Ethereum transactions, step 2 means that the sender transmits a cryptographically signed message to the recipient via off chain methods (e.g. email). This means only two transactions are required to support any number of transfers.
Bob is guaranteed to receive his funds because the smart contract escrows the Ether and honours a valid signed message. The smart contract also enforces a timeout, so Alice is guaranteed to eventually recover her funds even if the recipient refuses to close the channel. It is up to the participants in a payment channel to decide how long to keep it open. For a short-lived transaction, such as paying an internet café for each minute of network access, the payment channel may be kept open for a limited duration. On the other hand, for a recurring payment, such as paying an employee an hourly wage, the payment channel may be kept open for several months or years.
Opening the Payment Channel
To open the payment channel, Alice deploys the smart contract, attaching
the Ether to be escrowed and specifying the intended recipient and a
maximum duration for the channel to exist. This is the function
SimplePaymentChannel
in the contract, at the end of this section.
Making Payments
Alice makes payments by sending signed messages to Bob. This step is performed entirely outside of the Ethereum network. Messages are cryptographically signed by the sender and then transmitted directly to the recipient.
Each message includes the following information:
The smart contract’s address, used to prevent cross-contract replay attacks.
The total amount of Ether that is owed the recipient so far.
A payment channel is closed just once, at the end of a series of transfers. Because of this, only one of the messages sent is redeemed. This is why each message specifies a cumulative total amount of Ether owed, rather than the amount of the individual micropayment. The recipient will naturally choose to redeem the most recent message because that is the one with the highest total. The nonce per-message is not needed anymore, because the smart contract only honours a single message. The address of the smart contract is still used to prevent a message intended for one payment channel from being used for a different channel.
Here is the modified JavaScript code to cryptographically sign a message from the previous section:
function constructPaymentMessage(contractAddress, amount) {
return abi.soliditySHA3(
["address", "uint256"],
[contractAddress, amount]
);
}
function signMessage(message, callback) {
web3.eth.personal.sign(
"0x" + message.toString("hex"),
web3.eth.defaultAccount,
callback
);
}
// contractAddress is used to prevent cross-contract replay attacks.
// amount, in wei, specifies how much Ether should be sent.
function signPayment(contractAddress, amount, callback) {
var message = constructPaymentMessage(contractAddress, amount);
signMessage(message, callback);
}
Closing the Payment Channel
When Bob is ready to receive his funds, it is time to
close the payment channel by calling a close
function on the smart contract.
Closing the channel pays the recipient the Ether they are owed and
destroys the contract, sending any remaining Ether back to Alice. To
close the channel, Bob needs to provide a message signed by Alice.
The smart contract must verify that the message contains a valid signature from the sender.
The process for doing this verification is the same as the process the recipient uses.
The Solidity functions isValidSignature
and recoverSigner
work just like their
JavaScript counterparts in the previous section, with the latter function borrowed from the ReceiverPays
contract.
Only the payment channel recipient can call the close
function,
who naturally passes the most recent payment message because that message
carries the highest total owed. If the sender were allowed to call this function,
they could provide a message with a lower amount and cheat the recipient out of what they are owed.
The function verifies the signed message matches the given parameters.
If everything checks out, the recipient is sent their portion of the Ether,
and the sender is sent the rest via a selfdestruct
.
You can see the close
function in the full contract.
Channel Expiration
Bob can close the payment channel at any time, but if they fail to do so,
Alice needs a way to recover her escrowed funds. An expiration time was set
at the time of contract deployment. Once that time is reached, Alice can call
claimTimeout
to recover her funds. You can see the claimTimeout
function in the full contract.
After this function is called, Bob can no longer receive any Ether, so it is important that Bob closes the channel before the expiration is reached.
The full contract
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract SimplePaymentChannel {
address payable public sender; // The account sending payments.
address payable public recipient; // The account receiving the payments.
uint256 public expiration; // Timeout in case the recipient never closes.
constructor (address payable recipientAddress, uint256 duration)
payable
{
sender = payable(msg.sender);
recipient = recipientAddress;
expiration = block.timestamp + duration;
}
/// the recipient can close the channel at any time by presenting a
/// signed amount from the sender. the recipient will be sent that amount,
/// and the remainder will go back to the sender
function close(uint256 amount, bytes memory signature) external {
require(msg.sender == recipient);
require(isValidSignature(amount, signature));
recipient.transfer(amount);
selfdestruct(sender);
}
/// the sender can extend the expiration at any time
function extend(uint256 newExpiration) external {
require(msg.sender == sender);
require(newExpiration > expiration);
expiration = newExpiration;
}
/// if the timeout is reached without the recipient closing the channel,
/// then the Ether is released back to the sender.
function claimTimeout() external {
require(block.timestamp >= expiration);
selfdestruct(sender);
}
function isValidSignature(uint256 amount, bytes memory signature)
internal
view
returns (bool)
{
bytes32 message = prefixed(keccak256(abi.encodePacked(this, amount)));
// check that the signature is from the payment sender
return recoverSigner(message, signature) == sender;
}
/// All functions below this are just taken from the chapter
/// 'creating and verifying signatures' chapter.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);
assembly {
// first 32 bytes, after the length prefix
r := mload(add(sig, 32))
// second 32 bytes
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes)
v := byte(0, mload(add(sig, 96)))
}
return (v, r, s);
}
function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);
return ecrecover(message, v, r, s);
}
/// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
Note
The function splitSignature
does not use all security
checks. A real implementation should use a more rigorously tested library,
such as openzepplin’s version of this code.
Verifying Payments
Unlike in the previous section, messages in a payment channel aren’t redeemed right away. The recipient keeps track of the latest message and redeems it when it’s time to close the payment channel. This means it’s critical that the recipient perform their own verification of each message. Otherwise there is no guarantee that the recipient will be able to get paid in the end.
The recipient should verify each message using the following process:
Verify that the contract address in the message matches the payment channel.
Verify that the new total is the expected amount.
Verify that the new total does not exceed the amount of Ether escrowed.
Verify that the signature is valid and comes from the payment channel sender.
We’ll use the ethereumjs-util
library to write this verification. The final step can be done a number of ways,
and we use JavaScript. The following code borrows the constructPaymentMessage
function from the signing JavaScript code above:
// this mimics the prefixing behavior of the eth_sign JSON-RPC method.
function prefixed(hash) {
return ethereumjs.ABI.soliditySHA3(
["string", "bytes32"],
["\x19Ethereum Signed Message:\n32", hash]
);
}
function recoverSigner(message, signature) {
var split = ethereumjs.Util.fromRpcSig(signature);
var publicKey = ethereumjs.Util.ecrecover(message, split.v, split.r, split.s);
var signer = ethereumjs.Util.pubToAddress(publicKey).toString("hex");
return signer;
}
function isValidSignature(contractAddress, amount, signature, expectedSigner) {
var message = prefixed(constructPaymentMessage(contractAddress, amount));
var signer = recoverSigner(message, signature);
return signer.toLowerCase() ==
ethereumjs.Util.stripHexPrefix(expectedSigner).toLowerCase();
}
Modular Contracts
A modular approach to building your contracts helps you reduce the complexity
and improve the readability which will help to identify bugs and vulnerabilities
during development and code review.
If you specify and control the behaviour or each module in isolation, the
interactions you have to consider are only those between the module specifications
and not every other moving part of the contract.
In the example below, the contract uses the move
method
of the Balances
library to check that balances sent between
addresses match what you expect. In this way, the Balances
library
provides an isolated component that properly tracks balances of accounts.
It is easy to verify that the Balances
library never produces negative balances or overflows
and the sum of all balances is an invariant across the lifetime of the contract.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
library Balances {
function move(mapping(address => uint256) storage balances, address from, address to, uint amount) internal {
require(balances[from] >= amount);
require(balances[to] + amount >= balances[to]);
balances[from] -= amount;
balances[to] += amount;
}
}
contract Token {
mapping(address => uint256) balances;
using Balances for *;
mapping(address => mapping (address => uint256)) allowed;
event Transfer(address from, address to, uint amount);
event Approval(address owner, address spender, uint amount);
function transfer(address to, uint amount) external returns (bool success) {
balances.move(msg.sender, to, amount);
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(address from, address to, uint amount) external returns (bool success) {
require(allowed[from][msg.sender] >= amount);
allowed[from][msg.sender] -= amount;
balances.move(from, to, amount);
emit Transfer(from, to, amount);
return true;
}
function approve(address spender, uint tokens) external returns (bool success) {
require(allowed[msg.sender][spender] == 0, "");
allowed[msg.sender][spender] = tokens;
emit Approval(msg.sender, spender, tokens);
return true;
}
function balanceOf(address tokenOwner) external view returns (uint balance) {
return balances[tokenOwner];
}
}
Layout of a Solidity Source File
Source files can contain an arbitrary number of contract definitions, import , pragma and using for directives and struct, enum, function, error and constant variable definitions.
SPDX License Identifier
Trust in smart contracts can be better established if their source code is available. Since making source code available always touches on legal problems with regards to copyright, the Solidity compiler encourages the use of machine-readable SPDX license identifiers. Every source file should start with a comment indicating its license:
// SPDX-License-Identifier: MIT
The compiler does not validate that the license is part of the list allowed by SPDX, but it does include the supplied string in the bytecode metadata.
If you do not want to specify a license or if the source code is
not open-source, please use the special value UNLICENSED
.
Note that UNLICENSED
(no usage allowed, not present in SPDX license list)
is different from UNLICENSE
(grants all rights to everyone).
Solidity follows the npm recommendation.
Supplying this comment of course does not free you from other obligations related to licensing like having to mention a specific license header in each source file or the original copyright holder.
The comment is recognized by the compiler anywhere in the file at the file level, but it is recommended to put it at the top of the file.
More information about how to use SPDX license identifiers can be found at the SPDX website.
Pragmas
The pragma
keyword is used to enable certain compiler features
or checks. A pragma directive is always local to a source file, so
you have to add the pragma to all your files if you want to enable it
in your whole project. If you import another file, the pragma
from that file does not automatically apply to the importing file.
Version Pragma
Source files can (and should) be annotated with a version pragma to reject
compilation with future compiler versions that might introduce incompatible
changes. We try to keep these to an absolute minimum and
introduce them in a way that changes in semantics also require changes
in the syntax, but this is not always possible. Because of this, it is always
a good idea to read through the changelog at least for releases that contain
breaking changes. These releases always have versions of the form
0.x.0
or x.0.0
.
The version pragma is used as follows: pragma solidity ^0.5.2;
A source file with the line above does not compile with a compiler earlier than version 0.5.2,
and it also does not work on a compiler starting from version 0.6.0 (this
second condition is added by using ^
). Because
there will be no breaking changes until version 0.6.0
, you can
be sure that your code compiles the way you intended. The exact version of the
compiler is not fixed, so that bugfix releases are still possible.
It is possible to specify more complex rules for the compiler version, these follow the same syntax used by npm.
Note
Using the version pragma does not change the version of the compiler. It also does not enable or disable features of the compiler. It just instructs the compiler to check whether its version matches the one required by the pragma. If it does not match, the compiler issues an error.
ABI Coder Pragma
By using pragma abicoder v1
or pragma abicoder v2
you can
select between the two implementations of the ABI encoder and decoder.
The new ABI coder (v2) is able to encode and decode arbitrarily nested
arrays and structs. It might produce less optimal code and has not
received as much testing as the old encoder, but is considered
non-experimental as of Solidity 0.6.0. You still have to explicitly
activate it using pragma abicoder v2;
. Since it will be
activated by default starting from Solidity 0.8.0, there is the option to select
the old coder using pragma abicoder v1;
.
The set of types supported by the new encoder is a strict superset of
the ones supported by the old one. Contracts that use it can interact with ones
that do not without limitations. The reverse is possible only as long as the
non-abicoder v2
contract does not try to make calls that would require
decoding types only supported by the new encoder. The compiler can detect this
and will issue an error. Simply enabling abicoder v2
for your contract is
enough to make the error go away.
Note
This pragma applies to all the code defined in the file where it is activated, regardless of where that code ends up eventually. This means that a contract whose source file is selected to compile with ABI coder v1 can still contain code that uses the new encoder by inheriting it from another contract. This is allowed if the new types are only used internally and not in external function signatures.
Note
Up to Solidity 0.7.4, it was possible to select the ABI coder v2
by using pragma experimental ABIEncoderV2
, but it was not possible
to explicitly select coder v1 because it was the default.
Experimental Pragma
The second pragma is the experimental pragma. It can be used to enable features of the compiler or language that are not yet enabled by default. The following experimental pragmas are currently supported:
ABIEncoderV2
Because the ABI coder v2 is not considered experimental anymore,
it can be selected via pragma abicoder v2
(please see above)
since Solidity 0.7.4.
SMTChecker
This component has to be enabled when the Solidity compiler is built and therefore it is not available in all Solidity binaries. The build instructions explain how to activate this option. It is activated for the Ubuntu PPA releases in most versions, but not for the Docker images, Windows binaries or the statically-built Linux binaries. It can be activated for solc-js via the smtCallback if you have an SMT solver installed locally and run solc-js via node (not via the browser).
If you use pragma experimental SMTChecker;
, then you get additional
safety warnings which are obtained by querying an
SMT solver.
The component does not yet support all features of the Solidity language and
likely outputs many warnings. In case it reports unsupported features, the
analysis may not be fully sound.
Importing other Source Files
Syntax and Semantics
Solidity supports import statements to help modularise your code that are similar to those available in JavaScript (from ES6 on). However, Solidity does not support the concept of a default export.
At a global level, you can use import statements of the following form:
import "filename";
The filename
part is called an import path.
This statement imports all global symbols from “filename” (and symbols imported there) into the
current global scope (different than in ES6 but backwards-compatible for Solidity).
This form is not recommended for use, because it unpredictably pollutes the namespace.
If you add new top-level items inside “filename”, they automatically
appear in all files that import like this from “filename”. It is better to import specific
symbols explicitly.
The following example creates a new global symbol symbolName
whose members are all
the global symbols from "filename"
:
import * as symbolName from "filename";
which results in all global symbols being available in the format symbolName.symbol
.
A variant of this syntax that is not part of ES6, but possibly useful is:
import "filename" as symbolName;
which is equivalent to import * as symbolName from "filename";
.
If there is a naming collision, you can rename symbols while importing. For example,
the code below creates new global symbols alias
and symbol2
which reference
symbol1
and symbol2
from inside "filename"
, respectively.
import {symbol1 as alias, symbol2} from "filename";
Import Paths
In order to be able to support reproducible builds on all platforms, the Solidity compiler has to abstract away the details of the filesystem where source files are stored. For this reason import paths do not refer directly to files in the host filesystem. Instead the compiler maintains an internal database (virtual filesystem or VFS for short) where each source unit is assigned a unique source unit name which is an opaque and unstructured identifier. The import path specified in an import statement is translated into a source unit name and used to find the corresponding source unit in this database.
Using the Standard JSON API it is possible to directly provide the names and content of all the source files as a part of the compiler input. In this case source unit names are truly arbitrary. If, however, you want the compiler to automatically find and load source code into the VFS, your source unit names need to be structured in a way that makes it possible for an import callback to locate them. When using the command-line compiler the default import callback supports only loading source code from the host filesystem, which means that your source unit names must be paths. Some environments provide custom callbacks that are more versatile. For example the Remix IDE provides one that lets you import files from HTTP, IPFS and Swarm URLs or refer directly to packages in NPM registry.
For a complete description of the virtual filesystem and the path resolution logic used by the compiler see Path Resolution.
Structure of a Contract
Contracts in Solidity are similar to classes in object-oriented languages. Each contract can contain declarations of State Variables, Functions, Function Modifiers, Events, Errors, Struct Types and Enum Types. Furthermore, contracts can inherit from other contracts.
There are also special kinds of contracts called libraries and interfaces.
The section about contracts contains more details than this section, which serves to provide a quick overview.
State Variables
State variables are variables whose values are permanently stored in contract storage.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract SimpleStorage {
uint storedData; // State variable
// ...
}
See the Types section for valid state variable types and Visibility and Getters for possible choices for visibility.
Functions
Functions are the executable units of code. Functions are usually defined inside a contract, but they can also be defined outside of contracts.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract SimpleAuction {
function bid() public payable { // Function
// ...
}
}
// Helper function defined outside of a contract
function helper(uint x) pure returns (uint) {
return x * 2;
}
Function Calls can happen internally or externally and have different levels of visibility towards other contracts. Functions accept parameters and return variables to pass parameters and values between them.
Function Modifiers
Function modifiers can be used to amend the semantics of functions in a declarative way (see Function Modifiers in the contracts section).
Overloading, that is, having the same modifier name with different parameters, is not possible.
Like functions, modifiers can be overridden.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract Purchase {
address public seller;
modifier onlySeller() { // Modifier
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function abort() public view onlySeller { // Modifier usage
// ...
}
}
Events
Events are convenience interfaces with the EVM logging facilities.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.9.0;
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // Event
function bid() public payable {
// ...
emit HighestBidIncreased(msg.sender, msg.value); // Triggering event
}
}
See Events in contracts section for information on how events are declared and can be used from within a dapp.
Errors
Errors allow you to define descriptive names and data for failure situations. Errors can be used in revert statements. In comparison to string descriptions, errors are much cheaper and allow you to encode additional data. You can use NatSpec to describe the error to the user.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
/// Not enough funds for transfer. Requested `requested`,
/// but only `available` available.
error NotEnoughFunds(uint requested, uint available);
contract Token {
mapping(address => uint) balances;
function transfer(address to, uint amount) public {
uint balance = balances[msg.sender];
if (balance < amount)
revert NotEnoughFunds(amount, balance);
balances[msg.sender] -= amount;
balances[to] += amount;
// ...
}
}
See Errors and the Revert Statement in the contracts section for more information.
Struct Types
Structs are custom defined types that can group several variables (see Structs in types section).
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Ballot {
struct Voter { // Struct
uint weight;
bool voted;
address delegate;
uint vote;
}
}
Enum Types
Enums can be used to create custom types with a finite set of ‘constant values’ (see Enums in types section).
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Purchase {
enum State { Created, Locked, Inactive } // Enum
}
Types
Solidity is a statically typed language, which means that the type of each variable (state and local) needs to be specified. Solidity provides several elementary types which can be combined to form complex types.
In addition, types can interact with each other in expressions containing operators. For a quick reference of the various operators, see Order of Precedence of Operators.
The concept of “undefined” or “null” values does not exist in Solidity, but newly
declared variables always have a default value dependent
on its type. To handle any unexpected values, you should use the revert function to revert the whole transaction, or return a
tuple with a second bool
value denoting success.
Value Types
The following types are also called value types because variables of these types will always be passed by value, i.e. they are always copied when they are used as function arguments or in assignments.
Booleans
bool
: The possible values are constants true
and false
.
Operators:
!
(logical negation)&&
(logical conjunction, “and”)||
(logical disjunction, “or”)==
(equality)!=
(inequality)
The operators ||
and &&
apply the common short-circuiting rules. This means that in the expression f(x) || g(y)
, if f(x)
evaluates to true
, g(y)
will not be evaluated even if it may have side-effects.
Integers
int
/ uint
: Signed and unsigned integers of various sizes. Keywords uint8
to uint256
in steps of 8
(unsigned of 8 up to 256 bits) and int8
to int256
. uint
and int
are aliases for uint256
and int256
, respectively.
Operators:
Comparisons:
<=
,<
,==
,!=
,>=
,>
(evaluate tobool
)Bit operators:
&
,|
,^
(bitwise exclusive or),~
(bitwise negation)Shift operators:
<<
(left shift),>>
(right shift)Arithmetic operators:
+
,-
, unary-
(only for signed integers),*
,/
,%
(modulo),**
(exponentiation)
For an integer type X
, you can use type(X).min
and type(X).max
to
access the minimum and maximum value representable by the type.
Warning
Integers in Solidity are restricted to a certain range. For example, with uint32
, this is 0
up to 2**32 - 1
.
There are two modes in which arithmetic is performed on these types: The “wrapping” or “unchecked” mode and the “checked” mode.
By default, arithmetic is always “checked”, which mean that if the result of an operation falls outside the value range
of the type, the call is reverted through a failing assertion. You can switch to “unchecked” mode
using unchecked { ... }
. More details can be found in the section about unchecked.
Comparisons
The value of a comparison is the one obtained by comparing the integer value.
Bit operations
Bit operations are performed on the two’s complement representation of the number.
This means that, for example ~int256(0) == int256(-1)
.
Shifts
The result of a shift operation has the type of the left operand, truncating the result to match the type. The right operand must be of unsigned type, trying to shift by a signed type will produce a compilation error.
Shifts can be “simulated” using multiplication by powers of two in the following way. Note that the truncation to the type of the left operand is always performed at the end, but not mentioned explicitly.
x << y
is equivalent to the mathematical expressionx * 2**y
.x >> y
is equivalent to the mathematical expressionx / 2**y
, rounded towards negative infinity.
Warning
Before version 0.5.0
a right shift x >> y
for negative x
was equivalent to
the mathematical expression x / 2**y
rounded towards zero,
i.e., right shifts used rounding up (towards zero) instead of rounding down (towards negative infinity).
Note
Overflow checks are never performed for shift operations as they are done for arithmetic operations. Instead, the result is always truncated.
Addition, Subtraction and Multiplication
Addition, subtraction and multiplication have the usual semantics, with two different modes in regard to over- and underflow:
By default, all arithmetic is checked for under- or overflow, but this can be disabled using the unchecked block, resulting in wrapping arithmetic. More details can be found in that section.
The expression -x
is equivalent to (T(0) - x)
where
T
is the type of x
. It can only be applied to signed types.
The value of -x
can be
positive if x
is negative. There is another caveat also resulting
from two’s complement representation:
If you have int x = type(int).min;
, then -x
does not fit the positive range.
This means that unchecked { assert(-x == x); }
works, and the expression -x
when used in checked mode will result in a failing assertion.
Division
Since the type of the result of an operation is always the type of one of
the operands, division on integers always results in an integer.
In Solidity, division rounds towards zero. This means that int256(-5) / int256(2) == int256(-2)
.
Note that in contrast, division on literals results in fractional values of arbitrary precision.
Note
Division by zero causes a Panic error. This check can not be disabled through unchecked { ... }
.
Note
The expression type(int).min / (-1)
is the only case where division causes an overflow.
In checked arithmetic mode, this will cause a failing assertion, while in wrapping
mode, the value will be type(int).min
.
Modulo
The modulo operation a % n
yields the remainder r
after the division of the operand a
by the operand n
, where q = int(a / n)
and r = a - (n * q)
. This means that modulo
results in the same sign as its left operand (or zero) and a % n == -(-a % n)
holds for negative a
:
int256(5) % int256(2) == int256(1)
int256(5) % int256(-2) == int256(1)
int256(-5) % int256(2) == int256(-1)
int256(-5) % int256(-2) == int256(-1)
Note
Modulo with zero causes a Panic error. This check can not be disabled through unchecked { ... }
.
Exponentiation
Exponentiation is only available for unsigned types in the exponent. The resulting type of an exponentiation is always equal to the type of the base. Please take care that it is large enough to hold the result and prepare for potential assertion failures or wrapping behaviour.
Note
In checked mode, exponentiation only uses the comparatively cheap exp
opcode for small bases.
For the cases of x**3
, the expression x*x*x
might be cheaper.
In any case, gas cost tests and the use of the optimizer are advisable.
Note
Note that 0**0
is defined by the EVM as 1
.
Fixed Point Numbers
Warning
Fixed point numbers are not fully supported by Solidity yet. They can be declared, but cannot be assigned to or from.
fixed
/ ufixed
: Signed and unsigned fixed point number of various sizes. Keywords ufixedMxN
and fixedMxN
, where M
represents the number of bits taken by
the type and N
represents how many decimal points are available. M
must be divisible by 8 and goes from 8 to 256 bits. N
must be between 0 and 80, inclusive.
ufixed
and fixed
are aliases for ufixed128x18
and fixed128x18
, respectively.
Operators:
Comparisons:
<=
,<
,==
,!=
,>=
,>
(evaluate tobool
)Arithmetic operators:
+
,-
, unary-
,*
,/
,%
(modulo)
Note
The main difference between floating point (float
and double
in many languages, more precisely IEEE 754 numbers) and fixed point numbers is
that the number of bits used for the integer and the fractional part (the part after the decimal dot) is flexible in the former, while it is strictly
defined in the latter. Generally, in floating point almost the entire space is used to represent the number, while only a small number of bits define
where the decimal point is.
Address
The address type comes in two flavours, which are largely identical:
address
: Holds a 20 byte value (size of an Ethereum address).address payable
: Same asaddress
, but with the additional memberstransfer
andsend
.
The idea behind this distinction is that address payable
is an address you can send Ether to,
while you are not supposed to send Ether to a plain address
, for example because it might be a smart contract
that was not built to accept Ether.
Type conversions:
Implicit conversions from address payable
to address
are allowed, whereas conversions from address
to address payable
must be explicit via payable(<address>)
.
Explicit conversions to and from address
are allowed for uint160
, integer literals,
bytes20
and contract types.
Only expressions of type address
and contract-type can be converted to the type address
payable
via the explicit conversion payable(...)
. For contract-type, this conversion is only
allowed if the contract can receive Ether, i.e., the contract either has a receive or a payable fallback function. Note that payable(0)
is valid and is
an exception to this rule.
Note
If you need a variable of type address
and plan to send Ether to it, then
declare its type as address payable
to make this requirement visible. Also,
try to make this distinction or conversion as early as possible.
Operators:
<=
,<
,==
,!=
,>=
and>
Warning
If you convert a type that uses a larger byte size to an address
, for example bytes32
, then the address
is truncated.
To reduce conversion ambiguity version 0.4.24 and higher of the compiler force you make the truncation explicit in the conversion.
Take for example the 32-byte value 0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCCC
.
You can use address(uint160(bytes20(b)))
, which results in 0x111122223333444455556666777788889999aAaa
,
or you can use address(uint160(uint256(b)))
, which results in 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc
.
Note
The distinction between address
and address payable
was introduced with version 0.5.0.
Also starting from that version, contracts do not derive from the address type, but can still be explicitly converted to
address
or to address payable
, if they have a receive or payable fallback function.
Members of Addresses
For a quick reference of all members of address, see Members of Address Types.
balance
andtransfer
It is possible to query the balance of an address using the property balance
and to send Ether (in units of wei) to a payable address using the transfer
function:
address payable x = payable(0x123);
address myAddress = address(this);
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
The transfer
function fails if the balance of the current contract is not large enough
or if the Ether transfer is rejected by the receiving account. The transfer
function
reverts on failure.
Note
If x
is a contract address, its code (more specifically: its Receive Ether Function, if present, or otherwise its Fallback Function, if present) will be executed together with the transfer
call (this is a feature of the EVM and cannot be prevented). If that execution runs out of gas or fails in any way, the Ether transfer will be reverted and the current contract will stop with an exception.
send
Send is the low-level counterpart of transfer
. If the execution fails, the current contract will not stop with an exception, but send
will return false
.
Warning
There are some dangers in using send
: The transfer fails if the call stack depth is at 1024
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
to make safe Ether transfers, always check the return value of send
, use transfer
or even better:
use a pattern where the recipient withdraws the money.
call
,delegatecall
andstaticcall
In order to interface with contracts that do not adhere to the ABI,
or to get more direct control over the encoding,
the functions call
, delegatecall
and staticcall
are provided.
They all take a single bytes memory
parameter and
return the success condition (as a bool
) and the returned data
(bytes memory
).
The functions abi.encode
, abi.encodePacked
, abi.encodeWithSelector
and abi.encodeWithSignature
can be used to encode structured data.
Example:
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
Warning
All these functions are low-level functions and should be used with care.
Specifically, any unknown contract might be malicious and if you call it, you
hand over control to that contract which could in turn call back into
your contract, so be prepared for changes to your state variables
when the call returns. The regular way to interact with other contracts
is to call a function on a contract object (x.f()
).
Note
Previous versions of Solidity allowed these functions to receive
arbitrary arguments and would also handle a first argument of type
bytes4
differently. These edge cases were removed in version 0.5.0.
It is possible to adjust the supplied gas with the gas
modifier:
address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));
Similarly, the supplied Ether value can be controlled too:
address(nameReg).call{value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
Lastly, these modifiers can be combined. Their order does not matter:
address(nameReg).call{gas: 1000000, value: 1 ether}(abi.encodeWithSignature("register(string)", "MyName"));
In a similar way, the function delegatecall
can be used: the difference is that only the code of the given address is used, all other aspects (storage, balance, …) are taken from the current contract. The purpose of delegatecall
is to use library code which is stored in another contract. The user has to ensure that the layout of storage in both contracts is suitable for delegatecall to be used.
Note
Prior to homestead, only a limited variant called callcode
was available that did not provide access to the original msg.sender
and msg.value
values. This function was removed in version 0.5.0.
Since byzantium staticcall
can be used as well. This is basically the same as call
, but will revert if the called function modifies the state in any way.
All three functions call
, delegatecall
and staticcall
are very low-level functions and should only be used as a last resort as they break the type-safety of Solidity.
The gas
option is available on all three methods, while the value
option is only available
on call
.
Note
It is best to avoid relying on hardcoded gas values in your smart contract code, regardless of whether state is read from or written to, as this can have many pitfalls. Also, access to gas might change in the future.
code
andcodehash
You can query the deployed code for any smart contract. Use .code
to get the EVM bytecode as a
bytes memory
, which might be empty. Use .codehash
get the Keccak-256 hash of that code
(as a bytes32
). Note that addr.codehash
is cheaper than using keccak256(addr.code)
.
Note
All contracts can be converted to address
type, so it is possible to query the balance of the
current contract using address(this).balance
.
Contract Types
Every contract defines its own type.
You can implicitly convert contracts to contracts they inherit from.
Contracts can be explicitly converted to and from the address
type.
Explicit conversion to and from the address payable
type is only possible
if the contract type has a receive or payable fallback function. The conversion is still
performed using address(x)
. If the contract type does not have a receive or payable
fallback function, the conversion to address payable
can be done using
payable(address(x))
.
You can find more information in the section about
the address type.
Note
Before version 0.5.0, contracts directly derived from the address type
and there was no distinction between address
and address payable
.
If you declare a local variable of contract type (MyContract c
), you can call
functions on that contract. Take care to assign it from somewhere that is the
same contract type.
You can also instantiate contracts (which means they are newly created). You can find more details in the ‘Contracts via new’ section.
The data representation of a contract is identical to that of the address
type and this type is also used in the ABI.
Contracts do not support any operators.
The members of contract types are the external functions of the contract
including any state variables marked as public
.
For a contract C
you can use type(C)
to access
type information about the contract.
Fixed-size byte arrays
The value types bytes1
, bytes2
, bytes3
, …, bytes32
hold a sequence of bytes from one to up to 32.
Operators:
Comparisons:
<=
,<
,==
,!=
,>=
,>
(evaluate tobool
)Bit operators:
&
,|
,^
(bitwise exclusive or),~
(bitwise negation)Shift operators:
<<
(left shift),>>
(right shift)Index access: If
x
is of typebytesI
, thenx[k]
for0 <= k < I
returns thek
th byte (read-only).
The shifting operator works with unsigned integer type as right operand (but returns the type of the left operand), which denotes the number of bits to shift by. Shifting by a signed type will produce a compilation error.
Members:
.length
yields the fixed length of the byte array (read-only).
Note
The type bytes1[]
is an array of bytes, but due to padding rules, it wastes
31 bytes of space for each element (except in storage). It is better to use the bytes
type instead.
Note
Prior to version 0.8.0, byte
used to be an alias for bytes1
.
Dynamically-sized byte array
Address Literals
Hexadecimal literals that pass the address checksum test, for example
0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
are of address
type.
Hexadecimal literals that are between 39 and 41 digits
long and do not pass the checksum test produce
an error. You can prepend (for integer types) or append (for bytesNN types) zeros to remove the error.
Note
The mixed-case address checksum format is defined in EIP-55.
Rational and Integer Literals
Integer literals are formed from a sequence of digits in the range 0-9.
They are interpreted as decimals. For example, 69
means sixty nine.
Octal literals do not exist in Solidity and leading zeros are invalid.
Decimal fractional literals are formed by a .
with at least one number on
one side. Examples include 1.
, .1
and 1.3
.
Scientific notation in the form of 2e10
is also supported, where the
mantissa can be fractional but the exponent has to be an integer.
The literal MeE
is equivalent to M * 10**E
.
Examples include 2e10
, -2e10
, 2e-10
, 2.5e1
.
Underscores can be used to separate the digits of a numeric literal to aid readability.
For example, decimal 123_000
, hexadecimal 0x2eff_abde
, scientific decimal notation 1_2e345_678
are all valid.
Underscores are only allowed between two digits and only one consecutive underscore is allowed.
There is no additional semantic meaning added to a number literal containing underscores,
the underscores are ignored.
Number literal expressions retain arbitrary precision until they are converted to a non-literal type (i.e. by using them together with anything other than a number literal expression (like boolean literals) or by explicit conversion). This means that computations do not overflow and divisions do not truncate in number literal expressions.
For example, (2**800 + 1) - 2**800
results in the constant 1
(of type uint8
)
although intermediate results would not even fit the machine word size. Furthermore, .5 * 8
results
in the integer 4
(although non-integers were used in between).
Warning
While most operators produce a literal expression when applied to literals, there are certain operators that do not follow this pattern:
Ternary operator (
... ? ... : ...
),Array subscript (
<array>[<index>]
).
You might expect expressions like 255 + (true ? 1 : 0)
or 255 + [1, 2, 3][0]
to be equivalent to using the literal 256
directly, but in fact they are computed within the type uint8
and can overflow.
Any operator that can be applied to integers can also be applied to number literal expressions as long as the operands are integers. If any of the two is fractional, bit operations are disallowed and exponentiation is disallowed if the exponent is fractional (because that might result in a non-rational number).
Shifts and exponentiation with literal numbers as left (or base) operand and integer types
as the right (exponent) operand are always performed
in the uint256
(for non-negative literals) or int256
(for a negative literals) type,
regardless of the type of the right (exponent) operand.
Warning
Division on integer literals used to truncate in Solidity prior to version 0.4.0, but it now converts into a rational number, i.e. 5 / 2
is not equal to 2
, but to 2.5
.
Note
Solidity has a number literal type for each rational number.
Integer literals and rational number literals belong to number literal types.
Moreover, all number literal expressions (i.e. the expressions that
contain only number literals and operators) belong to number literal
types. So the number literal expressions 1 + 2
and 2 + 1
both
belong to the same number literal type for the rational number three.
Note
Number literal expressions are converted into a non-literal type as soon as they are used with non-literal
expressions. Disregarding types, the value of the expression assigned to b
below evaluates to an integer. Because a
is of type uint128
, the
expression 2.5 + a
has to have a proper type, though. Since there is no common type
for the type of 2.5
and uint128
, the Solidity compiler does not accept
this code.
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
String Literals and Types
String literals are written with either double or single-quotes ("foo"
or 'bar'
), and they can also be split into multiple consecutive parts ("foo" "bar"
is equivalent to "foobar"
) which can be helpful when dealing with long strings. They do not imply trailing zeroes as in C; "foo"
represents three bytes, not four. As with integer literals, their type can vary, but they are implicitly convertible to bytes1
, …, bytes32
, if they fit, to bytes
and to string
.
For example, with bytes32 samevar = "stringliteral"
the string literal is interpreted in its raw byte form when assigned to a bytes32
type.
String literals can only contain printable ASCII characters, which means the characters between and including 0x20 .. 0x7E.
Additionally, string literals also support the following escape characters:
\<newline>
(escapes an actual newline)\\
(backslash)\'
(single quote)\"
(double quote)\n
(newline)\r
(carriage return)\t
(tab)\xNN
(hex escape, see below)\uNNNN
(unicode escape, see below)
\xNN
takes a hex value and inserts the appropriate byte, while \uNNNN
takes a Unicode codepoint and inserts an UTF-8 sequence.
Note
Until version 0.8.0 there were three additional escape sequences: \b
, \f
and \v
.
They are commonly available in other languages but rarely needed in practice.
If you do need them, they can still be inserted via hexadecimal escapes, i.e. \x08
, \x0c
and \x0b
, respectively, just as any other ASCII character.
The string in the following example has a length of ten bytes.
It starts with a newline byte, followed by a double quote, a single
quote a backslash character and then (without separator) the
character sequence abcdef
.
"\n\"\'\\abc\
def"
Any Unicode line terminator which is not a newline (i.e. LF, VF, FF, CR, NEL, LS, PS) is considered to
terminate the string literal. Newline only terminates the string literal if it is not preceded by a \
.
Unicode Literals
While regular string literals can only contain ASCII, Unicode literals – prefixed with the keyword unicode
– can contain any valid UTF-8 sequence.
They also support the very same escape sequences as regular string literals.
string memory a = unicode"Hello 😃";
Hexadecimal Literals
Hexadecimal literals are prefixed with the keyword hex
and are enclosed in double
or single-quotes (hex"001122FF"
, hex'0011_22_FF'
). Their content must be
hexadecimal digits which can optionally use a single underscore as separator between
byte boundaries. The value of the literal will be the binary representation
of the hexadecimal sequence.
Multiple hexadecimal literals separated by whitespace are concatenated into a single literal:
hex"00112233" hex"44556677"
is equivalent to hex"0011223344556677"
Hexadecimal literals behave like string literals and have the same convertibility restrictions.
Enums
Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed. The explicit conversion from integer checks at runtime that the value lies inside the range of the enum and causes a Panic error otherwise. Enums require at least one member, and its default value when declared is the first member. Enums cannot have more than 256 members.
The data representation is the same as for enums in C: The options are represented by
subsequent unsigned integer values starting from 0
.
Using type(NameOfEnum).min
and type(NameOfEnum).max
you can get the
smallest and respectively largest value of the given enum.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
// Since enum types are not part of the ABI, the signature of "getChoice"
// will automatically be changed to "getChoice() returns (uint8)"
// for all matters external to Solidity.
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
function getLargestValue() public pure returns (ActionChoices) {
return type(ActionChoices).max;
}
function getSmallestValue() public pure returns (ActionChoices) {
return type(ActionChoices).min;
}
}
Note
Enums can also be declared on the file level, outside of contract or library definitions.
User Defined Value Types
A user defined value type allows creating a zero cost abstraction over an elementary value type. This is similar to an alias, but with stricter type requirements.
A user defined value type is defined using type C is V
, where C
is the name of the newly
introduced type and V
has to be a built-in value type (the “underlying type”). The function
C.wrap
is used to convert from the underlying type to the custom type. Similarly, the
function C.unwrap
is used to convert from the custom type to the underlying type.
The type C
does not have any operators or bound member functions. In particular, even the
operator ==
is not defined. Explicit and implicit conversions to and from other types are
disallowed.
The data-representation of values of such types are inherited from the underlying type and the underlying type is also used in the ABI.
The following example illustrates a custom type UFixed256x18
representing a decimal fixed point
type with 18 decimals and a minimal library to do arithmetic operations on the type.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
// Represent a 18 decimal, 256 bit wide fixed point type using a user defined value type.
type UFixed256x18 is uint256;
/// A minimal library to do fixed point operations on UFixed256x18.
library FixedMath {
uint constant multiplier = 10**18;
/// Adds two UFixed256x18 numbers. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function add(UFixed256x18 a, UFixed256x18 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) + UFixed256x18.unwrap(b));
}
/// Multiplies UFixed256x18 and uint256. Reverts on overflow, relying on checked
/// arithmetic on uint256.
function mul(UFixed256x18 a, uint256 b) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(UFixed256x18.unwrap(a) * b);
}
/// Take the floor of a UFixed256x18 number.
/// @return the largest integer that does not exceed `a`.
function floor(UFixed256x18 a) internal pure returns (uint256) {
return UFixed256x18.unwrap(a) / multiplier;
}
/// Turns a uint256 into a UFixed256x18 of the same value.
/// Reverts if the integer is too large.
function toUFixed256x18(uint256 a) internal pure returns (UFixed256x18) {
return UFixed256x18.wrap(a * multiplier);
}
}
Notice how UFixed256x18.wrap
and FixedMath.toUFixed256x18
have the same signature but
perform two very different operations: The UFixed256x18.wrap
function returns a UFixed256x18
that has the same data representation as the input, whereas toUFixed256x18
returns a
UFixed256x18
that has the same numerical value.
Function Types
Function types are the types of functions. Variables of function type can be assigned from functions and function parameters of function type can be used to pass functions to and return functions from function calls. Function types come in two flavours - internal and external functions:
Internal functions can only be called inside the current contract (more specifically, inside the current code unit, which also includes internal library functions and inherited functions) because they cannot be executed outside of the context of the current contract. Calling an internal function is realized by jumping to its entry label, just like when calling a function of the current contract internally.
External functions consist of an address and a function signature and they can be passed via and returned from external function calls.
Function types are notated as follows:
function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]
In contrast to the parameter types, the return types cannot be empty - if the
function type should not return anything, the whole returns (<return types>)
part has to be omitted.
By default, function types are internal, so the internal
keyword can be
omitted. Note that this only applies to function types. Visibility has
to be specified explicitly for functions defined in contracts, they
do not have a default.
Conversions:
A function type A
is implicitly convertible to a function type B
if and only if
their parameter types are identical, their return types are identical,
their internal/external property is identical and the state mutability of A
is more restrictive than the state mutability of B
. In particular:
pure
functions can be converted toview
andnon-payable
functionsview
functions can be converted tonon-payable
functionspayable
functions can be converted tonon-payable
functions
No other conversions between function types are possible.
The rule about payable
and non-payable
might be a little
confusing, but in essence, if a function is payable
, this means that it
also accepts a payment of zero Ether, so it also is non-payable
.
On the other hand, a non-payable
function will reject Ether sent to it,
so non-payable
functions cannot be converted to payable
functions.
If a function type variable is not initialised, calling it results
in a Panic error. The same happens if you call a function after using delete
on it.
If external function types are used outside of the context of Solidity,
they are treated as the function
type, which encodes the address
followed by the function identifier together in a single bytes24
type.
Note that public functions of the current contract can be used both as an
internal and as an external function. To use f
as an internal function,
just use f
, if you want to use its external form, use this.f
.
A function of an internal type can be assigned to a variable of an internal function type regardless
of where it is defined.
This includes private, internal and public functions of both contracts and libraries as well as free
functions.
External function types, on the other hand, are only compatible with public and external contract
functions.
Libraries are excluded because they require a delegatecall
and use a different ABI
convention for their selectors.
Functions declared in interfaces do not have definitions so pointing at them does not make sense either.
Members:
External (or public) functions have the following members:
.address
returns the address of the contract of the function..selector
returns the ABI function selector
Note
External (or public) functions used to have the additional members
.gas(uint)
and .value(uint)
. These were deprecated in Solidity 0.6.2
and removed in Solidity 0.7.0. Instead use {gas: ...}
and {value: ...}
to specify the amount of gas or the amount of wei sent to a function,
respectively. See External Function Calls for
more information.
Example that shows how to use the members:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.4 <0.9.0;
contract Example {
function f() public payable returns (bytes4) {
assert(this.f.address == address(this));
return this.f.selector;
}
function g() public {
this.f{gas: 10, value: 800}();
}
}
Example that shows how to use internal function types:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
library ArrayUtils {
// internal functions can be used in internal library functions because
// they will be part of the same code context
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal pure returns (uint) {
return x + y;
}
}
Another example that uses external function types:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract Oracle {
struct Request {
bytes data;
function(uint) external callback;
}
Request[] private requests;
event NewRequest(uint);
function query(bytes memory data, function(uint) external callback) public {
requests.push(Request(data, callback));
emit NewRequest(requests.length - 1);
}
function reply(uint requestID, uint response) public {
// Here goes the check that the reply comes from a trusted source
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant private ORACLE_CONST = Oracle(address(0x00000000219ab540356cBB839Cbe05303d7705Fa)); // known contract
uint private exchangeRate;
function buySomething() public {
ORACLE_CONST.query("USD", this.oracleResponse);
}
function oracleResponse(uint response) public {
require(
msg.sender == address(ORACLE_CONST),
"Only oracle can call this."
);
exchangeRate = response;
}
}
Note
Lambda or inline functions are planned but not yet supported.
Reference Types
Values of reference type can be modified through multiple different names.
Contrast this with value types where you get an independent copy whenever
a variable of value type is used. Because of that, reference types have to be handled
more carefully than value types. Currently, reference types comprise structs,
arrays and mappings. If you use a reference type, you always have to explicitly
provide the data area where the type is stored: memory
(whose lifetime is limited
to an external function call), storage
(the location where the state variables
are stored, where the lifetime is limited to the lifetime of a contract)
or calldata
(special data location that contains the function arguments).
An assignment or type conversion that changes the data location will always incur an automatic copy operation, while assignments inside the same data location only copy in some cases for storage types.
Data location
Every reference type has an additional
annotation, the “data location”, about where it is stored. There are three data locations:
memory
, storage
and calldata
. Calldata is a non-modifiable,
non-persistent area where function arguments are stored, and behaves mostly like memory.
Note
If you can, try to use calldata
as data location because it will avoid copies and
also makes sure that the data cannot be modified. Arrays and structs with calldata
data location can also be returned from functions, but it is not possible to
allocate such types.
Note
Prior to version 0.6.9 data location for reference-type arguments was limited to
calldata
in external functions, memory
in public functions and either
memory
or storage
in internal and private ones.
Now memory
and calldata
are allowed in all functions regardless of their visibility.
Note
Prior to version 0.5.0 the data location could be omitted, and would default to different locations depending on the kind of variable, function type, etc., but all complex types must now give an explicit data location.
Data location and assignment behaviour
Data locations are not only relevant for persistency of data, but also for the semantics of assignments:
Assignments between
storage
andmemory
(or fromcalldata
) always create an independent copy.Assignments from
memory
tomemory
only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data.Assignments from
storage
to a local storage variable also only assign a reference.All other assignments to
storage
always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
// The data location of x is storage.
// This is the only place where the
// data location can be omitted.
uint[] x;
// The data location of memoryArray is memory.
function f(uint[] memory memoryArray) public {
x = memoryArray; // works, copies the whole array to storage
uint[] storage y = x; // works, assigns a pointer, data location of y is storage
y[7]; // fine, returns the 8th element
y.pop(); // fine, modifies x through y
delete x; // fine, clears the array, also modifies y
// The following does not work; it would need to create a new temporary /
// unnamed array in storage, but storage is "statically" allocated:
// y = memoryArray;
// This does not work either, since it would "reset" the pointer, but there
// is no sensible location it could point to.
// delete y;
g(x); // calls g, handing over a reference to x
h(x); // calls h and creates an independent, temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
Arrays
Arrays can have a compile-time fixed size, or they can have a dynamic size.
The type of an array of fixed size k
and element type T
is written as T[k]
,
and an array of dynamic size as T[]
.
For example, an array of 5 dynamic arrays of uint
is written as
uint[][5]
. The notation is reversed compared to some other languages. In
Solidity, X[3]
is always an array containing three elements of type X
,
even if X
is itself an array. This is not the case in other languages such
as C.
Indices are zero-based, and access is in the opposite direction of the declaration.
For example, if you have a variable uint[][5] memory x
, you access the
seventh uint
in the third dynamic array using x[2][6]
, and to access the
third dynamic array, use x[2]
. Again,
if you have an array T[5] a
for a type T
that can also be an array,
then a[2]
always has type T
.
Array elements can be of any type, including mapping or struct. The general
restrictions for types apply, in that mappings can only be stored in the
storage
data location and publicly-visible functions need parameters that are ABI types.
It is possible to mark state variable arrays public
and have Solidity create a getter.
The numeric index becomes a required parameter for the getter.
Accessing an array past its end causes a failing assertion. Methods .push()
and .push(value)
can be used
to append a new element at the end of the array, where .push()
appends a zero-initialized element and returns
a reference to it.
bytes
and string
as Arrays
Variables of type bytes
and string
are special arrays. The bytes
type is similar to bytes1[]
,
but it is packed tightly in calldata and memory. string
is equal to bytes
but does not allow
length or index access.
Solidity does not have string manipulation functions, but there are
third-party string libraries. You can also compare two strings by their keccak256-hash using
keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2))
and
concatenate two strings using string.concat(s1, s2)
.
You should use bytes
over bytes1[]
because it is cheaper,
since using bytes1[]
in memory
adds 31 padding bytes between the elements. Note that in storage
, the
padding is absent due to tight packing, see bytes and string. As a general rule,
use bytes
for arbitrary-length raw byte data and string
for arbitrary-length
string (UTF-8) data. If you can limit the length to a certain number of bytes,
always use one of the value types bytes1
to bytes32
because they are much cheaper.
Note
If you want to access the byte-representation of a string s
, use
bytes(s).length
/ bytes(s)[7] = 'x';
. Keep in mind
that you are accessing the low-level bytes of the UTF-8 representation,
and not the individual characters.
The functions bytes.concat
and string.concat
You can concatenate an arbitrary number of string
values using string.concat
.
The function returns a single string memory
array that contains the contents of the arguments without padding.
If you want to use parameters of other types that are not implicitly convertible to string
, you need to convert them to string
first.
Analogously, the bytes.concat
function can concatenate an arbitrary number of bytes
or bytes1 ... bytes32
values.
The function returns a single bytes memory
array that contains the contents of the arguments without padding.
If you want to use string parameters or other types that are not implicitly convertible to bytes
, you need to convert them to bytes
or bytes1
/…/bytes32
first.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;
contract C {
string s = "Storage";
function f(bytes calldata bc, string memory sm, bytes16 b) public view {
string memory concatString = string.concat(s, string(bc), "Literal", sm);
assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concatString).length);
bytes memory concatBytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b);
assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concatBytes.length);
}
}
If you call string.concat
or bytes.concat
without arguments they return an empty array.
Allocating Memory Arrays
Memory arrays with dynamic length can be created using the new
operator.
As opposed to storage arrays, it is not possible to resize memory arrays (e.g.
the .push
member functions are not available).
You either have to calculate the required size in advance
or create a new memory array and copy every element.
As all variables in Solidity, the elements of newly allocated arrays are always initialized with the default value.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f(uint len) public pure {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
assert(a.length == 7);
assert(b.length == len);
a[6] = 8;
}
}
Array Literals
An array literal is a comma-separated list of one or more expressions, enclosed
in square brackets ([...]
). For example [1, a, f(3)]
. The type of the
array literal is determined as follows:
It is always a statically-sized memory array whose length is the number of expressions.
The base type of the array is the type of the first expression on the list such that all other expressions can be implicitly converted to it. It is a type error if this is not possible.
It is not enough that there is a type all the elements can be converted to. One of the elements has to be of that type.
In the example below, the type of [1, 2, 3]
is
uint8[3] memory
, because the type of each of these constants is uint8
. If
you want the result to be a uint[3] memory
type, you need to convert
the first element to uint
.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure {
g([uint(1), 2, 3]);
}
function g(uint[3] memory) public pure {
// ...
}
}
The array literal [1, -1]
is invalid because the type of the first expression
is uint8
while the type of the second is int8
and they cannot be implicitly
converted to each other. To make it work, you can use [int8(1), -1]
, for example.
Since fixed-size memory arrays of different type cannot be converted into each other (even if the base types can), you always have to specify a common base type explicitly if you want to use two-dimensional array literals:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure returns (uint24[2][4] memory) {
uint24[2][4] memory x = [[uint24(0x1), 1], [0xffffff, 2], [uint24(0xff), 3], [uint24(0xffff), 4]];
// The following does not work, because some of the inner arrays are not of the right type.
// uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]];
return x;
}
}
Fixed size memory arrays cannot be assigned to dynamically-sized memory arrays, i.e. the following is not possible:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
// This will not compile.
contract C {
function f() public {
// The next line creates a type error because uint[3] memory
// cannot be converted to uint[] memory.
uint[] memory x = [uint(1), 3, 4];
}
}
It is planned to remove this restriction in the future, but it creates some complications because of how arrays are passed in the ABI.
If you want to initialize dynamically-sized arrays, you have to assign the individual elements:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure {
uint[] memory x = new uint[](3);
x[0] = 1;
x[1] = 3;
x[2] = 4;
}
}
Array Members
- length:
Arrays have a
length
member that contains their number of elements. The length of memory arrays is fixed (but dynamic, i.e. it can depend on runtime parameters) once they are created.- push():
Dynamic storage arrays and
bytes
(notstring
) have a member function calledpush()
that you can use to append a zero-initialised element at the end of the array. It returns a reference to the element, so that it can be used likex.push().t = 2
orx.push() = b
.- push(x):
Dynamic storage arrays and
bytes
(notstring
) have a member function calledpush(x)
that you can use to append a given element at the end of the array. The function returns nothing.- pop():
Dynamic storage arrays and
bytes
(notstring
) have a member function calledpop()
that you can use to remove an element from the end of the array. This also implicitly calls delete on the removed element. The function returns nothing.
Note
Increasing the length of a storage array by calling push()
has constant gas costs because storage is zero-initialised,
while decreasing the length by calling pop()
has a
cost that depends on the “size” of the element being removed.
If that element is an array, it can be very costly, because
it includes explicitly clearing the removed
elements similar to calling delete on them.
Note
To use arrays of arrays in external (instead of public) functions, you need to activate ABI coder v2.
Note
In EVM versions before Byzantium, it was not possible to access dynamic arrays return from function calls. If you call functions that return dynamic arrays, make sure to use an EVM that is set to Byzantium mode.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract ArrayContract {
uint[2**20] aLotOfIntegers;
// Note that the following is not a pair of dynamic arrays but a
// dynamic array of pairs (i.e. of fixed size arrays of length two).
// Because of that, T[] is always a dynamic array of T, even if T
// itself is an array.
// Data location for all state variables is storage.
bool[2][] pairsOfFlags;
// newPairs is stored in memory - the only possibility
// for public contract function arguments
function setAllFlagPairs(bool[2][] memory newPairs) public {
// assignment to a storage array performs a copy of ``newPairs`` and
// replaces the complete array ``pairsOfFlags``.
pairsOfFlags = newPairs;
}
struct StructType {
uint[] contents;
uint moreInfo;
}
StructType s;
function f(uint[] memory c) public {
// stores a reference to ``s`` in ``g``
StructType storage g = s;
// also changes ``s.moreInfo``.
g.moreInfo = 2;
// assigns a copy because ``g.contents``
// is not a local variable, but a member of
// a local variable.
g.contents = c;
}
function setFlagPair(uint index, bool flagA, bool flagB) public {
// access to a non-existing index will throw an exception
pairsOfFlags[index][0] = flagA;
pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) public {
// using push and pop is the only way to change the
// length of an array
if (newSize < pairsOfFlags.length) {
while (pairsOfFlags.length > newSize)
pairsOfFlags.pop();
} else if (newSize > pairsOfFlags.length) {
while (pairsOfFlags.length < newSize)
pairsOfFlags.push();
}
}
function clear() public {
// these clear the arrays completely
delete pairsOfFlags;
delete aLotOfIntegers;
// identical effect here
pairsOfFlags = new bool[2][](0);
}
bytes byteData;
function byteArrays(bytes memory data) public {
// byte arrays ("bytes") are different as they are stored without padding,
// but can be treated identical to "uint8[]"
byteData = data;
for (uint i = 0; i < 7; i++)
byteData.push();
byteData[3] = 0x08;
delete byteData[2];
}
function addFlag(bool[2] memory flag) public returns (uint) {
pairsOfFlags.push(flag);
return pairsOfFlags.length;
}
function createMemoryArray(uint size) public pure returns (bytes memory) {
// Dynamic memory arrays are created using `new`:
uint[2][] memory arrayOfPairs = new uint[2][](size);
// Inline arrays are always statically-sized and if you only
// use literals, you have to provide at least one type.
arrayOfPairs[0] = [uint(1), 2];
// Create a dynamic byte array:
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = bytes1(uint8(i));
return b;
}
}
Array Slices
Array slices are a view on a contiguous portion of an array.
They are written as x[start:end]
, where start
and
end
are expressions resulting in a uint256 type (or
implicitly convertible to it). The first element of the
slice is x[start]
and the last element is x[end - 1]
.
If start
is greater than end
or if end
is greater
than the length of the array, an exception is thrown.
Both start
and end
are optional: start
defaults
to 0
and end
defaults to the length of the array.
Array slices do not have any members. They are implicitly convertible to arrays of their underlying type and support index access. Index access is not absolute in the underlying array, but relative to the start of the slice.
Array slices do not have a type name which means no variable can have an array slices as type, they only exist in intermediate expressions.
Note
As of now, array slices are only implemented for calldata arrays.
Array slices are useful to ABI-decode secondary data passed in function parameters:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.5 <0.9.0;
contract Proxy {
/// @dev Address of the client contract managed by proxy i.e., this contract
address client;
constructor(address client_) {
client = client_;
}
/// Forward call to "setOwner(address)" that is implemented by client
/// after doing basic validation on the address argument.
function forward(bytes calldata payload) external {
bytes4 sig = bytes4(payload[:4]);
// Due to truncating behaviour, bytes4(payload) performs identically.
// bytes4 sig = bytes4(payload);
if (sig == bytes4(keccak256("setOwner(address)"))) {
address owner = abi.decode(payload[4:], (address));
require(owner != address(0), "Address of owner cannot be zero.");
}
(bool status,) = client.delegatecall(payload);
require(status, "Forwarded call failed.");
}
}
Structs
Solidity provides a way to define new types in the form of structs, which is shown in the following example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
// Defines a new type with two fields.
// Declaring a struct outside of a contract allows
// it to be shared by multiple contracts.
// Here, this is not really needed.
struct Funder {
address addr;
uint amount;
}
contract CrowdFunding {
// Structs can also be defined inside contracts, which makes them
// visible only there and in derived contracts.
struct Campaign {
address payable beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address payable beneficiary, uint goal) public returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID is return variable
// We cannot use "campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)"
// because the right hand side creates a memory-struct "Campaign" that contains a mapping.
Campaign storage c = campaigns[campaignID];
c.beneficiary = beneficiary;
c.fundingGoal = goal;
}
function contribute(uint campaignID) public payable {
Campaign storage c = campaigns[campaignID];
// Creates a new temporary memory struct, initialised with the given values
// and copies it over to storage.
// Note that you can also use Funder(msg.sender, msg.value) to initialise.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) public returns (bool reached) {
Campaign storage c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
The contract does not provide the full functionality of a crowdfunding contract, but it contains the basic concepts necessary to understand structs. Struct types can be used inside mappings and arrays and they can themselves contain mappings and arrays.
It is not possible for a struct to contain a member of its own type, although the struct itself can be the value type of a mapping member or it can contain a dynamically-sized array of its type. This restriction is necessary, as the size of the struct has to be finite.
Note how in all the functions, a struct type is assigned to a local variable
with data location storage
.
This does not copy the struct but only stores a reference so that assignments to
members of the local variable actually write to the state.
Of course, you can also directly access the members of the struct without
assigning it to a local variable, as in
campaigns[campaignID].amount = 0
.
Note
Until Solidity 0.7.0, memory-structs containing members of storage-only types (e.g. mappings)
were allowed and assignments like campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0)
in the example above would work and just silently skip those members.
Mapping Types
Mapping types use the syntax mapping(KeyType => ValueType)
and variables
of mapping type are declared using the syntax mapping(KeyType => ValueType) VariableName
.
The KeyType
can be any
built-in value type, bytes
, string
, or any contract or enum type. Other user-defined
or complex types, such as mappings, structs or array types are not allowed.
ValueType
can be any type, including mappings, arrays and structs.
You can think of mappings as hash tables, which are virtually initialised
such that every possible key exists and is mapped to a value whose
byte-representation is all zeros, a type’s default value.
The similarity ends there, the key data is not stored in a
mapping, only its keccak256
hash is used to look up the value.
Because of this, mappings do not have a length or a concept of a key or value being set, and therefore cannot be erased without extra information regarding the assigned keys (see Clearing Mappings).
Mappings can only have a data location of storage
and thus
are allowed for state variables, as storage reference types
in functions, or as parameters for library functions.
They cannot be used as parameters or return parameters
of contract functions that are publicly visible.
These restrictions are also true for arrays and structs that contain mappings.
You can mark state variables of mapping type as public
and Solidity creates a
getter for you. The KeyType
becomes a parameter for the getter.
If ValueType
is a value type or a struct, the getter returns ValueType
.
If ValueType
is an array or a mapping, the getter has one parameter for
each KeyType
, recursively.
In the example below, the MappingExample
contract defines a public balances
mapping, with the key type an address
, and a value type a uint
, mapping
an Ethereum address to an unsigned integer value. As uint
is a value type, the getter
returns a value that matches the type, which you can see in the MappingUser
contract that returns the value at the specified address.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) public {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() public returns (uint) {
MappingExample m = new MappingExample();
m.update(100);
return m.balances(address(this));
}
}
The example below is a simplified version of an
ERC20 token.
_allowances
is an example of a mapping type inside another mapping type.
The example below uses _allowances
to record the amount someone else is allowed to withdraw from your account.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract MappingExample {
mapping (address => uint256) private _balances;
mapping (address => mapping (address => uint256)) private _allowances;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function allowance(address owner, address spender) public view returns (uint256) {
return _allowances[owner][spender];
}
function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
require(_allowances[sender][msg.sender] >= amount, "ERC20: Allowance not high enough.");
_allowances[sender][msg.sender] -= amount;
_transfer(sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) public returns (bool) {
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function _transfer(address sender, address recipient, uint256 amount) internal {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");
require(_balances[sender] >= amount, "ERC20: Not enough funds.");
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, amount);
}
}
Iterable Mappings
You cannot iterate over mappings, i.e. you cannot enumerate their keys.
It is possible, though, to implement a data structure on
top of them and iterate over that. For example, the code below implements an
IterableMapping
library that the User
contract then adds data to, and
the sum
function iterates over to sum all the values.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;
struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }
struct itmap {
mapping(uint => IndexValue) data;
KeyFlag[] keys;
uint size;
}
type Iterator is uint;
library IterableMapping {
function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {
uint keyIndex = self.data[key].keyIndex;
self.data[key].value = value;
if (keyIndex > 0)
return true;
else {
keyIndex = self.keys.length;
self.keys.push();
self.data[key].keyIndex = keyIndex + 1;
self.keys[keyIndex].key = key;
self.size++;
return false;
}
}
function remove(itmap storage self, uint key) internal returns (bool success) {
uint keyIndex = self.data[key].keyIndex;
if (keyIndex == 0)
return false;
delete self.data[key];
self.keys[keyIndex - 1].deleted = true;
self.size --;
}
function contains(itmap storage self, uint key) internal view returns (bool) {
return self.data[key].keyIndex > 0;
}
function iterateStart(itmap storage self) internal view returns (Iterator) {
return iteratorSkipDeleted(self, 0);
}
function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {
return Iterator.unwrap(iterator) < self.keys.length;
}
function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {
return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);
}
function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {
uint keyIndex = Iterator.unwrap(iterator);
key = self.keys[keyIndex].key;
value = self.data[key].value;
}
function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {
while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
keyIndex++;
return Iterator.wrap(keyIndex);
}
}
// How to use it
contract User {
// Just a struct holding our data.
itmap data;
// Apply library functions to the data type.
using IterableMapping for itmap;
// Insert something
function insert(uint k, uint v) public returns (uint size) {
// This calls IterableMapping.insert(data, k, v)
data.insert(k, v);
// We can still access members of the struct,
// but we should take care not to mess with them.
return data.size;
}
// Computes the sum of all stored data.
function sum() public view returns (uint s) {
for (
Iterator i = data.iterateStart();
data.iterateValid(i);
i = data.iterateNext(i)
) {
(, uint value) = data.iterateGet(i);
s += value;
}
}
}
Operators
Arithmetic and bit operators can be applied even if the two operands do not have the same type.
For example, you can compute y = x + z
, where x
is a uint8
and z
has
the type int32
. In these cases, the following mechanism will be used to determine
the type in which the operation is computed (this is important in case of overflow)
and the type of the operator’s result:
If the type of the right operand can be implicitly converted to the type of the left operand, use the type of the left operand,
if the type of the left operand can be implicitly converted to the type of the right operand, use the type of the right operand,
otherwise, the operation is not allowed.
In case one of the operands is a literal number it is first converted to its “mobile type”, which is the smallest type that can hold the value (unsigned types of the same bit-width are considered “smaller” than the signed types). If both are literal numbers, the operation is computed with arbitrary precision.
The operator’s result type is the same as the type the operation is performed in,
except for comparison operators where the result is always bool
.
The operators **
(exponentiation), <<
and >>
use the type of the
left operand for the operation and the result.
Ternary Operator
The ternary operator is used in expressions of the form <expression> ? <trueExpression> : <falseExpression>
.
It evaluates one of the latter two given expressions depending upon the result of the evaluation of the main <expression>
.
If <expression>
evaluates to true
, then <trueExpression>
will be evaluated, otherwise <falseExpression>
is evaluated.
The result of the ternary operator does not have a rational number type, even if all of its operands are rational number literals. The result type is determined from the types of the two operands in the same way as above, converting to their mobile type first if required.
As a consequence, 255 + (true ? 1 : 0)
will revert due to arithmetic overflow.
The reason is that (true ? 1 : 0)
is of uint8
type, which forces the addition to be performed in uint8
as well,
and 256 exceeds the range allowed for this type.
Another consequence is that an expression like 1.5 + 1.5
is valid but 1.5 + (true ? 1.5 : 2.5)
is not.
This is because the former is a rational expression evaluated in unlimited precision and only its final value matters.
The latter involves a conversion of a fractional rational number to an integer, which is currently disallowed.
Compound and Increment/Decrement Operators
If a
is an LValue (i.e. a variable or something that can be assigned to), the
following operators are available as shorthands:
a += e
is equivalent to a = a + e
. The operators -=
, *=
, /=
, %=
,
|=
, &=
, ^=
, <<=
and >>=
are defined accordingly. a++
and a--
are equivalent
to a += 1
/ a -= 1
but the expression itself still has the previous value
of a
. In contrast, --a
and ++a
have the same effect on a
but
return the value after the change.
delete
delete a
assigns the initial value for the type to a
. I.e. for integers it is
equivalent to a = 0
, but it can also be used on arrays, where it assigns a dynamic
array of length zero or a static array of the same length with all elements set to their
initial value. delete a[x]
deletes the item at index x
of the array and leaves
all other elements and the length of the array untouched. This especially means that it leaves
a gap in the array. If you plan to remove items, a mapping is probably a better choice.
For structs, it assigns a struct with all members reset. In other words,
the value of a
after delete a
is the same as if a
would be declared
without assignment, with the following caveat:
delete
has no effect on mappings (as the keys of mappings may be arbitrary and
are generally unknown). So if you delete a struct, it will reset all members that
are not mappings and also recurse into the members unless they are mappings.
However, individual keys and what they map to can be deleted: If a
is a
mapping, then delete a[x]
will delete the value stored at x
.
It is important to note that delete a
really behaves like an
assignment to a
, i.e. it stores a new object in a
.
This distinction is visible when a
is reference variable: It
will only reset a
itself, not the
value it referred to previously.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() public {
uint x = data;
delete x; // sets x to 0, does not affect data
delete data; // sets data to 0, does not affect x
uint[] storage y = dataArray;
delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also
// y is affected which is an alias to the storage object
// On the other hand: "delete y" is not valid, as assignments to local variables
// referencing storage objects can only be made from existing storage objects.
assert(y.length == 0);
}
}
Order of Precedence of Operators
The following is the order of precedence for operators, listed in order of evaluation.
Precedence |
Description |
Operator |
---|---|---|
1 |
Postfix increment and decrement |
|
New expression |
|
|
Array subscripting |
|
|
Member access |
|
|
Function-like call |
|
|
Parentheses |
|
|
2 |
Prefix increment and decrement |
|
Unary minus |
|
|
Unary operations |
|
|
Logical NOT |
|
|
Bitwise NOT |
|
|
3 |
Exponentiation |
|
4 |
Multiplication, division and modulo |
|
5 |
Addition and subtraction |
|
6 |
Bitwise shift operators |
|
7 |
Bitwise AND |
|
8 |
Bitwise XOR |
|
9 |
Bitwise OR |
|
10 |
Inequality operators |
|
11 |
Equality operators |
|
12 |
Logical AND |
|
13 |
Logical OR |
|
14 |
Ternary operator |
|
Assignment operators |
|
|
15 |
Comma operator |
|
Conversions between Elementary Types
Implicit Conversions
An implicit type conversion is automatically applied by the compiler in some cases during assignments, when passing arguments to functions and when applying operators. In general, an implicit conversion between value-types is possible if it makes sense semantically and no information is lost.
For example, uint8
is convertible to
uint16
and int128
to int256
, but int8
is not convertible to uint256
,
because uint256
cannot hold values such as -1
.
If an operator is applied to different types, the compiler tries to implicitly convert one of the operands to the type of the other (the same is true for assignments). This means that operations are always performed in the type of one of the operands.
For more details about which implicit conversions are possible, please consult the sections about the types themselves.
In the example below, y
and z
, the operands of the addition,
do not have the same type, but uint8
can
be implicitly converted to uint16
and not vice-versa. Because of that,
y
is converted to the type of z
before the addition is performed
in the uint16
type. The resulting type of the expression y + z
is uint16
.
Because it is assigned to a variable of type uint32
another implicit conversion
is performed after the addition.
uint8 y;
uint16 z;
uint32 x = y + z;
Explicit Conversions
If the compiler does not allow implicit conversion but you are confident a conversion will work, an explicit type conversion is sometimes possible. This may result in unexpected behaviour and allows you to bypass some security features of the compiler, so be sure to test that the result is what you want and expect!
Take the following example that converts a negative int
to a uint
:
int y = -3;
uint x = uint(y);
At the end of this code snippet, x
will have the value 0xfffff..fd
(64 hex
characters), which is -3 in the two’s complement representation of 256 bits.
If an integer is explicitly converted to a smaller type, higher-order bits are cut off:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b will be 0x5678 now
If an integer is explicitly converted to a larger type, it is padded on the left (i.e., at the higher order end). The result of the conversion will compare equal to the original integer:
uint16 a = 0x1234;
uint32 b = uint32(a); // b will be 0x00001234 now
assert(a == b);
Fixed-size bytes types behave differently during conversions. They can be thought of as sequences of individual bytes and converting to a smaller type will cut off the sequence:
bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b will be 0x12
If a fixed-size bytes type is explicitly converted to a larger type, it is padded on the right. Accessing the byte at a fixed index will result in the same value before and after the conversion (if the index is still in range):
bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b will be 0x12340000
assert(a[0] == b[0]);
assert(a[1] == b[1]);
Since integers and fixed-size byte arrays behave differently when truncating or padding, explicit conversions between integers and fixed-size byte arrays are only allowed, if both have the same size. If you want to convert between integers and fixed-size byte arrays of different size, you have to use intermediate conversions that make the desired truncation and padding rules explicit:
bytes2 a = 0x1234;
uint32 b = uint16(a); // b will be 0x00001234
uint32 c = uint32(bytes4(a)); // c will be 0x12340000
uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12
bytes
arrays and bytes
calldata slices can be converted explicitly to fixed bytes types (bytes1
/…/bytes32
).
In case the array is longer than the target fixed bytes type, truncation at the end will happen.
If the array is shorter than the target type, it will be padded with zeros at the end.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.5;
contract C {
bytes s = "abcdefgh";
function f(bytes calldata c, bytes memory m) public view returns (bytes16, bytes3) {
require(c.length == 16, "");
bytes16 b = bytes16(m); // if length of m is greater than 16, truncation will happen
b = bytes16(s); // padded on the right, so result is "abcdefgh\0\0\0\0\0\0\0\0"
bytes3 b1 = bytes3(s); // truncated, b1 equals to "abc"
b = bytes16(c[:8]); // also padded with zeros
return (b, b1);
}
}
Conversions between Literals and Elementary Types
Integer Types
Decimal and hexadecimal number literals can be implicitly converted to any integer type that is large enough to represent it without truncation:
uint8 a = 12; // fine
uint32 b = 1234; // fine
uint16 c = 0x123456; // fails, since it would have to truncate to 0x3456
Note
Prior to version 0.8.0, any decimal or hexadecimal number literals could be explicitly converted to an integer type. From 0.8.0, such explicit conversions are as strict as implicit conversions, i.e., they are only allowed if the literal fits in the resulting range.
Fixed-Size Byte Arrays
Decimal number literals cannot be implicitly converted to fixed-size byte arrays. Hexadecimal number literals can be, but only if the number of hex digits exactly fits the size of the bytes type. As an exception both decimal and hexadecimal literals which have a value of zero can be converted to any fixed-size bytes type:
bytes2 a = 54321; // not allowed
bytes2 b = 0x12; // not allowed
bytes2 c = 0x123; // not allowed
bytes2 d = 0x1234; // fine
bytes2 e = 0x0012; // fine
bytes4 f = 0; // fine
bytes4 g = 0x0; // fine
String literals and hex string literals can be implicitly converted to fixed-size byte arrays, if their number of characters matches the size of the bytes type:
bytes2 a = hex"1234"; // fine
bytes2 b = "xy"; // fine
bytes2 c = hex"12"; // not allowed
bytes2 d = hex"123"; // not allowed
bytes2 e = "x"; // not allowed
bytes2 f = "xyz"; // not allowed
Addresses
As described in Address Literals, hex literals of the correct size that pass the checksum
test are of address
type. No other literals can be implicitly converted to the address
type.
Explicit conversions from bytes20
or any integer type to address
result in address payable
.
An address a
can be converted to address payable
via payable(a)
.
Units and Globally Available Variables
Ether Units
A literal number can take a suffix of wei
, gwei
or ether
to specify a subdenomination of Ether, where Ether numbers without a postfix are assumed to be Wei.
assert(1 wei == 1);
assert(1 gwei == 1e9);
assert(1 ether == 1e18);
The only effect of the subdenomination suffix is a multiplication by a power of ten.
Note
The denominations finney
and szabo
have been removed in version 0.7.0.
Time Units
Suffixes like seconds
, minutes
, hours
, days
and weeks
after literal numbers can be used to specify units of time where seconds are the base
unit and units are considered naively in the following way:
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks == 7 days
Take care if you perform calendar calculations using these units, because not every year equals 365 days and not even every day has 24 hours because of leap seconds. Due to the fact that leap seconds cannot be predicted, an exact calendar library has to be updated by an external oracle.
Note
The suffix years
has been removed in version 0.5.0 due to the reasons above.
These suffixes cannot be applied to variables. For example, if you want to interpret a function parameter in days, you can in the following way:
function f(uint start, uint daysAfter) public {
if (block.timestamp >= start + daysAfter * 1 days) {
// ...
}
}
Special Variables and Functions
There are special variables and functions which always exist in the global namespace and are mainly used to provide information about the blockchain or are general-use utility functions.
Block and Transaction Properties
blockhash(uint blockNumber) returns (bytes32)
: hash of the given block whenblocknumber
is one of the 256 most recent blocks; otherwise returns zeroblock.basefee
(uint
): current block’s base fee (EIP-3198 and EIP-1559)block.chainid
(uint
): current chain idblock.coinbase
(address payable
): current block miner’s addressblock.difficulty
(uint
): current block difficultyblock.gaslimit
(uint
): current block gaslimitblock.number
(uint
): current block numberblock.timestamp
(uint
): current block timestamp as seconds since unix epochgasleft() returns (uint256)
: remaining gasmsg.data
(bytes calldata
): complete calldatamsg.sender
(address
): sender of the message (current call)msg.sig
(bytes4
): first four bytes of the calldata (i.e. function identifier)msg.value
(uint
): number of wei sent with the messagetx.gasprice
(uint
): gas price of the transactiontx.origin
(address
): sender of the transaction (full call chain)
Note
The values of all members of msg
, including msg.sender
and
msg.value
can change for every external function call.
This includes calls to library functions.
Note
When contracts are evaluated off-chain rather than in context of a transaction included in a
block, you should not assume that block.*
and tx.*
refer to values from any specific
block or transaction. These values are provided by the EVM implementation that executes the
contract and can be arbitrary.
Note
Do not rely on block.timestamp
or blockhash
as a source of randomness,
unless you know what you are doing.
Both the timestamp and the block hash can be influenced by miners to some degree. Bad actors in the mining community can for example run a casino payout function on a chosen hash and just retry a different hash if they did not receive any money.
The current block timestamp must be strictly larger than the timestamp of the last block, but the only guarantee is that it will be somewhere between the timestamps of two consecutive blocks in the canonical chain.
Note
The block hashes are not available for all blocks for scalability reasons. You can only access the hashes of the most recent 256 blocks, all other values will be zero.
Note
The function blockhash
was previously known as block.blockhash
, which was deprecated in
version 0.4.22 and removed in version 0.5.0.
Note
The function gasleft
was previously known as msg.gas
, which was deprecated in
version 0.4.21 and removed in version 0.5.0.
Note
In version 0.7.0, the alias now
(for block.timestamp
) was removed.
ABI Encoding and Decoding Functions
abi.decode(bytes memory encodedData, (...)) returns (...)
: ABI-decodes the given data, while the types are given in parentheses as second argument. Example:(uint a, uint[2] memory b, bytes memory c) = abi.decode(data, (uint, uint[2], bytes))
abi.encode(...) returns (bytes memory)
: ABI-encodes the given argumentsabi.encodePacked(...) returns (bytes memory)
: Performs packed encoding of the given arguments. Note that packed encoding can be ambiguous!abi.encodeWithSelector(bytes4 selector, ...) returns (bytes memory)
: ABI-encodes the given arguments starting from the second and prepends the given four-byte selectorabi.encodeWithSignature(string memory signature, ...) returns (bytes memory)
: Equivalent toabi.encodeWithSelector(bytes4(keccak256(bytes(signature))), ...)
abi.encodeCall(function functionPointer, (...)) returns (bytes memory)
: ABI-encodes a call tofunctionPointer
with the arguments found in the tuple. Performs a full type-check, ensuring the types match the function signature. Result equalsabi.encodeWithSelector(functionPointer.selector, (...))
Note
These encoding functions can be used to craft data for external function calls without actually
calling an external function. Furthermore, keccak256(abi.encodePacked(a, b))
is a way
to compute the hash of structured data (although be aware that it is possible to
craft a “hash collision” using different function parameter types).
See the documentation about the ABI and the tightly packed encoding for details about the encoding.
Members of bytes
bytes.concat(...) returns (bytes memory)
: Concatenates variable number of bytes and bytes1, …, bytes32 arguments to one byte array
Members of string
string.concat(...) returns (string memory)
: Concatenates variable number of string arguments to one string array
Error Handling
See the dedicated section on assert and require for more details on error handling and when to use which function.
assert(bool condition)
causes a Panic error and thus state change reversion if the condition is not met - to be used for internal errors.
require(bool condition)
reverts if the condition is not met - to be used for errors in inputs or external components.
require(bool condition, string memory message)
reverts if the condition is not met - to be used for errors in inputs or external components. Also provides an error message.
revert()
abort execution and revert state changes
revert(string memory reason)
abort execution and revert state changes, providing an explanatory string
Mathematical and Cryptographic Functions
addmod(uint x, uint y, uint k) returns (uint)
compute
(x + y) % k
where the addition is performed with arbitrary precision and does not wrap around at2**256
. Assert thatk != 0
starting from version 0.5.0.mulmod(uint x, uint y, uint k) returns (uint)
compute
(x * y) % k
where the multiplication is performed with arbitrary precision and does not wrap around at2**256
. Assert thatk != 0
starting from version 0.5.0.keccak256(bytes memory) returns (bytes32)
compute the Keccak-256 hash of the input
Note
There used to be an alias for keccak256
called sha3
, which was removed in version 0.5.0.
sha256(bytes memory) returns (bytes32)
compute the SHA-256 hash of the input
ripemd160(bytes memory) returns (bytes20)
compute RIPEMD-160 hash of the input
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address)
recover the address associated with the public key from elliptic curve signature or return zero on error. The function parameters correspond to ECDSA values of the signature:
r
= first 32 bytes of signatures
= second 32 bytes of signaturev
= final 1 byte of signature
ecrecover
returns anaddress
, and not anaddress payable
. See address payable for conversion, in case you need to transfer funds to the recovered address.For further details, read example usage.
Warning
If you use ecrecover
, be aware that a valid signature can be turned into a different valid signature without
requiring knowledge of the corresponding private key. In the Homestead hard fork, this issue was fixed
for _transaction_ signatures (see EIP-2), but
the ecrecover function remained unchanged.
This is usually not a problem unless you require signatures to be unique or
use them to identify items. OpenZeppelin have a ECDSA helper library that you can use as a wrapper for ecrecover
without this issue.
Note
When running sha256
, ripemd160
or ecrecover
on a private blockchain, you might encounter Out-of-Gas. This is because these functions are implemented as “precompiled contracts” and only really exist after they receive the first message (although their contract code is hardcoded). Messages to non-existing contracts are more expensive and thus the execution might run into an Out-of-Gas error. A workaround for this problem is to first send Wei (1 for example) to each of the contracts before you use them in your actual contracts. This is not an issue on the main or test net.
Members of Address Types
<address>.balance
(uint256
)balance of the Address in Wei
<address>.code
(bytes memory
)code at the Address (can be empty)
<address>.codehash
(bytes32
)the codehash of the Address
<address payable>.transfer(uint256 amount)
send given amount of Wei to Address, reverts on failure, forwards 2300 gas stipend, not adjustable
<address payable>.send(uint256 amount) returns (bool)
send given amount of Wei to Address, returns
false
on failure, forwards 2300 gas stipend, not adjustable<address>.call(bytes memory) returns (bool, bytes memory)
issue low-level
CALL
with the given payload, returns success condition and return data, forwards all available gas, adjustable<address>.delegatecall(bytes memory) returns (bool, bytes memory)
issue low-level
DELEGATECALL
with the given payload, returns success condition and return data, forwards all available gas, adjustable<address>.staticcall(bytes memory) returns (bool, bytes memory)
issue low-level
STATICCALL
with the given payload, returns success condition and return data, forwards all available gas, adjustable
For more information, see the section on Address.
Warning
You should avoid using .call()
whenever possible when executing another contract function as it bypasses type checking,
function existence check, and argument packing.
Warning
There are some dangers in using send
: The transfer fails if the call stack depth is at 1024
(this can always be forced by the caller) and it also fails if the recipient runs out of gas. So in order
to make safe Ether transfers, always check the return value of send
, use transfer
or even better:
Use a pattern where the recipient withdraws the money.
Warning
Due to the fact that the EVM considers a call to a non-existing contract to always succeed,
Solidity includes an extra check using the extcodesize
opcode when performing external calls.
This ensures that the contract that is about to be called either actually exists (it contains code)
or an exception is raised.
The low-level calls which operate on addresses rather than contract instances (i.e. .call()
,
.delegatecall()
, .staticcall()
, .send()
and .transfer()
) do not include this
check, which makes them cheaper in terms of gas but also less safe.
Note
Prior to version 0.5.0, Solidity allowed address members to be accessed by a contract instance, for example this.balance
.
This is now forbidden and an explicit conversion to address must be done: address(this).balance
.
Note
If state variables are accessed via a low-level delegatecall, the storage layout of the two contracts must align in order for the called contract to correctly access the storage variables of the calling contract by name. This is of course not the case if storage pointers are passed as function arguments as in the case for the high-level libraries.
Note
Prior to version 0.5.0, .call
, .delegatecall
and .staticcall
only returned the
success condition and not the return data.
Note
Prior to version 0.5.0, there was a member called callcode
with similar but slightly different
semantics than delegatecall
.
Type Information
The expression type(X)
can be used to retrieve information about the type
X
. Currently, there is limited support for this feature (X
can be either
a contract or an integer type) but it might be expanded in the future.
The following properties are available for a contract type C
:
type(C).name
The name of the contract.
type(C).creationCode
Memory byte array that contains the creation bytecode of the contract. This can be used in inline assembly to build custom creation routines, especially by using the
create2
opcode. This property can not be accessed in the contract itself or any derived contract. It causes the bytecode to be included in the bytecode of the call site and thus circular references like that are not possible.type(C).runtimeCode
Memory byte array that contains the runtime bytecode of the contract. This is the code that is usually deployed by the constructor of
C
. IfC
has a constructor that uses inline assembly, this might be different from the actually deployed bytecode. Also note that libraries modify their runtime bytecode at time of deployment to guard against regular calls. The same restrictions as with.creationCode
also apply for this property.
In addition to the properties above, the following properties are available
for an interface type I
:
type(I).interfaceId
:A
bytes4
value containing the EIP-165 interface identifier of the given interfaceI
. This identifier is defined as theXOR
of all function selectors defined within the interface itself - excluding all inherited functions.
The following properties are available for an integer type T
:
type(T).min
The smallest value representable by type
T
.type(T).max
The largest value representable by type
T
.
Expressions and Control Structures
Control Structures
Most of the control structures known from curly-braces languages are available in Solidity:
There is: if
, else
, while
, do
, for
, break
, continue
, return
, with
the usual semantics known from C or JavaScript.
Solidity also supports exception handling in the form of try
/catch
-statements,
but only for external function calls and
contract creation calls. Errors can be created using the revert statement.
Parentheses can not be omitted for conditionals, but curly braces can be omitted around single-statement bodies.
Note that there is no type conversion from non-boolean to boolean types as
there is in C and JavaScript, so if (1) { ... }
is not valid
Solidity.
Function Calls
Internal Function Calls
Functions of the current contract can be called directly (“internally”), also recursively, as seen in this nonsensical example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
// This will report a warning
contract C {
function g(uint a) public pure returns (uint ret) { return a + f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
These function calls are translated into simple jumps inside the EVM. This has the effect that the current memory is not cleared, i.e. passing memory references to internally-called functions is very efficient. Only functions of the same contract instance can be called internally.
You should still avoid excessive recursion, as every internal function call uses up at least one stack slot and there are only 1024 slots available.
External Function Calls
Functions can also be called using the this.g(8);
and c.g(2);
notation, where
c
is a contract instance and g
is a function belonging to c
.
Calling the function g
via either way results in it being called “externally”, using a
message call and not directly via jumps.
Please note that function calls on this
cannot be used in the constructor,
as the actual contract has not been created yet.
Functions of other contracts have to be called externally. For an external call, all function arguments have to be copied to memory.
Note
A function call from one contract to another does not create its own transaction, it is a message call as part of the overall transaction.
When calling functions of other contracts, you can specify the amount of Wei or
gas sent with the call with the special options {value: 10, gas: 10000}
.
Note that it is discouraged to specify gas values explicitly, since the gas costs
of opcodes can change in the future. Any Wei you send to the contract is added
to the total balance of that contract:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(InfoFeed addr) public { feed = addr; }
function callFeed() public { feed.info{value: 10, gas: 800}(); }
}
You need to use the modifier payable
with the info
function because
otherwise, the value
option would not be available.
Warning
Be careful that feed.info{value: 10, gas: 800}
only locally sets the
value
and amount of gas
sent with the function call, and the
parentheses at the end perform the actual call. So
feed.info{value: 10, gas: 800}
does not call the function and
the value
and gas
settings are lost, only
feed.info{value: 10, gas: 800}()
performs the function call.
Due to the fact that the EVM considers a call to a non-existing contract to
always succeed, Solidity uses the extcodesize
opcode to check that
the contract that is about to be called actually exists (it contains code)
and causes an exception if it does not. This check is skipped if the return
data will be decoded after the call and thus the ABI decoder will catch the
case of a non-existing contract.
Note that this check is not performed in case of low-level calls which operate on addresses rather than contract instances.
Note
Be careful when using high-level calls to precompiled contracts, since the compiler considers them non-existing according to the above logic even though they execute code and can return data.
Function calls also cause exceptions if the called contract itself throws an exception or goes out of gas.
Warning
Any interaction with another contract imposes a potential danger, especially if the source code of the contract is not known in advance. The current contract hands over control to the called contract and that may potentially do just about anything. Even if the called contract inherits from a known parent contract, the inheriting contract is only required to have a correct interface. The implementation of the contract, however, can be completely arbitrary and thus, pose a danger. In addition, be prepared in case it calls into other contracts of your system or even back into the calling contract before the first call returns. This means that the called contract can change state variables of the calling contract via its functions. Write your functions in a way that, for example, calls to external functions happen after any changes to state variables in your contract so your contract is not vulnerable to a reentrancy exploit.
Note
Before Solidity 0.6.2, the recommended way to specify the value and gas was to
use f.value(x).gas(g)()
. This was deprecated in Solidity 0.6.2 and is no
longer possible since Solidity 0.7.0.
Named Calls and Anonymous Function Parameters
Function call arguments can be given by name, in any order,
if they are enclosed in { }
as can be seen in the following
example. The argument list has to coincide by name with the list of
parameters from the function declaration, but can be in arbitrary order.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract C {
mapping(uint => uint) data;
function f() public {
set({value: 2, key: 3});
}
function set(uint key, uint value) public {
data[key] = value;
}
}
Omitted Function Parameter Names
The names of unused parameters (especially return parameters) can be omitted. Those parameters will still be present on the stack, but they are inaccessible.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract C {
// omitted name for parameter
function func(uint k, uint) public pure returns(uint) {
return k;
}
}
Creating Contracts via new
A contract can create other contracts using the new
keyword. The full
code of the contract being created has to be known when the creating contract
is compiled so recursive creation-dependencies are not possible.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) payable {
x = a;
}
}
contract C {
D d = new D(4); // will be executed as part of C's constructor
function createD(uint arg) public {
D newD = new D(arg);
newD.x();
}
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = new D{value: amount}(arg);
newD.x();
}
}
As seen in the example, it is possible to send Ether while creating
an instance of D
using the value
option, but it is not possible
to limit the amount of gas.
If the creation fails (due to out-of-stack, not enough balance or other problems),
an exception is thrown.
Salted contract creations / create2
When creating a contract, the address of the contract is computed from the address of the creating contract and a counter that is increased with each contract creation.
If you specify the option salt
(a bytes32 value), then contract creation will
use a different mechanism to come up with the address of the new contract:
It will compute the address from the address of the creating contract, the given salt value, the (creation) bytecode of the created contract and the constructor arguments.
In particular, the counter (“nonce”) is not used. This allows for more flexibility in creating contracts: You are able to derive the address of the new contract before it is created. Furthermore, you can rely on this address also in case the creating contracts creates other contracts in the meantime.
The main use-case here is contracts that act as judges for off-chain interactions, which only need to be created if there is a dispute.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract D {
uint public x;
constructor(uint a) {
x = a;
}
}
contract C {
function createDSalted(bytes32 salt, uint arg) public {
// This complicated expression just tells you how the address
// can be pre-computed. It is just there for illustration.
// You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
salt,
keccak256(abi.encodePacked(
type(D).creationCode,
abi.encode(arg)
))
)))));
D d = new D{salt: salt}(arg);
require(address(d) == predictedAddress);
}
}
Warning
There are some peculiarities in relation to salted creation. A contract can be re-created at the same address after having been destroyed. Yet, it is possible for that newly created contract to have a different deployed bytecode even though the creation bytecode has been the same (which is a requirement because otherwise the address would change). This is due to the fact that the constructor can query external state that might have changed between the two creations and incorporate that into the deployed bytecode before it is stored.
Order of Evaluation of Expressions
The evaluation order of expressions is not specified (more formally, the order in which the children of one node in the expression tree are evaluated is not specified, but they are of course evaluated before the node itself). It is only guaranteed that statements are executed in order and short-circuiting for boolean expressions is done.
Assignment
Destructuring Assignments and Returning Multiple Values
Solidity internally allows tuple types, i.e. a list of objects of potentially different types whose number is a constant at compile-time. Those tuples can be used to return multiple values at the same time. These can then either be assigned to newly declared variables or to pre-existing variables (or LValues in general).
Tuples are not proper types in Solidity, they can only be used to form syntactic groupings of expressions.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
uint index;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
}
function g() public {
// Variables declared with type and assigned from the returned tuple,
// not all elements have to be specified (but the number must match).
(uint x, , uint y) = f();
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
(index, , ) = f(); // Sets the index to 7
}
}
It is not possible to mix variable declarations and non-declaration assignments,
i.e. the following is not valid: (x, uint y) = (1, 2);
Note
Prior to version 0.5.0 it was possible to assign to tuples of smaller size, either filling up on the left or on the right side (which ever was empty). This is now disallowed, so both sides have to have the same number of components.
Warning
Be careful when assigning to multiple variables at the same time when reference types are involved, because it could lead to unexpected copying behaviour.
Complications for Arrays and Structs
The semantics of assignments are more complicated for non-value types like arrays and structs,
including bytes
and string
, see Data location and assignment behaviour for details.
In the example below the call to g(x)
has no effect on x
because it creates
an independent copy of the storage value in memory. However, h(x)
successfully modifies x
because only a reference and not a copy is passed.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract C {
uint[20] x;
function f() public {
g(x);
h(x);
}
function g(uint[20] memory y) internal pure {
y[2] = 3;
}
function h(uint[20] storage y) internal {
y[3] = 4;
}
}
Scoping and Declarations
A variable which is declared will have an initial default
value whose byte-representation is all zeros.
The “default values” of variables are the typical “zero-state”
of whatever the type is. For example, the default value for a bool
is false
. The default value for the uint
or int
types is 0
. For statically-sized arrays and bytes1
to
bytes32
, each individual
element will be initialized to the default value corresponding
to its type. For dynamically-sized arrays, bytes
and string
, the default value is an empty array or string.
For the enum
type, the default value is its first member.
Scoping in Solidity follows the widespread scoping rules of C99
(and many other languages): Variables are visible from the point right after their declaration
until the end of the smallest { }
-block that contains the declaration.
As an exception to this rule, variables declared in the
initialization part of a for-loop are only visible until the end of the for-loop.
Variables that are parameter-like (function parameters, modifier parameters, catch parameters, …) are visible inside the code block that follows - the body of the function/modifier for a function and modifier parameter and the catch block for a catch parameter.
Variables and other items declared outside of a code block, for example functions, contracts, user-defined types, etc., are visible even before they were declared. This means you can use state variables before they are declared and call functions recursively.
As a consequence, the following examples will compile without warnings, since the two variables have the same name but disjoint scopes.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
function minimalScoping() pure public {
{
uint same;
same = 1;
}
{
uint same;
same = 3;
}
}
}
As a special example of the C99 scoping rules, note that in the following,
the first assignment to x
will actually assign the outer and not the inner variable.
In any case, you will get a warning about the outer variable being shadowed.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
// This will report a warning
contract C {
function f() pure public returns (uint) {
uint x = 1;
{
x = 2; // this will assign to the outer variable
uint x;
}
return x; // x has value 2
}
}
Warning
Before version 0.5.0 Solidity followed the same scoping rules as JavaScript, that is, a variable declared anywhere within a function would be in scope for the entire function, regardless where it was declared. The following example shows a code snippet that used to compile but leads to an error starting from version 0.5.0.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
// This will not compile
contract C {
function f() pure public returns (uint) {
x = 2;
uint x;
return x;
}
}
Checked or Unchecked Arithmetic
An overflow or underflow is the situation where the resulting value of an arithmetic operation, when executed on an unrestricted integer, falls outside the range of the result type.
Prior to Solidity 0.8.0, arithmetic operations would always wrap in case of under- or overflow leading to widespread use of libraries that introduce additional checks.
Since Solidity 0.8.0, all arithmetic operations revert on over- and underflow by default, thus making the use of these libraries unnecessary.
To obtain the previous behaviour, an unchecked
block can be used:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
contract C {
function f(uint a, uint b) pure public returns (uint) {
// This subtraction will wrap on underflow.
unchecked { return a - b; }
}
function g(uint a, uint b) pure public returns (uint) {
// This subtraction will revert on underflow.
return a - b;
}
}
The call to f(2, 3)
will return 2**256-1
, while g(2, 3)
will cause
a failing assertion.
The unchecked
block can be used everywhere inside a block, but not as a replacement
for a block. It also cannot be nested.
The setting only affects the statements that are syntactically inside the block.
Functions called from within an unchecked
block do not inherit the property.
Note
To avoid ambiguity, you cannot use _;
inside an unchecked
block.
The following operators will cause a failing assertion on overflow or underflow and will wrap without an error if used inside an unchecked block:
++
, --
, +
, binary -
, unary -
, *
, /
, %
, **
+=
, -=
, *=
, /=
, %=
Warning
It is not possible to disable the check for division by zero
or modulo by zero using the unchecked
block.
Note
Bitwise operators do not perform overflow or underflow checks.
This is particularly visible when using bitwise shifts (<<
, >>
, <<=
, >>=
) in
place of integer division and multiplication by a power of 2.
For example type(uint256).max << 3
does not revert even though type(uint256).max * 8
would.
Note
The second statement in int x = type(int).min; -x;
will result in an overflow
because the negative range can hold one more value than the positive range.
Explicit type conversions will always truncate and never cause a failing assertion with the exception of a conversion from an integer to an enum type.
Error handling: Assert, Require, Revert and Exceptions
Solidity uses state-reverting exceptions to handle errors. Such an exception undoes all changes made to the state in the current call (and all its sub-calls) and flags an error to the caller.
When exceptions happen in a sub-call, they “bubble up” (i.e.,
exceptions are rethrown) automatically unless they are caught in
a try/catch
statement. Exceptions to this rule are send
and the low-level functions call
, delegatecall
and
staticcall
: they return false
as their first return value in case
of an exception instead of “bubbling up”.
Warning
The low-level functions call
, delegatecall
and
staticcall
return true
as their first return value
if the account called is non-existent, as part of the design
of the EVM. Account existence must be checked prior to calling if needed.
Exceptions can contain error data that is passed back to the caller
in the form of error instances.
The built-in errors Error(string)
and Panic(uint256)
are
used by special functions, as explained below. Error
is used for “regular” error conditions
while Panic
is used for errors that should not be present in bug-free code.
Panic via assert
and Error via require
The convenience functions assert
and require
can be used to check for conditions and throw an exception
if the condition is not met.
The assert
function creates an error of type Panic(uint256)
.
The same error is created by the compiler in certain situations as listed below.
Assert should only be used to test for internal errors, and to check invariants. Properly functioning code should never create a Panic, not even on invalid external input. If this happens, then there is a bug in your contract which you should fix. Language analysis tools can evaluate your contract to identify the conditions and function calls which will cause a Panic.
A Panic exception is generated in the following situations. The error code supplied with the error data indicates the kind of panic.
0x00: Used for generic compiler inserted panics.
0x01: If you call
assert
with an argument that evaluates to false.0x11: If an arithmetic operation results in underflow or overflow outside of an
unchecked { ... }
block.0x12; If you divide or modulo by zero (e.g.
5 / 0
or23 % 0
).0x21: If you convert a value that is too big or negative into an enum type.
0x22: If you access a storage byte array that is incorrectly encoded.
0x31: If you call
.pop()
on an empty array.0x32: If you access an array,
bytesN
or an array slice at an out-of-bounds or negative index (i.e.x[i]
wherei >= x.length
ori < 0
).0x41: If you allocate too much memory or create an array that is too large.
0x51: If you call a zero-initialized variable of internal function type.
The require
function either creates an error without any data or
an error of type Error(string)
. It
should be used to ensure valid conditions
that cannot be detected until execution time.
This includes conditions on inputs
or return values from calls to external contracts.
Note
It is currently not possible to use custom errors in combination
with require
. Please use if (!condition) revert CustomError();
instead.
An Error(string)
exception (or an exception without data) is generated
by the compiler
in the following situations:
Calling
require(x)
wherex
evaluates tofalse
.If you use
revert()
orrevert("description")
.If you perform an external function call targeting a contract that contains no code.
If your contract receives Ether via a public function without
payable
modifier (including the constructor and the fallback function).If your contract receives Ether via a public getter function.
For the following cases, the error data from the external call (if provided) is forwarded. This means that it can either cause an Error or a Panic (or whatever else was given):
If a
.transfer()
fails.If you call a function via a message call but it does not finish properly (i.e., it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation
call
,send
,delegatecall
,callcode
orstaticcall
is used. The low level operations never throw exceptions but indicate failures by returningfalse
.If you create a contract using the
new
keyword but the contract creation does not finish properly.
You can optionally provide a message string for require
, but not for assert
.
Note
If you do not provide a string argument to require
, it will revert
with empty error data, not even including the error selector.
The following example shows how you can use require
to check conditions on inputs
and assert
for internal error checking.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = address(this).balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2);
return address(this).balance;
}
}
Internally, Solidity performs a revert operation (instruction
0xfd
). This causes
the EVM to revert all changes made to the state. The reason for reverting
is that there is no safe way to continue execution, because an expected effect
did not occur. Because we want to keep the atomicity of transactions, the
safest action is to revert all changes and make the whole transaction
(or at least call) without effect.
In both cases, the caller can react on such failures using try
/catch
, but
the changes in the callee will always be reverted.
Note
Panic exceptions used to use the invalid
opcode before Solidity 0.8.0,
which consumed all gas available to the call.
Exceptions that use require
used to consume all gas until before the Metropolis release.
revert
A direct revert can be triggered using the revert
statement and the revert
function.
The revert
statement takes a custom error as direct argument without parentheses:
revert CustomError(arg1, arg2);
For backwards-compatibility reasons, there is also the revert()
function, which uses parentheses
and accepts a string:
revert(); revert(“description”);
The error data will be passed back to the caller and can be caught there.
Using revert()
causes a revert without any error data while revert("description")
will create an Error(string)
error.
Using a custom error instance will usually be much cheaper than a string description, because you can use the name of the error to describe it, which is encoded in only four bytes. A longer description can be supplied via NatSpec which does not incur any costs.
The following example shows how to use an error string and a custom error instance
together with revert
and the equivalent require
:
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract VendingMachine {
address owner;
error Unauthorized();
function buy(uint amount) public payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Alternative way to do it:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// Perform the purchase.
}
function withdraw() public {
if (msg.sender != owner)
revert Unauthorized();
payable(msg.sender).transfer(address(this).balance);
}
}
The two ways if (!condition) revert(...);
and require(condition, ...);
are
equivalent as long as the arguments to revert
and require
do not have side-effects,
for example if they are just strings.
Note
The require
function is evaluated just as any other function.
This means that all arguments are evaluated before the function itself is executed.
In particular, in require(condition, f())
the function f
is executed even if
condition
is true.
The provided string is abi-encoded as if it were a call to a function Error(string)
.
In the above example, revert("Not enough Ether provided.");
returns the following hexadecimal as error return data:
0x08c379a0 // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data
The provided message can be retrieved by the caller using try
/catch
as shown below.
Note
There used to be a keyword called throw
with the same semantics as revert()
which
was deprecated in version 0.4.13 and removed in version 0.5.0.
try
/catch
A failure in an external call can be caught using a try/catch statement, as follows:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.1;
interface DataFeed { function getData(address token) external returns (uint value); }
contract FeedConsumer {
DataFeed feed;
uint errorCount;
function rate(address token) public returns (uint value, bool success) {
// Permanently disable the mechanism if there are
// more than 10 errors.
require(errorCount < 10);
try feed.getData(token) returns (uint v) {
return (v, true);
} catch Error(string memory /*reason*/) {
// This is executed in case
// revert was called inside getData
// and a reason string was provided.
errorCount++;
return (0, false);
} catch Panic(uint /*errorCode*/) {
// This is executed in case of a panic,
// i.e. a serious error like division by zero
// or overflow. The error code can be used
// to determine the kind of error.
errorCount++;
return (0, false);
} catch (bytes memory /*lowLevelData*/) {
// This is executed in case revert() was used.
errorCount++;
return (0, false);
}
}
}
The try
keyword has to be followed by an expression representing an external function call
or a contract creation (new ContractName()
).
Errors inside the expression are not caught (for example if it is a complex expression
that also involves internal function calls), only a revert happening inside the external
call itself. The returns
part (which is optional) that follows declares return variables
matching the types returned by the external call. In case there was no error,
these variables are assigned and the contract’s execution continues inside the
first success block. If the end of the success block is reached, execution continues after the catch
blocks.
Solidity supports different kinds of catch blocks depending on the type of error:
catch Error(string memory reason) { ... }
: This catch clause is executed if the error was caused byrevert("reasonString")
orrequire(false, "reasonString")
(or an internal error that causes such an exception).catch Panic(uint errorCode) { ... }
: If the error was caused by a panic, i.e. by a failingassert
, division by zero, invalid array access, arithmetic overflow and others, this catch clause will be run.catch (bytes memory lowLevelData) { ... }
: This clause is executed if the error signature does not match any other clause, if there was an error while decoding the error message, or if no error data was provided with the exception. The declared variable provides access to the low-level error data in that case.catch { ... }
: If you are not interested in the error data, you can just usecatch { ... }
(even as the only catch clause) instead of the previous clause.
It is planned to support other types of error data in the future.
The strings Error
and Panic
are currently parsed as is and are not treated as identifiers.
In order to catch all error cases, you have to have at least the clause
catch { ...}
or the clause catch (bytes memory lowLevelData) { ... }
.
The variables declared in the returns
and the catch
clause are only
in scope in the block that follows.
Note
If an error happens during the decoding of the return data
inside a try/catch-statement, this causes an exception in the currently
executing contract and because of that, it is not caught in the catch clause.
If there is an error during decoding of catch Error(string memory reason)
and there is a low-level catch clause, this error is caught there.
Note
If execution reaches a catch-block, then the state-changing effects of the external call have been reverted. If execution reaches the success block, the effects were not reverted. If the effects have been reverted, then execution either continues in a catch block or the execution of the try/catch statement itself reverts (for example due to decoding failures as noted above or due to not providing a low-level catch clause).
Note
The reason behind a failed call can be manifold. Do not assume that the error message is coming directly from the called contract: The error might have happened deeper down in the call chain and the called contract just forwarded it. Also, it could be due to an out-of-gas situation and not a deliberate error condition: The caller always retains at least 1/64th of the gas in a call and thus even if the called contract goes out of gas, the caller still has some gas left.
Contracts
Contracts in Solidity are similar to classes in object-oriented languages. They contain persistent data in state variables, and functions that can modify these variables. Calling a function on a different contract (instance) will perform an EVM function call and thus switch the context such that state variables in the calling contract are inaccessible. A contract and its functions need to be called for anything to happen. There is no “cron” concept in Ethereum to call a function at a particular event automatically.
Creating Contracts
Contracts can be created “from outside” via Ethereum transactions or from within Solidity contracts.
IDEs, such as Remix, make the creation process seamless using UI elements.
One way to create contracts programmatically on Ethereum is via the JavaScript API web3.js. It has a function called web3.eth.Contract to facilitate contract creation.
When a contract is created, its constructor (a function declared with
the constructor
keyword) is executed once.
A constructor is optional. Only one constructor is allowed, which means overloading is not supported.
After the constructor has executed, the final code of the contract is stored on the blockchain. This code includes all public and external functions and all functions that are reachable from there through function calls. The deployed code does not include the constructor code or internal functions only called from the constructor.
Internally, constructor arguments are passed ABI encoded after the code of
the contract itself, but you do not have to care about this if you use web3.js
.
If a contract wants to create another contract, the source code (and the binary) of the created contract has to be known to the creator. This means that cyclic creation dependencies are impossible.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.9.0;
contract OwnedToken {
// `TokenCreator` is a contract type that is defined below.
// It is fine to reference it as long as it is not used
// to create a new contract.
TokenCreator creator;
address owner;
bytes32 name;
// This is the constructor which registers the
// creator and the assigned name.
constructor(bytes32 name_) {
// State variables are accessed via their name
// and not via e.g. `this.owner`. Functions can
// be accessed directly or through `this.f`,
// but the latter provides an external view
// to the function. Especially in the constructor,
// you should not access functions externally,
// because the function does not exist yet.
// See the next section for details.
owner = msg.sender;
// We perform an explicit type conversion from `address`
// to `TokenCreator` and assume that the type of
// the calling contract is `TokenCreator`, there is
// no real way to verify that.
// This does not create a new contract.
creator = TokenCreator(msg.sender);
name = name_;
}
function changeName(bytes32 newName) public {
// Only the creator can alter the name.
// We compare the contract based on its
// address which can be retrieved by
// explicit conversion to address.
if (msg.sender == address(creator))
name = newName;
}
function transfer(address newOwner) public {
// Only the current owner can transfer the token.
if (msg.sender != owner) return;
// We ask the creator contract if the transfer
// should proceed by using a function of the
// `TokenCreator` contract defined below. If
// the call fails (e.g. due to out-of-gas),
// the execution also fails here.
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name)
public
returns (OwnedToken tokenAddress)
{
// Create a new `Token` contract and return its address.
// From the JavaScript side, the return type
// of this function is `address`, as this is
// the closest type available in the ABI.
return new OwnedToken(name);
}
function changeName(OwnedToken tokenAddress, bytes32 name) public {
// Again, the external type of `tokenAddress` is
// simply `address`.
tokenAddress.changeName(name);
}
// Perform checks to determine if transferring a token to the
// `OwnedToken` contract should proceed
function isTokenTransferOK(address currentOwner, address newOwner)
public
pure
returns (bool ok)
{
// Check an arbitrary condition to see if transfer should proceed
return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f;
}
}
Visibility and Getters
State Variable Visibility
public
Public state variables differ from internal ones only in that the compiler automatically generates getter functions for them, which allows other contracts to read their values. When used within the same contract, the external access (e.g.
this.x
) invokes the getter while internal access (e.g.x
) gets the variable value directly from storage. Setter functions are not generated so other contracts cannot directly modify their values.internal
Internal state variables can only be accessed from within the contract they are defined in and in derived contracts. They cannot be accessed externally. This is the default visibility level for state variables.
private
Private state variables are like internal ones but they are not visible in derived contracts.
Warning
Making something private
or internal
only prevents other contracts from reading or modifying the information, but it will still be visible to the whole world outside of the blockchain.
Function Visibility
Solidity knows two kinds of function calls: external ones that do create an actual EVM message call and internal ones that do not. Furthermore, internal functions can be made inaccessible to derived contracts. This gives rise to four types of visibility for functions.
external
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, butthis.f()
works).public
Public functions are part of the contract interface and can be either called internally or via message calls.
internal
Internal functions can only be accessed from within the current contract or contracts deriving from it. They cannot be accessed externally. Since they are not exposed to the outside through the contract’s ABI, they can take parameters of internal types like mappings or storage references.
private
Private functions are like internal ones but they are not visible in derived contracts.
Warning
Making something private
or internal
only prevents other contracts from reading or modifying the information, but it will still be visible to the whole world outside of the blockchain.
The visibility specifier is given after the type for state variables and between parameter list and return parameter list for functions.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
In the following example, D
, can call c.getData()
to retrieve the value of
data
in state storage, but is not able to call f
. Contract E
is derived from
C
and, thus, can call compute
.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
uint private data;
function f(uint a) private pure returns(uint b) { return a + 1; }
function setData(uint a) public { data = a; }
function getData() public view returns(uint) { return data; }
function compute(uint a, uint b) internal pure returns (uint) { return a + b; }
}
// This will not compile
contract D {
function readData() public {
C c = new C();
uint local = c.f(7); // error: member `f` is not visible
c.setData(3);
local = c.getData();
local = c.compute(3, 5); // error: member `compute` is not visible
}
}
contract E is C {
function g() public {
C c = new C();
uint val = compute(3, 5); // access to internal member (from derived to parent contract)
}
}
Getter Functions
The compiler automatically creates getter functions for
all public state variables. For the contract given below, the compiler will
generate a function called data
that does not take any
arguments and returns a uint
, the value of the state
variable data
. State variables can be initialized
when they are declared.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public view returns (uint) {
return c.data();
}
}
The getter functions have external visibility. If the
symbol is accessed internally (i.e. without this.
),
it evaluates to a state variable. If it is accessed externally
(i.e. with this.
), it evaluates to a function.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract C {
uint public data;
function x() public returns (uint) {
data = 3; // internal access
return this.data(); // external access
}
}
If you have a public
state variable of array type, then you can only retrieve
single elements of the array via the generated getter function. This mechanism
exists to avoid high gas costs when returning an entire array. You can use
arguments to specify which individual element to return, for example
myArray(0)
. If you want to return an entire array in one call, then you need
to write a function, for example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract arrayExample {
// public state variable
uint[] public myArray;
// Getter function generated by the compiler
/*
function myArray(uint i) public view returns (uint) {
return myArray[i];
}
*/
// function that returns entire array
function getArray() public view returns (uint[] memory) {
return myArray;
}
}
Now you can use getArray()
to retrieve the entire array, instead of
myArray(i)
, which returns a single element per call.
The next example is more complex:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract Complex {
struct Data {
uint a;
bytes3 b;
mapping (uint => uint) map;
uint[3] c;
uint[] d;
bytes e;
}
mapping (uint => mapping(bool => Data[])) public data;
}
It generates a function of the following form. The mapping and arrays (with the exception of byte arrays) in the struct are omitted because there is no good way to select individual struct members or provide a key for the mapping:
function data(uint arg1, bool arg2, uint arg3)
public
returns (uint a, bytes3 b, bytes memory e)
{
a = data[arg1][arg2][arg3].a;
b = data[arg1][arg2][arg3].b;
e = data[arg1][arg2][arg3].e;
}
Function Modifiers
Modifiers can be used to change the behaviour of functions in a declarative way. For example, you can use a modifier to automatically check a condition prior to executing the function.
Modifiers are
inheritable properties of contracts and may be overridden by derived contracts, but only
if they are marked virtual
. For details, please see
Modifier Overriding.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
contract owned {
constructor() { owner = payable(msg.sender); }
address payable owner;
// This contract only defines a modifier but does not use
// it: it will be used in derived contracts.
// The function body is inserted where the special symbol
// `_;` in the definition of a modifier appears.
// This means that if the owner calls this function, the
// function is executed and otherwise, an exception is
// thrown.
modifier onlyOwner {
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
contract destructible is owned {
// This contract inherits the `onlyOwner` modifier from
// `owned` and applies it to the `destroy` function, which
// causes that calls to `destroy` only have an effect if
// they are made by the stored owner.
function destroy() public onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// Modifiers can receive arguments:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, destructible {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) { price = initialPrice; }
// It is important to also provide the
// `payable` keyword here, otherwise the function will
// automatically reject all Ether sent to it.
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint price_) public onlyOwner {
price = price_;
}
}
contract Mutex {
bool locked;
modifier noReentrancy() {
require(
!locked,
"Reentrant call."
);
locked = true;
_;
locked = false;
}
/// This function is protected by a mutex, which means that
/// reentrant calls from within `msg.sender.call` cannot call `f` again.
/// The `return 7` statement assigns 7 to the return value but still
/// executes the statement `locked = false` in the modifier.
function f() public noReentrancy returns (uint) {
(bool success,) = msg.sender.call("");
require(success);
return 7;
}
}
If you want to access a modifier m
defined in a contract C
, you can use C.m
to
reference it without virtual lookup. It is only possible to use modifiers defined in the current
contract or its base contracts. Modifiers can also be defined in libraries but their use is
limited to functions of the same library.
Multiple modifiers are applied to a function by specifying them in a whitespace-separated list and are evaluated in the order presented.
Modifiers cannot implicitly access or change the arguments and return values of functions they modify. Their values can only be passed to them explicitly at the point of invocation.
Explicit returns from a modifier or function body only leave the current
modifier or function body. Return variables are assigned and
control flow continues after the _
in the preceding modifier.
Warning
In an earlier version of Solidity, return
statements in functions
having modifiers behaved differently.
An explicit return from a modifier with return;
does not affect the values returned by the function.
The modifier can, however, choose not to execute the function body at all and in that case the return
variables are set to their default values just as if the function had an empty
body.
The _
symbol can appear in the modifier multiple times. Each occurrence is replaced with
the function body.
Arbitrary expressions are allowed for modifier arguments and in this context, all symbols visible from the function are visible in the modifier. Symbols introduced in the modifier are not visible in the function (as they might change by overriding).
Constant and Immutable State Variables
State variables can be declared as constant
or immutable
.
In both cases, the variables cannot be modified after the contract has been constructed.
For constant
variables, the value has to be fixed at compile-time, while
for immutable
, it can still be assigned at construction time.
It is also possible to define constant
variables at the file level.
The compiler does not reserve a storage slot for these variables, and every occurrence is replaced by the respective value.
Compared to regular state variables, the gas costs of constant and immutable variables are much lower. For a constant variable, the expression assigned to it is copied to all the places where it is accessed and also re-evaluated each time. This allows for local optimizations. Immutable variables are evaluated once at construction time and their value is copied to all the places in the code where they are accessed. For these values, 32 bytes are reserved, even if they would fit in fewer bytes. Due to this, constant values can sometimes be cheaper than immutable values.
Not all types for constants and immutables are implemented at this time. The only supported types are strings (only for constants) and value types.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.4;
uint constant X = 32**22 + 8;
contract C {
string constant TEXT = "abc";
bytes32 constant MY_HASH = keccak256("abc");
uint immutable decimals;
uint immutable maxBalance;
address immutable owner = msg.sender;
constructor(uint decimals_, address ref) {
decimals = decimals_;
// Assignments to immutables can even access the environment.
maxBalance = ref.balance;
}
function isBalanceTooHigh(address other) public view returns (bool) {
return other.balance > maxBalance;
}
}
Constant
For constant
variables, the value has to be a constant at compile time and it has to be
assigned where the variable is declared. Any expression
that accesses storage, blockchain data (e.g. block.timestamp
, address(this).balance
or
block.number
) or
execution data (msg.value
or gasleft()
) or makes calls to external contracts is disallowed. Expressions
that might have a side-effect on memory allocation are allowed, but those that
might have a side-effect on other memory objects are not. The built-in functions
keccak256
, sha256
, ripemd160
, ecrecover
, addmod
and mulmod
are allowed (even though, with the exception of keccak256
, they do call external contracts).
The reason behind allowing side-effects on the memory allocator is that it should be possible to construct complex objects like e.g. lookup-tables. This feature is not yet fully usable.
Immutable
Variables declared as immutable
are a bit less restricted than those
declared as constant
: Immutable variables can be assigned an arbitrary
value in the constructor of the contract or at the point of their declaration.
They can be assigned only once and can, from that point on, be read even during
construction time.
The contract creation code generated by the compiler will modify the contract’s runtime code before it is returned by replacing all references to immutables by the values assigned to the them. This is important if you are comparing the runtime code generated by the compiler with the one actually stored in the blockchain.
Note
Immutables that are assigned at their declaration are only considered initialized once the constructor of the contract is executing. This means you cannot initialize immutables inline with a value that depends on another immutable. You can do this, however, inside the constructor of the contract.
This is a safeguard against different interpretations about the order of state variable initialization and constructor execution, especially with regards to inheritance.
Functions
Functions can be defined inside and outside of contracts.
Functions outside of a contract, also called “free functions”, always have implicit internal
visibility. Their code is included in all contracts
that call them, similar to internal library functions.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.1 <0.9.0;
function sum(uint[] memory arr) pure returns (uint s) {
for (uint i = 0; i < arr.length; i++)
s += arr[i];
}
contract ArrayExample {
bool found;
function f(uint[] memory arr) public {
// This calls the free function internally.
// The compiler will add its code to the contract.
uint s = sum(arr);
require(s >= 10);
found = true;
}
}
Note
Functions defined outside a contract are still always executed
in the context of a contract. They still have access to the variable this
,
can call other contracts, send them Ether and destroy the contract that called them,
among other things. The main difference to functions defined inside a contract
is that free functions do not have direct access to storage variables and functions
not in their scope.
Function Parameters and Return Variables
Functions take typed parameters as input and may, unlike in many other languages, also return an arbitrary number of values as output.
Function Parameters
Function parameters are declared the same way as variables, and the name of unused parameters can be omitted.
For example, if you want your contract to accept one kind of external call with two integers, you would use something like the following:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract Simple {
uint sum;
function taker(uint a, uint b) public {
sum = a + b;
}
}
Function parameters can be used as any other local variable and they can also be assigned to.
Note
An external function cannot accept a
multi-dimensional array as an input
parameter. This functionality is possible if you enable the ABI coder v2
by adding pragma abicoder v2;
to your source file.
An internal function can accept a multi-dimensional array without enabling the feature.
Return Variables
Function return variables are declared with the same syntax after the
returns
keyword.
For example, suppose you want to return two results: the sum and the product of two integers passed as function parameters, then you use something like:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract Simple {
function arithmetic(uint a, uint b)
public
pure
returns (uint sum, uint product)
{
sum = a + b;
product = a * b;
}
}
The names of return variables can be omitted. Return variables can be used as any other local variable and they are initialized with their default value and have that value until they are (re-)assigned.
You can either explicitly assign to return variables and
then leave the function as above,
or you can provide return values
(either a single or multiple ones) directly with the return
statement:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract Simple {
function arithmetic(uint a, uint b)
public
pure
returns (uint sum, uint product)
{
return (a + b, a * b);
}
}
If you use an early return
to leave a function that has return variables,
you must provide return values together with the return statement.
Note
You cannot return some types from non-internal functions, notably
multi-dimensional dynamic arrays and structs. If you enable the
ABI coder v2 by adding pragma abicoder v2;
to your source file then more types are available, but
mapping
types are still limited to inside a single contract and you
cannot transfer them.
Returning Multiple Values
When a function has multiple return types, the statement return (v0, v1, ..., vn)
can be used to return multiple values.
The number of components must be the same as the number of return variables
and their types have to match, potentially after an implicit conversion.
State Mutability
View Functions
Functions can be declared view
in which case they promise not to modify the state.
Note
If the compiler’s EVM target is Byzantium or newer (default) the opcode
STATICCALL
is used when view
functions are called, which enforces the state
to stay unmodified as part of the EVM execution. For library view
functions
DELEGATECALL
is used, because there is no combined DELEGATECALL
and STATICCALL
.
This means library view
functions do not have run-time checks that prevent state
modifications. This should not impact security negatively because library code is
usually known at compile-time and the static checker performs compile-time checks.
The following statements are considered modifying the state:
Writing to state variables.
Using
selfdestruct
.Sending Ether via calls.
Calling any function not marked
view
orpure
.Using low-level calls.
Using inline assembly that contains certain opcodes.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
function f(uint a, uint b) public view returns (uint) {
return a * (b + 42) + block.timestamp;
}
}
Note
constant
on functions used to be an alias to view
, but this was dropped in version 0.5.0.
Note
Getter methods are automatically marked view
.
Note
Prior to version 0.5.0, the compiler did not use the STATICCALL
opcode
for view
functions.
This enabled state modifications in view
functions through the use of
invalid explicit type conversions.
By using STATICCALL
for view
functions, modifications to the
state are prevented on the level of the EVM.
Pure Functions
Functions can be declared pure
in which case they promise not to read from or modify the state.
In particular, it should be possible to evaluate a pure
function at compile-time given
only its inputs and msg.data
, but without any knowledge of the current blockchain state.
This means that reading from immutable
variables can be a non-pure operation.
Note
If the compiler’s EVM target is Byzantium or newer (default) the opcode STATICCALL
is used,
which does not guarantee that the state is not read, but at least that it is not modified.
In addition to the list of state modifying statements explained above, the following are considered reading from the state:
Reading from state variables.
Accessing
address(this).balance
or<address>.balance
.Accessing any of the members of
block
,tx
,msg
(with the exception ofmsg.sig
andmsg.data
).Calling any function not marked
pure
.Using inline assembly that contains certain opcodes.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
}
}
Pure functions are able to use the revert()
and require()
functions to revert
potential state changes when an error occurs.
Reverting a state change is not considered a “state modification”, as only changes to the
state made previously in code that did not have the view
or pure
restriction
are reverted and that code has the option to catch the revert
and not pass it on.
This behaviour is also in line with the STATICCALL
opcode.
Warning
It is not possible to prevent functions from reading the state at the level
of the EVM, it is only possible to prevent them from writing to the state
(i.e. only view
can be enforced at the EVM level, pure
can not).
Note
Prior to version 0.5.0, the compiler did not use the STATICCALL
opcode
for pure
functions.
This enabled state modifications in pure
functions through the use of
invalid explicit type conversions.
By using STATICCALL
for pure
functions, modifications to the
state are prevented on the level of the EVM.
Note
Prior to version 0.4.17 the compiler did not enforce that pure
is not reading the state.
It is a compile-time type check, which can be circumvented doing invalid explicit conversions
between contract types, because the compiler can verify that the type of the contract does
not do state-changing operations, but it cannot check that the contract that will be called
at runtime is actually of that type.
Special Functions
Receive Ether Function
A contract can have at most one receive
function, declared using
receive() external payable { ... }
(without the function
keyword).
This function cannot have arguments, cannot return anything and must have
external
visibility and payable
state mutability.
It can be virtual, can override and can have modifiers.
The receive function is executed on a
call to the contract with empty calldata. This is the function that is executed
on plain Ether transfers (e.g. via .send()
or .transfer()
). If no such
function exists, but a payable fallback function
exists, the fallback function will be called on a plain Ether transfer. If
neither a receive Ether nor a payable fallback function is present, the
contract cannot receive Ether through regular transactions and throws an
exception.
In the worst case, the receive
function can only rely on 2300 gas being
available (for example when send
or transfer
is used), leaving little
room to perform other operations except basic logging. The following operations
will consume more gas than the 2300 gas stipend:
Writing to storage
Creating a contract
Calling an external function which consumes a large amount of gas
Sending Ether
Warning
When Ether is sent directly to a contract (without a function call, i.e. sender uses send
or transfer
)
but the receiving contract does not define a receive Ether function or a payable fallback function,
an exception will be thrown, sending back the Ether (this was different
before Solidity v0.4.0). If you want your contract to receive Ether,
you have to implement a receive Ether function (using payable fallback functions for receiving Ether is
not recommended, since the fallback is invoked and would not fail for interface confusions
on the part of the sender).
Warning
A contract without a receive Ether function can receive Ether as a
recipient of a coinbase transaction (aka miner block reward)
or as a destination of a selfdestruct
.
A contract cannot react to such Ether transfers and thus also cannot reject them. This is a design choice of the EVM and Solidity cannot work around it.
It also means that address(this).balance
can be higher
than the sum of some manual accounting implemented in a
contract (i.e. having a counter updated in the receive Ether function).
Below you can see an example of a Sink contract that uses function receive
.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
event Received(address, uint);
receive() external payable {
emit Received(msg.sender, msg.value);
}
}
Fallback Function
A contract can have at most one fallback
function, declared using either fallback () external [payable]
or fallback (bytes calldata input) external [payable] returns (bytes memory output)
(both without the function
keyword).
This function must have external
visibility. A fallback function can be virtual, can override
and can have modifiers.
The fallback function is executed on a call to the contract if none of the other
functions match the given function signature, or if no data was supplied at
all and there is no receive Ether function.
The fallback function always receives data, but in order to also receive Ether
it must be marked payable
.
If the version with parameters is used, input
will contain the full data sent to the contract
(equal to msg.data
) and can return data in output
. The returned data will not be
ABI-encoded. Instead it will be returned without modifications (not even padding).
In the worst case, if a payable fallback function is also used in place of a receive function, it can only rely on 2300 gas being available (see receive Ether function for a brief description of the implications of this).
Like any function, the fallback function can execute complex operations as long as there is enough gas passed on to it.
Warning
A payable
fallback function is also executed for
plain Ether transfers, if no receive Ether function
is present. It is recommended to always define a receive Ether
function as well, if you define a payable fallback function
to distinguish Ether transfers from interface confusions.
Note
If you want to decode the input data, you can check the first four bytes
for the function selector and then
you can use abi.decode
together with the array slice syntax to
decode ABI-encoded data:
(c, d) = abi.decode(input[4:], (uint256, uint256));
Note that this should only be used as a last resort and
proper functions should be used instead.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.2 <0.9.0;
contract Test {
uint x;
// This function is called for all messages sent to
// this contract (there is no other function).
// Sending Ether to this contract will cause an exception,
// because the fallback function does not have the `payable`
// modifier.
fallback() external { x = 1; }
}
contract TestPayable {
uint x;
uint y;
// This function is called for all messages sent to
// this contract, except plain Ether transfers
// (there is no other function except the receive function).
// Any call with non-empty calldata to this contract will execute
// the fallback function (even if Ether is sent along with the call).
fallback() external payable { x = 1; y = msg.value; }
// This function is called for plain Ether transfers, i.e.
// for every call with empty calldata.
receive() external payable { x = 2; y = msg.value; }
}
contract Caller {
function callTest(Test test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1.
// address(test) will not allow to call ``send`` directly, since ``test`` has no payable
// fallback function.
// It has to be converted to the ``address payable`` type to even allow calling ``send`` on it.
address payable testPayable = payable(address(test));
// If someone sends Ether to that contract,
// the transfer will fail, i.e. this returns false here.
return testPayable.send(2 ether);
}
function callTestPayable(TestPayable test) public returns (bool) {
(bool success,) = address(test).call(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 0.
(success,) = address(test).call{value: 1}(abi.encodeWithSignature("nonExistingFunction()"));
require(success);
// results in test.x becoming == 1 and test.y becoming 1.
// If someone sends Ether to that contract, the receive function in TestPayable will be called.
// Since that function writes to storage, it takes more gas than is available with a
// simple ``send`` or ``transfer``. Because of that, we have to use a low-level call.
(success,) = address(test).call{value: 2 ether}("");
require(success);
// results in test.x becoming == 2 and test.y becoming 2 ether.
return true;
}
}
Function Overloading
A contract can have multiple functions of the same name but with different parameter
types.
This process is called “overloading” and also applies to inherited functions.
The following example shows overloading of the function
f
in the scope of contract A
.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract A {
function f(uint value) public pure returns (uint out) {
out = value;
}
function f(uint value, bool really) public pure returns (uint out) {
if (really)
out = value;
}
}
Overloaded functions are also present in the external interface. It is an error if two externally visible functions differ by their Solidity types but not by their external types.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
// This will not compile
contract A {
function f(B value) public pure returns (B out) {
out = value;
}
function f(address value) public pure returns (address out) {
out = value;
}
}
contract B {
}
Both f
function overloads above end up accepting the address type for the ABI although
they are considered different inside Solidity.
Overload resolution and Argument matching
Overloaded functions are selected by matching the function declarations in the current scope to the arguments supplied in the function call. Functions are selected as overload candidates if all arguments can be implicitly converted to the expected types. If there is not exactly one candidate, resolution fails.
Note
Return parameters are not taken into account for overload resolution.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract A {
function f(uint8 val) public pure returns (uint8 out) {
out = val;
}
function f(uint256 val) public pure returns (uint256 out) {
out = val;
}
}
Calling f(50)
would create a type error since 50
can be implicitly converted both to uint8
and uint256
types. On another hand f(256)
would resolve to f(uint256)
overload as 256
cannot be implicitly
converted to uint8
.
Events
Solidity events give an abstraction on top of the EVM’s logging functionality. Applications can subscribe and listen to these events through the RPC interface of an Ethereum client.
Events are inheritable members of contracts. When you call them, they cause the arguments to be stored in the transaction’s log - a special data structure in the blockchain. These logs are associated with the address of the contract, are incorporated into the blockchain, and stay there as long as a block is accessible (forever as of now, but this might change with Serenity). The Log and its event data is not accessible from within contracts (not even from the contract that created them).
It is possible to request a Merkle proof for logs, so if an external entity supplies a contract with such a proof, it can check that the log actually exists inside the blockchain. You have to supply block headers because the contract can only see the last 256 block hashes.
You can add the attribute indexed
to up to three parameters which adds them
to a special data structure known as “topics” instead of
the data part of the log.
A topic can only hold a single word (32 bytes) so if you use a reference type for an indexed argument, the Keccak-256 hash of the value is stored
as a topic instead.
All parameters without the indexed
attribute are ABI-encoded
into the data part of the log.
Topics allow you to search for events, for example when filtering a sequence of blocks for certain events. You can also filter events by the address of the contract that emitted the event.
For example, the code below uses the web3.js subscribe("logs")
method to filter
logs that match a topic with a certain address value:
var options = {
fromBlock: 0,
address: web3.eth.defaultAccount,
topics: ["0x0000000000000000000000000000000000000000000000000000000000000000", null, null]
};
web3.eth.subscribe('logs', options, function (error, result) {
if (!error)
console.log(result);
})
.on("data", function (log) {
console.log(log);
})
.on("changed", function (log) {
});
The hash of the signature of the event is one of the topics, except if you
declared the event with the anonymous
specifier. This means that it is
not possible to filter for specific anonymous events by name, you can
only filter by the contract address. The advantage of anonymous events
is that they are cheaper to deploy and call. It also allows you to declare
four indexed arguments rather than three.
Note
Since the transaction log only stores the event data and not the type, you have to know the type of the event, including which parameter is indexed and if the event is anonymous in order to correctly interpret the data. In particular, it is possible to “fake” the signature of another event using an anonymous event.
Members of Events
event.selector
: For non-anonymous events, this is abytes32
value containing thekeccak256
hash of the event signature, as used in the default topic.
Example
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.21 <0.9.0;
contract ClientReceipt {
event Deposit(
address indexed from,
bytes32 indexed id,
uint value
);
function deposit(bytes32 id) public payable {
// Events are emitted using `emit`, followed by
// the name of the event and the arguments
// (if any) in parentheses. Any such invocation
// (even deeply nested) can be detected from
// the JavaScript API by filtering for `Deposit`.
emit Deposit(msg.sender, id, msg.value);
}
}
The use in the JavaScript API is as follows:
var abi = /* abi as generated by the compiler */;
var ClientReceipt = web3.eth.contract(abi);
var clientReceipt = ClientReceipt.at("0x1234...ab67" /* address */);
var depositEvent = clientReceipt.Deposit();
// watch for changes
depositEvent.watch(function(error, result){
// result contains non-indexed arguments and topics
// given to the `Deposit` call.
if (!error)
console.log(result);
});
// Or pass a callback to start watching immediately
var depositEvent = clientReceipt.Deposit(function(error, result) {
if (!error)
console.log(result);
});
The output of the above looks like the following (trimmed):
{
"returnValues": {
"from": "0x1111…FFFFCCCC",
"id": "0x50…sd5adb20",
"value": "0x420042"
},
"raw": {
"data": "0x7f…91385",
"topics": ["0xfd4…b4ead7", "0x7f…1a91385"]
}
}
Additional Resources for Understanding Events
Errors and the Revert Statement
Errors in Solidity provide a convenient and gas-efficient way to explain to the user why an operation failed. They can be defined inside and outside of contracts (including interfaces and libraries).
They have to be used together with the revert statement which causes all changes in the current call to be reverted and passes the error data back to the caller.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
/// Insufficient balance for transfer. Needed `required` but only
/// `available` available.
/// @param available balance available.
/// @param required requested amount to transfer.
error InsufficientBalance(uint256 available, uint256 required);
contract TestToken {
mapping(address => uint) balance;
function transfer(address to, uint256 amount) public {
if (amount > balance[msg.sender])
revert InsufficientBalance({
available: balance[msg.sender],
required: amount
});
balance[msg.sender] -= amount;
balance[to] += amount;
}
// ...
}
Errors cannot be overloaded or overridden but are inherited.
The same error can be defined in multiple places as long as the scopes are distinct.
Instances of errors can only be created using revert
statements.
The error creates data that is then passed to the caller with the revert operation to either return to the off-chain component or catch it in a try/catch statement. Note that an error can only be caught when coming from an external call, reverts happening in internal calls or inside the same function cannot be caught.
If you do not provide any parameters, the error only needs four bytes of data and you can use NatSpec as above to further explain the reasons behind the error, which is not stored on chain. This makes this a very cheap and convenient error-reporting feature at the same time.
More specifically, an error instance is ABI-encoded in the same way as
a function call to a function of the same name and types would be
and then used as the return data in the revert
opcode.
This means that the data consists of a 4-byte selector followed by ABI-encoded data.
The selector consists of the first four bytes of the keccak256-hash of the signature of the error type.
Note
It is possible for a contract to revert with different errors of the same name or even with errors defined in different places that are indistinguishable by the caller. For the outside, i.e. the ABI, only the name of the error is relevant, not the contract or file where it is defined.
The statement require(condition, "description");
would be equivalent to
if (!condition) revert Error("description")
if you could define
error Error(string)
.
Note, however, that Error
is a built-in type and cannot be defined in user-supplied code.
Similarly, a failing assert
or similar conditions will revert with an error
of the built-in type Panic(uint256)
.
Note
Error data should only be used to give an indication of failure, but not as a means for control-flow. The reason is that the revert data of inner calls is propagated back through the chain of external calls by default. This means that an inner call can “forge” revert data that looks like it could have come from the contract that called it.
Inheritance
Solidity supports multiple inheritance including polymorphism.
Polymorphism means that a function call (internal and external)
always executes the function of the same name (and parameter types)
in the most derived contract in the inheritance hierarchy.
This has to be explicitly enabled on each function in the
hierarchy using the virtual
and override
keywords.
See Function Overriding for more details.
It is possible to call functions further up in the inheritance
hierarchy internally by explicitly specifying the contract
using ContractName.functionName()
or using super.functionName()
if you want to call the function one level higher up in
the flattened inheritance hierarchy (see below).
When a contract inherits from other contracts, only a single
contract is created on the blockchain, and the code from all the base contracts
is compiled into the created contract. This means that all internal calls
to functions of base contracts also just use internal function calls
(super.f(..)
will use JUMP and not a message call).
State variable shadowing is considered as an error. A derived contract can
only declare a state variable x
, if there is no visible state variable
with the same name in any of its bases.
The general inheritance system is very similar to Python’s, especially concerning multiple inheritance, but there are also some differences.
Details are given in the following example.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Owned {
constructor() { owner = payable(msg.sender); }
address payable owner;
}
// Use `is` to derive from another contract. Derived
// contracts can access all non-private members including
// internal functions and state variables. These cannot be
// accessed externally via `this`, though.
contract Destructible is Owned {
// The keyword `virtual` means that the function can change
// its behaviour in derived classes ("overriding").
function destroy() virtual public {
if (msg.sender == owner) selfdestruct(owner);
}
}
// These abstract contracts are only provided to make the
// interface known to the compiler. Note the function
// without body. If a contract does not implement all
// functions it can only be used as an interface.
abstract contract Config {
function lookup(uint id) public virtual returns (address adr);
}
abstract contract NameReg {
function register(bytes32 name) public virtual;
function unregister() public virtual;
}
// Multiple inheritance is possible. Note that `Owned` is
// also a base class of `Destructible`, yet there is only a single
// instance of `Owned` (as for virtual inheritance in C++).
contract Named is Owned, Destructible {
constructor(bytes32 name) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).register(name);
}
// Functions can be overridden by another function with the same name and
// the same number/types of inputs. If the overriding function has different
// types of output parameters, that causes an error.
// Both local and message-based function calls take these overrides
// into account.
// If you want the function to override, you need to use the
// `override` keyword. You need to specify the `virtual` keyword again
// if you want this function to be overridden again.
function destroy() public virtual override {
if (msg.sender == owner) {
Config config = Config(0xD5f9D8D94886E70b06E474c3fB14Fd43E2f23970);
NameReg(config.lookup(1)).unregister();
// It is still possible to call a specific
// overridden function.
Destructible.destroy();
}
}
}
// If a constructor takes an argument, it needs to be
// provided in the header or modifier-invocation-style at
// the constructor of the derived contract (see below).
contract PriceFeed is Owned, Destructible, Named("GoldFeed") {
function updateInfo(uint newInfo) public {
if (msg.sender == owner) info = newInfo;
}
// Here, we only specify `override` and not `virtual`.
// This means that contracts deriving from `PriceFeed`
// cannot change the behaviour of `destroy` anymore.
function destroy() public override(Destructible, Named) { Named.destroy(); }
function get() public view returns(uint r) { return info; }
uint info;
}
Note that above, we call Destructible.destroy()
to “forward” the
destruction request. The way this is done is problematic, as
seen in the following example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract owned {
constructor() { owner = payable(msg.sender); }
address payable owner;
}
contract Destructible is owned {
function destroy() public virtual {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is Destructible {
function destroy() public virtual override { /* do cleanup 1 */ Destructible.destroy(); }
}
contract Base2 is Destructible {
function destroy() public virtual override { /* do cleanup 2 */ Destructible.destroy(); }
}
contract Final is Base1, Base2 {
function destroy() public override(Base1, Base2) { Base2.destroy(); }
}
A call to Final.destroy()
will call Base2.destroy
because we specify it
explicitly in the final override, but this function will bypass
Base1.destroy
. The way around this is to use super
:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract owned {
constructor() { owner = payable(msg.sender); }
address payable owner;
}
contract Destructible is owned {
function destroy() virtual public {
if (msg.sender == owner) selfdestruct(owner);
}
}
contract Base1 is Destructible {
function destroy() public virtual override { /* do cleanup 1 */ super.destroy(); }
}
contract Base2 is Destructible {
function destroy() public virtual override { /* do cleanup 2 */ super.destroy(); }
}
contract Final is Base1, Base2 {
function destroy() public override(Base1, Base2) { super.destroy(); }
}
If Base2
calls a function of super
, it does not simply
call this function on one of its base contracts. Rather, it
calls this function on the next base contract in the final
inheritance graph, so it will call Base1.destroy()
(note that
the final inheritance sequence is – starting with the most
derived contract: Final, Base2, Base1, Destructible, owned).
The actual function that is called when using super is
not known in the context of the class where it is used,
although its type is known. This is similar for ordinary
virtual method lookup.
Function Overriding
Base functions can be overridden by inheriting contracts to change their
behavior if they are marked as virtual
. The overriding function must then
use the override
keyword in the function header.
The overriding function may only change the visibility of the overridden function from external
to public
.
The mutability may be changed to a more strict one following the order:
nonpayable
can be overridden by view
and pure
. view
can be overridden by pure
.
payable
is an exception and cannot be changed to any other mutability.
The following example demonstrates changing mutability and visibility:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Base
{
function foo() virtual external view {}
}
contract Middle is Base {}
contract Inherited is Middle
{
function foo() override public pure {}
}
For multiple inheritance, the most derived base contracts that define the same
function must be specified explicitly after the override
keyword.
In other words, you have to specify all base contracts that define the same function
and have not yet been overridden by another base contract (on some path through the inheritance graph).
Additionally, if a contract inherits the same function from multiple (unrelated)
bases, it has to explicitly override it:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract Base1
{
function foo() virtual public {}
}
contract Base2
{
function foo() virtual public {}
}
contract Inherited is Base1, Base2
{
// Derives from multiple bases defining foo(), so we must explicitly
// override it
function foo() public override(Base1, Base2) {}
}
An explicit override specifier is not required if the function is defined in a common base contract or if there is a unique function in a common base contract that already overrides all other functions.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract A { function f() public pure{} }
contract B is A {}
contract C is A {}
// No explicit override required
contract D is B, C {}
More formally, it is not required to override a function (directly or indirectly) inherited from multiple bases if there is a base contract that is part of all override paths for the signature, and (1) that base implements the function and no paths from the current contract to the base mentions a function with that signature or (2) that base does not implement the function and there is at most one mention of the function in all paths from the current contract to that base.
In this sense, an override path for a signature is a path through the inheritance graph that starts at the contract under consideration and ends at a contract mentioning a function with that signature that does not override.
If you do not mark a function that overrides as virtual
, derived
contracts can no longer change the behaviour of that function.
Note
Functions with the private
visibility cannot be virtual
.
Note
Functions without implementation have to be marked virtual
outside of interfaces. In interfaces, all functions are
automatically considered virtual
.
Note
Starting from Solidity 0.8.8, the override
keyword is not
required when overriding an interface function, except for the
case where the function is defined in multiple bases.
Public state variables can override external functions if the parameter and return types of the function matches the getter function of the variable:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract A
{
function f() external view virtual returns(uint) { return 5; }
}
contract B is A
{
uint public override f;
}
Note
While public state variables can override external functions, they themselves cannot be overridden.
Modifier Overriding
Function modifiers can override each other. This works in the same way as
function overriding (except that there is no overloading for modifiers). The
virtual
keyword must be used on the overridden modifier
and the override
keyword must be used in the overriding modifier:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract Base
{
modifier foo() virtual {_;}
}
contract Inherited is Base
{
modifier foo() override {_;}
}
In case of multiple inheritance, all direct base contracts must be specified explicitly:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
contract Base1
{
modifier foo() virtual {_;}
}
contract Base2
{
modifier foo() virtual {_;}
}
contract Inherited is Base1, Base2
{
modifier foo() override(Base1, Base2) {_;}
}
Constructors
A constructor is an optional function declared with the constructor
keyword
which is executed upon contract creation, and where you can run contract
initialisation code.
Before the constructor code is executed, state variables are initialised to their specified value if you initialise them inline, or their default value if you do not.
After the constructor has run, the final code of the contract is deployed to the blockchain. The deployment of the code costs additional gas linear to the length of the code. This code includes all functions that are part of the public interface and all functions that are reachable from there through function calls. It does not include the constructor code or internal functions that are only called from the constructor.
If there is no
constructor, the contract will assume the default constructor, which is
equivalent to constructor() {}
. For example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
abstract contract A {
uint public a;
constructor(uint a_) {
a = a_;
}
}
contract B is A(1) {
constructor() {}
}
You can use internal parameters in a constructor (for example storage pointers). In this case, the contract has to be marked abstract, because these parameters cannot be assigned valid values from outside but only through the constructors of derived contracts.
Warning
Prior to version 0.4.22, constructors were defined as functions with the same name as the contract. This syntax was deprecated and is not allowed anymore in version 0.5.0.
Warning
Prior to version 0.7.0, you had to specify the visibility of constructors as either
internal
or public
.
Arguments for Base Constructors
The constructors of all the base contracts will be called following the linearization rules explained below. If the base constructors have arguments, derived contracts need to specify all of them. This can be done in two ways:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Base {
uint x;
constructor(uint x_) { x = x_; }
}
// Either directly specify in the inheritance list...
contract Derived1 is Base(7) {
constructor() {}
}
// or through a "modifier" of the derived constructor...
contract Derived2 is Base {
constructor(uint y) Base(y * y) {}
}
// or declare abstract...
abstract contract Derived3 is Base {
}
// and have the next concrete derived contract initialize it.
contract DerivedFromDerived is Derived3 {
constructor() Base(10 + 10) {}
}
One way is directly in the inheritance list (is Base(7)
). The other is in
the way a modifier is invoked as part of
the derived constructor (Base(y * y)
). The first way to
do it is more convenient if the constructor argument is a
constant and defines the behaviour of the contract or
describes it. The second way has to be used if the
constructor arguments of the base depend on those of the
derived contract. Arguments have to be given either in the
inheritance list or in modifier-style in the derived constructor.
Specifying arguments in both places is an error.
If a derived contract does not specify the arguments to all of its base
contracts’ constructors, it must be declared abstract. In that case, when
another contract derives from it, that other contract’s inheritance list
or constructor must provide the necessary parameters
for all base classes that haven’t had their parameters specified (otherwise,
that other contract must be declared abstract as well). For example, in the above
code snippet, see Derived3
and DerivedFromDerived
.
Multiple Inheritance and Linearization
Languages that allow multiple inheritance have to deal with
several problems. One is the Diamond Problem.
Solidity is similar to Python in that it uses “C3 Linearization”
to force a specific order in the directed acyclic graph (DAG) of base classes. This
results in the desirable property of monotonicity but
disallows some inheritance graphs. Especially, the order in
which the base classes are given in the is
directive is
important: You have to list the direct base contracts
in the order from “most base-like” to “most derived”.
Note that this order is the reverse of the one used in Python.
Another simplifying way to explain this is that when a function is called that is defined multiple times in different contracts, the given bases are searched from right to left (left to right in Python) in a depth-first manner, stopping at the first match. If a base contract has already been searched, it is skipped.
In the following code, Solidity will give the error “Linearization of inheritance graph impossible”.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;
contract X {}
contract A is X {}
// This will not compile
contract C is A, X {}
The reason for this is that C
requests X
to override A
(by specifying A, X
in this order), but A
itself
requests to override X
, which is a contradiction that
cannot be resolved.
Due to the fact that you have to explicitly override a function that is inherited from multiple bases without a unique override, C3 linearization is not too important in practice.
One area where inheritance linearization is especially important and perhaps not as clear is when there are multiple constructors in the inheritance hierarchy. The constructors will always be executed in the linearized order, regardless of the order in which their arguments are provided in the inheriting contract’s constructor. For example:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract Base1 {
constructor() {}
}
contract Base2 {
constructor() {}
}
// Constructors are executed in the following order:
// 1 - Base1
// 2 - Base2
// 3 - Derived1
contract Derived1 is Base1, Base2 {
constructor() Base1() Base2() {}
}
// Constructors are executed in the following order:
// 1 - Base2
// 2 - Base1
// 3 - Derived2
contract Derived2 is Base2, Base1 {
constructor() Base2() Base1() {}
}
// Constructors are still executed in the following order:
// 1 - Base2
// 2 - Base1
// 3 - Derived3
contract Derived3 is Base2, Base1 {
constructor() Base1() Base2() {}
}
Inheriting Different Kinds of Members of the Same Name
- It is an error when any of the following pairs in a contract have the same name due to inheritance:
a function and a modifier
a function and an event
an event and a modifier
As an exception, a state variable getter can override an external function.
Abstract Contracts
Contracts must be marked as abstract when at least one of their functions is not implemented or when they do not provide arguments for all of their base contract constructors. Even if this is not the case, a contract may still be marked abstract, such as when you do not intend for the contract to be created directly. Abstract contracts are similar to Interfaces but an interface
Comments
Single-line comments (
//
) and multi-line comments (/*...*/
) are possible.open in Remix
Note
A single-line comment is terminated by any unicode line terminator (LF, VF, FF, CR, NEL, LS or PS) in UTF-8 encoding. The terminator is still part of the source code after the comment, so if it is not an ASCII symbol (these are NEL, LS and PS), it will lead to a parser error.
Additionally, there is another type of comment called a NatSpec comment, which is detailed in the style guide. They are written with a triple slash (
///
) or a double asterisk block (/** ... */
) and they should be used directly above function declarations or statements.