🎓 A1Learner
🔍
0 of 45 lessons completed

Solidity Advanced Introduction

Solidity Advanced is the combination of intermediate pieces into bigger working contracts. Each advanced lesson adds one new idea to pieces you already know.

A child reuses its parent's functions

MemberVault inherits from Vault. It gains deposit without rewriting it.

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

contract Vault {
    uint256 public balance;

    function deposit(uint256 amount) public {
        balance += amount;
    }
}

contract MemberVault is Vault {
    address public owner;

    event Withdrawn(uint256 amount);

    constructor() {
        owner = msg.sender;
    }

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

    function withdraw(uint256 amount) public onlyOwner {
        require(amount <= balance, "too much");
        balance -= amount;
        emit Withdrawn(amount);
    }
}

A guard and a pull withdrawal protect the balance

GuardedVault updates the balance before it sends ether. The guard blocks a second entry during the send.

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

contract GuardedVault {
    mapping(address => uint256) public balances;
    bool public locked;

    modifier nonReentrant() {
        require(!locked, "reentered");
        locked = true;
        _;
        locked = false;
    }

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }

    function withdraw(uint256 amount) public nonReentrant {
        require(balances[msg.sender] >= amount, "too much");
        balances[msg.sender] -= amount;
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "send failed");
    }
}

Roles and custom errors control who can act

RoleVault gives the admin the power to add members. Only a member can set the score.

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

contract RoleVault {
    address public admin;
    mapping(address => bool) public members;
    uint256 public score;

    error NotAdmin();
    error NotMember();

    constructor() {
        admin = msg.sender;
    }

    function addMember(address person) public {
        if (msg.sender != admin) revert NotAdmin();
        members[person] = true;
    }

    function setScore(uint256 newScore) public {
        if (!members[msg.sender]) revert NotMember();
        score = newScore;
    }
}

Example

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

contract Ledger {
    mapping(address => uint256) public balances;

    function deposit() public payable {
        balances[msg.sender] += msg.value;
    }
}

contract AdvancedVault is Ledger {
    address public owner;
    bool public locked;

    event Deposited(address depositor, uint256 amount);
    event Withdrawn(address withdrawer, uint256 amount);

    error NotOwner();
    error InsufficientBalance();

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        if (msg.sender != owner) revert NotOwner();
        _;
    }

    modifier nonReentrant() {
        require(!locked, "reentered");
        locked = true;
        _;
        locked = false;
    }

    function depositWithEvent() public payable {
        deposit();
        emit Deposited(msg.sender, msg.value);
    }

    function withdraw(uint256 amount) public nonReentrant {
        if (balances[msg.sender] < amount) revert InsufficientBalance();
        balances[msg.sender] -= amount;
        (bool success, ) = payable(msg.sender).call{value: amount}("");
        require(success, "send failed");
        emit Withdrawn(msg.sender, amount);
    }
}

Key points

  • Inheritance: A child contract reuses a parent contract's functions and state.
  • Modifiers: A modifier is a reusable check that runs before a function body.
  • Guards: A reentrancy guard blocks a second entry into a function while the first entry still runs.
  • Pull payments: A pull payment lets each person withdraw their own funds alone instead of the contract paying everyone at once.
  • Combination: Small pieces combine into one contract that holds a balance, checks the caller, and reports each deposit.

In this example, Ledger holds the balances mapping and the deposit function. AdvancedVault inherits from Ledger and adds an owner, a reentrancy guard, two events, and two custom errors. depositWithEvent reuses the inherited deposit and then announces the deposit. withdraw checks the balance with a custom error, updates the balance before sending, and then announces the withdrawal.

Note

A contract that combines pieces still follows the same intermediate rules for each piece.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Remix IDE

Exercise

AdvancedVault inherits deposit from Ledger and adds its own withdraw. Which part does the child reuse without rewriting it?