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是合同字段。账号字段会为与合约互动的每个用户账号单独存储一个值。它们始终声明为以
gcul.Account为键的字典 (dict),例如balance: dict[gcul.Account, int]。合约必须先获得用户的明确存储权限,然后才能写入用户账号。数据存储后,只有合约实例可以修改或删除该数据,用户无法执行此操作。
方法
方法定义了合约的可执行逻辑。它们类似于 Python 方法,可以读取或修改合约的字段。
__init__:合约首次部署时,构造函数仅会被调用一次。用于设置合同字段的初始状态。 在构造函数中未分配值的字段会获得合适的默认值,例如int字段的默认值为0,dict字段的默认值为空字典。私有方法:以下划线开头的方法(例如
_internal_logic)是私有方法,只能由同一合约中的其他方法调用。Universal Ledger 解释器会强制执行此限制。公共方法:任何不以下划线 (
_) 开头的方法都是公共方法。任何具有ROLE_CONTRACT_PARTICIPANT的用户都可以通过提交 InvokeContractMethod 交易来调用公共方法。
合同生命周期
以下部分将介绍 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已纳入路线图。 - 限制不允许使用
float和complex。
容器类型:
dict已受支持。- 路线图
list、tuple、set、dataclass已列入路线图。 - 限制:必须为容器中的值指定具体类型,例如允许使用
dict[str, int],但不允许使用纯dict或dict[str, Any]。 - 支持嵌套容器,例如
dict[str, list[int]]。
限制:所有变量(包括合约和账号字段、函数形参和返回值类型)都必须进行定义并静态输入。其类型无法在运行时更改,并且仅支持具体类型。 类型不能用作值,例如不能存储在变量中或作为实参传递给函数。尝试为未声明的字段赋值会导致编译时错误。
类和继承
最初,您只能定义作为基类 gcul.Contract 的直接子类的类。此严格规则可避免完整 Python 继承的复杂性,后者可能会引入难以发现的 bug 并使代码难以推理。出于安全考虑,尝试替换父类中的属性或方法会引发错误,从而提供明确的保障措施来防范意外行为。
路线图: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、not)。 - 路线图 对象身份 (
is)。 - 限制 不允许使用负指数,否则会引发运行时错误。
- 限制:不允许使用真除法 (
/),因为它具有float返回值类型,会导致编译时错误。
控制流
Python 中的大多数控制流语句都可以在 GCULpy 中使用,并且具有相同的语义:
pass语句。- 内部函数调用(相同合约,非递归)。
assert语句。if ... then .. else ...语句。for VAR in CONTAINER语句。- 路线图外部函数调用(到任何其他合约,非递归)。
- 路线图
break和continue语句。 - 路线图
raise和try ... except语句。 - 路线图
match声明。 - 路线图
generators和yield语句。 - 路线图上下文管理器和
with语句。
限制 GCULpy 有意设计为 图灵不完备,以防止无限循环、促进静态分析并确保可预测的交易处理费用。以下是其强制执行方式:
- 无无限循环:仅允许使用
for循环遍历有限容器;不允许使用while循环。在迭代某些容器时,不允许进行容器更新,例如向列表添加或从列表移除元素,或者向字典添加或从字典移除键。 - 无递归:函数不能直接或间接调用自身。运行时环境会执行静态检查和运行时检查,以检测并拒绝使用递归。
- 无异步控制流:不允许使用
async原语,以保持可预测性、安全性和确定性执行的核心设计原则。异步操作使得难以推断程序的控制流,通常会导致出现漏洞和竞态条件。
内置函数
路线图:以下是我们内置函数路线图,其中包含最基本和最常用的功能,可让您放心地进行构建。
|
A
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
版本说明
- 2026 年 1 月 28 日。向参与 Universal Ledger 非公开预览版的参与者提供
gculpyc编译器的早期版本。如需查看使用编译器的教程,请参阅部署可编程合约。