Building with Solidity

Basic Contract Walkthrough

This lesson demonstrates a complete smart contract example - a workshop attendance tracker that showcases core Solidity concepts including structs, mappings, events, access control modifiers, and constructor functions in a practical, real-world context.

Contract Overview

We’ll use a simple participation tracking contract, ideal for workshops or attendance-based dApps. It allows users to check in with a name, stores the record, and emits an event.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract WorkshopContract {
    address private owner;
    struct Participant {
        string name;
        uint256 timestamp;
    }
    mapping(address => Participant) public participants;
    event ParticipantCheckedIn(address indexed user, string name, uint256 timestamp);
    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this");
        _;
    }
    constructor() {
        owner = msg.sender;
    }
    function checkIn(string memory name) external {
        require(bytes(name).length > 0, "Name is required");
        require(participants[msg.sender].timestamp == 0, "Already checked in");
        participants[msg.sender] = Participant(name, block.timestamp);
        emit ParticipantCheckedIn(msg.sender, name, block.timestamp);
    }
    function getParticipant(address user) external view returns (string memory, uint256) {
        Participant memory p = participants[user];
        return (p.name, p.timestamp);
    }
    function getOwner() external view returns (address) {
        return owner;
    }
}

Concept Recap: WorkshopContract

Concept

What It Does

struct

Groups related data into a single type. In this case, Participant holds a name and timestamp.

mapping

Stores key-value pairs, in this case, address => Participant.

event

Emits onchain logs. ParticipantCheckedIn lets the frontend or explorer track activity.

modifier

Restricts function access. onlyOwner ensures that only the contract creator can call certain functions.

constructor

Runs once at deployment. Sets the contract owner using msg.sender.

require()

Enforces rules like ensuring a name is provided and preventing duplicate check-ins.

block.timestamp

Global variable that records the current time. Used to log when someone checks in.

msg.sender

Global variable that identifies who is interacting with the contract.

Contract Ownership and Constructors

In Solidity, the constructor function runs once when the contract is deployed. It's often used to assign the contract owner, who can then be given special privileges using modifiers.

address private contractOwner;
constructor() {
  contractOwner = msg.sender;
}

The msg.sender global variable represents the address that deployed the contract. By saving it, we can restrict certain functions to only this address later.

Access Control with Modifiers

A modifier is reusable logic that runs before a function executes. Common use: access restriction.

modifier onlyOwner() {
  if (msg.sender != contractOwner) {
    revert UnauthorizedAccess(msg.sender);
  }
  _;
}

The _ keyword is a placeholder for the function body. Any function using onlyOwner will first check the condition above.

Usage:

function addUser(...) public onlyOwner {
  ...
}