Smart Contracts

Chapter 8
Smart Contract Security

Diploma in Blockchain
Smart Contracts   •   Diploma

Smart Contract Security

Introduction

Smart contracts are programs deployed on blockchains that execute according to predefined rules. Their security requirements differ from those of ordinary software because deployed code may be difficult or impossible to modify, transactions are publicly observable, users may be pseudonymous, and financial value can be transferred automatically. A programming error can therefore become an irreversible economic loss.

Smart contract security is not limited to preventing coding mistakes. It involves threat modelling, secure architecture, defensive programming, testing, formal reasoning, deployment controls, monitoring and incident response. A secure contract should preserve three broad properties:

  • Confidentiality: information that should remain private is not unnecessarily exposed. Public blockchains provide very limited confidentiality.
  • Integrity: balances, ownership records and contract state cannot be altered except through authorised and valid operations.
  • Availability: legitimate users can continue to interact with the contract when required.

For financial contracts, two additional properties are essential: economic correctness, meaning that incentives and accounting rules behave as intended, and accountability, meaning that privileged actions are controlled, recorded and reviewable.

Learning Objectives

By the end of this chapter, learners should be able to:

  1. Explain major smart contract vulnerabilities and their causes.
  2. Identify reentrancy, access-control, arithmetic, denial-of-service, front-running, oracle and signature-related risks.
  3. Apply secure coding patterns such as checks-effects-interactions, pull payments, role separation and replay protection.
  4. Design a practical testing and audit process.
  5. Evaluate security, governance and operational risks in a contract deployment.

A Security Model for Smart Contracts

A useful security review begins by identifying four elements:

  1. Assets: tokens, Ether, voting rights, collateral, ownership records and privileged capabilities.
  2. Actors: ordinary users, administrators, keepers, oracle providers, other contracts and attackers.
  3. Trust boundaries: points at which the contract relies on external calls, off-chain data, signatures or privileged accounts.
  4. Invariants: conditions that must remain true, such as “total recorded balances equal the token reserve” or “only the owner can change the implementation address.”

An invariant is a property that should hold before and after every valid state transition. For example, in a simple token ledger:

Σ balances[a] = totalSupply

Here, a represents every address and Σ denotes summation over those addresses. This invariant applies only if the token does not permit minting, burning or rebasing during the operation; otherwise the invariant must be modified to account for those state changes.

Security is often described using the attack-surface model. The attack surface includes external and public functions, payable functions, token callbacks, upgrade mechanisms, administrative keys, oracle inputs, signature verification and interactions with other contracts. The more assumptions a contract makes about these components, the larger its security risk.

Reentrancy

Concept and mechanism

Reentrancy occurs when a contract makes an external call and the receiving contract calls back before the first execution has completed. If state has not yet been updated, the callback may observe an inconsistent state and repeat an operation.

A vulnerable withdrawal pattern is conceptually:

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok);
    balances[msg.sender] -= amount;
}

The balance is reduced after the external call. A malicious recipient can call withdraw again from its fallback function while the recorded balance still has its old value.

Prevention

The preferred pattern is checks-effects-interactions:

  1. Check conditions.
  2. Update internal state.
  3. Interact with external contracts last.
function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    balances[msg.sender] -= amount;
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok);
}

A reentrancy guard can also prevent nested entry into protected functions. However, a guard is not a substitute for correct state ordering. Contracts should also consider cross-function reentrancy, in which a callback enters a different function that relies on the same inconsistent state.

The pull-payment pattern is safer than automatically sending funds to many recipients. Each recipient withdraws its own payment, reducing the chance that one reverting recipient blocks a whole distribution.

Access-Control Failures

Access control determines who may perform sensitive actions. Common failures include missing modifiers, incorrect role checks, publicly callable initialisation functions, confusing tx.origin with msg.sender, and leaving upgrade or emergency functions controlled by a single unprotected address.

msg.sender is the immediate caller of a function. tx.origin is the externally owned account that initiated the entire transaction. Authorisation should normally use msg.sender, because a malicious intermediary contract can trick a user into initiating a transaction while tx.origin remains the user.

A robust design should:

  • define roles explicitly, such as administrator, pauser and minter;
  • apply least privilege, giving each role only necessary powers;
  • emit events for privileged changes;
  • protect initialisation and prevent repeated initialisation;
  • use multisignature wallets for high-impact administration;
  • impose timelocks on sensitive upgrades where appropriate;
  • provide a documented recovery process.

A role should not be regarded as secure merely because its address is stored in a variable. The contract must enforce the role at every relevant entry point.

Arithmetic and Precision Errors

Arithmetic vulnerabilities include integer overflow, underflow, truncation, incorrect unit conversion and unsafe order of operations. In Solidity versions with checked arithmetic, overflow and underflow generally revert unless unchecked arithmetic is deliberately used. Developers must still review expressions for logical errors.

Integer division discards the fractional remainder. For example, with integer values:

7 ÷ 3 = 2

The result is not 2.333… because the fractional component cannot be represented. This is important in interest, exchange-rate and reward calculations. A common technique is fixed-point scaling:

scaledResult = (x × scale) ÷ d

where x is the numerator quantity, d is the denominator, and scale is a positive scaling factor such as 10¹⁸. The units of x, d and scale must be documented. Multiplication should be ordered carefully to reduce truncation, while avoiding overflow.

For a proportional fee, if amount is measured in token units and feeRate is expressed in basis points, then:

fee = (amount × feeRate) ÷ 10,000

One basis point equals 0.01%, so 10,000 basis points equal 100%. This formula assumes feeRate is bounded between 0 and 10,000 and that rounding policy is defined. A contract should state whether it rounds down, rounds up or rejects amounts that produce an unacceptable rounding loss.

Typical controls include explicit unit naming, bounded parameters, SafeCast-style checked conversions, invariant testing and independent review of rounding behaviour.

Denial of Service

A denial-of-service vulnerability prevents legitimate operations from completing. Causes include unbounded loops, gas exhaustion, forced reverts, blocked withdrawals and reliance on one critical external service.

An operation that loops through an ever-growing list may eventually exceed the block gas limit. A safer design uses pagination, bounded batches or a pull model. For example, rather than distributing rewards to every investor in one transaction, the contract can record each investor’s entitlement and allow individual claims.

External calls may also cause denial of service. If a contract sends funds to many recipients and reverts when one recipient rejects payment, all recipients may be blocked. The contract should isolate failures, use pull payments or record failed payments for later withdrawal.

Gas limits are not merely an optimisation issue. They are an availability constraint. Developers should test worst-case state sizes and consider whether an operation remains executable after years of growth.

Front-Running and Transaction-Order Dependence

Transactions submitted to a public mempool may be observed before confirmation. An attacker can submit a transaction with a higher fee or otherwise obtain a more favourable position. This is commonly called front-running.

Examples include observing a large decentralised-exchange trade, copying a profitable arbitrage transaction, or taking a name before a registration transaction is confirmed. Transaction-order dependence occurs when the result depends on which valid transaction is processed first.

Mitigations include:

  • commit–reveal schemes;
  • slippage limits and deadlines for trades;
  • private transaction submission where suitable;
  • batch auctions or uniform clearing prices;
  • minimum and maximum bounds on user-controlled parameters;
  • avoiding designs that expose valuable intentions before execution.

A commit–reveal process usually has two stages. In the commit stage, a user submits a cryptographic commitment, such as a hash of a choice and secret. In the reveal stage, the user discloses the choice and secret, allowing verification. The secret prevents observers from learning the choice during commitment. The design must address unrevealed commitments, deposits, timing and griefing.

A deadline does not prevent all front-running, but it limits execution after the user’s intended time. Slippage protection ensures that a trade reverts if the outcome is worse than the user’s specified tolerance.

Insecure Randomness

Blockchains do not automatically provide secure, unpredictable randomness. Values such as the current timestamp, block number or publicly visible block hash may be influenced, predicted or observed by participants. They should not be used alone for high-value lotteries, games or allocation decisions.

A secure randomness design should consider:

  • who can predict or influence the random value;
  • when the value becomes known;
  • whether a participant can choose not to reveal;
  • whether the operator can bias or censor requests;
  • how failures and delayed responses are handled.

A verifiable random function or a decentralised randomness service can provide stronger guarantees, but the contract must still verify authenticity, handle delayed fulfillment and manage service availability. Randomness is a system property, not simply a line of code.

Oracle Manipulation

An oracle supplies off-chain or cross-system information such as asset prices, interest rates, weather data or event outcomes. The contract cannot independently know whether an external value is truthful. If an oracle is manipulated, a correctly written contract may execute an economically disastrous result.

Risks include dependence on one reporter, thin-market prices, stale data, abnormal price movements and incorrect decimal interpretation. Defensive controls include:

  • using multiple independent sources;
  • applying time-weighted or volume-aware prices where appropriate;
  • checking timestamps and maximum data age;
  • rejecting values outside reasonable bounds;
  • using circuit breakers and liquidation limits;
  • separating price observation from settlement where possible.

A price feed should be treated as valid only if it satisfies explicit conditions. For example, a system may require that the reported timestamp is recent and that the price is nonzero. These checks do not prove truth; they reduce certain failure modes.

Signature Replay and Domain Separation

A signature proves that a private key authorised specific data, but the contract must ensure that the signature cannot be reused in an unintended context.

Replay can occur when:

  • the same signature is submitted more than once;
  • a signature valid on one chain is accepted on another chain;
  • a signature intended for one contract is accepted by another;
  • a message is valid for a different function or user.

A secure signed-message design normally includes a nonce, chain identifier, contract address, function or action type, relevant parameters and an expiry time. The nonce must be consumed after successful use. Domain separation ensures that a signature for one purpose is not interpreted as authorisation for another.

Standards such as EIP-712 define structured, typed data and domain information. They improve clarity but do not remove the need for correct nonce management, signer checks and deadline validation.

Other Important Vulnerabilities

Unsafe external calls

A low-level call may fail, return unexpected data or invoke arbitrary code. Contracts should check return values, limit assumptions about callee behaviour and avoid unnecessary calls. Token standards are not perfectly uniform; some tokens do not return values in the same way. Established safe-transfer libraries reduce compatibility errors.

Delegatecall and proxy risks

delegatecall executes another contract’s code using the caller’s storage and address context. Storage-layout mismatches can corrupt state. Upgradeable systems also introduce risks involving unprotected upgrades, implementation self-destruction, storage collisions and initialisation errors. Upgrade authority should be explicit, restricted and monitored.

Denial through unexpected token behaviour

Tokens may charge transfer fees, rebase balances or invoke callbacks. A contract that assumes received = requested may mis-account. Accounting should measure actual balance changes where the design requires it, and supported token behaviours should be documented.

Unchecked return values and error handling

Ignoring a return value can make a failed operation appear successful. Every external operation should have an intentional failure policy: revert, record failure, retry or allow later recovery.

Secure Development and Audit Process

Security should be integrated throughout the software lifecycle.

1. Requirements and threat modelling

Document assets, actors, trust assumptions, privileged operations and failure consequences. Identify whether a loss is reversible and establish emergency procedures before coding.

2. Secure implementation

Use small functions, explicit state transitions, established libraries and clear naming. Avoid unnecessary complexity. Follow checks-effects-interactions, least privilege, pull payments and fail-safe defaults.

3. Testing

Unit tests verify individual functions. Integration tests examine interactions among contracts. Fuzz testing generates varied inputs, including extreme values and invalid sequences. Invariant testing checks properties that must remain true across many transactions. Differential testing compares the implementation with a trusted model where feasible.

4. Static and dynamic analysis

Static analysers identify patterns such as reentrancy, dangerous arithmetic and missing checks. Symbolic execution explores paths using symbolic inputs. These tools produce valuable findings but may generate false positives and cannot fully understand economic assumptions.

5. Manual review and audit

An audit should review architecture, code, tests, deployment scripts and operational controls. Findings should be classified by severity, exploitability and affected assets. A professional audit is not a guarantee of security; it is one layer in a defence-in-depth process.

6. Deployment and monitoring

Verify deployed bytecode, record privileged addresses, use multisignature administration and monitor events and unusual transfers. Establish pause, upgrade and communication procedures before an incident occurs.

Illustrative Case: A Vulnerable Lending Pool

Background

Consider an illustrative lending pool that accepts a collateral token and allows users to borrow a stable token. The pool obtains collateral prices from a single decentralised-exchange pair and permits an administrator to change the price-feed address immediately.

Problem

The design has three weaknesses. First, the price pair is shallow, so a trader can move its price temporarily. Second, the liquidation function accepts a price without checking its age. Third, the administrator key is a single externally owned account with no timelock or multisignature protection.

Analysis

An attacker could manipulate the shallow market, cause the oracle to report an inflated collateral value and borrow more than the collateral is worth. Alternatively, a stale price could permit borrowing after a market collapse. If the administrator key is compromised, an attacker could replace the oracle or upgrade the contract.

The core invariant is that the value of outstanding loans should remain adequately covered by collateral under the protocol’s defined valuation rules. The invariant is economic rather than purely syntactic: it depends on accurate prices, correct decimals, liquidation incentives and bounded execution.

Approach and solution

The improved design uses a robust multi-source oracle, checks data freshness, applies deviation limits and imposes borrowing caps. It uses explicit decimal conversion and tests rounding boundaries. Administrative changes require a multisignature wallet and timelock, with emergency pause authority separated from upgrade authority. Liquidation is tested under normal, stale, extreme and rapidly changing prices.

Outcome and learning points

The principal lesson is that secure code cannot compensate for insecure dependencies and governance. Oracle design, privileged-key management and economic limits must be reviewed together. A security audit should ask not only “Can this function be called safely?” but also “Can the system remain solvent when assumptions fail?”

Ethical, Governance and Regulatory Considerations

Smart contract developers control systems that may affect users who cannot easily reverse errors. Ethical practice therefore requires transparent documentation of risks, fees, administrative powers and emergency controls. Projects should not describe unaudited code as trustless or risk-free.

Governance mechanisms must balance responsiveness with protection against unilateral abuse. Emergency pauses can limit losses, but they may also restrict legitimate access. Governance votes can improve legitimacy while introducing voter concentration, bribery and low-participation risks.

Legal obligations vary by jurisdiction and application. Identity, consumer protection, financial regulation, sanctions compliance and data-protection requirements may apply. Immutability does not eliminate legal responsibility. Teams should obtain appropriate professional advice and design systems that support accountable operation.

Contemporary Developments and Best Practices

Modern security practice increasingly combines automated analysis, formal verification of critical invariants, bug bounties, runtime monitoring and secure deployment pipelines. AI-assisted tools can help summarise code, generate test cases and identify suspicious patterns, but generated findings require expert verification. AI does not understand all economic, governance or adversarial assumptions and must not replace human review.

A practical checklist is:

AreaQuestions to ask
StateWhat invariants must always hold?
AccessWho can call, upgrade, pause or initialise?
CallsCan external code execute during a sensitive operation?
ArithmeticAre units, bounds and rounding explicit?
AvailabilityCan loops, reverts or gas growth block users?
OrderingCan a transaction be observed or reordered advantageously?
RandomnessWho can predict, influence or refuse the result?
OraclesIs data fresh, independent and manipulation-resistant?
SignaturesAre domain, nonce, chain and deadline protected?
OperationsAre monitoring, key recovery and incident procedures ready?

Chapter Summary

Smart contract security requires more than syntactically correct code. Developers must identify assets, trust boundaries, actors and invariants, then reduce risk through architecture, defensive coding, testing, review and operational controls. Reentrancy is mitigated by correct state ordering, guards and pull payments. Access-control failures require explicit roles, least privilege and protected administration. Arithmetic security depends on checked operations, correct units, bounded values and defined rounding. Availability requires bounded work and failure isolation.

Public transaction ordering creates front-running and transaction-order dependence, while insecure randomness and manipulated or stale oracles can compromise otherwise correct logic. Signature systems require nonces, domain separation, chain identification and deadlines. Upgradeable contracts, external calls and token compatibility introduce additional risks. Finally, audits, formal reasoning, monitoring, governance and ethical disclosure provide complementary layers of protection. No single tool or audit guarantees security; resilience comes from defence in depth.

Key Terms

Reentrancy: A callback into a contract before the original execution has completed.

Checks-effects-interactions: A pattern that validates conditions, updates state and performs external calls last.

Access control: Rules determining which accounts may perform particular actions.

Least privilege: Giving an account or role only the authority it needs.

Invariant: A property that should remain true across valid state transitions.

Integer overflow: An arithmetic result exceeding the representable range.

Integer underflow: An arithmetic subtraction producing a result below the representable range.

Fixed-point arithmetic: Representing fractional values using scaled integers.

Denial of service: Preventing legitimate operations from completing.

Front-running: Gaining advantage by observing and ordering transactions before a target transaction.

Transaction-order dependence: A condition in which results change according to transaction sequence.

Commit–reveal: A two-stage process that hides a choice before later disclosure.

Oracle: A service or mechanism that supplies external data to a blockchain contract.

Oracle manipulation: Causing an oracle-dependent contract to use an economically false value.

Replay attack: Reusing a previously valid message or signature.

Nonce: A value used to ensure an operation or signature is used only as intended.

Domain separation: Including contextual information so data authorised for one purpose is not valid for another.

Delegatecall: Execution of another contract’s code using the caller’s storage and context.

Invariant testing: Testing that specified properties remain true across generated transaction sequences.

Multisignature wallet: A wallet requiring approval from multiple authorised signers.

Review Questions

Short Answer Questions

  1. What is reentrancy, and why does updating state after an external call create risk?
  2. Distinguish msg.sender from tx.origin in access-control design.
  3. Why can integer division create errors in token and fee calculations?
  4. State two reasons why a blockchain timestamp is unsuitable as high-value randomness.
  5. What information should commonly be included in a replay-resistant signed message?

Long Answer Questions

  1. Explain the checks-effects-interactions pattern and compare it with the use of a reentrancy guard.
  2. Describe how denial-of-service vulnerabilities arise from unbounded loops and reverting recipients. Propose suitable design alternatives.
  3. Explain front-running, transaction-order dependence and commit–reveal schemes, including their limitations.
  4. Discuss oracle manipulation risks and develop a framework for evaluating an oracle’s reliability.
  5. Describe a complete smart contract security lifecycle from threat modelling through deployment monitoring.

Analytical / Application Questions

  1. A contract distributes rewards by looping through every registered user and reverting if any transfer fails. Analyse the availability risks and redesign the distribution mechanism.
  2. A lending protocol uses one exchange pair for prices, accepts stale data and allows one account to replace the oracle instantly. Identify the attack paths and propose layered controls.
  3. A permit-style signature contains only an owner, spender and amount, with no nonce, chain identifier or expiry. Explain the replay risks and specify the fields and state changes required for a safer design.

Multiple Choice Questions with Answers

Question 1

Which sequence best represents the checks-effects-interactions pattern?

A. Interact, check, then update state

B. Check conditions, update state, then interact externally

C. Update state, interact externally, then check conditions

D. Interact twice, then update state

Correct Answer: B. Check conditions, update state, then interact externally

Question 2

Which value is normally preferred for contract-level authorisation?

A. tx.origin

B. The current block number

C. msg.sender

D. The transaction gas limit

Correct Answer: C. msg.sender

Question 3

What is the main problem with using integer division for 7 divided by 3 in a Solidity-style integer calculation?

A. It returns 3.5

B. It returns 2 and discards the fractional remainder

C. It always reverts

D. It returns a negative value

Correct Answer: B. It returns 2 and discards the fractional remainder

Question 4

Which design most directly reduces denial of service caused by one recipient reverting during mass payment?

A. Require all recipients to accept payment in one transaction

B. Use a pull-payment claim mechanism

C. Increase the loop size indefinitely

D. Ignore all transfer failures without recording them

Correct Answer: B. Use a pull-payment claim mechanism

Question 5

What is a principal purpose of a commit–reveal scheme?

A. To hide a user’s choice until a later reveal stage

B. To eliminate all transaction fees

C. To make private keys publicly visible

D. To guarantee that no participant can refuse to reveal

Correct Answer: A. To hide a user’s choice until a later reveal stage

Question 6

Why is a single shallow exchange pair a risky oracle source?

A. It cannot be queried by a smart contract

B. Its price may be moved substantially with comparatively little capital

C. It always reports a price with too many decimals

D. It prevents all liquidations

Correct Answer: B. Its price may be moved substantially with comparatively little capital

Question 7

Which feature is most directly intended to prevent a signature from being used repeatedly?

A. A nonce that is consumed after successful use

B. A larger block size

C. A payable fallback function

D. A public token balance

Correct Answer: A. A nonce that is consumed after successful use

Question 8

What is the principal security risk of an unbounded loop over a growing array?

A. The contract becomes confidential

B. The operation may exceed the block gas limit

C. The loop automatically creates randomness

D. The array becomes immutable

Correct Answer: B. The operation may exceed the block gas limit

Question 9

Which administrative arrangement generally provides stronger protection for a high-impact upgrade?

A. An unprotected public function

B. A single anonymous caller

C. A multisignature wallet, potentially combined with a timelock

D. A user-controlled timestamp

Correct Answer: C. A multisignature wallet, potentially combined with a timelock

Question 10

What does invariant testing primarily examine?

A. Whether specified properties remain true across generated transaction sequences

B. Whether every transaction has identical gas usage

C. Whether a private key can be reconstructed

D. Whether all external data is confidential

Correct Answer: A. Whether specified properties remain true across generated transaction sequences

Smart Contracts
Chapter 8 of 14