§ 3.2Module 3

Solidity Programming — Basics

3.2 — Solidity Programming: Basics

Recall first. From 3.1, a contract has state, functions, conditions, and events. If a contract must store a list of candidates and remember whether an address has voted, which two collection types would you choose? Commit to an answer before reading: array, mapping, or both.

Solidity’s mental model

Solidity is a statically typed language for contracts executed by the Ethereum Virtual Machine (EVM). A Solidity file defines one or more contracts. A contract resembles a class, but its state can be stored on a blockchain and its state-changing functions are invoked through transactions.1

The book extract teaches Solidity in the old 0.5.x family. The examples below intentionally use Solidity 0.5.1 syntax where practical. A modern compiler may reject old syntax, and a newer syntax may obscure the examination concepts. Always match the compiler version in Remix to the pragma line.1

Basic contract structure

pragma solidity ^0.5.1;

contract Counter {
    uint public count;

    constructor() public {
        count = 0;
    }

    function increment() public {
        count = count + 1;
    }

    function read() public view returns (uint) {
        return count;
    }
}

State variables and common types

State variables live in contract storage. Solidity is strongly typed, so the declaration determines what values and operations are valid. Common types in the book include unsigned integers, strings, booleans, addresses, arrays, structs, and mappings.1

TypeMeaningTypical use
uint / uint256Non-negative integercounts, balances, indexes
intSigned integervalues that may be negative
booltrue or falseflags such as hasVoted
stringTextnames or messages
addressEthereum addressowner, caller, recipient
bytesByte sequencecompact binary data
enumUser-defined finite choicesstatus values

address, address payable, bytes, and enum — source gap

The book covers address and separately explains the payable function qualifier, but it does not give a reliable, focused treatment of modern address payable conversions. It also does not develop bytes or enum as syllabus-level examples in the supplied extract. These are syllabus-aligned supplements, not claims about book coverage:

address voter;          // identifies an account
address payable wallet; // address intended to receive ether
bytes32 digest;         // fixed-size byte sequence
bytes data;             // dynamic byte sequence
enum Phase { Draft, Open, Closed }

Use address payable only where an ether transfer is intended, and use an enum when a variable must occupy one named state from a finite set. Exact syntax and conversion rules depend on compiler version; for an exam answer, define the purpose and distinguish the types rather than mixing versions.1

Functions, visibility, and activity qualifiers

A function reads or changes contract state. The book distinguishes four function-visibility levels:1

VisibilityCallable fromExam memory hook
privateSame contract onlymost restrictive
internalSame contract and derived contractsinheritance allowed
externalOutside calls; not ordinary internal callsexternal interface
publicOutside and inside the contractmost permissive

Use the least permissive visibility that satisfies the design. Making every function public unnecessarily increases the exposed interface.

Activity/state qualifiers describe what a function may do:

QualifierMeaning
viewmay read state but must not modify it
puremay compute without reading or modifying contract state
payablemay receive ether with the call
noneordinary function; may change state if called by transaction

A state-changing function call becomes a transaction when sent from outside the chain. A view call can normally be performed locally without a state-changing transaction. payable is an ability to receive ether, not a guarantee that ether was sent.1

The message context: msg.sender

Every call has message metadata. msg.sender is the address that initiated the current call. A constructor commonly stores it as the deployer or owner:

address public owner;

constructor() public {
    owner = msg.sender;
}

Do not confuse msg.sender with the contract address. The former is the caller; the latter is the deployed location of the contract.1

Modifiers and require

A modifier is reusable logic placed before a function body. The book uses an owner check and then presents require as a readable conditional failure mechanism:1

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

function changeSomething() public onlyOwner {
    // runs only after the modifier check passes
}

The _ placeholder means “insert the protected function body here.” If the condition is false, execution stops and the state change is reverted. A modifier centralises a rule instead of repeating it across every protected function.

Events

An event writes a log entry as part of a transaction. External applications can listen for events and update their interfaces. Events are not a replacement for state: a contract should store data needed for later contract logic, while events communicate useful history to off-chain consumers.1

event ValueChanged(address indexed who, uint oldValue, uint newValue);

function setValue(uint next) public {
    uint previous = value;
    value = next;
    emit ValueChanged(msg.sender, previous, next);
}

indexed allows a client to filter efficiently by that field. The event is emitted only if the transaction succeeds.

Arrays

An array is an ordered collection of values of one type. Solidity uses zero-based indexes.1

Dynamic and fixed-size arrays

uint[] public scores;       // dynamic storage array
uint[3] public topScores;   // fixed-size array

function addScore(uint score) public {
    scores.push(score);
}

function removeLastScore() public {
    require(scores.length > 0, "empty");
    scores.pop();
}

Special arrays: bytes and string

The syllabus names bytes and string as special arrays. Treat string as text and bytes as raw byte data. The supplied book extract mentions strings in examples and summaries but does not provide a full bytes-focused treatment; do not invent a book example. For exam purposes, state the distinction and note that byte-level operations are compiler/version-sensitive.

Structs

A struct defines a custom record containing related fields:

struct Candidate {
    string name;
    uint voteCount;
}

Candidate public first;

A struct makes related values travel together. It does not automatically create application rules; functions and checks still determine how records may change.1

Mappings

A mapping associates a key with a value:

mapping(address => bool) public hasVoted;
mapping(address => uint) public ticketsBought;

Conceptually, hasVoted[addressA] answers whether addressA has voted. A mapping is useful for direct key lookup, but it is not an iterable list of keys. If the application must enumerate all candidates or members, keep a separate array of keys/records.

The book uses an address-to-integer mapping to record how many tickets each purchaser bought. Public mappings expose generated getter access for a supplied key; they do not automatically expose every key.1

Inheritance and interfaces

A contract can inherit behaviour from another contract:

contract Base {
    function label() public pure returns (string memory) {
        return "base";
    }
}

contract Child is Base {
    // inherits label()
}

Inheritance reuses or extends contract functionality. An interface specifies callable function signatures without their implementation; a contract implementing it must supply those functions. The book also describes abstract functions and libraries for reusable code.1

Use inheritance when the “is-a” relationship and shared interface are real. Do not add it merely to avoid a few repeated lines.

Error handling

The book covers revert, require, and assert:1

MechanismUse
require(condition, message)reject invalid input, permissions, or business preconditions
revert(message)explicitly abort a branch
assert(condition)detect an internal invariant that should never fail

When an error stops execution, later instructions do not run and the state changes from that call are reverted. Gas may still be consumed according to the execution and compiler/runtime rules. Old throw syntax is deprecated in the book’s own discussion; prefer the version-compatible mechanism required by the compiler.1

Rule of thumb: use require for caller-controlled conditions, revert for explicit conditional branches, and assert only for impossible internal failures—not ordinary user input.

Worked example — choosing collections

A voting contract needs to:

The smallest suitable design is:

struct Candidate { string name; uint votes; }
Candidate[] public candidates;
mapping(address => bool) public hasVoted;

The array answers “which candidates exist, and in what order?” The struct keeps each name and count together. The mapping answers “has this address voted?” A mapping alone cannot enumerate candidates; an array alone would make the per-address check awkward.

Exercise

  1. Match each declaration to its best use: uint[], mapping(address => bool), struct, address, enum.
  2. Which qualifier belongs on each function?
    • (a) read a stored counter only;
    • (b) calculate 2 + 2 without state;
    • (c) accept ether;
    • (d) update a stored counter.
  3. Why does a fixed-size array not behave like a dynamic array with push?
  4. Write a modifier condition that allows only owner to call adminAction().
  5. Why should assert not be the normal check for an invalid user input?
Revealed answers
  1. uint[]: ordered numeric collection; mapping(address => bool): direct per-address flag; struct: related fields as one record; address: account identifier; enum: one value from a finite named set.
  2. (a) view; (b) pure; (c) payable; (d) no read-only qualifier—an ordinary state-changing function.
  3. Its length is fixed at declaration, so dynamic growth is not available; assign by a valid index instead.
  4. modifier onlyOwner() { require(msg.sender == owner, "owner only"); _; } and then function adminAction() public onlyOwner { ... }.
  5. Invalid input is an expected caller-controlled condition. require communicates that precondition; assert is for an internal invariant or unexpected programming failure.

Exam lens

For a Solidity-basics answer, organise the response by data, behaviour, access, and failure:

  1. Data: state variables, arrays, structs, mappings.
  2. Behaviour: functions and constructors.
  3. Access: function visibility, variable visibility, modifiers, msg.sender.
  4. Activity: view, pure, payable.
  5. Observability: events.
  6. Failure: require, revert, assert, and state reversion.

High-value distinctions:

Rapid revision

Key takeaways

Sources

Footnotes

  1. Blockchain Technology (converted book extract), Ch. 14, §§14.1–14.17, “Blockchain Ethereum Platform using Solidity,” Markdown lines 8367–8811: contract structure, variables, constructors, addresses, modifiers, events, arrays, visibility, view/pure, mappings, inheritance, libraries, and error handling. 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18