Hashtag Web3 Logo

Solidity

The most popular programming language for writing smart contracts on Ethereum and EVM-compatible blockchains, designed for creating decentralized applications.

Smart ContractsIntermediate
Programming code for smart contracts

Solidity is a statically-typed, object-oriented programming language specifically designed for writing smart contracts that run on the Ethereum Virtual Machine (EVM). Created in 2014 by Gavin Wood, Christian Reitwiessner, and others, Solidity has become the dominant language for blockchain development with syntax similar to JavaScript and C++.

Why Solidity Exists

Traditional programming languages weren't designed for blockchain's unique constraints:

Immutability: Once deployed, smart contracts cannot be changed (without upgrade patterns) Gas Costs: Every operation costs gas, requiring optimization Determinism: Code must produce identical results across all nodes Value Transfer: Language needs built-in support for cryptocurrency handling Security: Bugs can result in millions of dollars lost

Solidity addresses these blockchain-specific needs with features like:

  • Gas-aware optimizations
  • Built-in Ether handling
  • Event logging for off-chain indexing
  • Modifier patterns for access control
  • Special address types and mappings

Language Basics

File Structure:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MyToken {
    string public name = "MyToken";
    mapping(address => uint256) public balances;
    
    function transfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        balances[msg.sender] -= amount;
        balances[to] += amount;
    }
}

Key Elements:

  • pragma: Specifies compiler version
  • contract: Defines a smart contract (similar to a class)
  • State variables: Stored permanently on blockchain
  • Functions: Execute logic and modify state
  • msg.sender: Built-in variable for transaction sender
  • require: Validation that reverts if condition fails

Data Types

Value Types:

  • bool: true/false
  • uint: Unsigned integers (uint8 to uint256)
  • int: Signed integers
  • address: 20-byte Ethereum address
  • bytes: Fixed or dynamic byte arrays
  • string: UTF-8 encoded text

Reference Types:

  • arrays: Fixed or dynamic lists
  • mappings: Key-value stores (like hash tables)
  • structs: Custom data structures

Special Types:

  • address payable: Can receive Ether
  • enum: Enumerated types for state machines

Example:

contract DataTypes {
    address public owner;
    uint256 public totalSupply;
    mapping(address => uint256) public balances;
    struct User {
        string name;
        uint256 age;
        bool active;
    }
}

Functions and Visibility

Visibility Specifiers:

  • public: Callable internally and externally
  • external: Only callable from outside
  • internal: Only within contract and derived contracts
  • private: Only within current contract

State Mutability:

  • view: Reads state but doesn't modify
  • pure: Neither reads nor modifies state
  • payable: Can receive Ether

Example:

function getBalance(address user) public view returns (uint256) {
    return balances[user];
}

function deposit() public payable {
    balances[msg.sender] += msg.value;
}

Modifiers

Modifiers add reusable checks to functions:

modifier onlyOwner() {
    require(msg.sender == owner, "Not authorized");
    _; // Function body executes here
}

function withdraw() public onlyOwner {
    // Only owner can execute
}

Common modifier patterns:

  • Access control (onlyOwner, onlyAdmin)
  • Reentrancy guards
  • Pausing mechanisms
  • Time locks

Events

Events log information for off-chain applications to react to:

event Transfer(address indexed from, address indexed to, uint256 value);

function transfer(address to, uint256 amount) public {
    // ... transfer logic
    emit Transfer(msg.sender, to, amount);
}

Events are cheaper than storage and essential for:

  • DApp front-ends monitoring activity
  • Analytics and indexing (The Graph)
  • Transaction receipts
  • Historical lookups

Inheritance

Solidity supports multiple inheritance:

contract ERC20 {
    function transfer(address to, uint256 amount) public virtual {
        // Base implementation
    }
}

contract MyToken is ERC20 {
    function transfer(address to, uint256 amount) public override {
        // Custom implementation
        super.transfer(to, amount);
    }
}

Enables code reuse through:

  • OpenZeppelin contracts (standardized, audited implementations)
  • Abstract contracts defining interfaces
  • Libraries for common functionality

Interfaces

Define contract structure without implementation:

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract MyContract {
    function interactWith(IERC20 token) public {
        uint256 balance = token.balanceOf(address(this));
    }
}

Crucial for contract composability—interact with any contract implementing the interface.

Libraries

Reusable code deployed once, used by many contracts:

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "Overflow");
        return c;
    }
}

contract MyContract {
    using SafeMath for uint256;
    
    function calculate(uint256 a, uint256 b) public pure returns (uint256) {
        return a.add(b);
    }
}

OpenZeppelin libraries provide battle-tested implementations for:

  • Math operations
  • Access control
  • Token standards
  • Security patterns

Error Handling

require: Validates inputs/conditions, refunds remaining gas revert: Similar to require but can include custom errors assert: Checks for internal errors, consumes all gas (use sparingly)

// Before Solidity 0.8.4
require(balance >= amount, "Insufficient balance");

// Custom errors (0.8.4+)
error InsufficientBalance(uint256 available, uint256 required);

function transfer(uint256 amount) public {
    if (balance < amount) {
        revert InsufficientBalance(balance, amount);
    }
}

Custom errors save gas compared to string messages.

Gas Optimization Techniques

Solidity developers must optimize for gas costs:

Storage vs Memory: Storage is expensive, memory is cheap

// Expensive: Multiple storage reads
function badLoop() public {
    for (uint i = 0; i < users.length; i++) {
        // Each users.length is a storage read
    }
}

// Optimized: Cache in memory
function goodLoop() public {
    uint256 length = users.length; // One storage read
    for (uint i = 0; i < length; i++) {
        // Uses memory variable
    }
}

Variable Packing: Pack multiple small variables into single storage slot

// Uses 3 storage slots (expensive)
uint128 a;
uint256 b;
uint128 c;

// Uses 2 storage slots (cheaper)
uint128 a;
uint128 c;
uint256 b;

Short-Circuit Evaluation: Order conditions to fail fast Use events not storage: Where possible, emit events instead of storing data External vs Public: External functions are cheaper for external calls

Security Considerations

Reentrancy: Attacker calls back into contract before state updates

// Vulnerable
function withdraw() public {
    uint256 amount = balances[msg.sender];
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
    balances[msg.sender] = 0; // Too late!
}

// Secure: Checks-Effects-Interactions pattern
function withdraw() public {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0; // Update state first
    (bool success,) = msg.sender.call{value: amount}("");
    require(success);
}

Integer Overflow: Solidity 0.8+ has built-in overflow protection Access Control: Always validate msg.sender for privileged functions Oracle Manipulation: Validate external data sources Front-Running: Be aware of MEV and transaction ordering

The DAO hack ($60M), Parity wallet freeze ($300M+), and numerous other incidents demonstrate the critical importance of secure Solidity code.

Development Tools

Hardhat: Most popular development environment, testing, deployment Foundry: Rust-based toolkit, extremely fast, solidity-native testing Remix: Browser-based IDE, great for learning and quick prototyping Truffle: Older framework, still widely used

Testing:

const { expect } = require("chai");

describe("Token", function () {
  it("Should transfer tokens", async function () {
    const Token = await ethers.getContractFactory("MyToken");
    const token = await Token.deploy();
    await token.transfer(addr1.address, 50);
    expect(await token.balanceOf(addr1.address)).to.equal(50);
  });
});

Solidity Versions and Evolution

Major Updates:

  • 0.4.x: Early production versions
  • 0.5.x: Breaking changes improving safety
  • 0.6.x: Improved syntax
  • 0.7.x: More security features
  • 0.8.x: Built-in overflow protection, custom errors

Always specify exact version or tight range:

pragma solidity 0.8.19; // Exact
pragma solidity ^0.8.0; // Compatible with 0.8.x

Breaking changes between versions mean older contracts need updates.

EVM Compatibility

Solidity compiles to EVM bytecode, making it portable across:

  • Ethereum mainnet
  • Layer 2s (Arbitrum, Optimism, etc.)
  • Sidechains (Polygon, BNB Chain)
  • Alt-L1s (Avalanche C-Chain, Fantom)

One codebase deploys across dozens of chains, though gas costs and available opcodes may vary slightly.

Alternatives to Solidity

Vyper: Python-like syntax, prioritizes security and auditability Rust: Solana and NEAR smart contracts Move: Aptos and Sui, designed for asset-oriented programming Cairo: StarkNet's language for ZK proofs

Solidity remains dominant with the largest developer community and most tooling.

Career Opportunities

Solidity Developer ($150k-$400k+): Writes smart contracts for DeFi, NFTs, DAOs. High demand, especially with security expertise.

Smart Contract Auditor ($120k-$300k+): Reviews code for vulnerabilities. Critical role given stakes involved. Firms like Trail of Bits, OpenZeppelin pay premium salaries.

Protocol Engineer ($160k-$450k+): Designs complex protocol interactions, optimizes gas, implements upgrade patterns.

Blockchain Architect ($170k-$400k+): Plans contract architecture, selects design patterns, oversees development teams.

DevRel Engineer ($100k-$200k): Creates tutorials, documentation, sample contracts. Helps developers adopt protocols.

Solidity Instructor ($80k-$150k): Teaches bootcamps, creates courses, writes educational content.

Security Researcher ($130k-$400k+): Finds vulnerabilities, participates in bug bounties, publishes security research.

Learning Path

  1. Basics: Variables, functions, control flow
  2. Intermediate: Inheritance, interfaces, events, error handling
  3. Advanced: Gas optimization, security patterns, upgrade mechanisms
  4. Expert: Complex protocols, cross-contract interactions, novel patterns

Resources:

  • Solidity documentation (official)
  • CryptoZombies (interactive tutorials)
  • OpenZeppelin contracts (code examples)
  • Ethernaut (security challenges)
  • Immunefi (bug bounties for practice)

Mastering Solidity opens doors to the highest-paid programming roles in tech. The language's specificity means fewer developers with expertise, driving salaries up. Understanding Solidity deeply—from language features to gas optimization to security patterns—is the foundation of a lucrative blockchain development career. The ecosystem continues evolving with new patterns, tools, and best practices emerging constantly, rewarding those who stay current.

Find Solidity Jobs

Explore positions at companies working with Solidity technology

Browse open roles

Learn More

Read in-depth articles and guides about Web3 careers and technology

Explore the blog