SECTION 1: LEARNING OBJECTIVES

By the end of this lesson, you will be able to:

  • Define formal verification and its role in blockchain security.

  • Explain theorem proving, model checking, and property-based testing.

  • Understand the limitations and benefits of formal verification.

  • Describe secure development practices for smart contracts.

  • Differentiate between various verification tools and approaches.

  • Identify when formal verification is necessary.

  • Implement a basic formal verification simulation in Python.

  • Develop a framework for secure smart contract development.


SECTION 2: WHAT IS FORMAL VERIFICATION?

2.1 Definition

Formal verification is the process of using mathematical techniques to prove that a system (such as a smart contract) satisfies certain properties or specifications. It provides a higher level of assurance than testing alone, as it exhaustively checks all possible states and inputs.

2.2 Why Formal Verification Matters

 
 
Reason Description
Exhaustive Checking Covers all possible execution paths, not just a sample.
Mathematical Guarantee Provides mathematical proof of correctness.
Bug Prevention Catches bugs that testing might miss.
High-Value Systems Essential for systems handling significant value.
Regulatory Requirement Increasingly required for critical applications.
Trust and Reputation Demonstrates commitment to security and quality.

SECTION 3: FORMAL VERIFICATION APPROACHES

3.1 Overview of Approaches

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                    FORMAL VERIFICATION APPROACHES                          │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. THEOREM PROVING                                                         │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Uses mathematical logic to prove properties                       │   │
│  │ • Requires manual guidance (proof assistants)                       │   │
│  │ • Examples: Coq, Isabelle/HOL, Lean                               │   │
│  │ • Pros: Very powerful, can prove complex properties               │   │
│  │ • Cons: Requires expert knowledge, time-consuming                 │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  2. MODEL CHECKING                                                         │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Exhaustively checks all states of a finite-state model           │   │
│  │ • Automated, no manual guidance                                      │   │
│  │ • Examples: SPIN, NuSMV                                              │   │
│  │ • Pros: Automated, finds bugs                                        │   │
│  │ • Cons: State explosion problem                                     │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  3. SYMBOLIC EXECUTION                                                     │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Explores all possible execution paths with symbolic values       │   │
│  │ • Examples: Mythril, Manticore                                      │   │
│  │ • Pros: Finds bugs, works on existing code                          │   │
│  │ • Cons: Path explosion                                               │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
│  4. PROPERTY-BASED TESTING                                                  │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Tests properties with random inputs                                │   │
│  │ • Examples: QuickCheck, Echidna, Foundry fuzzing                   │   │
│  │ • Pros: Practical, discovers bugs                                   │   │
│  │ • Cons: Not exhaustive                                               │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

3.2 Properties to Verify

 
 
Property Type Description Example
Invariants Properties that always hold “Balance is never negative”
Pre-conditions Conditions before a function call “Caller has sufficient balance”
Post-conditions Conditions after a function call “Balances update correctly”
State Transitions How state changes “Token transfer updates both accounts”
Safety Properties Nothing bad ever happens “No one can steal tokens”
Liveness Properties Something good eventually happens “Transaction eventually completes”

3.3 Verification Tools

 
 
Tool Type Language Key Features
Certora Prover Solidity Formal verification with custom spec
Scribble Instrumentation Solidity Runtime verification
Echidna Fuzzing Solidity Property-based testing
Mythril Symbolic Solidity Security analysis
Slither Static Solidity Vulnerability detection
Foundry Testing Solidity Fuzzing, invariant testing
Hardhat Testing Solidity Testing framework
ACT (Act) Spec Solidity Formal specification

SECTION 4: SECURE DEVELOPMENT PRACTICES

4.1 Development Lifecycle with Security

text
┌─────────────────────────────────────────────────────────────────────────────┐
│                    SECURE DEVELOPMENT LIFECYCLE                             │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  1. REQUIREMENTS                                                           │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Define security requirements                                      │   │
│  │ • Identify threat model                                              │   │
│  │ • Specify invariants                                                 │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│                                    v                                        │
│  2. DESIGN                                                                 │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Security by design                                                 │   │
│  │ • Minimise attack surface                                           │   │
│  │ • Define access controls                                             │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│                                    v                                        │
│  3. IMPLEMENTATION                                                         │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Follow secure coding practices                                    │   │
│  │ • Use established libraries                                         │   │
│  │ • Regular code review                                               │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│                                    v                                        │
│  4. TESTING                                                                │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Unit tests                                                         │   │
│  │ • Integration tests                                                  │   │
│  │ • Fuzzing                                                           │   │
│  │ • Formal verification                                                │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                    │                                        │
│                                    v                                        │
│  5. DEPLOYMENT & MONITORING                                               │
│  ┌──────────────────────────────────────────────────────────────────────┐   │
│  │ • Deploy with monitoring                                            │   │
│  │ • Incident response ready                                           │   │
│  │ • Continuous security monitoring                                   │   │
│  └──────────────────────────────────────────────────────────────────────┘   │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

4.2 Secure Coding Checklist

 
 
Category Best Practices
Access Control Use modifiers, role-based access, timelocks
Data Validation Validate inputs, use SafeMath/solidity 0.8+
Interaction Use Checks-Effects-Interactions pattern
Events Emit events for all state changes
Upgradeability Use secure upgrade patterns
Error Handling Use require, revert, custom errors
Gas Optimisation Avoid unbounded loops, pack variables

4.3 Threat Modeling

 
 
Step Description
1. Identify Assets What is at stake? (Tokens, funds, data)
2. Identify Threats Who are the attackers? (External, internal)
3. Identify Vulnerabilities Where are the weaknesses? (Code, design)
4. Assess Risks Likelihood and impact of each threat
5. Mitigate Implement controls to reduce risks

SECTION 5: IMPLEMENTATION IN PYTHON

python
# ===================================================================
# MODULE 9, LESSON 3: FORMAL VERIFICATION AND SECURE DEVELOPMENT
# ===================================================================

import hashlib
import random
from typing import Dict, List, Callable, Tuple
import warnings
warnings.filterwarnings('ignore')

print("="*70)
print("FORMAL VERIFICATION AND SECURE DEVELOPMENT")
print("="*70)

# ----------------------------------------------------------------
# PART A: PROPERTY-BASED TESTING SIMULATION
# ----------------------------------------------------------------

print("\n" + "-"*60)
print("PART A: Property-Based Testing Simulation")
print("-"*60)

class PropertyTester:
    """
    Simulated property-based testing for smart contracts.
    """
    def __init__(self):
        self.properties = []
        self.results = []
    
    def add_property(self, name: str, test_func: Callable, description: str):
        """Add a property to test."""
        self.properties.append({
            'name': name,
            'test': test_func,
            'description': description
        })
    
    def run_tests(self, test_cases: int = 100) -> Dict:
        """Run property tests with random inputs."""
        results = {}
        for prop in self.properties:
            passed = 0
            failures = []
            for i in range(test_cases):
                try:
                    if prop['test']():
                        passed += 1
                    else:
                        failures.append(i)
                except Exception as e:
                    failures.append({'index': i, 'error': str(e)})
            
            results[prop['name']] = {
                'passed': passed,
                'total': test_cases,
                'success_rate': passed / test_cases,
                'failures': len(failures),
                'description': prop['description']
            }
        
        return results

# Define test properties
def balance_invariant():
    """Simulate balance non-negativity invariant."""
    balance = random.randint(-10, 100)
    return balance >= 0

def transfer_invariant():
    """Simulate transfer preserving total supply."""
    sender_balance = random.randint(0, 100)
    recipient_balance = random.randint(0, 100)
    amount = random.randint(0, min(50, sender_balance))
    
    new_sender = sender_balance - amount
    new_recipient = recipient_balance + amount
    
    # Check total balance is preserved
    old_total = sender_balance + recipient_balance
    new_total = new_sender + new_recipient
    
    return old_total == new_total

def access_control_test():
    """Simulate access control check."""
    roles = ['admin', 'user', 'guest']
    role = random.choice(roles)
    function = random.choice(['admin_only', 'user_only', 'public'])
    
    if function == 'admin_only':
        return role == 'admin'
    elif function == 'user_only':
        return role in ['admin', 'user']
    else:
        return True

# Run property tests
tester = PropertyTester()
tester.add_property('Balance Non-negative', balance_invariant, 'Balances are never negative')
tester.add_property('Total Supply Preserved', transfer_invariant, 'Total supply is constant')
tester.add_property('Access Control Correct', access_control_test, 'Access control enforces roles')

print("Property-Based Testing Simulation:")
results = tester.run_tests(100)

for prop, result in results.items():
    status = "✅" if result['success_rate'] == 1.0 else "⚠️" if result['success_rate'] > 0.8 else "❌"
    print(f"\n{status} {prop}: {result['success_rate']:.0%} passed")
    print(f"  {result['description']}")
    if result['failures'] > 0:
        print(f"  Failures: {result['failures']}/{result['total']}")

# ----------------------------------------------------------------
# PART B: INVARIANT CHECKING SIMULATION
# -----------------------------------------------------------------

print("\n" + "-"*60)
print("PART B: Invariant Checking Simulation")
print("-"*60)

class InvariantChecker:
    """
    Simulated invariant checking for a contract.
    """
    def __init__(self):
        self.state = {}
        self.invariants = []
        self.violations = []
    
    def set_state(self, key: str, value):
        self.state[key] = value
    
    def add_invariant(self, name: str, check_func: Callable):
        self.invariants.append({
            'name': name,
            'check': check_func
        })
    
    def check_invariants(self) -> List[Dict]:
        """Check all invariants on the current state."""
        violations = []
        for invariant in self.invariants:
            is_valid = invariant['check'](self.state)
            if not is_valid:
                violations.append({
                    'invariant': invariant['name'],
                    'state': self.state.copy()
                })
        self.violations.extend(violations)
        return violations

# Define invariants
def total_supply_invariant(state):
    """Total supply should never exceed max supply."""
    total = state.get('total_supply', 0)
    max_supply = state.get('max_supply', 1000000)
    return total <= max_supply

def balance_non_negative(state):
    """All balances should be non-negative."""
    for key, value in state.items():
        if key.startswith('balance_') and value < 0:
            return False
    return True

# Simulate invariant checking
checker = InvariantChecker()

# Add invariants
checker.add_invariant('Total Supply ≤ Max Supply', total_supply_invariant)
checker.add_invariant('Balances ≥ 0', balance_non_negative)

print("Invariant Checking Simulation:")

# Test with valid state
print("\nValid State:")
checker.set_state('total_supply', 500000)
checker.set_state('max_supply', 1000000)
checker.set_state('balance_alice', 100)
checker.set_state('balance_bob', 200)
violations = checker.check_invariants()
print(f"  Violations: {len(violations)}")

# Test with invalid state
print("\nInvalid State:")
checker.set_state('total_supply', 2000000)  # Exceeds max
checker.set_state('balance_alice', -50)     # Negative balance
violations = checker.check_invariants()
print(f"  Violations: {len(violations)}")
for v in violations:
    print(f"  ❌ {v['invariant']} violated")
    print(f"     State: {v['state']}")

# ----------------------------------------------------------------
# PART C: SECURITY DEVELOPMENT CHECKLIST
# -----------------------------------------------------------------

print("\n" + "-"*60)
print("PART C: Secure Development Checklist")
print("-"*60)

security_checklist = {
    "Design Phase": [
        "Threat model documented",
        "Security requirements defined",
        "Attack surface minimised",
        "Upgradeability strategy defined",
        "Emergency response plan prepared"
    ],
    "Implementation Phase": [
        "Use latest Solidity version",
        "Use audited libraries (OpenZeppelin)",
        "Follow Checks-Effects-Interactions pattern",
        "Proper access control implemented",
        "Events emitted for all state changes"
    ],
    "Testing Phase": [
        "Unit tests written and passing",
        "Integration tests performed",
        "Fuzzing conducted",
        "Formal verification considered",
        "Gas usage optimised"
    ],
    "Audit Phase": [
        "Internal security review conducted",
        "External audit commissioned",
        "Findings addressed",
        "Bug bounty program considered"
    ],
    "Deployment Phase": [
        "Testnet deployment successful",
        "Mainnet deployment planned",
        "Monitoring and alerting configured",
        "Incident response ready"
    ]
}

for phase, items in security_checklist.items():
    print(f"\n{phase.upper()}:")
    for item in items:
        print(f"  □ {item}")

# ----------------------------------------------------------------
# PART D: SUMMARY AND RECOMMENDATIONS
# -----------------------------------------------------------------

print("\n" + "="*70)
print("PART D: Summary and Recommendations")
print("="*70)

print("""
Formal Verification and Secure Development – Key Takeaways:

1. Formal verification uses mathematical proofs to ensure correctness.
2. Approaches: theorem proving, model checking, symbolic execution, property-based testing.
3. Properties: invariants, pre/post-conditions, safety/liveness properties.
4. Tools: Certora, Scribble, Echidna, Mythril, Slither, Foundry.
5. Secure development lifecycle: requirements → design → implementation → testing → deployment.
6. Threat modeling: identify assets, threats, vulnerabilities, risks, and mitigations.

Recommendations:
  - Use formal verification for high-value contracts.
  - Implement property-based testing and fuzzing.
  - Follow secure coding practices.
  - Conduct multiple audits.
  - Monitor contracts in production.
  - Have an incident response plan.
  - Continuously improve security processes.
""")