Building a Simple Storage Contract
A great first contract is a simple storage contract that lets anyone save a number and read it back, then grows to include ownership and events. It begins with the license and pragma, declares a contract, and defines a state variable to hold the stored value. A constructor runs exactly once at deployment to set initial state, such as recording who deployed the contract as its owner. From there you add functions to change and read state, and you learn the difference between transactions that write to storage and cost gas versus view calls that only read and are free when called off-chain.
Cricket analogy: The constructor is like the pre-match toss that happens once to set who bats first, establishing initial conditions before any ball is bowled, just as it sets the owner before any function is called.
Functions, Events, and Access Control
Functions that modify state, like a setter that writes a new value, require a transaction and cost gas, while functions marked view only read state and cost nothing when called externally off-chain. Events let a contract log data to the blockchain's log system cheaply; front-end applications and indexers subscribe to events like ValueChanged to react to on-chain activity without polling. Access control restricts sensitive functions to authorized callers, typically by comparing msg.sender against a stored owner. A require statement enforces this: if the condition fails, the whole transaction reverts and any gas-consuming state changes are rolled back, though the gas spent up to that point is not refunded.
Cricket analogy: Events are like the stadium PA announcing each wicket so fans react instantly, and require-based access control is like only the captain being allowed to declare an innings, with an illegal attempt being disallowed.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleStorage {
uint256 private value;
address public owner;
event ValueChanged(address indexed by, uint256 newValue);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
function set(uint256 newValue) external onlyOwner {
value = newValue;
emit ValueChanged(msg.sender, newValue);
}
function get() external view returns (uint256) {
return value;
}
// Accept ether sent to the contract
function deposit() external payable {}
}The payable Keyword and Modifiers
A function must be marked payable to receive ether; calling a non-payable function with ether attached reverts. Inside a payable function, msg.value holds the amount of wei sent, and the contract's balance increases automatically. Modifiers like onlyOwner are reusable code wrappers: the body runs where the underscore appears, so you can attach access-control checks to many functions without repeating the require line. This example combines all the pieces, a constructor setting the owner, a modifier guarding the setter, an event logging changes, a view getter, and a payable deposit function, into a coherent first contract you can deploy on Remix or Hardhat.
Cricket analogy: The payable keyword is like a designated collection box that can accept prize money, while a modifier is like a reusable pre-delivery ritual every bowler performs before each ball without rewriting it.
Ether amounts are measured in wei, the smallest unit, where 1 ether equals 10^18 wei. Solidity provides units like 1 ether and 1 gwei as readable literals, so you rarely have to type long numbers by hand.
A failed require rolls back all state changes but does not refund the gas already consumed before the failure. Validate inputs early in a function so a revert happens before expensive storage writes waste the caller's gas.
- A constructor runs once at deployment to initialize state such as the owner.
- State-changing functions require a gas-paying transaction; view functions only read and are free off-chain.
- Events log data cheaply so front-ends and indexers can react without polling.
- Access control compares msg.sender against a stored owner, enforced by require.
- A failed require reverts all state changes but does not refund gas already spent.
- Functions must be payable to receive ether, and msg.value holds the wei sent.
- Modifiers wrap reusable checks like onlyOwner, running around the function body at the underscore.
Practice what you learned
1. When does a contract's constructor run?
2. What is required for a function to receive ether?
3. What happens to state changes when a require condition fails?
4. Why are events used in smart contracts?
5. What does a modifier like onlyOwner do?
Was this page helpful?
You May Also Like
What Is Solidity?
Solidity is a statically typed, contract-oriented programming language for writing smart contracts that run on the Ethereum Virtual Machine and compatible blockchains.
Solidity Syntax and Variables
Understand Solidity's file structure, the difference between state, local, and global variables, and how visibility and data location affect where values live and who can read them.
Solidity Data Types and Value Types
Explore Solidity's value types and reference types, including sized integers, booleans, addresses, bytes, enums, arrays, structs, and mappings, and how they are copied and stored.
Setting Up Remix and Hardhat
Learn the two most common Solidity development environments: the browser-based Remix IDE for quick experiments and the Node.js-based Hardhat framework for professional project workflows.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics