Skip to content

Smart Contract Development with Solidity — Complete Guide

DodaTech 6 min read

This tutorial teaches you Solidity smart contract development — you will learn the language fundamentals, write production-ready contracts, and understand patterns for building secure decentralized applications on Ethereum.

Why It Matters

Smart contracts are the backbone of DeFi, NFTs, DAOs, and the entire Web3 ecosystem. Every decentralized application relies on deterministic, trust-minimized code that runs on the Ethereum Virtual Machine.

Real-World Use

A token vesting contract releases funds to team members on a schedule. A decentralized exchange matches buy and sell orders without a central order book. An NFT marketplace mints and trades unique digital assets — all powered by Solidity smart contracts.

flowchart LR
    A[Write Contract in Solidity] --> B[Compile to EVM Bytecode]
    B --> C[Deploy to Ethereum Network]
    C --> D[Users Interact via Transactions]
    D --> E[State Changes on Chain]
    E --> F[Events Emitted to Frontend]
    style A fill:#4CAF50,color:#fff
    style C fill:#EF7B4D,color:#fff
    style F fill:#2196F3,color:#fff

Solidity Language Basics

Solidity is a statically-typed, contract-oriented language designed for the Ethereum Virtual Machine. It supports inheritance, libraries, and user-defined types.

Data Types and State Variables

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

contract DataTypes {
    uint256 public count;
    address public owner;
    bool public active;
    string public name;
    mapping(address => uint256) public balances;

    constructor() {
        owner = msg.sender;
        count = 42;
        active = true;
        name = "MyContract";
    }

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

Expected behavior: deploying this contract sets count to 42, owner to the deployer address, active to true, and initializes an empty mapping. Calling getBalance returns zero for any address that has never been funded.

Functions and Modifiers

Functions can read or write state. Reading is free (view/pure), writing costs gas.

contract Counter {
    uint256 private value;

    event ValueChanged(uint256 newValue);

    function increment() public {
        value += 1;
        emit ValueChanged(value);
    }

    function set(uint256 _value) public {
        require(_value > value, "New value must be higher");
        value = _value;
        emit ValueChanged(value);
    }

    function get() public view returns (uint256) {
        return value;
    }
}

Expected interaction sequence: deploy the contract, call increment three times, call get — returns 3. Call set(10) — emits ValueChanged(10). Call set(5) — reverts with "New value must be higher".

Modifiers for Access Control

contract AccessControl {
    address public owner;
    bool public paused;

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }

    modifier whenNotPaused() {
        require(!paused, "Contract is paused");
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function pause() public onlyOwner {
        paused = true;
    }

    function sensitiveAction() public whenNotPaused onlyOwner {
        // critical logic here
    }
}

Expected behavior: only the deployer can call pause or sensitiveAction. If paused is true, sensitiveAction reverts even for the owner. Calling sensitiveAction from a non-owner address reverts with "Not owner".

Inheritance and Interfaces

Solidity supports multiple inheritance and abstract contracts. Interfaces define contract shapes for interoperability.

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

abstract contract BaseToken {
    string public name;
    string public symbol;

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    function mint(address to, uint256 amount) public virtual;
}

contract MyToken is BaseToken, IERC20 {
    mapping(address => uint256) private _balances;
    uint256 public totalSupply;

    constructor() BaseToken("MyToken", "MTK") {}

    function mint(address to, uint256 amount) public override {
        _balances[to] += amount;
        totalSupply += amount;
    }

    function transfer(address to, uint256 amount) external override returns (bool) {
        require(_balances[msg.sender] >= amount, "Insufficient balance");
        _balances[msg.sender] -= amount;
        _balances[to] += amount;
        return true;
    }

    function balanceOf(address account) external view override returns (uint256) {
        return _balances[account];
    }
}

Expected behavior: deploy MyTokenname returns "MyToken", symbol returns "MTK". Call mint(0x123..., 1000). Call balanceOf(0x123...) — returns 1000. Call transfer(0x456..., 500) from 0x123... — returns true, balances update accordingly.

Events and Logging

Events are the primary way smart contracts communicate with off-chain code. They are stored in Transaction logs and are cheaper than storage.

contract EventLogger {
    event Transfer(address indexed from, address indexed to, uint256 value);
    event ContractCreated(address indexed creator, uint256 timestamp);

    constructor() {
        emit ContractCreated(msg.sender, block.timestamp);
    }

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

Expected behavior: deploy the contract — a ContractCreated event appears in the Transaction receipt with the deployer address and block timestamp. Call transfer(0x789..., 500) — emits a Transfer event indexed by sender and receiver.

Common Errors

  1. Reentrancy — Calling external contracts before updating state allows attackers to re-enter and drain funds. Always follow the checks-effects-interactions pattern.
  2. Integer overflow/underflow — Solidity 0.8+ includes built-in overflow checks, but explicit unchecked blocks can reintroduce them.
  3. Uninitialized storage pointers — Local storage references can accidentally overwrite contract state variables.
  4. Incorrect visibility — Marking functions public when they should be internal exposes internal logic to attackers.
  5. Gas griefing — Loops over dynamic arrays can exceed block gas limits, making functions uncallable.

Practice Questions

  1. What is the difference between view and pure functions in Solidity? view functions promise not to modify state but can read it. pure functions promise neither to read nor modify state — they only compute from their parameters.

  2. Why should you use require instead of if statements for input validation? require automatically reverts the Transaction, refunds unused gas, and provides a reason string. An if statement without revert leaves the contract in an inconsistent state.

  3. What is the purpose of indexed keyword in event parameters? indexed parameters are stored in a special topic section of the Transaction log, making them searchable by off-chain code. Non-indexed parameters are stored in the data section and cannot be filtered.

Frequently Asked Questions

{{< faq question="How much does it cost to deploy a smart contract on Ethereum?">}} Deployment costs depend on contract bytecode size and gas price. A simple ERC-20 token costs approximately 200,000-500,000 gas. At 20 gwei gas price and $3,000 ETH, this equals $12-$30. Use Layer 2 networks like Arbitrum or Optimism to reduce costs by 10-100x. See the Layer 2 guide for details. {{< /faq >}}

{{< faq question="Can I upgrade a deployed smart contract?">}} Solidity contracts are immutable by default — once deployed, the code cannot change. Upgradeability requires a proxy pattern: a proxy contract delegates calls to an implementation contract, and the proxy's implementation address can be updated by an admin. Popular patterns include OpenZeppelin's UUPS and Transparent Proxy. Always test upgrade paths thoroughly because a failed upgrade can freeze all user funds. {{< /faq >}}

{{< faq question="What IDE should I use for Solidity development?">}} Remix IDE (remix.ethereum.org) is the fastest way to Prototype — it runs in the browser with a built-in compiler, debugger, and test network. For serious development, use Hardhat or Foundry with VS Code and the Solidity extension. These tools provide Type Checking, test frameworks, mainnet forking, and deployment scripts that Remix lacks.{{< /faq >}}

Next Steps

DeFi Explained — Understand decentralized finance protocols built on smart contracts.

Solidity Design Patterns — Learn production patterns for secure, gas-efficient contracts.

Smart Contract Security Audits — Protect your contracts from common vulnerabilities.

Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro