Solidity 智慧合約開發基礎
Solidity 是以太坊智慧合約的主要程式語言,專為區塊鏈上的去中心化應用設計。自 2014 年首次發布以來,Solidity 已經成為智慧合約開發的業界標準,被廣泛應用於 DeFi、NFT、DAO 等各種區塊鏈應用。
Solidity 智慧合約開發基礎
概述
Solidity 是以太坊智慧合約的主要程式語言,專為區塊鏈上的去中心化應用設計。自 2014 年首次發布以來,Solidity 已經成為智慧合約開發的業界標準,被廣泛應用於 DeFi、NFT、DAO 等各種區塊鏈應用。
本文從基礎語法到實際開發,逐步引導讀者掌握 Solidity 的核心概念與開發技巧。
開發環境設置
本地開發環境
1. 安裝 Node.js 與 npm
Solidity 開發通常使用 Hardhat 或 Foundry 框架,這些工具基於 Node.js 環境:
# 使用 nvm 安裝 Node.js LTS 版本
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 18
nvm use 18
# 驗證安裝
node --version
npm --version
2. 安裝 Hardhat
Hardhat 是最流行的以太坊開發環境:
# 建立專案目錄
mkdir my-solidity-project
cd my-solidity-project
# 初始化 npm
npm init -y
# 安裝 Hardhat
npm install --save-dev hardhat
# 初始化 Hardhat 專案
npx hardhat init
選擇「Create a JavaScript project」開始一個基本專案。
3. 安裝 Foundry(可選)
Foundry 以其高速測試著稱:
# 安裝 Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# 初始化專案
forge init my-project
線上開發環境
Remix IDE
Remix 是瀏覽器版的 Solidity IDE,無需本地安裝:
- 訪問 https://remix.ethereum.org
- 建立新檔案(.sol 副檔名)
- 編譯並部署
Solidity 基礎語法
版本聲明
每個 Solidity 合約必須聲明編譯器版本:
// 精確版本
pragma solidity 0.8.19;
// 版本範圍
pragma solidity ^0.8.0; // >= 0.8.0 且 < 0.9.0
pragma solidity >=0.8.0 <0.9.0;
// 多版本範圍
pragma solidity ^0.8.19;
基本資料型態
數值型態
// 整數
uint8 // 無符號 8 位元整數:0 到 2^8-1
uint256 // 無符號 256 位元整數:0 到 2^256-1(最常用)
int8 // 有符號 8 位元整數:-2^7 到 2^7-1
int256 // 有符號 256 位元整數
// 地址
address // 20 位元組地址
address payable // 可接收 ETH 的地址
// 位元組
bytes1 bytes32 // 固定長度位元組陣列
bytes // 動態長度位元組陣列
// 字串
string // UTF-8 字串
// 布林值
bool // true 或 false
變數宣告
// 狀態變數(儲存在區塊鏈上)
contract Example {
uint256 public count = 0; // 公開狀態變數
address public owner; // 地址類型
mapping(address => uint256) balances; // 映射
bool public isActive = true;
bytes32 public dataHash;
// 建構函數
constructor() {
owner = msg.sender;
}
}
// 局部變數(函數內使用)
function foo() public pure returns (uint256) {
uint256 localVar = 10;
bool flag = true;
address sender = msg.sender;
return localVar;
}
函數結構
// 基本函數結構
function functionName(parameters) [visibility] [stateMutability] [returns(returnType)] {
// 函數主體
}
// 完整範例
function transfer(address to, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
可視性(Visibility)
contract Visibility {
uint256 private privateVar; // 僅合約內可存取
uint256 internal internalVar; // 合約及繼承合約可存取
uint256 public publicVar; // 外部與內部都可存取(自動生成 getter)
// 外部函數:只能透過交易呼叫
function externalFunc() external view returns (uint256) {
return privateVar; // 可存取
}
// 內部函數:合約內與繼承合約可呼叫
function internalFunc() internal view returns (uint256) {
return internalVar;
}
// 公開函數:內外部都可呼叫
function publicFunc() public pure returns (string memory) {
return "public";
}
// 私有函數:僅合約內可呼叫
function privateFunc() private pure returns (bool) {
return true;
}
}
狀態可變性(State Mutability)
contract Mutability {
// pure:完全不讀寫狀態
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b;
}
// view:讀取狀態但不修改
function getBalance(address account) public view returns (uint256) {
return account.balance;
}
// payable:可接收 ETH
function deposit() public payable returns (uint256) {
return msg.value;
}
// 預設:可讀寫狀態
uint256 public count;
function increment() public {
count++;
}
}
控制流語句
// 條件判斷
function check(uint256 x) public pure returns (string memory) {
if (x > 10) {
return "big";
} else if (x > 5) {
return "medium";
} else {
return "small";
}
}
// 迴圈(需注意 Gas 限制)
function sum(uint256 n) public pure returns (uint256) {
uint256 total = 0;
for (uint256 i = 1; i <= n; i++) {
total += i;
}
return total;
}
// require 用於錯誤處理
function withdraw(uint256 amount) public {
require(amount > 0, "Amount must be greater than 0");
require(balances[msg.sender] >= amount, "Insufficient balance");
// ...
}
// revert 用於主動回滾
function transfer(address to, uint256 amount) public {
if (balances[msg.sender] < amount) {
revert("Insufficient balance");
}
// ...
}
映射(Mapping)與陣列
映射(Mapping)
Mapping 是 Solidity 中最重要的資料結構之一:
contract MappingExample {
// 基本映射
mapping(address => uint256) public balances;
// 巢狀映射
mapping(address => mapping(address => uint256)) public allowances;
// 映射操作
function setBalance(address account, uint256 amount) public {
balances[account] = amount;
}
function getBalance(address account) public view returns (uint256) {
return balances[account];
}
// 映射無法直接迭代,需搭配陣列
address[] public accountList;
function addAccount(address account) public {
accountList.push(account);
}
function getAllBalances() public view returns (uint256[] memory) {
uint256[] memory result = new uint256[](accountList.length);
for (uint256 i = 0; i < accountList.length; i++) {
result[i] = balances[accountList[i]];
}
return result;
}
}
陣列
contract ArrayExample {
// 動態陣列
uint256[] public dynamicArray;
address[] public addressArray;
// 固定長度陣列
uint256[5] public fixedArray;
// 陣列操作
function arrayOps() public {
// push:新增元素
dynamicArray.push(1);
dynamicArray.push(2);
// length:取得長度
uint256 len = dynamicArray.length;
// 存取元素
uint256 first = dynamicArray[0];
// pop:移除最後元素
dynamicArray.pop();
// 刪除(重置為預設值)
delete dynamicArray[0];
delete dynamicArray; // 重置整個陣列
}
// 結構體陣列
struct Person {
string name;
uint256 age;
}
Person[] public people;
function addPerson(string memory _name, uint256 _age) public {
people.push(Person(_name, _age));
// 或使用具名參數
people.push(Person({name: _name, age: _age}));
}
}
結構體與列舉
結構體(Struct)
contract StructExample {
struct Token {
string name;
string symbol;
uint256 totalSupply;
mapping(address => uint256) balances;
}
// 建立結構體變數
Token public token;
// 初始化結構體
function initToken() public {
token = Token({
name: "My Token",
symbol: "MTK",
totalSupply: 1000000
});
}
// 使用 mapping 搭配結構體
mapping(address => Token[]) public userTokens;
function mint(address to, string memory name, string memory symbol) public {
Token memory newToken = Token({
name: name,
symbol: symbol,
totalSupply: 0
});
userTokens[to].push(newToken);
}
}
列舉(Enum)
contract EnumExample {
enum Status { Pending, Active, Completed, Cancelled }
Status public currentStatus;
function setStatus(Status _status) public {
currentStatus = _status;
}
function complete() public {
currentStatus = Status.Completed;
}
function isCompleted() public view returns (bool) {
return currentStatus == Status.Completed;
}
}
事件與日誌
事件(Event)
事件是用於記錄區塊鏈狀態變更的機制:
contract EventExample {
// 定義事件
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
// 映射餘額
mapping(address => uint256) public balances;
mapping(address => mapping(address => uint256)) public allowances;
// 發送代幣
function transfer(address to, uint256 amount) public returns (bool) {
require(balances[msg.sender] >= amount, "Insufficient balance");
balances[msg.sender] -= amount;
balances[to] += amount;
// 發送事件
emit Transfer(msg.sender, to, amount);
return true;
}
// 授權
function approve(address spender, uint256 amount) public returns (bool) {
allowances[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
// 轉讓(使用授權額度)
function transferFrom(address from, address to, uint256 amount) public returns (bool) {
require(balances[from] >= amount, "Insufficient balance");
require(allowances[from][msg.sender] >= amount, "Allowance exceeded");
balances[from] -= amount;
balances[to] += amount;
allowances[from][msg.sender] -= amount;
emit Transfer(from, to, amount);
return true;
}
}
indexed 關鍵字
// indexed 參數可用於篩選事件
event Transfer(
address indexed from, // 可被索引
address indexed to, // 可被索引
uint256 value // 不可被索引
);
// 在客戶端篩選特定地址的轉帳
// contract.Transfer({ from: "0x123..." })
繼承與修飾符
繼承
// 基底合約
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
owner = msg.sender;
}
modifier onlyOwner() {
require(msg.sender == owner, "Not the owner");
_;
}
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "Invalid address");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
// 繼承合約
contract MyToken is Ownable {
string public name = "My Token";
string public symbol = "MTK";
uint256 public totalSupply;
mapping(address => uint256) public balances;
function mint(address to, uint256 amount) public onlyOwner {
balances[to] += amount;
totalSupply += amount;
}
}
多重繼承
contract Base1 {
function foo() public pure virtual returns (string memory) {
return "Base1";
}
}
contract Base2 {
function foo() public pure virtual returns (string memory) {
return "Base2";
}
}
// 線性繼承順序:Derived -> Base2 -> Base1
contract Derived is Base2, Base1 {
// 如果 Base1 和 Base2 都有 foo,需覆蓋
function foo() public pure override(Base2, Base1)) {
return returns (string memory "Derived";
}
}
修飾符(Modifier)
contract ModifierExample {
address public owner;
uint256 public count = 0;
mapping(address => uint256) public lastAction;
constructor() {
owner = msg.sender;
}
// 簡單修飾符
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
// 帶參數的 modifier only修飾符
After(uint256 time) {
require(block.timestamp >= time, "Too early");
_;
}
// 檢查並更新狀態的修飾符
modifier rateLimit() {
require(
block.timestamp > lastAction[msg.sender] + 1 minutes,
"Rate limit exceeded"
);
_;
lastAction[msg.sender] = block.timestamp;
}
function increment() public onlyOwner rateLimit {
count++;
}
function delayedAction() public onlyAfter(1234567890) {
// ...
}
}
介面與函式庫
介面(Interface)
// ERC-20 代幣介面
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
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);
}
// 使用介面
contract TokenSwap {
function swap(address tokenA, address tokenB, uint256 amount) public {
IERC20(tokenA).transferFrom(msg.sender, address(this), amount);
// ...
}
}
函式庫(Library)
// 數學函式庫
library Math {
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
}
// 使用函式庫
contract UseLibrary {
function testMath(uint256 a, uint256 b) public pure returns (uint256) {
return Math.min(a, b);
}
}
// 使用 using for
library Address {
function isContract(address account) internal view returns (bool) {
uint256 size;
assembly {
size := extcodesize(account)
}
return size > 0;
}
}
contract UseAddress {
using Address for address;
function checkContract(address addr) public view returns (bool) {
return addr.isContract();
}
}
ERC 標準
ERC-20:代幣標準
// 簡化的 ERC-20 實現
contract SimpleToken is IERC20 {
string public name = "Simple Token";
string public symbol = "SIM";
uint8 public decimals = 18;
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
function transfer(address to, uint256 amount) public override returns (bool) {
require(balanceOf[msg.sender] >= amount, "Insufficient balance");
balanceOf[msg.sender] -= amount;
balanceOf[to] += amount;
emit Transfer(msg.sender, to, amount);
return true;
}
function approve(address spender, uint256 amount) public override returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
require(balanceOf[from] >= amount, "Insufficient balance");
require(allowance[from][msg.sender] >= amount, "Allowance exceeded");
balanceOf[from] -= amount;
balanceOf[to] += amount;
allowance[from][msg.sender] -= amount;
emit Transfer(from, to, amount);
return true;
}
function mint(address to, uint256 amount) public {
balanceOf[to] += amount;
totalSupply += amount;
emit Transfer(address(0), to, amount);
}
}
ERC-721:NFT 標準
// ERC-721 核心介面
interface IERC721 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256);
function ownerOf(uint256 tokenId) external view returns (address);
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool approved) external;
function getApproved(uint256 tokenId) external view returns (address);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
常見安全漏洞與防範
1. 重入攻擊
// 有漏洞的合約
contract Vulnerable {
mapping(address => uint256) public balances;
function withdraw() public {
(bool success, ) = msg.sender.call{value: balances[msg.sender]}("");
balances[msg.sender] = 0; // 在呼叫後修改狀態!
}
}
// 安全版本:Checks-Effects-Interactions 模式
contract Secure {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
// Checks
// Effects(先修改狀態)
balances[msg.sender] = 0;
// Interactions(最後與其他合約互動)
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
}
}
2. 整數溢位
// 使用 SafeMath 庫(舊版本)
import "@openzeppelin/contracts/utils/SafeMath.sol";
contract WithSafeMath {
using SafeMath for uint256;
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a.add(b);
}
}
// Solidity 0.8+ 內建溢位檢查
contract Solidity08Safe {
function add(uint256 a, uint256 b) public pure returns (uint256) {
return a + b; // 自動檢查溢位,失敗會 revert
}
// 如需繞過檢查
function addUnchecked(uint256 a, uint256 b) public pure returns (uint256) {
unchecked {
return a + b;
}
}
}
3. 存取控制
// 錯誤:初始化函數可被任何人呼叫
contract BadInit {
address public owner;
bool public initialized;
function init() public {
require(!initialized);
owner = msg.sender;
initialized = true;
}
}
// 正確:使用 constructor
contract GoodInit {
address public owner;
constructor() {
owner = msg.sender;
}
}
// 或使用 initializer 修飾符(OpenZeppelin)
contract Initializable {
bool private initialized;
modifier initializer() {
require(!initialized, "Already initialized");
_;
initialized = true;
}
function init() public initializer {
// 初始化邏輯
}
}
測試與部署
Hardhat 測試
// test/sample-test.js
const { expect } = require("chai");
describe("Token", function () {
let token;
let owner;
let addr1;
beforeEach(async function () {
const Token = await ethers.getContractFactory("MyToken");
[owner, addr1] = await ethers.getSigners();
token = await Token.deploy();
await token.deployed();
});
it("Should assign total supply to owner", async function () {
const ownerBalance = await token.balanceOf(owner.address);
expect(await token.totalSupply()).to.equal(ownerBalance);
});
it("Should transfer tokens between accounts", async function () {
await token.transfer(addr1.address, 50);
expect(await token.balanceOf(addr1.address)).to.equal(50);
});
});
部署到測試網路
# 部署到 Sepolia 測試網路
npx hardhat run scripts/deploy.js --network sepolia
# 或使用 Hardhat Ignition
npx hardhat ignition deploy
開發最佳實踐
1. 程式碼組織
- 使用 OpenZeppelin 經過審計的合約庫
- 將複雜邏輯拆分為多個合約
- 遵循「合約簡單」原則
2. Gas 優化
- 減少不必要的狀態變更
- 使用 mapping 代替陣列
- 打包結構體成單一 slot
- 使用 view/pure 函數
3. 安全檢查清單
- [ ] 使用 ReentrancyGuard
- [ ] 實作存取控制
- [ ] 驗證輸入參數
- [ ] 處理邊界情況
- [ ] 進行專業安全審計
4. 文件記錄
- 為每個合約和函數添加 NatSpec 註釋
- 說明假設條件與限制
/// @title Simple Storage
/// @author Your Name
/// @notice 儲存簡單數值的合約
/// @dev 使用 mapping 儲存資料
contract SimpleStorage {
/// @notice 儲存一個數值
/// @param value 要儲存的數值
function set(uint256 value) external {
// ...
}
}
總結
Solidity 作為以太坊智慧合約的主要開發語言,其語法與 JavaScript 相似但包含區塊鏈特有的概念:Gas 成本、狀態儲存、與不可變性。掌握這些概念對於開發安全可靠的智慧合約至關重要。
本文介紹了 Solidity 的核心語法,包括變數、函數、控制流、資料結構、繼承、介面、以及安全最佳實踐。透過不斷練習與實際專案經驗,開發者可以逐漸精通這門語言並構建各類創新的去中心化應用。
建議開發者持續關注以太坊改進提案(EIP)的動態,因為 Solidity 語言與 EVM 都在持續演進。同時,遵循安全最佳實踐並使用經過審計的開源庫(如 OpenZeppelin)是構建生產級智慧合約的關鍵。
相關文章
- Flashbots MEV-Boost 完整指南:以太坊 MEV 基礎設施深度解析 — Flashbots 是以太坊生態系統中最重要的 MEV(最大可提取價值)基礎設施之一。自 2020 年成立以來,Flashbots 從一個研究組織發展成為涵蓋 MEV 提取、交易隱私、去中心化排序等多個領域的綜合性平台。MEV-Boost 作為 Flashbots 的核心產品,已經成為以太坊網路中不可或缺的基礎設施,顯著改變了 MEV 的分配方式和區塊生產的生態格局。本文深入解析 Flashbot
- 隱私協議合規框架與實務操作指南:以 Aztec、Railgun 為核心的深度分析 — 區塊鏈隱私協議在提供交易隱私的同時,也面臨著全球監管機構日益嚴格的審查。2022 年 Tornado Cash 遭受 OFAC 制裁後,整個隱私協議領域陷入了合規性的深層次討論。Aztec Network 和 Railgun 作為以太坊生態中最重要的兩個隱私解決方案,它們在技術架構和合規策略上走出了一條不同的道路。本文深入分析這兩個協議的合規框架,提供詳細的實務操作指南,幫助用戶和開發者在保護隱私
- Noir 隱私合約開發完整指南:從零知識證明到實際應用 — Noir 是由 Aztec Labs 開發的開源程式語言,專為編寫零知識證明(Zero-Knowledge Proof)電路而設計。作為以太坊隱私 Layer 2 解決方案 Aztec Network 的核心組成部分,Noir 提供了一種抽象化的方式,讓開發者能夠以傳統程式設計的思維編寫隱私保護應用,而無需深入理解複雜的密碼學底層實現。本文將全面介紹 Noir 的語言特性、开发環境、實際應用場景,
- OpenZeppelin 智慧合約庫使用完整指南 — OpenZeppelin 是以太坊智慧合約開發領域最重要的開源庫和工具提供商。其合約庫經過嚴格審計、被廣泛採用,並成為智慧合約安全的行業標準。本文將全面介紹 OpenZeppelin 庫的核心組件、使用方法、最佳實踐,以及在實際項目中的集成策略。適合具有一定 Solidity 基礎的開發者閱讀。
- Module Chain 生態完整指南:2025-2026 年模組化區塊鏈的技術架構與發展趨勢 — 區塊鏈產業經歷了從單一鏈到多鏈生態的演進,如今正在邁向一個更加專業化、模組化的未來。Module Chain(模組化區塊鏈)作為這一趨勢的代表作,代表了一種全新的區塊鏈設計範式——將區塊鏈的核心功能(共識、執行、結算、數據可用性、治理)拆解為可插拔的模組,讓開發者能夠根據具體需求自由組合。
延伸閱讀與來源
- Ethereum.org Developers 官方開發者入口與技術文件
- EIPs 以太坊改進提案
這篇文章對您有幫助嗎?
請告訴我們如何改進:
評論
發表評論
注意:由於這是靜態網站,您的評論將儲存在本地瀏覽器中,不會公開顯示。
目前尚無評論,成為第一個發表評論的人吧!