ποΈ Solidity Fundamentals
π― What is Solidity?
Solidity is an object-oriented, high-level language for implementing smart contracts on Ethereum and EVM-compatible blockchains. Created by Gavin Wood (Ethereum co-founder) and developed by the Solidity team, it's statically typed, supports inheritance, libraries, and complex user-defined types. Solidity compiles to bytecode executed on the Ethereum Virtual Machine (EVM).
π¬ Core Concepts
- Smart Contracts: Self-executing code with state variables and functions.
- EVM (Ethereum Virtual Machine): Deterministic execution environment.
- Gas Model: Every operation costs gas (measured in wei/gwei).
- ABI (Application Binary Interface): Interface for interacting with contracts.
- Version Pragma: Specifies compiler version (e.g., ^0.8.0).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract FirstContract {
string public greeting = "Hello, Web3!";
function setGreeting(string memory _newGreeting) public {
greeting = _newGreeting;
}
}
π’ Value Types & Variables
π― Primitive Types
bool, int/uint (8-256 bits), address, bytes1..bytes32, string. Variables have state (storage), local (memory), or constant/immutable.
- uint256: Unsigned integer (default for gas optimization)
- address: 20-byte Ethereum address (payable address variant)
- bytes32: Fixed-size byte array
- enum: Custom user-defined type
pragma solidity ^0.8.0;
contract TypesDemo {
uint256 public balance = 1000 wei;
address public owner = msg.sender;
bool public isActive = true;
bytes32 public hash = keccak256("Solidity");
enum Status { Pending, Active, Closed }
Status public currentStatus = Status.Pending;
}
βοΈ Functions & Modifiers
π― Function Visibility & State Mutability
public, private, internal, external. Modifiers: view (read-only), pure (no read/write), payable (receive ether). Custom modifiers for reusable checks.
contract ModifierExample {
address public owner;
constructor() { owner = msg.sender; }
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function changeOwner(address _newOwner) public onlyOwner {
owner = _newOwner;
}
function getBalance() public view returns (uint256) {
return address(this).balance;
}
}
π Control Structures
π― if-else, loops, conditional
Solidity supports if/else, for, while, do-while, break/continue. Loops should be used carefully to avoid gas limit issues.
function sumArray(uint[] memory arr) public pure returns (uint total) {
for (uint i = 0; i < arr.length; i++) {
total += arr[i];
}
}
function maxValue(uint a, uint b) public pure returns (uint) {
return a > b ? a : b;
}
π Arrays & Structs
π― Dynamic/Fixed Arrays + Custom Types
T[] dynamic array, T[5] fixed. Structs group data. Arrays have .push(), .length methods.
struct Player {
string name;
uint score;
}
contract Game {
Player[] public players;
function addPlayer(string memory _name) public {
players.push(Player(_name, 0));
}
function getPlayer(uint idx) public view returns (string memory, uint) {
Player memory p = players[idx];
return (p.name, p.score);
}
}
πΊοΈ Mappings & Enums
π― Key-Value Storage
Mappings: mapping(keyType => valueType) β hash tables for efficient lookups. Keys are not stored, only keccak256 hash. Enums create named constants.
mapping(address => uint) public balances;
mapping(uint => address) public idToAddress;
enum OrderState { Created, Shipped, Delivered }
function setBalance(address _user, uint _amount) public {
balances[_user] = _amount;
}
πΎ Storage / Memory / Calldata
π― Data Locations
storage (persistent on blockchain), memory (temporary), calldata (immutable function params). Gas costs vary drastically.
contract DataLocations {
uint[] public storedArray; // storage
function example(uint[] calldata _externalArr) external {
uint[] memory tempArray = new uint[](3); // memory
tempArray[0] = _externalArr[0];
storedArray = tempArray; // copy to storage
}
}
π’ Events & Logging
π― Emit logs for off-chain apps
Events are indexed EVM logs, gas-efficient way to track contract activity. Up to 3 indexed parameters for efficient filtering.
event Transfer(address indexed from, address indexed to, uint256 value);
event Log(string message, uint timestamp);
function sendToken(address _to, uint _amount) public {
// transfer logic
emit Transfer(msg.sender, _to, _amount);
emit Log("Transfer executed", block.timestamp);
}
𧬠Inheritance & Polymorphism
π― Multiple inheritance, virtual/override
Solidity supports multiple inheritance with linearization (C3). virtual and override keywords.
contract Base {
function foo() public virtual pure returns (string memory) {
return "Base";
}
}
contract Derived is Base {
function foo() public override pure returns (string memory) {
return "Derived";
}
}
β οΈ Error Handling (require/revert)
π― Require, Assert, Revert
require(condition, "error") β refunds remaining gas. assert(condition) β consumes gas, used for invariants. revert() β manual rollback. Custom errors (Solidity 0.8.4+) reduce gas.
error Unauthorized(address caller);
error InsufficientBalance(uint requested, uint available);
function withdraw(uint amount) public {
if (amount > balances[msg.sender]) {
revert InsufficientBalance(amount, balances[msg.sender]);
}
require(msg.sender != address(0), "Zero address");
// logic
}
π Interfaces & Abstract Contracts
π― Define standards without implementation
interface β only external functions, no implementation. abstract contract β mix of implemented/unimplemented functions. ERC20/ERC721 are standard interfaces.
interface IERC20 {
function totalSupply() external view returns (uint);
function transfer(address to, uint amount) external returns (bool);
}
abstract contract Token is IERC20 {
function totalSupply() public view virtual override returns (uint);
// other logic
}
π Libraries & Using For
π― Reusable code, no storage
Libraries are deployed once and can be attached to types via using ... for .... Great for math, safe operations.
library SafeMath {
function add(uint a, uint b) internal pure returns (uint) {
uint c = a + b;
require(c >= a, "overflow");
return c;
}
}
contract Calculator {
using SafeMath for uint;
uint public value = 10;
function addValue(uint x) public {
value = value.add(x);
}
}
π° Payable & Ether Transfers
π― Receive Ether & Send
payable modifier allows functions to accept ether. Methods: transfer() (2300 gas), send(), call{value: }() (recommended). receive() and fallback() handle plain ether transfers.
contract Wallet {
event Received(address sender, uint amount);
receive() external payable {
emit Received(msg.sender, msg.value);
}
function donate() public payable {}
function withdraw(uint amount) public {
payable(msg.sender).transfer(amount);
}
function callTransfer(address payable _to) public payable {
(bool success, ) = _to.call{value: msg.value}("");
require(success, "Transfer failed");
}
}
π‘οΈ Security Patterns (Reentrancy)
π― Smart Contract Security Best Practices
Reentrancy Guard (Checks-Effects-Interactions), Access Control, Timestamps manipulation, Integer overflow/underflow (Solidity 0.8+ built-in checks). Use OpenZeppelin contracts.
contract SecureWithdraw {
mapping(address => uint) public balances;
bool internal locked;
modifier noReentrancy() {
require(!locked, "Reentrancy");
locked = true;
_;
locked = false;
}
function withdraw(uint amount) public noReentrancy {
require(balances[msg.sender] >= amount, "Insufficient");
balances[msg.sender] -= amount; // Effects
(bool sent, ) = msg.sender.call{value: amount}(""); // Interaction
require(sent, "Failed");
}
}