What is Solidity?
Solidity is the primary programming language for writing smart contracts on Ethereum and other EVM-compatible blockchains. It is statically typed, object-oriented, and compiles to bytecode that runs on the Ethereum Virtual Machine (EVM). This page introduces core concepts using the diagrams below.
1. Basics: Contract Structure, Pragma, and Constructors
A Solidity contract is defined with the contract keyword. The
pragma directive specifies the compiler version; Solidity compiles to EVM
bytecode. State variables are stored on the blockchain. The constructor runs once at
deployment; functions are the entry points for contract logic.
Basics: Pragma and version, contract structure, state variables (e.g. uint256, address), constructor, and functions (e.g. setNumber, getNumber, resetNumber with access control).
2. Variables and Storage
Solidity has several variable kinds: state variables (stored on-chain),
immutable (set only in the constructor), constant (fixed at
compile time), and local (temporary, discarded after the function returns).
Data location matters: memory is temporary and modifiable;
storage is a reference to on-chain data; calldata is
read-only for external function arguments. Global variables like
msg.sender and block.timestamp provide blockchain context.
Variables and storage: State, immutable, constant, memory, storage, local, and global variables with code examples.
3. Functions
Functions can have access modifiers: private (only inside the contract),
internal (contract and derived contracts), external (only from
outside), and public (anywhere). view functions read state but
do not modify it; pure functions neither read nor modify state.
payable functions can receive Ether. Functions declare return types and can
use modifiers to run checks before or after execution.
Functions: Access modifiers (private, internal, external, public), view/pure, payable, return values, and function modifiers (e.g. onlyOwner).
4. Events
Events are declared with the event keyword and allow
logging to the blockchain. Parameters can be indexed for efficient filtering.
Functions trigger events with emit. Events are useful for off-chain
listeners (e.g. updating UIs) and as a cheap form of historical data. They cannot be used
in view or pure functions because they alter state (logs).
Events: Declaring events, indexing for fast retrieval, emitting from functions, and use cases (logging, UI updates, cheap storage).
5. Structs, Enums, and Mappings
Structs define custom types with multiple fields. Enums restrict a variable to a fixed set of named values. Mappings are key-value stores; they are not iterable and have no length. For ordered or countable data, keep a separate counter or array. The diagram shows an order system using an enum for status, a struct for order data, and a mapping keyed by order ID.
Structs, enums, and mappings: Enum for order status, struct for order data, mapping for orders, and a counter for the next order ID.
6. Conditionals and Loops
require(condition, "message") reverts the transaction if the condition
fails. if/else works like in JavaScript or Java. for loops
are common; using ++i instead of i++ can save gas.
while loops are possible but can be costly if not bounded. The example
shows require for validation, if/else for branching, and for/while for iteration.
Conditionals and loops: require for validation, if/else for logic, for loops (with gas tip: prefer ++i), and while loops (use with care).
7. Gas Optimization
Gas is the unit of cost on Ethereum. Optimizations include: using calldata
for external function arguments (read-only, no copy to memory), pre-increment
(++i) in loops to save gas vs post-increment, loading storage into
memory when you read a value multiple times to avoid repeated storage reads, and
caching array elements in local variables inside loops to reduce
repeated accesses.
Gas optimization: calldata for external args, ++i in loops, loading storage to memory, and caching array elements.
8. Inheritance and Override
Use import to bring in other contracts or libraries (e.g. OpenZeppelin).
Inheritance is expressed with is: contract Child is
Parent { }. Parent constructors are invoked in the child constructor by naming the
parent and passing arguments. Functions marked virtual in a parent can be
overriden in the child; use super.functionName() to call the
parent implementation. Override order (e.g. in multiple inheritance) is right-to-left,
with the most base-like contract on the left.
Inheritance: import, is for inheritance, parent constructor calls, override and super for virtual functions.