2023-05-10|閱讀時間 ‧ 約 16 分鐘

【開發智能合約 — Solidity系列】實作篇Ep.6 -關於可視範圍(Visibility)

為什麼要特別介紹可視範圍呢? 試想,假如我們的合約裡有些非常重要的內容只能侷限於合約內使用,此時就可以運用可視範圍的技巧,將某些重要的功能、狀態鎖定在合約內使用,不隨意開放給外部調用,避免汙染內部,但有些又是共用的內容及功能時,我們就可以利用公開的可視範圍讓相同的功能能夠重複使用。
合約中又可以依照不同的分責區分可視範圍,在Solidity的世界中主要會分為兩大類型的可視範圍,分別是狀態變數(State Variable)跟功能(Function)。

狀態變數(State Variable)的可視範圍

public

這種可視範圍最為寬鬆,除了內部合約以外,繼承與外部合約也都能夠進行調用。
contract Base {
   // [Scope]
   //   - inside Base Contract
   //   - inside contracts that inherit this contract
   //   - by other contracts
   string public publicMsg = "public state variables";    function testScope() public virtual  {
       // ✔️ 可以存取「Base」的public 變數
       require(bytes(publicMsg).length > 0, "cannot access public state variable");
   }} contract Extend is Base {    function testScope() public view override {
      
// ✔️ 可以存取「Base」的public 變數
       require(bytes(publicMsg).length > 0, "cannot access public state variable");
   }
}contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();        // ✔️ 可以存取「Base」的public變數
       b.publicMsg;
   }
}

internal

除了自身的合約以外,繼承的合約也能夠調用,但外部合約是不可視的。
contract Base {
   // [Scope]
   //   - inside Base Contract
   //   - inside contracts that inherit this contract
   string internal internalMsg = "internal state variables";    function testScope() public virtual  {
       // ✔️ 可以存取「Base」的internal 變數
       require(bytes(internalMsg).length > 0, "cannot access internal state variable");
   }} contract Extend is Base {    function testScope() public view override {
       // ✔️ 可以存取「Base」的internal 變數
       require(bytes(internalMsg).length > 0, "cannot access internal state variable");
   }
}contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();
 
       // ❌ 無法存取「Base」的internal變數
       // @notice 語法檢測錯誤: not found or not visible after argument-dependent lookup in contract Base.
       // b.internalMsg;
   }
}

private

這種可視範圍僅侷限於自身的合約之中,不可被外部合約調用,包括繼承的合約也是不可視的。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7.0;contract Base {
   // State variables
   // [Scope]
   //   - inside Base Contract
   string private privateMsg = "private state variables";
   function testScope() public virtual  {
       // ✔️ 可以存取「Base」的private 變數
       require(bytes(privateMsg).length > 0, "cannot access private state variable");
   }} contract Extend is Base {
   function testScope() public view override {
       // ❌ 無法使用基礎合約的私有狀態變數
       // @notice 語法檢測錯誤: DeclarationError: Undeclared identifier.
       // require(bytes(privateMsg).length > 0, "cannot access private state variable");
   }
}contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();        // ❌ 無法存取「Base」的private變數
       // @notice 語法檢測錯誤: not found or not visible after argument-dependent lookup in contract Base.
       // b.privateMsg;
   }
}

功能(Function)的可視範圍

Function的部分比較特殊,擴增了external,這也是我們在傳統的程式語言中比較少見的一種應用,以下就讓我們來好好介紹這個部分。

external

僅外部的合約可以進行調用,內部以及繼承的合約皆不可視,主要用意是當我們不確定這個功能是否內部也會調用時,可以宣告為external,一旦宣告為external之後,內部要調用就得使用this.xxx()才能調用,另外參數的部分是使用Calldata作為來源,會減少許多不必要的Copy。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7.0;contract Base {
   // [Scope]
   // - by other contracts
   function externalJob() external pure returns (string memory) {
       return "external job called";
   }    function testScope() public virtual  {
       // ❌ 僅能由外部合約進行呼叫
       // @notice 語法檢測錯誤: DeclarationError: Undeclared identifier. Did you mean "internalJob" or "externalJob"?
       // externalJob();        // ✔️ 但可以使用這種方式在內部進行調用
       this.externalJob();
   }} contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();
       b.externalJob();
   }
}

public

可視範圍就如同狀態變數的public一樣,除了內部合約以外,繼承與外部合約也都能夠進行調用。
contract Base {
   // [Scope]
   //   - inside this contract
   //   - inside contracts that inherit this contract
   //   - by other contracts
   function publicJob() public pure returns (string memory) {
       return "public job called";
   }    function testScope() public virtual  {
       // ✔️ 可以存取「Base」的public Function
       publicJob();
   }} contract Extend is Base {    function testScope() public view override {
       // ✔️ 可以存取「Base」的public Function
       publicJob();
   }
}contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();        // ✔️ 可以存取「Base」的public Function
       b.publicJob();
   }
}

internal

可視範圍一樣同狀態變數的internal,除了自身的合約以外,繼承的合約也能夠調用,但外部合約是不可視的。
contract Base {
   // [Scope]
   //   - inside this contract
   //   - inside contracts that inherit this contract
   function internalJob() internal pure returns (string memory) {
       return "internal job called";
   }    function testScope() public virtual  {
       // ✔️ 可以存取「Base」的internal Function
       internalJob();
   }} contract Extend is Base {    function testScope() public view override {
       // ✔️ 可以存取「Base」的internal Function
       internalJob();
   }
}contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();        // ❌ 無法存取「Base」的internal Function
       // @notice 語法檢測錯誤: not found or not visible after argument-dependent lookup in contract Base.
       // b.internalJob();
   }
}

private

這種可視範圍僅侷限於自身的合約之中,不可被外部合約調用,包括繼承的合約也是不可視的。
contract Base {
   // [Scope]
   //   - inside Base Contract
   function privateJob() private pure returns(string memory) {
       return "private job called";
   }    function testScope() public virtual  {
        // ✔️ 可以存取「Base」的private Function
       privateJob();
   }} contract Extend is Base {    function testScope() public view override {
       // ❌ 無法使用基礎合約的私有成員Function
       // @notice 語法檢測錯誤: DeclarationError: Undeclared identifier
       // privateJob();
   }
}contract External {
   function testScope() public {
       // 宣告Base合約並呼叫其開放外部的Job
       Base b = new Base();        // ❌ 無法存取「Base」的private Function
       // @notice 語法檢測錯誤: not found or not visible after argument-dependent lookup in contract Base.
       // b.privateJob();
}

結語

以上是這次篇章介紹的可視範圍用法,撰寫一份安全的合約時,我們務必注意什麼能開放,開放到什麼程度,這些都與可視範圍息息相關,後續維護時也較能明確的界定範圍,不至於全部開放導致混亂,或者全部封閉導致一堆重複的內容。
今天的範例都在這裡「📦 solidity-remix-toturial/Ep6」歡迎自行取用。
分享至
成為作者繼續創作的動力吧!
主軸圍繞於軟體科技, 除了過往經驗成章以外也持續學習新技能, 並將學習心法記錄與分享, 以期幫助相同道路之夥伴。 裡面包含著各種程式語言的疑難雜症解題技巧, 也提供資料庫、AI、認證與授權、工具庫...等技巧, 讓您自由找出您想要的解答, 如果您想要系統化的教學課程也歡迎至「🔒 阿Han的軟體心法實戰營」。
從 Google News 追蹤更多 vocus 的最新精選內容從 Google News 追蹤更多 vocus 的最新精選內容

發表回應

成為會員 後即可發表留言