2.1 Solidity Overview
Solidity is the dominant programming language for Ethereum smart contracts. As a legal professional, understanding Solidity enables you to read contract code, identify potential issues during due diligence, and communicate effectively with technical teams.
Why Legal Professionals Should Learn Solidity
- Due Diligence: Review code to verify it matches documentation and legal agreements
- Risk Assessment: Identify potential vulnerabilities or problematic patterns
- Dispute Resolution: Understand what the code actually does vs. what parties expected
- Client Communication: Bridge the gap between technical and legal teams
- Regulatory Compliance: Assess whether code behavior complies with regulations
Basic Contract Structure
// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; /// @title Simple Storage Contract /// @notice Demonstrates basic Solidity structure contract SimpleStorage { // State variable - stored on blockchain uint256 private storedValue; // Event - logs activity for off-chain monitoring event ValueChanged(uint256 newValue, address changedBy); // Function - modifies state (costs gas) function setValue(uint256 _value) external { storedValue = _value; emit ValueChanged(_value, msg.sender); } // View function - reads state (free when called externally) function getValue() external view returns (uint256) { return storedValue; } }
SPDX License: Required license identifier
Pragma: Specifies compiler version
Contract: Similar to a class in OOP
State Variables: Data stored on blockchain
Events: Logging mechanism for off-chain applications
Functions: Executable code units
2.2 Data Types
Value Types
| Type | Description | Example | Legal Relevance |
|---|---|---|---|
uint256 | Unsigned integer (0 to 2^256-1) | uint256 balance = 1000; | Token amounts, timestamps |
int256 | Signed integer | int256 change = -50; | Price changes, adjustments |
bool | True or false | bool isActive = true; | Status flags, permissions |
address | 20-byte Ethereum address | address owner; | User identity, ownership |
bytes32 | Fixed-size byte array | bytes32 hash; | Document hashes, IDs |
Prior to Solidity 0.8.0, integers could overflow silently (e.g., 255 + 1 = 0 for uint8). This caused numerous exploits. Always verify contracts use Solidity 0.8+ or SafeMath library.
Reference Types
// Arrays - ordered collections uint256[] public dynamicArray; uint256[10] public fixedArray; // Mappings - key-value storage (like dictionaries) mapping(address => uint256) public balances; mapping(address => mapping(address => uint256)) public allowances; // Structs - custom data structures struct Agreement { address partyA; address partyB; uint256 amount; uint256 deadline; bool executed; } Agreement[] public agreements;
When reviewing contracts, examine how data is stored. Mappings cannot be iterated - if a contract needs to enumerate all users, it must maintain a separate array. This affects functionalities like airdrops, refunds, or mass distributions.
Special Variables
| Variable | Type | Description |
|---|---|---|
msg.sender | address | Address that called the function |
msg.value | uint256 | ETH sent with the transaction (in wei) |
block.timestamp | uint256 | Current block timestamp (can be manipulated +/- 15 seconds) |
block.number | uint256 | Current block number |
tx.origin | address | Original transaction sender (avoid using!) |
2.3 Functions and Modifiers
Function Visibility
| Visibility | Who Can Call | Use Case |
|---|---|---|
public | Anyone (internal + external) | General access functions |
external | Only from outside the contract | API endpoints |
internal | This contract + derived contracts | Shared logic in inheritance |
private | Only this contract | Implementation details |
All blockchain data is public. "Private" only restricts which contracts can call a function - the data is still visible to anyone reading the blockchain. Never store sensitive information on-chain.
Function Modifiers
contract AccessControl { address public owner; bool public paused; // Custom modifier - reusable access control modifier onlyOwner() { require(msg.sender == owner, "Not authorized"); _; // Placeholder for function body } modifier whenNotPaused() { require(!paused, "Contract is paused"); _; } // Function with multiple modifiers function sensitiveAction() external onlyOwner whenNotPaused { // Only executes if both modifiers pass } function pause() external onlyOwner { paused = true; } }
State Mutability
- No keyword (default): Can read and write state, costs gas
- view: Can only read state, free when called externally
- pure: Cannot read or write state, only uses inputs
- payable: Can receive ETH with the transaction
function transfer(address to, uint256 amount) external { // Modifies state - costs gas balances[msg.sender] -= amount; balances[to] += amount; } function getBalance(address account) external view returns (uint256) { // Only reads - free when called externally return balances[account]; } function calculateFee(uint256 amount) external pure returns (uint256) { // No state access - pure computation return amount * 3 / 100; // 3% fee } function deposit() external payable { // Can receive ETH - msg.value contains amount balances[msg.sender] += msg.value; }
2.4 Common Patterns for Legal Review
Ownership Pattern
Most contracts implement ownership for administrative control. Review who can change ownership and what powers the owner has.
contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Ownable: caller is not the owner"); _; } function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Ownable: new owner is the zero address"); emit OwnershipTransferred(owner, newOwner); owner = newOwner; } // DANGEROUS: Owner can renounce ownership permanently function renounceOwnership() public onlyOwner { emit OwnershipTransferred(owner, address(0)); owner = address(0); } }
1. Who is the initial owner?
2. What functions are restricted to the owner?
3. Can ownership be transferred? To whom?
4. Is there a renounceOwnership function? What happens if used?
5. Is there a timelock on ownership changes?
Pausable Pattern
contract Pausable is Ownable { bool public paused; event Paused(address account); event Unpaused(address account); modifier whenNotPaused() { require(!paused, "Pausable: paused"); _; } modifier whenPaused() { require(paused, "Pausable: not paused"); _; } function pause() external onlyOwner whenNotPaused { paused = true; emit Paused(msg.sender); } function unpause() external onlyOwner whenPaused { paused = false; emit Unpaused(msg.sender); } }
Pause functions give owners the power to freeze user funds or prevent withdrawals. This creates significant counterparty risk. In legal terms, this may constitute potential breach of fiduciary duty if misused.
ERC-20 Token Standard
The most common smart contract standard. Understanding ERC-20 is essential for token-related legal work.
interface IERC20 { // Returns total token supply function totalSupply() external view returns (uint256); // Returns account balance function balanceOf(address account) external view returns (uint256); // Transfer tokens to recipient function transfer(address to, uint256 amount) external returns (bool); // Check spending allowance function allowance(address owner, address spender) external view returns (uint256); // Approve spender to use tokens function approve(address spender, uint256 amount) external returns (bool); // Transfer from another account (requires allowance) function transferFrom(address from, address to, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); }
2.5 Gas Optimization
Gas costs directly affect usability and economics of smart contracts. Understanding gas optimization helps identify efficient vs. wasteful contract designs.
Storage vs. Memory vs. Calldata
| Location | Persistence | Gas Cost | Use Case |
|---|---|---|---|
storage | Permanent (on-chain) | Very High (20,000 gas for new slot) | State variables |
memory | Function execution only | Low (3 gas per word) | Local computations |
calldata | Function execution only | Lowest (read-only) | External function parameters |
Gas Optimization Techniques
// INEFFICIENT: Multiple storage reads function inefficientSum() external view returns (uint256) { uint256 total = 0; for (uint256 i = 0; i < values.length; i++) { total += values[i]; // Storage read each iteration! } return total; }
// EFFICIENT: Cache in memory function efficientSum() external view returns (uint256) { uint256[] memory _values = values; // Single storage read uint256 total = 0; uint256 length = _values.length; // Cache length for (uint256 i = 0; i < length; ) { total += _values[i]; unchecked { ++i; } // Skip overflow check (safe here) } return total; }
Watch for: loops over storage arrays, repeated storage reads of the same variable, string comparisons, unnecessary state variables. High gas costs may indicate poor design or intentional barriers to user actions.
Key Takeaways
- Solidity knowledge enables legal professionals to conduct meaningful technical due diligence
- Private does not mean secret - all blockchain data is publicly readable
- Review ownership patterns and pause functions for centralization risks
- Understand ERC-20 standard for token-related legal work
- Gas costs affect contract usability - high costs may be intentional barriers
- Always verify Solidity version (0.8+ has built-in overflow protection)