Introduction to Smart Contracts — Solidity 0.8.27 documentation (2024)

A Simple Smart Contract

Let us begin with a basic example that sets the value of a variable and exposesit for other contracts to access. It is fine if you do not understandeverything right now, we will go into more details later.

Storage Example

open in Remix

// SPDX-License-Identifier: GPL-3.0pragma 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 theGPL version 3.0. Machine-readable license specifiers are importantin a setting where publishing the source code is the default.

The next line specifies that the source code is written forSolidity 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 thesource code (e.g. pragma once).

A contract in the sense of Solidity is a collection of code (its functions) anddata (its state) that resides at a specific address on the Ethereumblockchain. The line uint storedData; declares a state variable called storedData oftype uint (unsigned integer of 256 bits). You can think of it as a single slotin a database that you can query and alter by calling functions of thecode that manages the database. In this example, the contract defines thefunctions set and get that can be used to modifyor 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 infrastructurebuilt by Ethereum) allowing anyone to store a single number that is accessible byanyone in the world without a (feasible) way to prevent you from publishingthis number. Anyone could call set again with a different valueand overwrite your number, but the number is still stored in the historyof the blockchain. Later, you will see how you can impose access restrictionsso that only you can alter the number.

Warning

Be careful with using Unicode text, as similar looking (or even identical) characters canhave 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 tothe 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 acryptocurrency. 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 forregistering with a username and password, all you need is an Ethereum keypair.

open in Remix

// SPDX-License-Identifier: GPL-3.0pragma solidity ^0.8.26;// This will only compile via IRcontract 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 { require(amount <= balances[msg.sender], InsufficientBalance(amount, 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 halfof a keypair belonging to external accounts.

The keyword public automatically generates a function that allows you to access the current value of the statevariable 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 equivalentto the following (ignore external and view for now):

open in Remix

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; alsocreates 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 arevirtually initialized such that every possible key exists from the start and is mapped to avalue whose byte-representation is all zeros. However, it is neither possible to obtain a list of all keys ofa mapping, nor a list of all values. Record what youadded to the mapping, or use it in a context where this is not needed. Oreven better, keep a list, or use a more suitable data type.

The getter function created by the public keywordis more complex in the case of a mapping. It looks like thefollowing:

open in Remix

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); declaresan “event”, which is emitted in the last line of the functionsend. Ethereum clients such as web applications canlisten for these events emitted on the blockchain without muchcost. As soon as it is emitted, the listener receives thearguments from, to and amount, which makes it possible to tracktransactions.

To listen for this event, you could use the followingJavaScript 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 andcannot be called afterwards. In this case, it permanently stores the address of the person creating thecontract. The msg variable (together with tx and block) is aspecial global variable thatcontains properties which allow access to the blockchain. msg.sender isalways 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 thisexample, require(msg.sender == minter); ensures that only the creator of the contract can callmint. In general, the creator can mint as many tokens as they like, but at some point, this willlead 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 largerthan the maximum value of uint (2**256 - 1). This is also true for the statementbalances[receiver] += amount; in the function send.

Errors allow you to provide more information to the caller aboutwhy a condition or operation failed. Errors are used together with therevert statement. The revert statement unconditionallyaborts and reverts all changes, much like the require function.Both approaches allow 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 thata failure can more easily be debugged or reacted upon.

The send function can be used by anyone (who alreadyhas some of these coins) to send coins to anyone else. If the sender does not haveenough coins to send, the if condition evaluates to true. As a result, the revert will cause the operation to failwhile providing the sender with error details using the InsufficientBalance error.

Note

If you usethis contract to send coins to an address, you will not see anything when youlook at that address on a blockchain explorer, because the record that you sentcoins and the changed balances are only stored in the data storage of thisparticular coin contract. By using events, you can createa “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 thecoin owners.

Blockchain Basics

Blockchains as a concept are not too hard to understand for programmers. The reason is thatmost 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 thesefeatures as given, you do not have to worry about the underlying technology - or do you haveto 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 transactionwhich has to be accepted by all others.The word transaction implies that the change you want to make (assume you want to changetwo 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 anelectronic currency. If a transfer from one account to another is requested,the transactional nature of the database ensures that if the amount issubtracted from one account, it is always added to the other account. If dueto 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 thedatabase. In the example of the electronic currency, a simple check ensures thatonly the person holding the keys to the account can transfer some compensation, e.g. Ether, 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 transactionswill 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 willbe 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 at regular intervals, although these intervals may be subject to change in the future.For the most up-to-date information, it is recommended to monitor the network, for example, on Etherscan.

As part of the “order selection mechanism”, which is called attestation, it may happen thatblocks are reverted from time to time, but only at the “tip” of the chain. The moreblocks are added on top of a particular block, the less likely this block will be reverted. So it might be that your transactionsare reverted and even removed from the blockchain, but the longer you wait, the lesslikely 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 usea smart contract automation tool or an oracle service.

The Ethereum Virtual Machine

Overview

The Ethereum Virtual Machine or EVM is the runtime environmentfor smart contracts in Ethereum. It is not only sandboxed butactually completely isolated, which means that code runninginside 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 sameaddress space: External accounts that are controlled bypublic-private key pairs (i.e. humans) and contract accounts which arecontrolled by the code stored together with the account.

The address of an external account is determined fromthe public key while the address of a contract isdetermined at the time the contract is created(it is derived from the creator address and the numberof transactions sent from that address, the so-called “nonce”).

Regardless of whether or not the account stores code, the two types aretreated equally by the EVM.

Every account has a persistent key-value store mapping 256-bit words to 256-bitwords called storage.

Furthermore, every account has a balance inEther (in “Wei” to be exact, 1 ether is 10**18 wei) which can be modified by sending transactions thatinclude Ether.

Transactions

A transaction is a message that is sent from one account to anotheraccount (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 andthe payload is provided as input data.

If the target account is not set (the transaction does not havea recipient or the recipient is set to null), the transactioncreates a new contract.As already mentioned, the address of that contract is notthe zero address but an address derived from the sender andits number of transactions sent (the “nonce”). The payloadof such a contract creation transaction is taken to beEVM bytecode and executed. The output data of this execution ispermanently stored as the code of the contract.This means that in order to create a contract, you do notsend the actual code of the contract, but in fact code thatreturns 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 thecontract under construction until its constructor hasfinished executing.

Gas

Upon creation, each transaction is charged with a certain amount of gasthat has to be paid for by the originator of the transaction (tx.origin).While the EVM executes thetransaction, 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 modificationsmade to the state in the current call frame.

This mechanism incentivizes economical use of EVM execution timeand also compensates EVM executors (i.e. miners / stakers) for their work.Since each block has a maximum amount of gas, it also limits the amountof work needed to validate a block.

The gas price is a value set by the originator of the transaction, whohas 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 callsand 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 iscomparatively 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 obtainsa freshly cleared instance for each message call. Memory is linear and can beaddressed at byte level, but reads are limited to a width of 256 bits, while writescan be either 8 bits or 256 bits wide. Memory is expanded by a word (256-bit), whenaccessing (either reading or writing) a previously untouched memory word (i.e. any offsetwithin a word). At the time of expansion, the cost in gas must be paid. Memory is morecostly the larger it grows (it scales quadratically).

The EVM is not a register machine but a stack machine, so allcomputations are performed on a data area called the stack. It has a maximum size of1024 elements and contains words of 256 bits. Access to the stack islimited to the top end in the following way:It is possible to copy one ofthe topmost 16 elements to the top of the stack or swap thetopmost element with one of the 16 elements below it.All other operations take the topmost two (or one, or more, depending onthe operation) elements from the stack and push the result onto the stack.Of course it is possible to move stack elements to storage or memoryin order to get deeper access to the stack,but it is not possible to just access arbitrary elements deeper in the stackwithout first removing the top of the stack.

Instruction Set

The instruction set of the EVM is kept minimal in order to avoidincorrect 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 blocklike its number and timestamp.

For a complete list, please see the list of opcodes as part of the inlineassembly documentation.

Message Calls

Contracts can call other contracts or send Ether to non-contractaccounts by the means of message calls. Message calls are similarto transactions, in that they have a source, a target, data payload,Ether, gas and return data. In fact, every transaction consists ofa top-level message call which in turn can create further message calls.

A contract can decide how much of its remaining gas should be sentwith the inner message call and how much it wants to retain.If an out-of-gas exception happens in the inner call (or anyother 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 insuch 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 thecall 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 ata 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 complexoperations, loops should be preferred over recursive calls. Furthermore,only 63/64th of the gas can be forwarded in a message call, which causes adepth limit of a little less than 1000 in practice.

Delegatecall and Libraries

There exists a special variant of a message call, named delegatecallwhich is identical to a message call apart from the fact thatthe code at the target address is executed in the context (i.e. at the address) of the callingcontract and msg.sender and msg.value do not change their values.

This means that a contract can dynamically load code from a differentaddress at runtime. Storage, current address and balance stillrefer 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. inorder to implement a complex data structure.

Logs

It is possible to store data in a specially indexed data structurethat maps all the way up to the block level. This feature called logsis used by Solidity in order to implement events.Contracts cannot access log data after it has been created, but theycan be efficiently accessed from outside the blockchain.Since some part of the log data is stored in bloom filters, it ispossible to search for this data in an efficient and cryptographicallysecure 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 betweenthese create calls and normal message calls is that the payload data isexecuted and the result stored as code and the caller / creatorreceives 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 thataddress performs the selfdestruct operation. The remaining Ether storedat that address is sent to a designated target and then the storage and codeis removed from the state. Removing the contract in theory sounds like a goodidea, but it is potentially dangerous, as if someone sends Ether to removedcontracts, the Ether is forever lost.

Warning

From EVM >= Cancun onwards, selfdestruct will only send all Ether in the account to the given recipient and not destroy the contract.However, when selfdestruct is called in the same transaction that creates the contract calling it,the behaviour of selfdestruct before Cancun hardfork (i.e., EVM <= Shanghai) is preserved and will destroy the current contract,deleting any data, including storage keys, code and the account itself.See EIP-6780 for more details.

The new behaviour is the result of a network-wide change that affects all contracts present onthe Ethereum mainnet and testnets.It is important to note that this change is dependent on the EVM version of the chain on whichthe contract is deployed.The --evm-version setting used when compiling the contract has no bearing on it.

Also, note that the selfdestruct opcode has been deprecated in Solidity version 0.8.18,as recommended by EIP-6049.The deprecation is still in effect and the compiler will still emit warnings on its use.Any use in newly deployed contracts is strongly discouraged even if the new behavior is taken into account.Future changes to the EVM might further reduce the functionality of the opcode.

Warning

Even if a contract is removed by selfdestruct, it is still part of thehistory 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 themby changing some internal state which causes all functions to revert. Thismakes 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) 0x0a contains“precompiled contracts” that can be called as any other contractbut their behavior (and their gas consumption) is not definedby 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 ofprecompiled contracts. It might also be possible that newprecompiled contracts are added to the Ethereum main chain in the future,but you can reasonably expect them to always be in the range between1 and 0xffff (inclusive).

Introduction to Smart Contracts — Solidity 0.8.27 documentation (2024)

FAQs

How hard is Solidity to learn? ›

Is Solidity difficult to learn? Learning Solidity can be challenging, especially if you are new to programming or blockchain development. However, with dedication and the right resources, it is attainable.

Are smart contracts hard to write? ›

While the nuts and bolts of the technology can seem intimidating, every developer can understand the basics of how to create a smart contract.

Are smart contracts hard to learn? ›

Learning how to design, develop and test smart contracts is not radically different from learning any other type of programming, Guyer said. It helps to have a basic understanding of the execution environment in which your code will run.

What is a Solidity contract? ›

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).

Is Solidity harder than Python? ›

Yes, Solidity is harder than Python. While both Solidity and Python are object-oriented, Solidity is considered more difficult due to its focus on blockchain applications. However, it offers more features for complex contracts compared to Python.

Can I get a job if I learn Solidity? ›

Yes, there are many jobs available for Solidity developers.

How long does it take to learn smart contracts? ›

Final Words. On average, it might take a few weeks to become comfortable with the basics of Solidity and start writing simple smart contracts. To become proficient and tackle more complex projects, it could take several months to a year or more of consistent learning and practice.

Can you make money writing smart contracts? ›

Smart contract developers can earn fees and commissions for creating and executing contracts. These fees vary depending on the complexity of the contracts, the blockchain platform used, and market demand. Successfully established developers can generate significant income through fees.

How much do smart contract developers make? ›

The average smart contract developer salary in the USA is $160,000 per year or $76.92 per hour.

Is Solidity worth learning in 2024? ›

Solidity. It is worth exploring Solidity, which is currently used for smart contracts. This can help you understand the technology currently shrouded in hype.

Are smart contracts risky? ›

Security Flaws and Loopholes

Security flaws, such as reentrancy attacks or overflow/underflow bugs, pose serious threats to smart contracts. These vulnerabilities can be exploited by attackers, leading to unauthorized access or manipulation of contract functions.

Can I learn Solidity as my first language? ›

If Solidity is your primary first language, you'll have a lot of trouble because switching to another language from Solidity takes time. If, on the other hand, your primary language is something like JavaScript/Python, you can easily switch to Solidity.

What is the salary of a Solidity? ›

Average Yearly Salary Breakdown 📊

As of February 2024, the average yearly salary for a Solidity Developer stands at a robust $130,000. However, it's crucial to note that this figure represents the midpoint in a spectrum that spans from a minimum base salary of $60,000 to a maximum of $270,000.

Is Solidity still in demand? ›

Solidity developers are in high demand, and as a result, their salaries are on the rise.

Is Solidity good for beginners? ›

It is a statically typed language used for creating machine-level code and compiling it on top of EVM (Ethereum Virtual Machine). Anyone who has learned any object-oriented language like C or C++ would be able to learn Solidity quickly.

How long will it take to learn Solidity? ›

If you've already had previous experience with coding in languages such as JavaScript, C++, and/or Python, you'll most likely find it easy to learn Solidity within weeks to several months of intensive study. However, if you have no experience in programming, it might take you from 6 months to a year.

Is Solidity still worth learning? ›

YES. The main programming language for building smart contracts on the Ethereum blockchain is Solidity. Crypto, NFT, and Defi enthusiasts should learn this programming language. One of the key elements of the Solidity Ethereum connection is the contract-oriented nature of the this programming language.

References

Top Articles
Latest Posts
Article information

Author: Sen. Ignacio Ratke

Last Updated:

Views: 6741

Rating: 4.6 / 5 (76 voted)

Reviews: 83% of readers found this page helpful

Author information

Name: Sen. Ignacio Ratke

Birthday: 1999-05-27

Address: Apt. 171 8116 Bailey Via, Roberthaven, GA 58289

Phone: +2585395768220

Job: Lead Liaison

Hobby: Lockpicking, LARPing, Lego building, Lapidary, Macrame, Book restoration, Bodybuilding

Introduction: My name is Sen. Ignacio Ratke, I am a adventurous, zealous, outstanding, agreeable, precious, excited, gifted person who loves writing and wants to share my knowledge and understanding with you.