Anatomy of a contract
A Solidity file declares a license identifier, a pragma pinning the compiler version, then a contract with state variables, a constructor, functions, events and modifiers. Deployment stores the compiled bytecode at an address; the ABI is the JSON description used by wallets and ethers.js to encode calls.
- •public / external / internal / private control who can call.
- •view and pure functions read but never write state — free via eth_call.
- •payable functions may receive ETH with the call.
Storage, memory and calldata
Storage is the contract's permanent, expensive key-value state. Memory is a scratchpad wiped after the call. Calldata is the read-only input buffer. Writing storage costs gas per slot, so minimising writes is the main optimisation in practice.
- •mapping(address => uint256) is the standard per-user balance pattern.
- •Prefer calldata for large read-only function arguments.
- •Cache a storage value in a local variable when reading it repeatedly.
Errors and events
require and custom errors revert the whole transaction and refund unused gas — state changes are all-or-nothing. Events write cheap, indexed logs that front-ends and explorers subscribe to, since contracts cannot push data to users.
Safety patterns in SimpleStaking
The lab contract follows checks-effects-interactions: validate inputs, update balances in storage, and only then transfer value out. That ordering is what defeats reentrancy, where a malicious receiver calls back in before your bookkeeping is finished.
- •Update state before external calls or transfers.
- •Never trust block.timestamp for precise timing or randomness.
- •Fund the reward pool explicitly — a contract cannot mint ETH.
