Solidity

#28
TIOBE#41
GitHub#53
IEEESpectrum#23
programming languageblockchainsmart contractsEthereumdecentralized applicationsWeb3

Programming Language

Solidity

Overview

Solidity is a statically-typed programming language designed for developing smart contracts that run on the Ethereum Virtual Machine (EVM).

Details

Solidity is a blockchain-specific programming language developed by the Ethereum Foundation (primarily by Gavin Wood) in 2014, used for developing smart contracts that execute on the Ethereum Virtual Machine (EVM). It has syntax influenced by JavaScript, Python, and C++, and is based on a contract-oriented design philosophy. It supports contracts, inheritance, libraries, and complex user-defined types, and is used to develop decentralized applications (DApps) such as financial applications (DeFi), NFTs, decentralized autonomous organizations (DAOs), and games. It provides features to address blockchain-specific challenges such as gas efficiency optimization, security considerations, and upgradeability patterns. Besides Ethereum, it is also used on EVM-compatible blockchains such as Binance Smart Chain, Polygon, and Avalanche.

Code Examples

Hello World

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

// Basic contract
contract HelloWorld {
    string private message;
    
    constructor() {
        message = "Hello, World!";
    }
    
    function getMessage() public view returns (string memory) {
        return message;
    }
    
    function setMessage(string memory newMessage) public {
        message = newMessage;
    }
}

// Example with events
contract HelloWorldWithEvents {
    string private message;
    
    event MessageUpdated(string oldMessage, string newMessage, address updatedBy);
    
    constructor() {
        message = "Hello, Solidity!";
    }
    
    function getMessage() public view returns (string memory) {
        return message;
    }
    
    function setMessage(string memory newMessage) public {
        string memory oldMessage = message;
        message = newMessage;
        emit MessageUpdated(oldMessage, newMessage, msg.sender);
    }
}

Basic Data Types

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

contract DataTypes {
    // Signed integers
    int8 smallInt = -128;
    int256 bigInt = -2**255;  // int256 = int
    
    // Unsigned integers
    uint8 smallUint = 255;
    uint256 bigUint = 2**256 - 1;  // uint256 = uint
    
    // Fixed point numbers
    // fixed128x18 fixedPoint = 3.14;  // Future implementation
    
    // Address types
    address public owner;
    address payable public recipient;
    
    // Boolean
    bool public isActive = true;
    
    // Byte types
    bytes1 singleByte = 0x42;
    bytes32 hash = keccak256("Hello");
    bytes dynamicBytes = "Dynamic byte array";
    
    // Strings
    string public name = "Contract Name";
    
    // Arrays
    uint[] public dynamicArray;
    uint[5] public fixedArray = [1, 2, 3, 4, 5];
    
    // Mappings (hash tables)
    mapping(address => uint) public balances;
    mapping(string => bool) public permissions;
    
    // Structs
    struct User {
        string name;
        uint age;
        bool isActive;
    }
    
    User[] public users;
    mapping(address => User) public userProfiles;
    
    // Enums
    enum Status { Pending, Active, Inactive, Suspended }
    Status public currentStatus = Status.Pending;
    
    constructor() {
        owner = msg.sender;
        recipient = payable(msg.sender);
    }
    
    function addUser(string memory _name, uint _age) public {
        users.push(User(_name, _age, true));
        userProfiles[msg.sender] = User(_name, _age, true);
    }
    
    function updateBalance(address user, uint amount) public {
        balances[user] = amount;
    }
    
    function setPermission(string memory action, bool allowed) public {
        permissions[action] = allowed;
    }
    
    function changeStatus(Status newStatus) public {
        require(msg.sender == owner, "Only owner can change status");
        currentStatus = newStatus;
    }
}

Functions and Modifiers

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

contract FunctionsAndModifiers {
    address public owner;
    uint public totalSupply;
    bool public paused;
    
    mapping(address => uint) public balances;
    
    event Transfer(address indexed from, address indexed to, uint amount);
    event Paused(address account);
    event Unpaused(address account);
    
    constructor(uint _initialSupply) {
        owner = msg.sender;
        totalSupply = _initialSupply;
        balances[owner] = _initialSupply;
        paused = false;
    }
    
    // Modifiers
    modifier onlyOwner() {
        require(msg.sender == owner, "Not the owner");
        _;
    }
    
    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }
    
    modifier validAddress(address _addr) {
        require(_addr != address(0), "Invalid address");
        _;
    }
    
    // View function (doesn't modify state)
    function getBalance(address account) public view returns (uint) {
        return balances[account];
    }
    
    // Pure function (doesn't access state)
    function calculatePercentage(uint amount, uint percentage) 
        public 
        pure 
        returns (uint) 
    {
        return (amount * percentage) / 100;
    }
    
    // State-changing function
    function transfer(address to, uint amount) 
        public 
        whenNotPaused 
        validAddress(to) 
        returns (bool) 
    {
        require(balances[msg.sender] >= amount, "Insufficient balance");
        require(to != msg.sender, "Cannot transfer to yourself");
        
        balances[msg.sender] -= amount;
        balances[to] += amount;
        
        emit Transfer(msg.sender, to, amount);
        return true;
    }
    
    // Owner-only function
    function mint(address to, uint amount) 
        public 
        onlyOwner 
        validAddress(to) 
    {
        totalSupply += amount;
        balances[to] += amount;
        emit Transfer(address(0), to, amount);
    }
    
    function pause() public onlyOwner {
        paused = true;
        emit Paused(msg.sender);
    }
    
    function unpause() public onlyOwner {
        paused = false;
        emit Unpaused(msg.sender);
    }
    
    // Ownership transfer
    function transferOwnership(address newOwner) 
        public 
        onlyOwner 
        validAddress(newOwner) 
    {
        owner = newOwner;
    }
    
    // Fallback and receive functions
    receive() external payable {
        // Handle plain Ether transfers
    }
    
    fallback() external payable {
        // Handle calls to non-existent functions
    }
}

Events and Error Handling

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

contract EventsAndErrors {
    address public owner;
    mapping(address => uint) public deposits;
    
    // Event definitions
    event Deposit(address indexed user, uint amount, uint timestamp);
    event Withdrawal(address indexed user, uint amount, uint timestamp);
    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    
    // Custom errors (Solidity 0.8.4+)
    error InsufficientBalance(uint requested, uint available);
    error Unauthorized(address caller);
    error InvalidAmount();
    
    constructor() {
        owner = msg.sender;
    }
    
    modifier onlyOwner() {
        if (msg.sender != owner) {
            revert Unauthorized(msg.sender);
        }
        _;
    }
    
    function deposit() public payable {
        if (msg.value == 0) {
            revert InvalidAmount();
        }
        
        deposits[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value, block.timestamp);
    }
    
    function withdraw(uint amount) public {
        uint balance = deposits[msg.sender];
        
        if (amount > balance) {
            revert InsufficientBalance(amount, balance);
        }
        
        deposits[msg.sender] -= amount;
        payable(msg.sender).transfer(amount);
        
        emit Withdrawal(msg.sender, amount, block.timestamp);
    }
    
    function emergencyWithdraw() public onlyOwner {
        uint contractBalance = address(this).balance;
        payable(owner).transfer(contractBalance);
    }
    
    // Error handling examples: require, assert, revert
    function demonstrateErrorHandling(uint value) public pure returns (uint) {
        // require: input validation and condition checks
        require(value > 0, "Value must be positive");
        require(value <= 1000, "Value too large");
        
        uint result = value * 2;
        
        // assert: internal errors, invariant checks
        assert(result >= value);  // Overflow check
        
        // revert: explicit error with conditions
        if (result > 1500) {
            revert("Result exceeds maximum allowed");
        }
        
        return result;
    }
    
    // try-catch example (external contract calls)
    function safeExternalCall(address target, bytes calldata data) 
        public 
        returns (bool success, bytes memory result) 
    {
        try this.externalCall(target, data) returns (bytes memory returnData) {
            return (true, returnData);
        } catch Error(string memory reason) {
            // revert with reason string
            return (false, bytes(reason));
        } catch Panic(uint errorCode) {
            // panic error (e.g., division by zero, array access out of bounds)
            return (false, abi.encodePacked("Panic: ", errorCode));
        } catch (bytes memory lowLevelData) {
            // other errors
            return (false, lowLevelData);
        }
    }
    
    function externalCall(address target, bytes calldata data) 
        external 
        returns (bytes memory) 
    {
        (bool success, bytes memory result) = target.call(data);
        require(success, "External call failed");
        return result;
    }
}

Inheritance and Interfaces

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

// Abstract contract
abstract contract Animal {
    string public name;
    
    constructor(string memory _name) {
        name = _name;
    }
    
    function makeSound() public virtual returns (string memory);
    
    function introduce() public view returns (string memory) {
        return string(abi.encodePacked("I am ", name));
    }
}

// Interfaces
interface IFlyable {
    function fly() external returns (string memory);
    function getAltitude() external view returns (uint);
}

interface ISwimmable {
    function swim() external returns (string memory);
    function getDivingDepth() external view returns (uint);
}

// Inheritance and interface implementation
contract Dog is Animal {
    string public breed;
    
    constructor(string memory _name, string memory _breed) 
        Animal(_name) 
    {
        breed = _breed;
    }
    
    function makeSound() public pure override returns (string memory) {
        return "Woof!";
    }
    
    function fetch() public view returns (string memory) {
        return string(abi.encodePacked(name, " fetched the ball!"));
    }
}

contract Duck is Animal, IFlyable, ISwimmable {
    uint private altitude;
    uint private divingDepth;
    
    constructor(string memory _name) Animal(_name) {
        altitude = 0;
        divingDepth = 0;
    }
    
    function makeSound() public pure override returns (string memory) {
        return "Quack!";
    }
    
    function fly() public override returns (string memory) {
        altitude = 100;
        return string(abi.encodePacked(name, " is flying at ", toString(altitude), "m"));
    }
    
    function swim() public override returns (string memory) {
        divingDepth = 5;
        return string(abi.encodePacked(name, " is swimming at ", toString(divingDepth), "m depth"));
    }
    
    function getAltitude() public view override returns (uint) {
        return altitude;
    }
    
    function getDivingDepth() public view override returns (uint) {
        return divingDepth;
    }
    
    // Utility function
    function toString(uint value) internal pure returns (string memory) {
        if (value == 0) {
            return "0";
        }
        uint temp = value;
        uint digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}

// Library
library StringUtils {
    function concat(string memory a, string memory b) 
        internal 
        pure 
        returns (string memory) 
    {
        return string(abi.encodePacked(a, b));
    }
    
    function length(string memory str) internal pure returns (uint) {
        return bytes(str).length;
    }
}

// Contract using library
contract Zoo {
    using StringUtils for string;
    
    Animal[] public animals;
    
    function addAnimal(Animal animal) public {
        animals.push(animal);
    }
    
    function getAnimalInfo(uint index) public view returns (string memory) {
        require(index < animals.length, "Animal not found");
        Animal animal = animals[index];
        return animal.name().concat(" says ").concat(animal.makeSound());
    }
}

DeFi Application Example

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

// ERC20 Token Interface
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
    
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

// Simple Staking Contract
contract SimpleStaking {
    IERC20 public stakingToken;
    IERC20 public rewardToken;
    
    address public owner;
    uint public rewardRate; // Reward rate per second
    uint public lastUpdateTime;
    uint public rewardPerTokenStored;
    
    mapping(address => uint) public userRewardPerTokenPaid;
    mapping(address => uint) public rewards;
    mapping(address => uint) public balances;
    
    uint private _totalSupply;
    
    event Staked(address indexed user, uint amount);
    event Withdrawn(address indexed user, uint amount);
    event RewardPaid(address indexed user, uint reward);
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    modifier updateReward(address account) {
        rewardPerTokenStored = rewardPerToken();
        lastUpdateTime = block.timestamp;
        
        if (account != address(0)) {
            rewards[account] = earned(account);
            userRewardPerTokenPaid[account] = rewardPerTokenStored;
        }
        _;
    }
    
    constructor(address _stakingToken, address _rewardToken, uint _rewardRate) {
        owner = msg.sender;
        stakingToken = IERC20(_stakingToken);
        rewardToken = IERC20(_rewardToken);
        rewardRate = _rewardRate;
        lastUpdateTime = block.timestamp;
    }
    
    function rewardPerToken() public view returns (uint) {
        if (_totalSupply == 0) {
            return rewardPerTokenStored;
        }
        return rewardPerTokenStored + 
            ((block.timestamp - lastUpdateTime) * rewardRate * 1e18) / _totalSupply;
    }
    
    function earned(address account) public view returns (uint) {
        return (balances[account] * 
            (rewardPerToken() - userRewardPerTokenPaid[account])) / 1e18 + 
            rewards[account];
    }
    
    function stake(uint amount) external updateReward(msg.sender) {
        require(amount > 0, "Cannot stake 0");
        _totalSupply += amount;
        balances[msg.sender] += amount;
        stakingToken.transferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount);
    }
    
    function withdraw(uint amount) public updateReward(msg.sender) {
        require(amount > 0, "Cannot withdraw 0");
        require(balances[msg.sender] >= amount, "Insufficient balance");
        _totalSupply -= amount;
        balances[msg.sender] -= amount;
        stakingToken.transfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }
    
    function getReward() public updateReward(msg.sender) {
        uint reward = rewards[msg.sender];
        if (reward > 0) {
            rewards[msg.sender] = 0;
            rewardToken.transfer(msg.sender, reward);
            emit RewardPaid(msg.sender, reward);
        }
    }
    
    function exit() external {
        withdraw(balances[msg.sender]);
        getReward();
    }
    
    function setRewardRate(uint _rewardRate) external onlyOwner updateReward(address(0)) {
        rewardRate = _rewardRate;
    }
    
    function totalSupply() external view returns (uint) {
        return _totalSupply;
    }
    
    function balanceOf(address account) external view returns (uint) {
        return balances[account];
    }
}

Special Features

Gas Optimization Techniques

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

contract GasOptimization {
    // Packing: struct field order saves gas
    struct OptimizedStruct {
        uint128 value1;  // 16 bytes
        uint128 value2;  // 16 bytes - fits in same slot
        address addr;    // 20 bytes
        uint96 value3;   // 12 bytes - fits with addr in same slot
    }
    
    struct UnoptimizedStruct {
        uint256 value1;  // 32 bytes - 1 slot
        address addr;    // 20 bytes - 1 slot (12 bytes wasted)
        uint256 value2;  // 32 bytes - 1 slot
    }
    
    // Array processing optimization
    uint[] public numbers;
    
    function inefficientLoop() public view returns (uint sum) {
        for (uint i = 0; i < numbers.length; i++) {  // reads length every iteration
            sum += numbers[i];
        }
    }
    
    function efficientLoop() public view returns (uint sum) {
        uint length = numbers.length;  // read length once
        for (uint i = 0; i < length; i++) {
            sum += numbers[i];
        }
    }
    
    // unchecked block to disable overflow checks
    function efficientMath(uint a, uint b) public pure returns (uint) {
        unchecked {
            return a + b;  // no overflow check (use only when appropriate)
        }
    }
    
    // Using constants and immutable
    uint256 public constant RATE = 100;  // compile-time constant
    uint256 public immutable DEPLOYED_TIMESTAMP;  // set at deployment
    
    constructor() {
        DEPLOYED_TIMESTAMP = block.timestamp;
    }
    
    // pure/view functions for bytecode optimization
    function calculateFee(uint amount) public pure returns (uint) {
        return (amount * RATE) / 10000;
    }
}

Upgradeable Contracts

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

// Proxy pattern (simplified)
contract Proxy {
    address public implementation;
    address public admin;
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }
    
    constructor(address _implementation) {
        implementation = _implementation;
        admin = msg.sender;
    }
    
    function upgrade(address newImplementation) external onlyAdmin {
        implementation = newImplementation;
    }
    
    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
}

// Implementation contract V1
contract ImplementationV1 {
    uint256 public value;
    
    function setValue(uint256 _value) external {
        value = _value;
    }
    
    function getValue() external view returns (uint256) {
        return value;
    }
}

// Implementation contract V2 (with new features)
contract ImplementationV2 {
    uint256 public value;
    uint256 public multiplier;  // new state variable
    
    function setValue(uint256 _value) external {
        value = _value;
    }
    
    function getValue() external view returns (uint256) {
        return value * (multiplier == 0 ? 1 : multiplier);
    }
    
    function setMultiplier(uint256 _multiplier) external {  // new function
        multiplier = _multiplier;
    }
}

Security Patterns

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

contract SecurityPatterns {
    mapping(address => uint) public balances;
    bool private locked;
    
    event Withdrawal(address indexed user, uint amount);
    
    // Reentrancy protection
    modifier nonReentrant() {
        require(!locked, "Reentrant call");
        locked = true;
        _;
        locked = false;
    }
    
    // Pull Over Push pattern
    function withdraw() external nonReentrant {
        uint amount = balances[msg.sender];
        require(amount > 0, "No balance to withdraw");
        
        // Update state first (Check-Effects-Interactions pattern)
        balances[msg.sender] = 0;
        
        // External call last
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "Transfer failed");
        
        emit Withdrawal(msg.sender, amount);
    }
    
    // Time lock
    mapping(address => uint) public unlockTime;
    
    function lockFunds(uint _unlockTime) external payable {
        require(_unlockTime > block.timestamp, "Unlock time must be in future");
        balances[msg.sender] += msg.value;
        unlockTime[msg.sender] = _unlockTime;
    }
    
    function withdrawLocked() external {
        require(block.timestamp >= unlockTime[msg.sender], "Funds still locked");
        require(balances[msg.sender] > 0, "No balance");
        
        uint amount = balances[msg.sender];
        balances[msg.sender] = 0;
        unlockTime[msg.sender] = 0;
        
        payable(msg.sender).transfer(amount);
    }
    
    // Oracle problem mitigation example
    mapping(address => bool) public trustedOracles;
    mapping(bytes32 => uint) public prices;
    
    modifier onlyTrustedOracle() {
        require(trustedOracles[msg.sender], "Not trusted oracle");
        _;
    }
    
    function updatePrice(bytes32 asset, uint price) external onlyTrustedOracle {
        prices[asset] = price;
    }
}

Versions

Version Status Key Features Release Year
Solidity 0.8.x Latest Overflow protection, custom errors 2021
Solidity 0.7.x Maintenance Inline assembly improvements 2020
Solidity 0.6.x Legacy Inheritance linearization improvements 2019
Solidity 0.5.x Legacy ABI encoder v2 2018

Reference Pages

Official Documentation

Learning Resources

Development Tools