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 是合同字段。

  • 账号字段会为与合约互动的每个用户账号单独存储一个值。它们始终声明为以 gcul.Account 为键的字典 (dict),例如 balance: dict[gcul.Account, int]。合约必须先获得用户的明确存储权限,然后才能写入用户账号。数据存储后,只有合约实例可以修改或删除该数据,用户无法执行此操作。

方法

方法定义了合约的可执行逻辑。它们类似于 Python 方法,可以读取或修改合约的字段。

  • __init__:合约首次部署时,构造函数仅会被调用一次。用于设置合同字段的初始状态。 在构造函数中未分配值的字段会获得合适的默认值,例如 int 字段的默认值为 0dict 字段的默认值为空字典。

  • 私有方法:以下划线开头的方法(例如 _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 支持一系列常见的变量类型,并非常强调静态类型。

核心值类型:

  • intboolstrNone 已受支持。
  • 路线图 DecimalbytesEnum 已纳入路线图。
  • 限制不允许使用 floatcomplex

容器类型:

  • dict 已受支持。
  • 路线图 listtuplesetdataclass 已列入路线图。
  • 限制:必须为容器中的值指定具体类型,例如允许使用 dict[str, int],但不允许使用纯 dictdict[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 中的大多数运算符,并且这些运算符可按预期运行。

  • 加法 (+) 和减法 (-),包括一元形式和二元形式。
  • 乘法 (*)、向下取整除法 (//) 和取模 (%)。
  • 正指数的指数运算 (**)。
  • 比较(<<=>>===!=)。
  • 按位与 (&)、或 (|)、异或 (^)、左移 (<<)、右移 (>>)、取反 (~)。
  • 布尔运算(andornot)。
  • 路线图 对象身份 (is)。
  • 限制 不允许使用负指数,否则会引发运行时错误。
  • 限制:不允许使用真除法 (/),因为它具有 float 返回值类型,会导致编译时错误。

控制流

Python 中的大多数控制流语句都可以在 GCULpy 中使用,并且具有相同的语义:

  • pass 语句。
  • 内部函数调用(相同合约,非递归)。
  • assert 语句。
  • if ... then .. else ... 语句。
  • for VAR in CONTAINER 语句。
  • 路线图外部函数调用(到任何其他合约,非递归)。
  • 路线图 breakcontinue 语句。
  • 路线图 raisetry ... except 语句。
  • 路线图 match声明。
  • 路线图 generatorsyield 语句。
  • 路线图上下文管理器和 with 语句。

限制 GCULpy 有意设计为 图灵不完备,以防止无限循环、促进静态分析并确保可预测的交易处理费用。以下是其强制执行方式:

  • 无无限循环:仅允许使用 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 日。向参与 Universal Ledger 非公开预览版的参与者提供 gculpyc 编译器的早期版本。如需查看使用编译器的教程,请参阅部署可编程合约