Ethereum & Smart Contracts

Solidity Fundamentals

Solidity Fundamentals

Module 3 of Ethereum & Smart Contracts


What Is Solidity?

Solidity is the most popular smart contract language:

  • Designed specifically for the EVM
  • Statically typed
  • Object-oriented with inheritance
  • Compiles to EVM bytecode

Similar syntax to JavaScript/C++, but with blockchain-specific features.


Basic Structure

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract SimpleStorage {
    // State variables
    uint256 private storedValue;

    // Events
    event ValueChanged(uint256 newValue);

    // Functions
    function set(uint256 value) public {
        storedValue = value;
        emit ValueChanged(value);
    }

    function get() public view returns (uint256) {
        return storedValue;
    }
}

Data Types

Value Types

// Integers
uint256 a = 100;        // Unsigned, 0 to 2^256-1
int256 b = -50;         // Signed, -2^255 to 2^255-1
uint8 c = 255;          // Smaller sizes available

// Boolean
bool isActive = true;

// Address
address user = 0x1234...;
address payable recipient = payable(user);  // Can receive ETH

// Bytes
bytes32 hash = keccak256("hello");
bytes1 singleByte = 0xff;

Reference Types

// Arrays
uint256[] dynamicArray;
uint256[10] fixedArray;

// Strings
string name = "Ethereum";

// Mappings
mapping(address => uint256) balances;
mapping(address => mapping(address => uint256)) allowances;

// Structs
struct User {
    address addr;
    uint256 balance;
    bool isActive;
}

Functions

Visibility

function publicFunc() public {}     // Anyone can call
function externalFunc() external {} // Only external calls
function internalFunc() internal {} // This + derived contracts
function privateFunc() private {}   // Only this contract

State Mutability

function readWrite() public {}              // Modifies state
function viewOnly() public view {}          // Reads state only
function pureCalc() public pure {}          // No state access
function receiveETH() public payable {}     // Can receive ETH

Function Modifiers

modifier onlyOwner() {
    require(msg.sender == owner, "Not owner");
    _;  // Continue with function
}

function sensitiveAction() public onlyOwner {
    // Only owner can call
}

Control Structures

// If/Else
if (amount > 100) {
    // do something
} else if (amount > 50) {
    // do something else
} else {
    // default
}

// Loops
for (uint i = 0; i < 10; i++) {
    // iterate (be careful of gas!)
}

while (condition) {
    // loop while true
}

// Error Handling
require(amount > 0, "Amount must be positive");
assert(totalSupply == expectedSupply);
revert("Something went wrong");

Important Concepts

msg Object

msg.sender  // Caller's address
msg.value   // ETH sent (in wei)
msg.data    // Complete calldata

Ether Units

1 wei = 1
1 gwei = 10^9 wei
1 ether = 10^18 wei

require(msg.value >= 1 ether, "Send at least 1 ETH");

Events

event Transfer(
    address indexed from,
    address indexed to,
    uint256 value
);

function transfer(address to, uint256 amount) public {
    // ... logic ...
    emit Transfer(msg.sender, to, amount);
}
  • Indexed parameters are searchable
  • Events are cheap (stored in logs, not state)
  • Essential for dApp frontends

Inheritance

contract Ownable {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
}

contract MyContract is Ownable {
    // Inherits owner and onlyOwner

    function adminOnly() public onlyOwner {
        // Protected function
    }
}

Multiple Inheritance

contract Child is Parent1, Parent2 {
    // Inherits from both
    // Order matters for constructor calls
}

Interfaces

interface IERC20 {
    function transfer(address to, uint256 amount) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract MyContract {
    function sendTokens(address token, address to, uint256 amount) external {
        IERC20(token).transfer(to, amount);
    }
}

Common Patterns

Checks-Effects-Interactions

function withdraw(uint256 amount) public {
    // CHECKS
    require(balances[msg.sender] >= amount, "Insufficient");

    // EFFECTS
    balances[msg.sender] -= amount;

    // INTERACTIONS
    (bool success, ) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

Pull Over Push

// BAD: Push payments
function distribute() public {
    for (uint i = 0; i < users.length; i++) {
        payable(users[i]).transfer(amounts[i]);  // Can fail!
    }
}

// GOOD: Pull payments
mapping(address => uint256) public pendingWithdrawals;

function withdraw() public {
    uint256 amount = pendingWithdrawals[msg.sender];
    pendingWithdrawals[msg.sender] = 0;
    payable(msg.sender).transfer(amount);
}

Gas Optimization Tips

  1. Use calldata for external function arrays
function process(uint256[] calldata data) external {} // Cheaper
  1. Pack storage variables
// Uses 2 slots
uint256 a;
uint128 b;
uint128 c;

// Uses 2 slots (packed)
uint128 b;
uint128 c;  // Packed with b
uint256 a;
  1. Use immutable for constants
address public immutable WETH;  // Set once in constructor, no SLOAD
  1. Cache storage reads
uint256 cachedBalance = balances[user];
// Use cachedBalance multiple times

Security Best Practices

  1. Use latest Solidity (0.8+ has overflow protection)
  2. Check return values of external calls
  3. Use ReentrancyGuard for state-changing functions
  4. Validate all inputs
  5. Use OpenZeppelin battle-tested contracts
  6. Get audited before mainnet

Key Takeaways

  1. Solidity is EVM-specific — different from web development
  2. State changes cost gas — optimize storage
  3. Security is critical — code handles real money
  4. Events are essential — for off-chain tracking
  5. Inheritance and interfaces enable composability
  6. Follow patterns — Checks-Effects-Interactions, etc.

Questions to Consider

  1. Why is there no floating-point in Solidity?
  2. When should you use memory vs calldata?
  3. What's the cost difference between storage and memory?
  4. How do you test Solidity contracts?