Solidity語法指南

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: 布林值(truefalse)。
    • 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 但不可修改。當呼叫一個 externalpublic 函式時,傳入的參數會被打包並儲存在 calldata 中。
特性calldatamemorystorage
可修改性唯讀(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;  
      }