Writing Your First Smart Contract

Smart Contracts, DeFi & Web3 Security
Course 2 · Chapter 1 · Writing Your First Smart Contract: Solidity Basics

Blockchain & Web3 Fundamentals (Course 1) got you as far as knowing what a Contract Account is (Chapter 6) and roughly what the EVM, Solidity, and gas are for. This course builds directly on that foundation and gets hands-on: real Solidity syntax, a real worked contract, and exactly what happens on-chain when you deploy one.

Solidity's Real Origin

Solidity was proposed by Gavin Wood (one of Ethereum's own founding team members, per Course 1 Chapter 6) in August 2014, and subsequently developed further by Christian Reitwiessner, Alex Beregszaszi, and several other Ethereum core contributors. It's a statically-typed, contract-oriented language, purpose-built for writing code that runs on the EVM — compiling down to the actual bytecode the EVM executes, exactly as Course 1 Chapter 6 described. Its syntax deliberately draws on JavaScript, C++, and Python, making it comparatively approachable for developers coming from any of those backgrounds, while still enforcing static typing that plain JavaScript doesn't have.

The Anatomy of a Contract

Here's a real, complete, minimal Solidity contract — a simple counter that anyone can increment, with its current value publicly readable by anyone:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Counter { uint public count; constructor() { count = 0; } function increment() public { count += 1; } function getCount() public view returns (uint) { return count; } }

Breaking down each real piece:

  • pragma solidity ^0.8.0; — declares which compiler version(s) this contract is written for. The ^ means "this version or any later compatible 0.8.x version" — a real safeguard against a future compiler version silently changing how the code behaves.
  • contract Counter { ... } — the contract keyword defines the contract's own scope, directly comparable to a class definition in an object-oriented language.
  • uint public count; — a state variable. Unlike a local variable inside a function, a state variable's value is permanently stored as part of the Contract Account's own on-chain data (Course 1 Chapter 6), persisting between separate transactions.
  • constructor() { ... } — code that runs exactly once, at the moment the contract is deployed, and never again afterward.
  • function increment() public { ... } — a function anyone can call to change the contract's own state (here, increasing count by one).

Function Visibility

Solidity has four real visibility keywords, controlling exactly who can call a given function:

KeywordWho Can Call It
publicAnyone — other contracts, external accounts, and code inside this same contract
externalOnly from outside the contract — not callable directly from other functions inside the same contract
internalOnly from inside this contract, or a contract that inherits from it
privateOnly from inside this exact contract — not even inheriting contracts can call it
"Private" Doesn't Mean "Secret" This is a genuinely important, easy-to-misunderstand distinction: private only restricts which other Solidity code can directly call a function or read a variable. It does nothing to hide the underlying data itself — every transaction, every state variable's own value, is stored on a public, replicated blockchain (Course 1, Chapters 1 and 3) and can be read directly off the chain by anyone with the right tools, regardless of what visibility keyword Solidity uses. "Private" is an access-control concept for other contract code, not a real confidentiality guarantee.

view and pure: Promising Not to Change (or Even Touch) State

Two further real modifiers describe a function's relationship to the contract's own state:

  • view — the function can read state variables, but promises not to modify any of them. getCount() above is a real example.
  • pure — the function promises not to even read state variables at all; it only operates on whatever arguments it's given.

This distinction has a genuine, practical payoff: because a view or pure call doesn't change anything on-chain, it can typically be executed locally by a node without needing to be broadcast as a real, gas-costing transaction at all — you can call getCount() for free to simply read the current value, while calling increment() genuinely requires a real, signed, gas-paying transaction, since it actually changes the contract's own permanent state.

The address Type and msg.sender

Solidity has a dedicated address type representing an Ethereum account — either an Externally Owned Account or a Contract Account (Course 1, Chapter 6). Inside any function, the built-in variable msg.sender always holds the address that actually called the currently-executing function — the real mechanism that lets a contract know who is interacting with it, since a Contract Account has no private key of its own to check a signature against directly the way an EOA does.

Other Common Basic Types Beyond uint, address, and the boolean bool, Solidity's mapping type is especially common — a key-value store, most often written as mapping(address => uint), letting a contract associate a value (like a token balance) with each address. Chapter 2's own gas coverage will show exactly why reading and writing to a mapping's own storage has a real, measurable cost.

Deploying a Contract: Creating a Real Contract Account

Writing Solidity code doesn't put it on-chain by itself. Deploying a contract means compiling the source code down to real EVM bytecode, then sending a special transaction (signed with a real private key, exactly like any other transaction from Course 1 Chapter 2) whose destination is empty rather than an existing address. The network responds by creating a brand-new Contract Account — the exact account type Course 1 Chapter 6 introduced — at a new address, with the compiled bytecode permanently stored as that account's own code. From that point on, anyone can call the contract's own public functions by sending transactions to that new address.

Why This Ties Directly Back to Course 1 Deploying a contract isn't a separate, special blockchain operation — it's still just a signed transaction, broadcast, verified, and eventually mined or validated into a block, exactly like every other transaction Course 1 covered. The only genuinely new thing is what the transaction's own effect is: instead of moving value between two existing accounts, it creates a brand-new Contract Account and gives it its own permanent code.

Hands-On Exercises

Three exercises reinforcing Solidity's basic building blocks before Chapter 2 covers the EVM and gas in real depth.

Exercise 1
Using this chapter's own Counter contract as a model, describe (in plain English, no code required) what a decrement() function would need to do, and whether it should be marked view, pure, or neither. Justify your answer.
Exercise 2
A colleague says: "I marked this function private so nobody else can see the secret value it stores." Using this chapter's own warning about what private actually restricts, explain what's wrong with this reasoning.
Exercise 3
Explain, using this chapter's own deployment description and Course 1 Chapter 6's account model, exactly what kind of Ethereum account exists immediately before a contract is deployed at a given address, and what kind exists immediately afterward.

Quick Reference

  • Solidity — proposed by Gavin Wood in August 2014; statically-typed, compiles to EVM bytecode.
  • Contract anatomypragma (compiler version), contract (scope), state variables (persistent on-chain data), constructor (runs once, at deployment), functions.
  • Visibilitypublic, external, internal, private; private restricts other Solidity code, not on-chain data visibility.
  • view / pure — promise not to modify state / not to touch state at all; both can typically be called for free, without a real transaction.
  • address / msg.sender — the type representing an account; the built-in variable holding whoever called the current function.
  • Deployment — a real, signed transaction with no destination address, which creates a brand-new Contract Account holding the compiled bytecode.