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?