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: str和total_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 支援多種常見變數類型,並著重於靜態型別。
核心價值類型:
int、bool、str、None。- 藍圖
Decimal、bytes、Enum都在藍圖中。 - 不得使用 Restriction
float和complex。
容器類型:
dict已支援。- 藍圖
list、tuple、set、dataclass都在藍圖中。 - 限制:必須為容器中的值指定具體型別,例如允許使用
dict[str, int],但不允許使用純dict或dict[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 (^)、左移 (<<)、右移 (>>)、否定 (~)。 - 布林運算子 (
and、or、not)。 - 路線圖 物件 ID (
is)。 - 限制:不允許負指數,否則會引發執行階段錯誤。
- 限制:不允許使用實數除法 (
/),因為這類除法的傳回類型為float,會導致編譯時間錯誤。
控制流程
Python 的大多數控制流程陳述式都適用於 GCULpy,語意相同:
pass陳述式來變更這些使用者的權限。- 內部函式呼叫 (相同合約,非遞迴)。
assert陳述式來變更這些使用者的權限。if ... then .. else ...陳述式來變更這些使用者的權限。for VAR in CONTAINER陳述式來變更這些使用者的權限。- Roadmap 外部函式呼叫 (至任何其他合約,非遞迴)。
- 藍圖
break和continue陳述式。 - 藍圖
raise和try ... except陳述式。 - 路線圖
match陳述。 - 藍圖
generators和yield陳述式。 - 發展藍圖內容管理員和
with陳述式。
限制:GCULpy 刻意不完整,以防止無限迴圈、簡化靜態分析,並確保交易處理費用可預測。Google 的做法如下:
- 不得出現無限迴圈:只能使用
for迴圈疊代有限容器;不得使用while迴圈。在疊代容器時,不允許進行部分容器更新,例如在清單中新增或移除元素,或在字典中新增或移除鍵。 - 不得遞迴:函式不得直接或間接呼叫自身。執行階段環境會執行靜態和執行階段檢查,偵測並拒絕使用遞迴。
- 不得使用非同步控制流程:為維持可預測性、安全性及確定性執行的核心設計原則,不得使用
async基元。非同步作業會導致程式的控制流程難以推斷,經常導致安全漏洞和競爭狀況。
內建函式
產品規劃:我們將推出內建函式,提供最基本且常用的功能,讓您安心建構應用程式。
|
A
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
版本資訊
- 2026 年 1 月 28 日。
gculpyc編譯器的早期版本已提供給 Universal Ledger 非公開預先發布計畫的參與者。如需使用編譯器的教學課程,請參閱「部署可程式化合約」。