% % % % %
% % % % % % % % % % % % % % % % % % % %
BitBlog Logo

Smart Contracts: How Code Runs on Chain

BitBlog
BitBlog
· October 31, 2025 · ⏱ 9 min
Smart Contracts: How Code Runs on Chain

Smart contracts are programs that run on blockchains like Ethereum. Once deployed, their code executes exactly as written — transparently, predictably, and without centralized servers. They power DeFi, NFTs, DAOs, and countless on-chain applications.


🧠 What Is a Smart Contract?

A smart contract is a set of functions and state (data) stored at a blockchain address. Users and other contracts interact with it by sending transactions that call those functions. Because the rules are enforced by the network, outcomes are trust-minimized and verifiable.

In short:

Smart contracts are autonomous programs that execute on-chain — no backend, no admin buttons (unless you code them in).

🚀 From Source Code to On-Chain Contract

  1. Write code (e.g., in Solidity for the EVM).
  2. Compile to bytecode + generate an ABI (interface for function calls/events).
  3. Deploy by sending a transaction with the bytecode (optionally via a factory contract).
  4. Verify the source on a block explorer (Etherscan) so others can read the code.
Term Meaning
Address Where the contract lives on-chain; users send calls/ETH to this address.
ABI Application Binary Interface — defines callable functions and event signatures.
Bytecode Compiled code executed by the EVM.
Events Logs emitted by contracts; used by UIs and indexers to track activity.

🕹️ How Users Interact

Users (or dApps) send a transaction calling a function with parameters encoded using the ABI. The EVM executes the function, updates state, and emits events. If a call fails (e.g., a require condition), the transaction reverts and state changes are discarded.

Common interaction paths:

  • 🧩 Wallet → dApp UI → Contract (most common)
  • 📜 Direct calls via explorer “Write/Read” tabs
  • 🤖 Contracts calling contracts (composability)

👷 A Minimal Solidity Example

pragma solidity ^0.8.20;

contract SimpleVault { address public owner; mapping(address => uint256) public balances;

event Deposited(address indexed user, uint256 amount);
event Withdrawn(address indexed user, uint256 amount);

constructor() { owner = msg.sender; }

function deposit() external payable {
    balances[msg.sender] += msg.value;
    emit Deposited(msg.sender, msg.value);
}

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient");
    balances[msg.sender] -= amount;
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "Send failed");
    emit Withdrawn(msg.sender, amount);
}

}

Note: This simplistic example is not production-ready; see the risks section for safer patterns.


🏗️ Common Design Patterns

🔑 Ownable (Access Control)

Restrict sensitive functions (e.g., pausing, upgrading, changing parameters) to an admin role.

  • Use libraries like OpenZeppelin’s Ownable / AccessControl.
  • Consider multisig (e.g., Gnosis Safe) or DAO-controlled ownership to reduce key risk.

🧱 Upgradeability

On-chain code is immutable, so teams use upgrade patterns to fix bugs or add features while keeping state and address stable.

  • Transparent Proxy — Admin-only upgrades; users interact with the proxy.
  • UUPS — Upgrade logic lives in the implementation; proxy is minimal.
  • Beacon — Many proxies share one upgradeable implementation via a beacon.

Governance & audits are essential — upgrades introduce trust assumptions and potential attack surface.

🪞 Proxies

A proxy holds state and forwards calls to a logic contract using delegatecall. This preserves the caller context and storage layout at the proxy address.

Proxy Benefit Considerations
Upgradeable logic without changing address Storage layout must remain compatible across versions
Fix issues post-deployment Admin keys/governance become critical security points
Gas savings via shared implementations (beacon) Complexity increases audit surface

⚠️ Risks & How to Mitigate

  • Bugs & Logic Errors: Off-by-one, missing checks, incorrect math.
    Mitigate: Use battle-tested libraries (OpenZeppelin), unit tests, fuzzing, formal verification when critical.
  • Reentrancy: An external call re-enters before state is updated, draining funds.
    Mitigate: Follow Checks-Effects-Interactions, use ReentrancyGuard, minimize external calls, prefer call patterns carefully.
  • Access Control Misconfig: Unprotected admin functions or incorrect role assignments.
    Mitigate: Explicit modifiers (onlyOwner/onlyRole), multisig ownership, timelocks for high-impact changes.
  • Upgrade Risks: Malicious or broken implementation, storage collisions.
    Mitigate: Timelocked upgrades, audits on each release, storage gap patterns, immutable core where possible.
  • Oracle & Price Manipulation: Relying on manipulable on-chain prices.
    Mitigate: Use robust oracles (Chainlink), TWAPs, sanity checks.

🧪 Audit Basics

Audits don’t guarantee safety, but they significantly reduce risk.

  1. Threat Modeling: Define assets at risk and adversary capabilities.
  2. Testing: Unit tests, integration tests, fuzzing (property-based), differential testing.
  3. Static & Dynamic Analysis: Slither, Echidna, Foundry fuzzing.
  4. Code Review & Best Practices: Use established patterns, minimize complexity, fail safely.
  5. Operational Security: Multisig admins, key rotation, deployment scripts, emergency pause (“circuit breaker”).

🧠 Key Takeaways

  • Smart contracts are immutable programs that manage value and rules on-chain.
  • Users interact via transactions calling ABI-defined functions.
  • Common patterns: Ownable access control, Upgradeable architectures, and Proxy design.
  • Main risks include reentrancy, access misconfig, and upgrade errors — mitigate with audits, tests, and governance.

Written by BitBlog — explaining how on-chain code powers the open economy.

#smart contracts#solidity#patterns#security#ethereum

Comments

Log in to leave a comment.