Solidity 是用於編寫以太坊智能合約的高級程式語言,語法類似 JavaScript,專為以太坊虛擬機(EVM)設計。以下是 Solidity 基本語法的簡要介紹,涵蓋核心概念與範例
1. 合約結構
Solidity 程式以「合約」(Contract)為單位,類似物件導向語言中的類(Class)。每個合約可以包含狀態變數、函數、事件等。
pragma solidity ^0.8.0;
contract MyContract {
// Content
}
pragma solidity
: 指定編譯器版本,例如^0.8.0
表示使用 0.8.x 版本。contract
: 定義一個智能合約,類似 JavaScript 的 Class。
2. 變數與資料型態
Solidity 是靜態型別語言,變數需在宣告時指定型別。
常見資料型態
- 基本型態:
uint
: 無符號整數(非負數,例如uint256
表示 256 位元整數)。int
: 有符號整數。bool
: 布林值(true
或false
)。address
: 以太坊地址(20 字節,用於儲存帳戶或合約地址)。address payable
: 可接收/發送以太幣的地址bytes
: 動態字節陣列。string
: 字串(UTF-8 編碼)。
- 複雜型態:
array
: 陣列(固定或動態長度,例如uint[]
或uint[5]
)。mapping
: 鍵值對映射(類似字典,例如mapping(address => uint)
)。struct
: 自訂結構體。enum
: 枚舉型別。
- 變數可見性:包括
public
(公開)、private
(私有)、internal
(僅限合約內部)、external
(僅外部呼叫)。
範例
contract Variables {
uint256 public myNumber = 42;
address public owner;
address payable public beneficiary;
string public name = "Hello, Solidity!";
bool public isActive = true;
uint[] public numbers = [1, 2, 3];
mapping(address => uint) public balances;
struct User {
string name;
uint age;
}
enum Status { Pending, Active, Inactive }
Status public status = Status.Pending;
}
3. 函數
函數是 Solidity 的核心,用於定義合約的行為。
語法
function functionName(parameterType param) [visibility] [state mutability] [returns (returnType)] {
// Content
}
- 參數:
visibility
: 函數可見性,包含:public
: 最寬鬆,任何人都可以呼叫。external
: 只能從合約外部呼叫,不能在內部呼叫。internal
: 只能在合約內部或由繼承它的合約呼叫。private
: 最嚴格,只能在當前合約內部呼叫。
state mutability
: 狀態可變性,包含:pure
: 不讀寫區塊鏈狀態(純計算)。view
: 僅讀取區塊鏈狀態,不修改。- (無標記): 可讀寫區塊鏈狀態。
payable
:內建修飾詞,允許函數接收以太幣。未標記為 payable 的函數若接收以太幣會失敗。modifier
:自訂修飾符,封裝重複邏輯(例如權限檢查),在函數執行前後應用。returns
: 定義返回值型別。
範例
contract Functions {
uint public count = 0;
function increment() public {
count += 1;
}
function getCount() public view returns (uint) {
return count;
}
function add(uint a, uint b) public pure returns (uint) {
return a + b;
}
function deposit() public payable onlyOwner {
require(msg.value > 0, "Must send Ether");
balances[msg.sender] += msg.value;
}
}
4. 修飾符(Modifiers)
修飾符用於在函數執行前檢查條件,減少重複代碼。
語法
modifier modifierName() {
// Check condition
_; // Continue execution
}
範例
contract Modifiers {
address public owner;
constructor() {
owner = msg.sender; // The deployer is the contract owner
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function restrictedFunction() public onlyOwner {
// Only the owner can execute
}
}
5. 特殊函數
constructor
建構函數,在合約部署時執行一次,用於初始化。
contract ConstructorExample {
address public owner;
uint public initialValue;
constructor(uint _initialValue) {
owner = msg.sender;
initialValue = _initialValue;
}
}
receive
當合約接收以太幣(Ether)且 msg.data 為空時(純粹轉帳)觸發。
receive() external payable {
totalDonations += msg.value;
}
fallback
當合約接收到不匹配任何函數簽名的調用(或帶數據的以太幣轉帳,若為 payable)時觸發。
fallback() external {
revert("Unknown function called");
}
6. 事件(Events)
事件用於記錄區塊鏈上的活動,便於前端或外部應用監聽。
event:定義事件
- 作用:event 用於定義一個事件,指定事件的結構(名稱和參數),類似於宣告一個模板或藍圖。
- 用途:告訴編譯器和區塊鏈這個合約可能會記錄哪些日誌(logs),並定義日誌的格式(參數名稱、型別、是否索引)。
emit:觸發事件
- 作用:emit 用於觸發已定義的事件,將具體的參數值記錄到區塊鏈的日誌中。
- 用途:在合約執行時,透過 emit 將事件資料寫入區塊鏈,通知外部應用某個事件發生(例如轉帳、狀態變化)。
語法
event EventName(type param1, type param2);
emit EventName(value1, value2);
範例
contract Events {
event Transfer(address indexed from, address indexed to, uint amount);
function transfer(address to, uint amount) public {
emit Transfer(msg.sender, to, amount);
}
}
7. 錯誤處理
Solidity 提供三種錯誤處理方式:
require(condition, "error message")
: 檢查條件,失敗時回滾並返回訊息。revert("error message")
: 主動回滾交易。assert(condition)
: 用於內部錯誤檢查,失敗時消耗所有 gas。
範例
contract ErrorHandling {
function deposit(uint amount) public {
require(amount > 0, "Amount must be greater than 0");
// Operation logic
}
}
8. 常見內建變數
也稱為全局變數或特殊變數
關於交易的資訊
msg
: 包含當前交易發送者的資訊。msg.sender
(address
):發起交易的地址。這是最常用且最重要的變數,經常用於權限控制。msg.value
(uint
):交易中附加的 Wei 數量(以太幣的最小單位)。msg.data
(bytes
):完整的呼叫資料,包含函式選擇器和參數編碼。
tx
: 包含整個交易的資訊。tx.origin
(address
):發起整個交易鏈的原始地址。請注意,不建議在智能合約中使用tx.origin
進行權限控制,因為它容易受到釣魚攻擊。使用msg.sender
更安全。
關於區塊的資訊
block
: 包含當前區塊的資訊。block.timestamp
(uint
):當前區塊的時間戳(Unix 時間)。block.number
(uint
):當前區塊的區塊號。block.gaslimit
(uint
):當前區塊的 gas 上限。block.difficulty
(uint
):當前區塊的難度。
關於地址的資訊
address
: 地址相關資訊。address.balance
(uint256
):以太幣餘額
9.儲存位置
Solidity 中的資料根據儲存位置分為三種:
storage
: 儲存在區塊鏈上的持久化資料,類似硬碟。- 在合約層級定義的狀態變數,默認都是這種
memory
:臨時儲存,僅在函數執行期間存在,類似記憶體。- 函數內定義的參數和局部變數,默認是這種
calldata
:唯讀的臨時儲存,用於外部函數呼叫的參數,類似memory
但不可修改。當呼叫一個external
或public
函式時,傳入的參數會被打包並儲存在calldata
中。
特性 | calldata | memory | storage |
---|---|---|---|
可修改性 | 唯讀(Read-Only) | 可修改 | 可修改 |
持久性 | 暫時性,函式結束即清除 | 暫時性,函式結束即清除 | 永久性,儲存在區塊鏈上 |
gas 成本 | 最低 | 較低 | 最高 |
用途 | 外部函式的參數 | 內部函式參數、臨時變數 | 狀態變數(永久儲存) |
範例
function storageExample() public {
uint[] storage localArray = dynamicArray;
uint[] memory tempArray = new uint[](5);
string calldata inputData;
}
10.繼承
Solidity 支援單繼承和多重繼承,允許合約從其他合約繼承狀態變數和函數。
定義
- 使用 is 關鍵字指定父合約。
- 子合約可以繼承父合約的狀態變數、函數和事件。
- 可以使用 virtual(父合約)和 override(子合約)處理函數覆寫。virtual 用於標記父合約中的函數,表示該函數可以被子合約覆寫(override)。另外virtual 和 override 必須成對使用,否則編譯錯誤。
範例
contract Parent {
uint public value;
function setValue(uint _value) public virtual {
value = _value;
}
}
contract Child is Parent {
function setValue(uint _value) public override {
value = _value * 2;
}
}
11.接口
定義了合約的外部函數,允許合約與其他合約交互,而無需知道對方的實現細節,格式如下
interface IContract {
function functionName(uint param) external returns (uint);
event EventName(address indexed user, uint value);
}
範例
合約1:定義接口
interface ICustom {
function transfer(address to, uint amount) external returns (bool);
}
contract MyContract is ICustom {
mapping(address => uint) public balances;
constructor() {
balances[msg.sender] = 1000;
}
function transfer(address to, uint amount) external override returns (bool) {
//
}
}
合約2:使用合約1定義的接口
interface ICustom {
function transfer(address to, uint amount) external returns (bool);
}
contract TokenInteraction {
Icustom public ExternalContract;
constructor(address externalContractAddress) {
ExternalContract = Icustom(externalContractAddress);
}
function transferTokens(address to, uint amount) public {
require(ExternalContract.transfer(to, amount), "Transfer failed");
}
}
Icustom public ExternalContract;
使用接口類型作為狀態變量
ExternalContract = Icustom(externalContractAddress)
這行是指定要使用哪個合給的接口, externalContractAddress
要填合約1的地址
12.命名慣例
在 Solidity 中,帶有下底線前綴的變數(例如 _myVariable)通常出現在以下情境:
函數參數:
- 當函數參數名稱可能與合約中的狀態變數或其他變數名稱衝突時,開發者常使用下底線前綴來區分。如下,_myNumber 是函數參數,用來區分合約中的狀態變數 myNumber。
contract Example {
uint public myNumber;
function setNumber(uint _myNumber) public {
myNumber = _myNumber;
}
}
私有變數的命名慣例:
- 開發者有時會用 _ 前綴來命名私有變數或內部變數,表明它們不應被外部直接訪問。這是 Solidity 社群的慣例,類似於 Python 或其他語言的私有變數命名。
contract Example {
uint private _counter;
}