Solidity Smart Contract Language
Solidity is the most popular language for Ethereum smart contracts. Its syntax is similar to JavaScript, but because smart contracts run on the blockchain, there are many unique concepts you need to understand.
Solidity Data Types
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract DataTypes {
// === Basic Types ===
bool public isActive; // true / false
uint256 public count; // Unsigned integer (0 to 2^256-1)
int256 public balance; // Signed integer
address public owner; // Ethereum address (20 bytes)
bytes32 public hash; // 32 bytes of data
string public name; // String
// === Enum ===
enum Status { Pending, Active, Inactive, Banned }
Status public userStatus;
// === Struct ===
struct User {
string name;
uint256 age;
address wallet;
bool isVerified;
}
User public defaultUser;
// === Arrays ===
uint256[] public numbers; // Dynamic array
uint256[10] public fixedArray; // Fixed-size array
User[] public users; // Array of structs
// === Mapping โ The Most Used Data Structure ===
mapping(address => uint256) public balances; // address -> balance
mapping(uint256 => address) public idToAddress; // ID -> address
mapping(address => mapping(uint256 => bool)) public hasClaimed; // Nested mapping
constructor() {
owner = msg.sender;
}
}
Function Modifiers
contract FunctionModifiers {
address public owner;
bool public paused;
constructor() {
owner = msg.sender;
}
// === Visibility Modifiers ===
function publicFunction() public {} // Anyone can call
function externalFunction() external {} // Only external calls (cheaper gas)
function internalFunction() internal {} // Only this contract and children
function privateFunction() private {} // Only this contract
// === State Mutability ===
function pureFunction() public pure returns (uint256) {
// pure: doesn't read or modify blockchain state
return 42;
}
function viewFunction() public view returns (address) {
// view: reads but doesn't modify state
return owner;
}
function payableFunction() public payable {
// payable: can receive ETH
}
// === Custom Modifiers ===
modifier onlyOwner() {
require(msg.sender == owner, "Not the contract owner");
_; // Continue executing the original function
}
modifier whenNotPaused() {
require(!paused, "Contract is paused");
_;
}
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
function togglePause() public onlyOwner {
paused = !paused;
}
}
Events
Events are crucial in Solidity. When emitted, they are recorded in the blockchain's transaction logs, and frontends can listen to them:
contract EventsExample {
event Transfer(address indexed from, address indexed to, uint256 value);
event Deposit(address indexed user, uint256 amount, uint256 timestamp);
event UserRegistered(address indexed user, string name);
mapping(address => uint256) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
emit Deposit(msg.sender, msg.value, block.timestamp);
}
function transfer(address to, uint256 amount) public {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
}
}
Error Handling
Solidity provides three ways to handle errors:
contract ErrorHandling {
// 1. require() โ Most common. Reverts with remaining gas refunded
function checkRequire(uint256 x) public pure {
require(x > 0, "Value must be positive");
require(x < 100, "Value must be less than 100");
}
// 2. revert() โ Conditional revert with custom message
function checkRevert(uint256 x) public pure {
if (x == 0) {
revert("Value cannot be zero");
}
if (x > 1000) {
revert("Value too large");
}
}
// 3. assert() โ For internal errors (consumes all gas). Only use for invariants
function checkAssert(uint256 x) public pure {
// This should never happen if our logic is correct
assert(x <= type(uint256).max);
}
}
Inheritance
Solidity supports multiple inheritance, similar to C++:
contract Base {
event Log(string message);
function foo() public virtual {
emit Log("Base.foo called");
}
}
contract Middle is Base {
function foo() public virtual override {
emit Log("Middle.foo called");
}
}
contract Child is Middle {
function foo() public override {
super.foo(); // Calls Middle.foo, which calls Base.foo
emit Log("Child.foo called");
}
}
Chapter Summary
| Concept | Description | |---------|-------------| | Data Types | uint, int, bool, address, bytes, string, enum, struct | | Mapping | Key-value store, the most common data structure | | Visibility | public, external, internal, private | | Modifiers | pure, view, payable, custom (onlyOwner, etc.) | | Events | Blockchain logs, frontend can subscribe | | Error Handling | require(), revert(), assert() | | Units & Globals | wei, gwei, ether; seconds, minutes, hours, days; msg.sender, block.timestamp, block.number | | Inheritance | Multiple inheritance with virtual/override |
Practice Exercise
๐ก Vibe Practice: Ask AI to build a simple crowdfunding contract that:
- Allows users to donate ETH to a campaign
- Has a funding goal and deadline
- Refunds donors if the goal isn't met
- Emits events for every donation and refund
- Uses modifiers for access control
Up Next
In the next chapter, we'll deploy our own ERC-20 token on the Sepolia testnet!