Smart Contracts: From Concept to Ethereum

Section VII: Ethereum Framework

Army Cyber Institute

June 3, 2026

Smart Contracts: Concept and Scope

  • In the last lesson we traced how transactions flow through the network. Now we look at what those transactions actually execute.

  • A smart contract is a program and its state at an address on the blockchain.

  • Its functions run automatically when a transaction or call reaches that contract.

  • Different from ‘legal contracts’: execution is automatic and deterministic, not interpretive.

  • We will briefly survey non-Ethereum smart contract models to situate the landscape.

From Bitcoin Script to Solidity

Feature Bitcoin Script Solidity (Ethereum)
Purpose Defines conditions to spend a specific UTXO. Defines programs that manage state and logic.
Language Type Stack-based, declarative, non–Turing complete. High-level, imperative, Turing complete.
State Model Stateless: no memory of prior executions. Stateful: contracts maintain persistent storage between calls.
Execution Context Runs only when spending an output. Invoked via transactions or calls; read/write storage, call contracts.
Control Flow No loops or recursion; limited branching. Full control flow (loops, branching, function calls, libraries).
Resource Control Bounded by design. Bounded by gas metering; execution stops when gas runs out.
Security Model Simplicity and determinism reduce attack surface. Expressiveness increases flexibility and vulnerability (reentrancy, etc.).

Beyond Ethereum: Other Smart Contract Models

  • UTXO-plus models (e.g., Cardano eUTXO): richer logic while keeping UTXO structure.
    • Stronger formal verification, but stateless contracts are harder to program.
  • Account-centric but non-EVM (e.g., Solana): program-owned accounts with explicit parallelism.
    • High throughput, but developers must declare all touched accounts upfront.
  • WebAssembly-based contracts (e.g., CosmWasm, Near): compile Rust/Go to a WASM sandbox.
    • Familiar languages and memory safety, but smaller ecosystem and tooling.
  • Private/permissioned chaincode (e.g., Hyperledger Fabric): enterprise settings with known participants.
    • Fine-grained access control, but sacrifices permissionless composability.

Ethereum’s Advantage for Smart Contracts

  • Largest and most mature ecosystem for contract development and deployment.
  • Clear execution environment (EVM) with deterministic outcomes and gas-metered computation.
  • Contracts compile to bytecode that every EVM node executes in the same way.
  • Shared standards enable composability and predictable interfaces.
  • Rich tooling and libraries lower barriers to safe experimentation.
  • Open participation model: anyone can verify, deploy, or compose with existing code.

Ethereum Accounts and Contract Review

  • Two account types: EOA (user, has private key) and Contract (code + storage).
    • Both share the same address format and can hold ETH, but only EOAs can sign transactions.
  • Contracts execute on message calls; they cannot self-initiate.
    • Every on-chain action traces back to an EOA signature, preserving explicit causality.
  • Contract state lives in storage (persistent), while each call gets temporary working memory.
  • Each call executes with msg.sender, msg.value, available gas, calldata (function inputs), block context, etc.
    • msg.sender is the immediate caller, not necessarily the original user. This matters for access control.

Contract Deployment

Deployment happens in a short sequence:

  1. A developer writes Solidity and compiles it into deployment bytecode.
  2. The deployment transaction carries compiled creation code (initcode).
  3. The EVM runs that code, including constructor logic, and charges gas while it executes.
  4. The result is a new contract address with runtime bytecode and initialized storage on-chain.

The contract address is derived deterministically from the deployment rule in use.

Source-code verification on explorers is separate from deployment.

deployflow src Solidity source comp Compile src->comp tx Deployment tx with initcode comp->tx exec Initcode + constructor run in the EVM tx->exec out Contract address runtime bytecode + storage exec->out

Calls and Control Flow in the EVM

  • External calls hand control to untrusted code; treat with care.
    • The callee can execute arbitrary logic, including calling back into your contract (reentrancy).
  • Reverts cascade unless caught; state changes roll back per frame.
    • If contract B reverts and A does not handle it (try/catch), A reverts too and the whole transaction unwinds.
  • msg.sender and msg.value are per-frame; gas forwarding is explicit.
    • When A calls B, B sees A as msg.sender, not the original user. This is critical for access control.
  • Each call frame has a gas budget.
    • If a loop runs too long or the callee uses too much gas, that frame fails when its gas is exhausted.

callflow user User (EOA) a Contract A msg.sender = User user->a tx: A.f(x) value = v a->user return / revert b Contract B msg.sender = A a->b external call B.g(y) b->a return / revert

Call frame: the EVM’s execution context for a single call. Each frame has its own msg.sender, msg.value, gas budget, memory, and return data. A revert undoes state changes within that frame only.

Solidity: File Structure, From Top to Bottom

Each section in a Solidity file explains its behavior:

  • pragma solidity ... sets compiler expectations, so readers know which language rules and safety checks apply.
  • contract Name { ... } identifies the unit of code and state that will be deployed at one address.
  • State variables show which data persists between calls.
  • Functions and modifiers show how callers interact with the contract and whether state can change.
  • Constructor and events show what happens at deployment and what off-chain systems can observe afterward.
  • The source file is what humans review; the deployed contract is compiled runtime bytecode.

Types: Value Types

Solidity groups types into value types and reference types. Value types are copied when assigned or passed around, so they do not use a data-location label like storage, memory, or calldata.

Type Represents Uses
uint / int Signed and unsigned integers for arithmetic Balances, counters, timestamps, and range checks
bool true / false values Flags, checks, and branching logic
address / address payable Account identifiers; payable can receive ETH Ownership, recipients, and access-control decisions
enum A limited set of named states Helps constrain contract state to valid options

Literal suffixes like 1 ether and time units are syntax conveniences, not a separate datatype family.

Types: Reference Types

Reference types types refer to larger data structures–their behavior depends on where the data lives: storage, memory, or calldata.

Type Represents Uses
Arrays Ordered collections of values, fixed-size or dynamic Lists of items, histories, and batched data
bytes / string Dynamic byte arrays and text data Raw data/metadata
struct A custom grouped record with multiple fields Models richer state such as users, proposals, or assets
mapping(Key => Value) Key-value lookup table Balances, permissions, and registries

Reference types often need an explicit data location, and copying them can change both gas cost and behavior.

Visibility, Mutability, and Payable

These are function-level modifiers. They tell readers who can call a function, whether it may read or change state, and whether it may receive ETH.

  • Visibility controls who can call or access the function:
    • public: callable from inside the contract and from outside
    • external: callable from outside the contract
    • internal: callable only inside the contract or derived contracts
    • private: callable only inside the same contract
  • Mutability describes how the function interacts with state:
    • view: may read state, but not write it
    • pure: does not read or write contract state
    • default: may change state
  • payable means the function can receive ETH with the call.
    • nonpayable is the contrasting default: attached ETH causes the call to revert.
  • Public state variables get auto-generated getters.

Data Locations: Storage, Memory, Calldata

  • Storage: persistent across transactions
    • Balances, owners, mappings
    • Most expensive to write because the new state must be recorded on-chain
  • Memory: temp workspace for one call
    • Local arrays, decoded values, ABI encoding buffers
    • Zeroed between calls
  • Calldata: read-only transaction inputs
    • Function arguments from the caller
    • Often cheapest; external functions can read it without making a copy
  • Wrong location choices can waste gas, trigger unnecessary copies, or change how updates behave.

dataloc cluster_contract Smart Contract tx Transaction calldata Calldata function args from caller (read-only) tx->calldata input bytes memory Memory local arrays, encoding buffers (cleared each call) calldata->memory copy if needed storage Storage balances, owners, mappings (persists forever) memory->storage SSTORE storage->memory SLOAD

Example: SimpleStorage (Code)

pragma solidity ^0.8.26;
contract SimpleStorage {
    uint256 private stored;
    event Updated(uint256 oldValue, uint256 newValue);

    function set(uint256 x) public {
        emit Updated(stored, x);
        stored = x;
    }

    function get() public view returns (uint256) {
        return stored;
    }
}
  • One state variable; one setter, one getter, one event.
  • stored is a storage variable; x exists only for the duration of the call.
  • Demonstrates state write vs view read.
  • Events record history clients can subscribe to.
  • What about access control or input validation?

Example: Coin (Code, Part 1)

pragma solidity ^0.8.26;
contract Coin {
    address public minter;
    mapping(address => uint) public balances;
    event Sent(address from, address to, uint amount);
    constructor() { minter = msg.sender; }
    function mint(address receiver, uint amount) public {
        require(msg.sender == minter, "not minter");
        balances[receiver] += amount;
    }
  • Public state vars auto-generate getters.
  • minter and balances live in storage.
  • Constructor initializes the minter to deployer.
  • mint restricts access to minter via require.
  • Mapping initializes to zero.

Example: Coin (Code, Part 2)

    error InsufficientBalance(uint requested, uint available);
    function send(address receiver, uint amount) public {
        require(amount <= balances[msg.sender], 
                InsufficientBalance(amount, balances[msg.sender]));
        balances[msg.sender] -= amount;
        balances[receiver] += amount;
        emit Sent(msg.sender, receiver, amount);
    }
}
  • The custom error function names the failure case and returns the requested and available amounts.
  • The require check stops the transfer unless the sender has enough balance.
  • Lines 6-7 move value by subtracting from the sender and adding to the receiver in storage.
  • The event on line 8 gives wallets, explorers, and indexers a record of the transfer.
  • This is the core accounting pattern behind token transfers, even though this example does not include allowances.

Events and Logs

  • Contracts emit events, append-only records stored in the transaction’s receipt.
  • Each event creates a log entry with:
    • emitter address
    • topics (up to 4, including the event signature hash)
    • data (unindexed fields, ABI-encoded)
  • Logs stored in transaction but are not contract storage; they are not visible to smart contracts but are visible to off-chain systems (UIs, indexers, proofs).
  • Indexed fields (indexed) create searchable topics; non-indexed fields go into the data blob.
  • Events vanish if the transaction reverts.

Topic: a 32-byte indexed field in a log entry. Topic 0 is the keccak-256 hash of the event signature (e.g., Transfer(address,address,uint256)); topics 1–3 hold indexed parameter values. Clients filter logs by topic without scanning the full data blob.

ABI, Encoding, and Function Selectors

  • ABI (Application Binary Interface) is the byte-level interface that tells wallets, apps, and other contracts how to call a contract and interpret what comes back.
  • Defines the format for: function calls, return values, event data
  • ABI Function call format:
    • 4-byte selector = keccak256("name(types)")[:4]
    • Followed by arguments, each encoded per ABI rules (32-byte words, offsets for dynamic types).
  • Callers interact with deployed bytecode without needing the Solidity source on-chain.
  • Return data and event logs follow ABI conventions.
  • receive handles plain ETH transfers; fallback handles unknown selectors or bad calldata.

Reentrancy: Anatomy and Defenses

  • Occurs when an external call reenters before your function finishes.
  • Here, the victim contract is trying to pay the attacker a withdrawal from the attacker’s recorded balance.
  • The bug is that the contract sends ETH before it updates that balance to zero.
  • Classics: withdraw pattern before balance update; ERC-777 hooks misused.

reentrancy cluster_attacker Attacker Contract cluster_victim Victim Contract v1 1. attacker calls withdraw() v2 2. victim sends ETH before balance update v1->v2 v3 5. balance[attacker] = 0 (too late!) v2->v3 never reached a1 3. receive() triggered v2->a1 ETH transfer a2 4. call withdraw() again! a1->a2 a2->v1 re-enters (balance still 10!)

  • Defenses: Check-Effects-Interactions (CEI) ordering, ReentrancyGuard, pull over push payments.

Reentrancy: Vulnerable Pattern (Code)

mapping(address => uint) public balances;

function withdraw() public {
    uint amount = balances[msg.sender];
    require(amount > 0, "no funds");
    (bool ok,) = msg.sender.call{value: amount}("");
    require(ok, "send failed");
    balances[msg.sender] = 0;
}
  • External call before state update enables reentry.
  • Attacker’s fallback re-calls withdraw() before zeroing balance.
  • Result: multiple withdrawals from one balance.

Reentrancy: Checks-Effects-Interactions (Fix)

The fix is disciplined ordering:

  1. Check preconditions (require)
  2. Update state (effects) before yielding control
  3. External call last (interactions)

A reentrant call now sees zero balance and fails the check. The attack loop from the previous slide is broken.

Some contracts add a second protection: a reentry lock. That pattern blocks the same function from being entered again before the first call finishes.

CEI pattern:

function withdraw() public {
    uint amount = balances[msg.sender];
    require(amount > 0, "no funds");   // CHECK
    balances[msg.sender] = 0;          // EFFECT
    (bool ok,) = payable(msg.sender)   // INTERACTION
        .call{value: amount}("");
    require(ok, "send failed");
}

 

ReentrancyGuard (mutex):

import "...utils/ReentrancyGuard.sol";

contract Vault is ReentrancyGuard {
    function withdraw() public nonReentrant {
        // CEI ordering + mutex lock
    }
}

Case Study: The DAO Hack (2016)

  • “The DAO,” a decentralized investment fund built on Ethereum, launched in 2016. It raised over 11 million ETH (≈ $150 million at the time, 14% of all ETH).
  • Vulnerability: contract sent ETH before updating balances.
    • Exploited on June 17, 2016: attacker’s fallback reentered withdraw() before balance changed.
    • Each loop drained more funds; state always one step behind reality.
  • Ethereum hard fork to revert chain, dissenters split off into Ethereum Classic (ETC)

Upgradeability and Proxy Patterns

  • Contracts on Ethereum are immutable once deployed.
    • No patching; bugs and all are permanent unless you plan ahead.
  • The proxy pattern separates storage (state) from logic (code) so new logic can be deployed without migrating data.
  • delegatecall executes the logic contract’s code in the proxy’s storage context.
    • The proxy address never changes; users and other contracts always call the same address.
  • Governance controls (multi-sig, timelocks) prevent malicious upgrades.

Without proxy (immutable)

noproxy user User v1 Contract v1 (code + state) user->v1 v2 Contract v2 (new deploy) user->v2 migrate?

With proxy (upgradeable)

proxy user User proxy Proxy (state lives here) user->proxy v1 Logic v1 proxy->v1 old v2 Logic v2 proxy->v2 delegatecall

Randomness, Oracles, and External Data

  • The EVM is deterministic: every node must compute the same result. That means contracts cannot reach outside the chain for data, generate true randomness, or call external APIs.
  • Naive on-chain “randomness” (block hash, timestamp) is manipulable by block proposers.
    • Safer approaches: commit-reveal schemes or Verifiable Random Functions (VRF) with cryptographic proofs.
  • External facts (prices, weather, sports scores) require oracles that post data on-chain.
    • Oracles introduce a trust assumption: who posts the data, how it is aggregated, and how manipulation is detected.
  • Defensive patterns for oracle failure:
    • Staleness checks (reject data older than N blocks)
    • Bounds/rate-of-change limits (reject implausible jumps)
    • Circuit breakers (pause critical operations if feeds go offline)
    • Multi-source aggregation (Chainlink, UMA, Pyth)

Key Takeaways

  • A smart contract is code + persistent state that executes deterministically on every node.

  • Ethereum’s EVM provides the execution environment: accounts, gas metering, runtime bytecode, and a shared state trie.

  • Solidity source is compiled into bytecode for deployment; after creation, the contract account stores runtime bytecode on-chain.

  • External calls transfer control to untrusted code; the Checks-Effects-Interactions pattern and reentrancy guards are the primary defenses.

  • Contracts cannot reach outside the chain; oracles and VRFs bridge that gap, each with their own trust assumptions.

  • Immutability is the default; proxy patterns trade it for upgradeability at the cost of added governance complexity.

  • Next lesson: we apply these concepts to digital assets, tokenization, and NFTs (ERC-20, ERC-721, ERC-1155).

References

[1]
V. Buterin, “Ethereum: A Next-Generation Smart Contract and Decentralized Application Platform.” Ethereum.org, 2014. Available: https://ethereum.org/content/whitepaper/whitepaper-pdf/Ethereum_Whitepaper_-_Buterin_2014.pdf
[2]
Ethereum.org, “Smart ContractsDeveloper Docs.” 2025. Available: https://ethereum.org/en/developers/docs/smart-contracts/
[3]
S. Nakamoto, “Bitcoin: A Peer-to-Peer Electronic Cash System.” Satoshi Nakamoto Institute, Oct. 31, 2008. Accessed: Sep. 12, 2025. [Online]. Available: https://cdn.nakamotoinstitute.org/docs/bitcoin.pdf
[4]
G. Wood, “Ethereum: A Secure Decentralised Generalised Transaction Ledger, EIP-150 Revision.” 2016. Available: https://ethereum.github.io/yellowpaper/paper.pdf
[5]
Ethereum.org, “Accounts — Developer Docs.” 2025. Available: https://ethereum.org/en/developers/docs/accounts/
[6]
Solidity Team, “Solidity by Example.” 2025. Available: https://docs.soliditylang.org/en/latest/solidity-by-example.html
[7]
D. A. Siegel, “Understanding the DAO Attack.” 2016. Available: https://papers.ssrn.com/sol3/papers.cfm?abstract_id=3014782
[8]
Ethereum Foundation, “Critical update re: DAO vulnerability.” Accessed: Mar. 23, 2026. [Online]. Available: https://blog.ethereum.org/2016/06/17/critical-update-re-dao-vulnerability