Smart contracts are the building blocks of decentralized applications on blockchain platforms like Ethereum. Written in programming languages such as Solidity, they enable trustless, transparent, and automated execution of agreements without intermediaries. This guide explores the fundamentals of smart contracts, their structure, and how they operate within the Ethereum Virtual Machine (EVM), offering developers a clear path to understanding and creating secure, efficient blockchain-based logic.
A Simple Smart Contract
Let’s start with a basic example to illustrate how smart contracts work. Don’t worry if every detail isn’t immediately clear—we’ll break it down step by step.
Storage
pragma solidity ^0.4.0;
contract SimpleStorage {
uint storedData;
function set(uint x) public {
storedData = x;
}
function get() public view returns (uint) {
return storedData;
}
}The first line, pragma solidity ^0.4.0;, specifies that this code is compatible with Solidity version 0.4.0 and above, but below 0.5.0. This ensures consistent behavior across compiler versions and prevents unexpected changes due to updates. The pragma keyword provides instructions to the compiler—similar to directives like #pragma once in C/C++.
👉 Discover how modern smart contracts are built using updated Solidity standards.
In Solidity, a contract is a collection of code (functions) and data (state) residing at a specific address on the Ethereum blockchain. The line uint storedData; declares a state variable of type uint (an unsigned 256-bit integer). Think of it as a single slot in a decentralized database that can be read or modified by calling functions within the contract.
Functions set and get allow users to update or retrieve the value of storedData. Unlike in many object-oriented languages, you don’t need to use this. to access state variables—direct reference is sufficient.
Currently, this contract allows anyone to overwrite the stored value. While previous values remain in blockchain history, there are no access controls. Later, we’ll explore how to restrict permissions so only authorized users can modify data.
Note: All identifiers—contract names, function names, and variable names—are restricted to ASCII characters. However, UTF-8 encoded data can be stored in string variables.
Warning: Be cautious when using Unicode characters that appear identical but have different code points, as they compile into distinct bytecode.
Custom Cryptocurrency Example
Here’s a more advanced example: a simple cryptocurrency implementation.
pragma solidity ^0.4.0;
contract Coin {
address public minter;
mapping (address => uint) public balances;
event Sent(address from, address to, uint amount);
function Coin() public {
minter = msg.sender;
}
function mint(address receiver, uint amount) public {
if (msg.sender != minter) return;
balances[receiver] += amount;
}
function send(address receiver, uint amount) public {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Sent(msg.sender, receiver, amount);
}
}This contract introduces several key concepts:
State Variables and Visibility
address public minter; declares a public state variable of type address, which holds a 160-bit value used for Ethereum addresses. The public keyword automatically generates a getter function, allowing external access to the variable’s value.
Similarly, mapping (address => uint) public balances; creates a public mapping that associates Ethereum addresses with token balances. Mappings act like virtual hash tables—every possible key exists by default, initialized to zero.
Although you can query individual balances via balances[address], mappings cannot be iterated—there's no built-in way to retrieve all keys or values. If enumeration is needed, additional data structures must be implemented.
Events for Efficient Monitoring
event Sent(address from, address to, uint amount); defines an event emitted during token transfers. External applications (e.g., wallets or explorers) can listen for these events efficiently using minimal resources.
For example:
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins sent from " + result.args.from +
" to " + result.args.to + ".");
}
});Events help track transactions without scanning the entire blockchain—ideal for light clients and decentralized interfaces.
Constructor and Message Context
The Coin() function is a constructor—it runs once when the contract is deployed and sets msg.sender (the deployer) as the minter. In Solidity, msg.sender represents the account that initiated the current call, enabling identity verification and access control.
Only the minter can call mint() to issue new tokens. The send() function allows any user with a balance to transfer tokens securely using cryptographic signatures.
Note: Transfers affect only this contract’s internal state—not visible on general blockchain explorers unless they index custom token events.
Blockchain Basics
Understanding blockchains is easier than it seems—once you accept their core features, underlying complexity (like mining or P2P networking) becomes abstracted away, much like using cloud services without knowing their internals.
Transactions
A blockchain functions as a globally shared transactional database. To change data, users submit transactions that must be validated by the network. Transactions are atomic: either all operations succeed, or none do.
For instance, in a token transfer:
- Deducting from the sender’s balance
- Adding to the recipient’s balance
Both actions happen together—or not at all—ensuring data consistency.
All transactions are cryptographically signed by the sender, proving ownership and preventing unauthorized modifications.
Blocks
To resolve conflicts like double-spending, transactions are grouped into blocks and processed in sequence. Conflicting transactions are ordered through consensus mechanisms like mining.
Blocks form a chronological chain—hence “blockchain”—with new blocks added approximately every 17 seconds on Ethereum. While reorganizations (reverts) can occur at the chain tip, deeper blocks become increasingly immutable over time.
Ethereum Virtual Machine (EVM)
The EVM is the runtime environment for smart contracts on Ethereum. It's fully isolated—code inside cannot access external systems like networks or file storage—and interacts solely through defined interfaces.
Accounts
Ethereum has two account types:
- Externally Owned Accounts (EOAs): Controlled by private keys (typically users).
- Contract Accounts: Governed by their deployed code.
Both share the same address space and possess:
- A balance in Wei (1 Ether = 10¹⁸ Wei)
- Storage: A persistent key-value store (256-bit → 256-bit)
Contract addresses are derived from the creator’s address and nonce (transaction count).
Transactions and Contract Creation
A transaction sends a message between accounts. If the target has code, it executes with the transaction’s payload as input.
Sending a transaction to the zero address (0x0) creates a new contract:
- The payload contains initialization bytecode
- Execution output becomes the contract’s permanent code
- Address is deterministically generated from creator and nonce
👉 Learn how developers deploy and interact with live smart contracts today.
Gas and Execution Costs
Every transaction consumes gas, which measures computational effort. Users set a gas price (in Ether), paying gas_price * gas_used.
Gas prevents infinite loops and abuse:
- Depleted gas triggers an out-of-gas exception
- All state changes revert
- Unused gas is refunded
Costs vary by operation:
- Storage writes are expensive
- Memory usage scales quadratically
- Stack operations are cheap
Memory Layout
The EVM uses three data areas:
- Storage: Persistent, contract-specific key-value store
- Memory: Temporary linear space cleared per external call
- Stack: LIFO structure (max 1024 items) for runtime computations
Access is restricted:
- Contracts can’t read other contracts’ storage
- Memory expands word-by-word (256 bits), costing gas
- Stack access limited to top 16 elements
Message Calls and Delegation
Contracts can invoke others via message calls, similar to transactions but nested. They include sender, target, data, Ether, gas, and return data.
Calls support depth up to 1024—favor loops over recursion.
Delegatecall allows code reuse:
- Executes code from another address
- Runs in caller’s context (
msg.sender, storage unchanged) - Enables libraries that operate on caller’s state
This powers modular design patterns like upgradeable contracts and shared logic libraries.
Logs and Event Indexing
Logs store indexed data accessible off-chain via bloom filters. Contracts emit events (e.g., Sent) for external monitoring—ideal for lightweight clients that don’t store full blockchain data.
Logs are immutable and efficient to query but cannot be read by contracts post-creation.
Contract Creation and Self-Destruction
Contracts can create others via create, receiving the new address on the stack.
Only selfdestruct(target) removes contract code:
- Sends remaining Ether to
target - Clears storage and code from state
- Irreversible once executed
Warning: Even without explicit
selfdestruct, malicious logic viadelegatecallcould trigger it indirectly.Note: Archive nodes may preserve historical contract data indefinitely.
Frequently Asked Questions
Q: What is a smart contract?
A: A self-executing program on a blockchain that enforces rules and automates actions based on predefined conditions.
Q: Can smart contracts interact with external data?
A: Not directly. They rely on oracles—trusted third-party services—to fetch off-chain information securely.
Q: How do I secure my smart contract?
A: Follow best practices: use established libraries, test thoroughly, audit code, avoid known vulnerabilities (e.g., reentrancy), and start with small deployments.
Q: Are deleted contracts gone forever?
A: Once selfdestruct runs, code and storage are removed from active state—but historical records remain on the blockchain.
Q: Can I upgrade a deployed smart contract?
A: Direct upgrades aren’t possible, but proxy patterns using delegatecall allow logic updates while preserving data and address.
Q: Why use events instead of storage for tracking?
A: Events are cheaper to emit and easier to index externally—perfect for logging activity without bloating contract storage.
👉 Explore tools and platforms that streamline smart contract development and deployment.
Core Keywords
smart contract, Solidity, Ethereum Virtual Machine (EVM), blockchain development, decentralized applications (dApps), gas fees, contract deployment