Web 3.0: Exploring Smart Contracts with Solidity

blockchain dev

Web3, blockchain, smart contracts—the buzzwords are everywhere. As a developer, I wanted to understand what’s actually there. Here’s what I learned exploring Solidity.

What Are Smart Contracts?

Smart contracts are programs stored on a blockchain:

// A simple smart contract
contract SimpleStorage {
    uint256 storedData;
    
    function set(uint256 x) public {
        storedData = x;
    }
    
    function get() public view returns (uint256) {
        return storedData;
    }
}

Key properties:

Solidity Basics

Solidity is the primary language for Ethereum smart contracts:

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

contract Counter {
    uint256 private count;
    address public owner;
    
    event CountChanged(uint256 newCount);
    
    constructor() {
        owner = msg.sender;
        count = 0;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    function increment() public {
        count += 1;
        emit CountChanged(count);
    }
    
    function getCount() public view returns (uint256) {
        return count;
    }
}

Key Concepts

State Variables: Permanently stored on blockchain

uint256 public balance;
mapping(address => uint256) public balances;

Functions: Contract methods

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

Events: Logs for off-chain consumption

event Transfer(address indexed from, address indexed to, uint256 amount);
emit Transfer(msg.sender, to, amount);

Modifiers: Reusable condition checks

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

Development Environment

Hardhat Setup

npm init -y
npm install --save-dev hardhat
npx hardhat
// hardhat.config.js
module.exports = {
  solidity: "0.8.17",
  networks: {
    hardhat: {},
    goerli: {
      url: process.env.GOERLI_URL,
      accounts: [process.env.PRIVATE_KEY]
    }
  }
};

Testing

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

describe("Counter", function () {
  it("Should increment count", async function () {
    const Counter = await ethers.getContractFactory("Counter");
    const counter = await Counter.deploy();
    await counter.deployed();
    
    await counter.increment();
    expect(await counter.getCount()).to.equal(1);
  });
});

A Simple Token

ERC-20 token implementation:

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

contract SimpleToken {
    string public name = "Simple Token";
    string public symbol = "SIMP";
    uint8 public decimals = 18;
    uint256 public totalSupply;
    
    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;
    
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    
    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply * 10 ** decimals;
        balanceOf[msg.sender] = totalSupply;
    }
    
    function transfer(address to, uint256 value) public returns (bool) {
        require(balanceOf[msg.sender] >= value, "Insufficient balance");
        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;
        emit Transfer(msg.sender, to, value);
        return true;
    }
    
    function approve(address spender, uint256 value) public returns (bool) {
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }
}

Security Considerations

Reentrancy Attack

The most famous vulnerability:

// VULNERABLE
function withdraw() public {
    uint256 amount = balances[msg.sender];
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
    balances[msg.sender] = 0;  // Updated AFTER external call
}

// SAFE
function withdrawSafe() public {
    uint256 amount = balances[msg.sender];
    balances[msg.sender] = 0;  // Updated BEFORE external call
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success);
}

Integer Overflow

Solidity 0.8+ has built-in overflow protection:

// Pre-0.8: Would wrap around
uint8 x = 255;
x += 1;  // Now reverts instead of becoming 0

Access Control

// Use OpenZeppelin's battle-tested contracts
import "@openzeppelin/contracts/access/Ownable.sol";

contract MyContract is Ownable {
    function sensitiveAction() public onlyOwner {
        // Only owner can call
    }
}

Gas Costs

Every operation costs gas:

OperationGas Cost
Store value~20,000
Read value~200
Transfer ETH21,000
Deploy contract100,000+

Gas optimization matters:

// Expensive: Multiple storage writes
function inefficient(uint256[] memory values) public {
    for (uint i = 0; i < values.length; i++) {
        total += values[i];  // Storage write each iteration
    }
}

// Better: Single storage write
function efficient(uint256[] memory values) public {
    uint256 sum = 0;
    for (uint i = 0; i < values.length; i++) {
        sum += values[i];  // Memory only
    }
    total = sum;  // One storage write
}

My Honest Assessment

What’s Interesting

What’s Concerning

Use Cases That Make Sense

Use Cases That Don’t (Yet)

Final Thoughts

Smart contracts are interesting technology with real limitations. The developer experience is improving, but the constraints are fundamental.

Learn Solidity to understand the space. Deploy with caution.


Code is law, until you find a bug.

All posts