GCULpy 語言

GCULpy 是 Python 的嚴格靜態型別子集,旨在確保安全性、可讀性及可稽核性。其設計刻意限制 Python 的某些動態功能,以防範常見的智慧合約安全漏洞,並確保合約行為一律可預測。

本頁提供 GCULpy 語言規格的參考資料,涵蓋通用帳本網路上的合約核心概念和生命週期。

核心概念

下列合約會在 GCULpy 中定義範例 ERC20 權杖

import gcul

class ERC20Token(gcul.Contract):
    """Sample ERC20 implementation for the Universal Ledger."""

    symbol: str
    total_supply: int
    balance: dict[gcul.Account, int]

    def __init__(self, symbol: str):
        self.symbol = symbol

    def mint(self, beneficiary: gcul.Account, value: int) -> int:
        """Mints tokens to the given beneficiary."""
        assert self.is_owner(gcul.sender), "Only the owner can mint"
        assert value >= 0, "Mint amount must be non-negative"
        self.total_supply += value
        self.balance[beneficiary] += value
        return value

    def transfer(self, beneficiary: gcul.Account, value: int) -> int:
        """Transfers tokens from the sender to the given beneficiary."""
        assert value >= 0, "Transfer amount must be non-negative"
        assert (
            value <= self.balance[gcul.sender]
        ), "Sender does not have enough balance"
        self.balance[gcul.sender] -= value
        self.balance[beneficiary] += value
        return value

GCULpy 合約是繼承自 gcul.Contract 的類別。其中包含欄位 (儲存狀態) 和方法 (處理對欄位執行的邏輯)。

欄位

GCULpy 合約中的狀態會儲存在欄位中。所有欄位都必須在類別層級以靜態型別宣告。欄位分為兩種:

  • 合約欄位會保留與合約本身一起儲存的單一值。 在 ERC20Token 範例中,symbol: strtotal_supply: int 是合約欄位。

  • 帳戶欄位會為與合約互動的每個使用者帳戶儲存不同的值。這類項目一律會宣告為字典 (dict),並以 gcul.Account 做為鍵,例如 balance: dict[gcul.Account, int]。合約必須先取得使用者的明確儲存空間存取權,才能寫入使用者帳戶。資料儲存後,只有合約例項可以修改或刪除資料,使用者無法執行這類操作。

方法

方法會定義合約的可執行邏輯。這些函式的行為與 Python 方法類似,可以讀取或修改合約的欄位。

  • __init__:首次部署合約時,系統只會呼叫一次建構函式。用於設定合約欄位的初始狀態。建構函式中未指派值的欄位會取得適當的預設值,例如 int 欄位的 0,或是 dict 欄位的空白字典。

  • 私有方法:以底線開頭的方法 (例如 _internal_logic) 是私有方法,只能由同一合約中的其他方法呼叫。Universal Ledger 解譯器會強制執行這項限制。

  • 公開方法:任何不以底線 (_) 開頭的方法都是公開方法。任何使用者都可以透過提交 InvokeContractMethod 交易,呼叫具有 ROLE_CONTRACT_PARTICIPANT 的公開方法。

合約生命週期

下列各節將逐步說明 GCULPy 合約生命週期中涉及的典型作業。

部署合約

首先,請使用 gculpyc 編譯器編譯 GCULpy 原始碼。接著,具有 ROLE_CONTRACT_CREATOR 的使用者可以提交 CreateContract 交易,將編譯後的位元碼部署至通用帳本網路。如需詳細操作說明,請參閱「部署可程式化合約」教學課程。

這類交易如下所示:

client_transaction {
  sender_id: "OWNER_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.CreateContract] {
      contract_bytes: "COMPILED_BYTECODE"
      arguments {
        key: "symbol"
        value: { str_value: "US02079K1079" }
      }
    }
  }
}

網路處理這筆交易時:

  • 系統會執行建構函式 (即 __init__ 方法),建立新的合約例項。
  • 交易的傳送者會成為合約擁有者
  • 合約執行個體會永久儲存在帳本中,並指派專屬的合約 ID,該 ID 會做為交易輸出內容的一部分傳回。

授予權限

合約必須先取得使用者的儲存權限,才能代表使用者將資料儲存在帳戶欄位中。這是重要的安全步驟。具有 ROLE_CONTRACT_PARTICIPANT 的使用者可以為特定合約 ID 提交 GrantContractPermissions 交易。

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.GrantContractPermissions] {
      contract_id: "CONTRACT_ID"
      permissions: CONTRACT_PERMISSION_STORAGE
    }
  }
}

網路處理這筆交易時:

  • 如果合約未定義任何帳戶欄位,交易就會遭到拒絕。
  • 如果合約定義了帳戶欄位,系統會以預設值 (例如 contract.balance[gcul.sender] = 0) 填入所有欄位。這些值隨後會儲存在世界狀態中,做為帳戶資料的一部分,而交易傳送者會註冊為參與這個特定合約例項。

叫用合約方法

部署合約並授予必要權限後,使用者即可呼叫合約的公開方法,與合約互動。擁有 ROLE_CONTRACT_PARTICIPANT 的使用者可以提交 InvokeContractMethod 交易,指定合約 ID、方法名稱和引數值。

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  app {
    [type.googleapis.com/google.cloud.universalledger.v1.InvokeContractMethod] {
      contract_id: "CONTRACT_ID"
      method_name: "mint"
      arguments {
        key: "beneficiary"
        value: { account_id: "BENEFICIARY_ID" }
      }
      arguments {
        key: "value"
        value: { int_value: 10 }
      }
    }
  }
}

網路處理這筆交易時:

  • 系統會擷取與所提供 CONTRACT_ID 相關聯的合約執行個體。
  • 系統會執行 mint(beneficiary=Account("BENEFICIARY_ID"), value=10) 方法。受益人的 Account 物件是由執行階段建構及驗證。這個方法的邏輯可以安全地假設所提供的 ID 有效,且參照帳本中的現有帳戶。
  • 如果方法因任何原因失敗,交易就會失敗,且合約狀態「不會」更新。
  • 如果方法成功,合約的更新狀態會記錄在世界狀態中。

語言規格

GCULpy 的設計宗旨是安全和可預測,因此禁止使用多項 Python 功能;這些功能會以「限制」標籤標示。這些限制是為了讓合約邏輯更容易閱讀、稽核及靜態分析,並限制令人意外或不安全的行為,因此會成為永久語言功能。

其他功能會標示「規劃中」標籤,這些功能位於實作路線圖中,但 gculpyc 編譯器尚未支援。

類型

GCULpy 支援多種常見變數類型,並著重於靜態型別。

核心價值類型:

  • intboolstrNone
  • 藍圖 DecimalbytesEnum 都在藍圖中。
  • 不得使用 Restriction floatcomplex

容器類型:

  • dict 已支援。
  • 藍圖 listtuplesetdataclass 都在藍圖中。
  • 限制:必須為容器中的值指定具體型別,例如允許使用 dict[str, int],但不允許使用純 dictdict[str, Any]
  • 系統支援容器巢狀結構,例如 dict[str, list[int]]

限制:所有變數 (包括合約和帳戶欄位、函式參數和傳回型別) 都必須定義並靜態輸入。這類型的物件無法在執行階段變更,且僅支援具體型別。 型別無法做為值使用,例如無法儲存在變數中,或做為引數傳遞至函式。嘗試將值指派給未宣告的欄位會導致編譯時期錯誤。

類別和繼承

一開始,您只能定義基底 gcul.Contract 類別的直接子類別。這項嚴格規則可避免完整的 Python 繼承機制帶來的複雜性,因為這可能會導致難以尋找的錯誤,並使程式碼難以推論。為確保安全,嘗試從父類別覆寫屬性或方法時會引發錯誤,清楚防範非預期的行為。

藍圖:GCULpy 將在維持核心原則的同時,提供更多彈性。路線圖包括支援使用者定義類別的單一繼承,以及使用 @override 裝飾器明確管理的方法覆寫。此外,為確保直接且可預測的作業,super() 內建函式只支援無引數形式。

gcul 模組

GCULpy 提供內建的 gcul 模組,其中包含合約開發作業所需的基本型別和變數。

類別 gcul.Contract

所有合約的基礎類別。您無法直接建立執行個體;合約只能透過 CreateContract 交易例項化。子類別無法覆寫基本 gcul.Contract 類別的方法和屬性。

  • Contract.is_owner(account: Account) -> bool

    如果提供的帳戶是合約擁有者,則傳回 True

類別 gcul.Account

內建型別,代表帳本中的使用者帳戶。您無法直接建立 gcul.Account 物件,執行階段環境會為您建立這些物件,並以函式或方法引數的形式提供。當您將帳戶 ID 做為交易引數傳遞時,執行階段會自動驗證該 ID。如果是已註冊帳戶的有效 ID,系統會將其轉換為完整的帳戶物件。否則交易會失敗。確保您只會使用有效帳戶。

這個類別的定義大致等同於:

@dataclasses.dataclass(frozen=True)
class Account:
  """A valid account on the ledger."""

  id: str  # The ID of the account as a string.

gcul.sender: gcul.Account

這個特殊變數適用於任何方法,可保留對簽署及提交目前交易的帳戶的參照。

路線圖:提升開發人員管理合約和帳戶的能力,並與之互動。您將能夠將合約物件的參照做為引數傳遞、儲存在欄位中,以及存取其專屬 ID (contract.id: str)。同樣地,您將能夠儲存帳戶物件的參照,並擷取其 ID。

運算子

GCULpy 支援 Python 中的大多數運算子,且運作方式與預期相同。

  • 加法 (+) 和減法 (-),包括一元和二元形式。
  • 乘法 (*)、底除法 (//) 和模數 (%)。
  • 正指數的指數運算 (**)。
  • 比較 (<<=>>===!=)。
  • 位元 AND (&)、OR (|)、XOR (^)、左移 (<<)、右移 (>>)、否定 (~)。
  • 布林運算子 (andornot)。
  • 路線圖 物件 ID (is)。
  • 限制:不允許負指數,否則會引發執行階段錯誤。
  • 限制:不允許使用實數除法 (/),因為這類除法的傳回類型為 float,會導致編譯時間錯誤。

控制流程

Python 的大多數控制流程陳述式都適用於 GCULpy,語意相同:

  • pass 陳述式來變更這些使用者的權限。
  • 內部函式呼叫 (相同合約,非遞迴)。
  • assert 陳述式來變更這些使用者的權限。
  • if ... then .. else ... 陳述式來變更這些使用者的權限。
  • for VAR in CONTAINER 陳述式來變更這些使用者的權限。
  • Roadmap 外部函式呼叫 (至任何其他合約,非遞迴)。
  • 藍圖 breakcontinue 陳述式。
  • 藍圖 raisetry ... except 陳述式。
  • 路線圖 match 陳述。
  • 藍圖 generatorsyield 陳述式。
  • 發展藍圖內容管理員和 with 陳述式。

限制:GCULpy 刻意不完整,以防止無限迴圈、簡化靜態分析,並確保交易處理費用可預測。Google 的做法如下:

  • 不得出現無限迴圈:只能使用 for 迴圈疊代有限容器;不得使用 while 迴圈。在疊代容器時,不允許進行部分容器更新,例如在清單中新增或移除元素,或在字典中新增或移除鍵。
  • 不得遞迴:函式不得直接或間接呼叫自身。執行階段環境會執行靜態和執行階段檢查,偵測並拒絕使用遞迴。
  • 不得使用非同步控制流程:為維持可預測性、安全性及確定性執行的核心設計原則,不得使用 async 基元。非同步作業會導致程式的控制流程難以推斷,經常導致安全漏洞和競爭狀況。

內建函式

產品規劃:我們將推出內建函式,提供最基本且常用的功能,讓您安心建構應用程式。

A
abs()
all()
any()

B
bin()
bool()
bytes()

C
chr()

D
dict()
divmod()

E
enumerate()

F
format()
frozenset()

H
hash()
hex()

I
id()
int()

L
len()
list()

M
max()
min()

O
oct()
ord()

P
pow()
property()

R
range()
repr()
reversed()

S
set()
sorted()
staticmethod()
str()
sum()
super()

T
tuple()

Z
zip()

版本資訊

  • 2026 年 1 月 28 日gculpyc 編譯器的早期版本已提供給 Universal Ledger 非公開預先發布計畫的參與者。如需使用編譯器的教學課程,請參閱「部署可程式化合約」。