Smart contracts are self-executing programs that run on blockchain networks like Ethereum. Written in languages such as Solidity, they automate trustless interactions by enforcing predefined rules without intermediaries. This guide explores the fundamentals of smart contracts using Solidity 0.8.18, covering core concepts, code examples, and the underlying mechanics of the Ethereum Virtual Machine (EVM).
Understanding Simple Smart Contracts
Let’s begin with a foundational example: a contract that stores a value and allows other contracts to read or modify it.
// 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 specifies the license under which the source code is released—GPL-3.0—ensuring transparency and compliance for open-source use.
The pragma solidity directive defines version compatibility. Here, the code supports Solidity versions from 0.4.16 up to, but not including, 0.9.0. This prevents unexpected behavior due to breaking changes in future compiler versions.
In Solidity, a contract encapsulates both code (functions) and data (state). The line uint storedData; declares a state variable of type uint—an unsigned 256-bit integer—persisted on the blockchain. Think of it as a database field accessible only through controlled functions.
The set and get functions allow external users to update and retrieve this value. Notably, within a contract, you access members directly—without needing this.—as omitting it affects scoping and execution context.
While simple, this contract demonstrates key blockchain traits: immutability (past values remain in history), transparency (anyone can read), and openness (anyone can overwrite). Later, we’ll explore access controls to restrict modifications.
Tip: Be cautious with Unicode characters—even visually identical ones may differ in encoding, leading to vulnerabilities.
Building a Custom Cryptocurrency
Now let’s create a more complex contract: a basic cryptocurrency.
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.4;
contract Coin {
address public minter;
mapping(address => uint) public balances;
event Sent(address from, address to, uint amount);
constructor() {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
require(msg.sender == minter);
balances[receiver] += amount;
}
error InsufficientBalance(uint requested, uint available);
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);
}
}Key Concepts Explained
address public minter;: Declares a public state variable storing the creator’s Ethereum address. Thepublickeyword auto-generates a getter function.mapping(address => uint) public balances;: Creates a lookup table linking Ethereum addresses to token balances. Mappings act like hash tables—pre-initialized with zero values for all possible keys.Events (
event Sent): Emit log entries that off-chain apps (e.g., wallets or block explorers) can monitor efficiently. For example:Coin.Sent().watch({}, '', function(error, result) { if (!error) { console.log("Transfer: " + result.args.amount + " tokens from " + result.args.from); } });- Constructor: Runs once during deployment, setting
msg.sender(the deployer) as theminter. Only this address can mint new tokens via themintfunction. - Error Handling: The
InsufficientBalancecustom error provides detailed failure context when users attempt invalid transfers. Usingrevertrolls back state changes and returns error data to the frontend.
👉 Discover how to deploy and interact with token contracts securely.
Blockchain Fundamentals for Developers
Blockchain may seem complex, but developers need only understand its guarantees—not its cryptography or networking layers.
Transactions and Consistency
A blockchain is a shared transactional ledger. Every change must be packaged into a transaction, ensuring atomicity: either all operations succeed or none do.
For instance, transferring tokens deducts from one balance and credits another—both happen or neither does. Transactions are also isolated; concurrent writes don’t interfere.
Each transaction is signed cryptographically, proving ownership without passwords—only private keys.
Blocks and Finality
To prevent double-spending, transactions are grouped into blocks every ~17 seconds on Ethereum. Conflicting transactions are resolved by consensus: only the first included transaction wins.
Blocks form a chain; reorganizations (reorgs) can roll back recent blocks, though probability decreases rapidly with each new block added.
Note: There’s no guarantee your transaction will appear in the next block—it depends on miner selection and gas fees.
👉 Learn how to optimize transaction speed and cost on Ethereum.
The Ethereum Virtual Machine (EVM)
The EVM executes smart contracts in a secure, sandboxed environment isolated from network and system resources.
Accounts
There are two account types:
- Externally Owned Accounts (EOAs): Controlled by private keys (users).
- Contract Accounts: Governed by code, triggered by EOAs or other contracts.
Both share an address space and hold ether balances (in wei: 1 ether = 10¹⁸ wei). Contract addresses are derived from the creator’s address and nonce (transaction count).
Storage, Memory, and Stack
The EVM uses three data areas:
- Storage: Persistent key-value store (256-bit → 256-bit). Expensive to read/write; ideal for essential state.
- Memory: Temporary linear space per external call. Grows byte-by-byte; costs increase quadratically.
- Stack: Holds up to 1024 elements (each 256 bits). All computations occur here with limited depth access.
Gas and Execution Costs
Every operation consumes gas, paid by the transaction sender. Gas prevents infinite loops and compensates validators.
- Gas price is set by the user.
- Unused gas is refunded after execution.
- Out-of-gas exceptions revert all state changes.
- Internal calls forward only 63/64 of available gas, limiting recursion depth.
Advanced EVM Features
Message Calls
Contracts interact via message calls, similar to transactions but internal. These are synchronous and return data. Failed calls propagate errors unless explicitly handled.
Delegatecall and Libraries
Delegatecall executes code from another address in the caller’s context—preserving storage, balance, and address. This enables reusable libraries (e.g., math or data structures) shared across contracts.
Logs and Events
Events write to an append-only log structure searchable via bloom filters—ideal for lightweight clients tracking activity without syncing full blockchain data.
Contract Creation and Self-Destruct
Contracts can create others using create, where initialization code returns runtime bytecode.
selfdestruct removes contract code and sends remaining funds to a target. However:
- It’s deprecated as of Solidity 0.8.18 due to upcoming EIP-6049 changes.
- Historical data remains on-chain even after destruction.
Use logical deactivation (e.g., pausing functions via a paused flag) instead of self-destruct for safer upgrades.Frequently Asked Questions
Q: What is Solidity?
A: Solidity is a statically-typed programming language designed for writing smart contracts on Ethereum and EVM-compatible blockchains.
Q: How do I make my contract upgradeable?
A: Use proxy patterns with delegatecall to separate logic from storage, enabling updates without redeploying.
Q: Why use events instead of storage for tracking?
A: Events are cheaper to emit and ideal for off-chain monitoring, while storage is costly but readable on-chain.
Q: Can deleted contracts be recovered?
A: No. Once self-destructed, a contract’s functionality is gone—but its history remains part of the blockchain permanently.
Q: How do I prevent reentrancy attacks?
A: Use checks-effects-interactions patterns or reentrancy guards to block recursive calls during sensitive operations.
Q: What tools should I use to test Solidity code?
A: Leverage Hardhat or Foundry for local testing, combined with static analyzers like Slither for security audits.
👉 Start building secure, efficient smart contracts today with advanced developer tools.