Smart contracts represent the most significant distinction between Bitcoin and Ethereum, enabling complex, programmable transactions on the blockchain.
What Are Smart Contracts?
A smart contract is a self-executing code deployed on a blockchain. The code defines the contract's logic and rules. Each smart contract has its own account, maintaining several key pieces of information:
- Balance: The current amount of Ether held by the contract.
- Nonce: A count of transactions associated with this contract address.
- Code: The compiled bytecode of the contract itself.
- Storage: Data persistence, structured as a Modified Patricia Trie (MPT).
These contracts are typically written in Solidity, a programming language with syntax similarities to JavaScript, designed specifically for the Ethereum Virtual Machine (EVM).
Key Features of Solidity
address: A unique data type representing an Ethereum account address.mapping: A key-value store, similar to a hash table or dictionary.event: Used for logging specific occurrences on the blockchain, which applications can listen for.- Loops and Arrays: Solidity supports both fixed-size and dynamically-sized arrays. However, it does not natively support iterating over all keys in a
mapping; developers must manually manage a separate array of keys for this purpose.
Contract Structure
- Constructor: A special function initializes the contract. Older syntax used a function with the same name as the contract. The modern, recommended approach is to use the
constructorkeyword. This function executes only once—when the contract is first deployed. - Payable Functions: A critical concept in Ethereum. Any function that needs to receive Ether must be marked with the
payablemodifier. Functions not markedpayablewill reject any incoming Ether, causing the transaction to fail.
Example: A Simple Auction Contract
Imagine an auction where participants bid by sending Ether. The bid function must be payable to accept the Ether and lock it in the contract. After the auction, the winner's funds are transferred to the beneficiary, and other bidders can call a withdraw function to reclaim their funds. Since withdraw only returns existing funds and doesn't accept new ones, it does not need to be payable.
How to Call a Smart Contract
From an External Account
Calling a contract is similar to a transfer. A transaction is sent from an external account (EOA) to the contract's address. The data field of the transaction specifies which function to call and includes any required parameters. This field is crucial; if the recipient is a contract and the data field is empty, it may trigger the contract's fallback function.
From Another Contract (Internal Calls)
A contract cannot initiate a transaction on its own; it must be triggered by an external account. However, one contract can call functions in another. There are several methods for this, each with different error-handling semantics:
- Direct Call: The simplest method (e.g.,
AContract.foo()). If the called function (foo) fails or throws an exception, it causes a complete reversion, undoing all changes made by the calling contract as well. call()method: A lower-level function (e.g.,addr.call("foo()")). This method returns a booleanfalseif the call fails, instead of propagating an exception. This allows the calling contract to handle the failure gracefully and continue execution.delegatecall()method: Similar tocall(), but it executes the target contract's code in the context of the calling contract. It uses the caller's storage, balance, and address. This is primarily used for creating proxy contracts or libraries.
👉 Explore advanced contract interaction methods
The Fallback Function
Every contract can have one special fallback function. It is unnamed, has no arguments, and returns no data. It executes in two scenarios:
- A transaction is sent to the contract with no
data(a plain Ether transfer). - A transaction is sent with
datathat doesn't match any existing function signature.
The fallback function should often be marked payable if the contract is designed to receive plain Ether transfers. If no function is payable, including the fallback function, the contract cannot receive Ether at all.
Creating a Smart Contract
Creating a contract involves an external account sending a transaction to the zero address (0x0). The contract's compiled code is placed in the data field of this transaction. Miners process this transaction, and the contract's address is generated and deployed onto the blockchain.
Gas: Fuel for the Network
Ethereum aims to be Turing-complete, meaning it can run any computational task. This power introduces a problem: how to prevent infinite loops or excessively costly computations? The solution is gas.
- Every computational operation (opcode) in the EVM has a predefined gas cost.
- When initiating a transaction, the sender sets a
gas limit(the maximum units they are willing to consume) and agas price(the amount of Ether they will pay per unit of gas). - The total fee is
Gas Used * Gas Price. Upfront, the maximum possible fee (Gas Limit * Gas Price) is deducted from the sender's account. - If the transaction completes, any unused gas is refunded. If it runs out of gas mid-execution, it reverts all state changes, but the spent gas is not refunded to discourage network abuse.
This mechanism shifts the responsibility of estimating computational cost to the user and compensates miners for their work.
Error Handling and Atomicity
Transactions on Ethereum are atomic—they either fully complete or fully revert as if they never happened. This is true for both simple transfers and complex contract interactions.
assert()andrequire(): These functions check conditions and throw an exception if the condition is not met.require()is typically used to validate inputs or external conditions (e.g.,require(now <= auctionEndTime)), whileassert()is used for checking internal invariants (logic that should never be false).revert(): This function unconditionally aborts execution and reverts the state changes.
Solidity lacks traditional try-catch blocks. Error handling is primarily done through these revert mechanisms.
Nested Calls and Reversion
The behavior of a failed nested call depends on how it was made:
- A direct call failure causes a chain reaction, reverting the entire transaction.
- A failure using
call()only returnsfalseto the caller, allowing the calling contract to handle the error without a full reversion.
Data Structures and Blockchain Properties
- Block Gas Limit: The total amount of gas that all transactions in a block can consume. Unlike Bitcoin's fixed block size, Ethereum's gas limit is adjustable by miners, providing network flexibility.
- World State: The global state of all accounts and their storage is maintained in a state trie. Executing a transaction modifies this local state, which is only finalized and shared once a block containing the transaction is mined and validated by the network.
Frequently Asked Questions
How do I know if a transaction was successful?
Every transaction generates a receipt stored on the blockchain. A key field in this receipt is status, which is 1 for success or 0 for failure (reversion). You can check this status using a block explorer.
Can smart contracts generate random numbers?
True randomness is impossible on a deterministic system like Ethereum. Contracts can only use pseudorandomness (e.g., using block hashes as a seed), but this can be manipulated by miners to some degree and is not secure for high-value applications.
Why can't my contract execute automatically at a specific time?
Solidity and the EVM have no built-in scheduler. A transaction (from an EOA or another contract) must always initiate any contract function's execution. To create time-based actions, you need an external "keeper" service to trigger the function.
What is a reentrancy attack?
It's a critical vulnerability where a malicious contract exploits the single-threaded execution model. During a withdraw operation, the malicious contract's fallback function can call back into the vulnerable contract before its balance is updated, potentially draining funds in a recursive loop.
How can I prevent reentrancy attacks?
- Use the Checks-Effects-Interactions pattern: Always update the contract's internal state before sending Ether to external addresses.
- Use
transfer()orsend(): These methods forward only a tiny, fixed amount of gas (2300 units), which is enough to log an event but not enough for the recipient to perform another complex call and re-enter your contract.
Are deployed smart contracts editable?
No. By design, contract code is immutable once deployed. This ensures fairness and predictability but means bugs cannot be patched. Developers often include upgradeability patterns using proxy contracts or design contracts with an emergency "pause" function controlled by a trusted address.