Die GCULpy-Sprache

GCULpy ist eine strikt statisch typisierte Teilmenge von Python, die sicher, lesbar und prüfbar sein soll. Bestimmte dynamische Funktionen von Python werden bewusst eingeschränkt, um häufige Sicherheitslücken bei Smart Contracts zu vermeiden und sicherzustellen, dass das Verhalten eines Vertrags immer vorhersehbar ist.

Auf dieser Seite finden Sie eine Referenz zur GCULpy-Sprachspezifikation, die die wichtigsten Konzepte und den Lebenszyklus eines Vertrags in einem Universal Ledger-Netzwerk abdeckt.

Wichtige Konzepte

Der folgende Vertrag definiert ein Beispiel für ein ERC20-Token in GCULpy:

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

Ein GCULpy-Vertrag ist eine Klasse, die von gcul.Contract erbt. Er enthält Felder (zum Speichern des Status) und Methoden (Verarbeitungslogik, die auf Felder angewendet wird).

Felder

Der Status in einem GCULpy-Vertrag wird in Feldern gespeichert. Alle Felder müssen auf Klassenebene mit einem statischen Typ deklariert werden. Es gibt zwei Arten von Feldern:

  • Vertragsfelder enthalten einen einzelnen Wert, der mit dem Vertrag selbst gespeichert wird. Im Beispiel ERC20Token sind symbol: str und total_supply: int Vertragsfelder.

  • In Kontofeldern wird für jedes Nutzerkonto, das mit einem Vertrag interagiert, ein separater Wert gespeichert. Sie werden immer als Wörterbuch (dict) mit gcul.Account als Schlüssel deklariert, z. B. balance: dict[gcul.Account, int]. Bevor ein Vertrag in das Konto eines Nutzers schreiben kann, muss der Nutzer dem Vertrag explizit die Speicherberechtigung erteilen. Sobald die Daten gespeichert sind, kann nur die Vertragsinstanz sie ändern oder löschen – der Nutzer nicht.

Methoden

Methoden definieren die ausführbare Logik eines Vertrags. Sie verhalten sich wie Python-Methoden und können die Felder des Vertrags lesen oder ändern.

  • __init__: Der Konstruktor wird nur einmal aufgerufen, wenn der Vertrag zum ersten Mal bereitgestellt wird. Er wird verwendet, um den Anfangsstatus der Vertragsfelder festzulegen. Felder, denen im Konstruktor kein Wert zugewiesen ist, erhalten einen geeigneten Standardwert, z. B. 0 für ein int-Feld oder ein leeres Wörterbuch für ein dict-Feld.

  • Private Methoden: Methoden, die mit einem Unterstrich beginnen (z. B. _internal_logic) sind privat und können nur von anderen Methoden innerhalb desselben Vertrags aufgerufen werden. Diese Einschränkung wird vom Universal Ledger-Interpreter erzwungen.

  • Öffentliche Methoden: Jede Methode, die nicht mit einem Unterstrich (_) beginnt, ist öffentlich. Öffentliche Methoden können von jedem Nutzer mit der ROLE_CONTRACT_PARTICIPANT aufgerufen werden, indem er eine InvokeContractMethod Transaktion einreicht.

Lebenszyklus eines Vertrags

In den folgenden Abschnitten werden die typischen Vorgänge im Lebenszyklus eines GCULpy-Vertrags beschrieben.

Vertrag bereitstellen

Zuerst kompilieren Sie Ihren GCULpy-Quellcode mit dem Compiler gculpyc. Anschließend kann ein Nutzer mit der ROLE_CONTRACT_CREATOR eine CreateContract Transaktion einreichen, um den kompilierten Bytecode in einem Universal Ledger-Netzwerk bereitzustellen. Eine detaillierte Anleitung finden Sie im Tutorial Programmierbaren Vertrag bereitstellen.

Eine solche Transaktion würde so aussehen:

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" }
      }
    }
  }
}

Wenn das Netzwerk diese Transaktion verarbeitet, geschieht Folgendes:

  • Der Konstruktor, also die Methode __init__, wird ausgeführt, um eine neue Vertragsinstanz zu erstellen.
  • Der Absender der Transaktion wird zum Eigentümer des Vertrags.
  • Die Vertragsinstanz wird dauerhaft im Ledger gespeichert und erhält eine eindeutige Vertrags-ID , die als Teil der Transaktionsausgabe zurückgegeben wird.

Berechtigungen erteilen

Bevor ein Vertrag im Namen eines Nutzers Daten in einem Kontofeld speichern kann, muss der Nutzer ihm zuerst die Speicherberechtigung erteilen. Dies ist ein wichtiger Sicherheitsschritt. Ein Nutzer mit der Rolle ROLE_CONTRACT_PARTICIPANT kann eine GrantContractPermissions -Transaktion für eine bestimmte Vertrags-ID einreichen.

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

Wenn das Netzwerk diese Transaktion verarbeitet, geschieht Folgendes:

  • Wenn der Vertrag keine Kontofelder definiert, wird die Transaktion abgelehnt.
  • Wenn der Vertrag Kontofelder definiert, werden alle mit Standardwerten gefüllt (z. B. contract.balance[gcul.sender] = 0). Diese Werte werden dann als Teil der Kontodaten im Weltstatus gespeichert und der Absender der Transaktion wird als Teilnehmer an dieser bestimmten Vertragsinstanz registriert.

Vertragsmethoden aufrufen

Sobald ein Vertrag bereitgestellt und alle erforderlichen Berechtigungen erteilt wurden, können Nutzer mit ihm interagieren, indem sie seine öffentlichen Methoden aufrufen. Ein Nutzer mit der Rolle ROLE_CONTRACT_PARTICIPANT kann eine InvokeContractMethod Transaktion einreichen, in der die Vertrags-ID, der Methodenname und die Argumentwerte angegeben sind.

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 }
      }
    }
  }
}

Wenn das Netzwerk diese Transaktion verarbeitet, geschieht Folgendes:

  • Die Vertragsinstanz, die mit der angegebenen CONTRACT_ID verknüpft ist, wird abgerufen.
  • Die Methode mint(beneficiary=Account("BENEFICIARY_ID"), value=10) wird ausgeführt. Das Account-Objekt für den Begünstigten wird von der Laufzeit erstellt und validiert. Die Logik der Methode kann davon ausgehen, dass die angegebene ID gültig ist und sich auf ein vorhandenes Konto im Ledger bezieht.
  • Wenn die Methode aus irgendeinem Grund fehlschlägt, schlägt auch die Transaktion fehl und es werden keine Aktualisierungen am Status des Vertrags vorgenommen.
  • Wenn die Methode erfolgreich ist, wird der aktualisierte Status des Vertrags im Weltstatus erfasst.

Sprachspezifikation

GCULpy wurde mit Blick auf Sicherheit und Vorhersehbarkeit entwickelt und lässt daher einige Python-Funktionen nicht zu. Diese werden mit dem Label „Einschränkung“ gekennzeichnet. Diese Einschränkungen sind als dauerhafte Sprachfunktionen gedacht, die eingeführt wurden, um die Vertragslogik leichter lesbar, prüfbar und statisch analysierbar zu machen und überraschende oder unsichere Verhaltensweisen zu begrenzen.

Andere Funktionen werden mit dem Label „Roadmap“ gekennzeichnet. Diese sind in der Implementierungs-Roadmap enthalten, werden aber vom gculpyc Compiler noch nicht unterstützt.

Typen

GCULpy unterstützt eine Reihe gängiger Variablentypen mit starkem Fokus auf statische Typisierung.

Zentrale Werttypen:

  • int, bool, str, None werden bereits unterstützt.
  • Roadmap Decimal, bytes, Enum sind in der Roadmap enthalten.
  • Einschränkung float und complex sind nicht zulässig.

Containertypen:

  • dict wird bereits unterstützt.
  • Roadmap list, tuple, set, dataclass sind in der Roadmap enthalten.
  • Einschränkung Für Werte in einem Container müssen konkrete Typen angegeben werden. dict[str, int] ist beispielsweise zulässig, ein einfaches dict oder dict[str, Any] jedoch nicht.
  • Das Verschachteln von Containern wird unterstützt, z. B. dict[str, list[int]].

Einschränkung Alle Variablen, einschließlich Vertrags- und Kontofeldern, Funktionsparametern und Rückgabetypen, müssen statisch definiert und typisiert werden. Ihr Typ kann zur Laufzeit nicht geändert werden und es werden nur konkrete Typen unterstützt. Typen können nicht als Werte verwendet werden, z. B. können sie nicht in Variablen gespeichert oder als Argumente an Funktionen übergeben werden. Wenn Sie versuchen, einem nicht deklarierten Feld einen Wert zuzuweisen, tritt ein Kompilierzeitfehler auf.

Klassen und Vererbung

Anfangs können Sie nur Klassen definieren, die direkte Unterklassen der Basisklasse gcul.Contract sind. Diese strenge Regel vermeidet die Komplexität der vollständigen Python-Vererbung, die schwer zu findende Fehler verursachen und den Code schwer verständlich machen kann. Aus Sicherheitsgründen wird beim Versuch, ein Attribut oder eine Methode aus einer übergeordneten Klasse zu überschreiben, ein Fehler ausgelöst, der eine klare Sicherheitsmaßnahme gegen unerwartetes Verhalten darstellt.

Roadmap GCULpy wird mehr Flexibilität bieten, ohne seine Grundprinzipien zu verletzen. Die Roadmap umfasst die Unterstützung für einfache Vererbung für benutzerdefinierte Klassen, wobei das Überschreiben von Methoden explizit mit einem @override-Decorator verwaltet wird. Außerdem wird die integrierte Funktion super() nur in ihrer argumentfreien Form unterstützt, um direkte, vorhersehbare Vorgänge zu gewährleisten.

Das Modul „gcul“

GCULpy bietet ein integriertes gcul-Modul mit wichtigen Typen und Variablen für die Vertragsentwicklung.

Klasse gcul.Contract

Die Basisklasse für alle Verträge. Sie können keine Instanzen davon direkt erstellen; Verträge werden nur über CreateContract Transaktionen instanziiert. Methoden und Attribute aus der Basisklasse gcul.Contract können in Unterklassen nicht überschrieben werden.

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

    Gibt True zurück, wenn das angegebene Konto der Eigentümer des Vertrags ist.

Klasse gcul.Account

Ein integrierter Typ, der ein Nutzerkonto im Ledger darstellt. Sie können keine gcul.Account-Objekte direkt erstellen. Die Laufzeitumgebung erstellt sie für Sie und stellt sie als Funktions- oder Methodenargumente bereit. Wenn Sie eine Konto-ID als Transaktionsargument übergeben, wird sie von der Laufzeit automatisch validiert. Wenn es sich um eine gültige ID für ein registriertes Konto handelt, wird sie in ein vollständiges Kontoobjekt konvertiert. Andernfalls schlägt die Transaktion fehl. So arbeiten Sie immer nur mit gültigen Konten.

Die Definition der Klasse entspricht ungefähr:

@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

Eine spezielle Variable, die in jeder Methode verfügbar ist und einen Verweis auf das Konto enthält, mit dem die aktuelle Transaktion signiert und eingereicht wurde.

Roadmap Verbesserung der Möglichkeiten für Entwickler, Verträge und Konten zu verwalten und mit ihnen zu interagieren. Sie können Verweise auf Vertragsobjekte als Argumente übergeben, sie in Feldern speichern und auf ihre eindeutige ID (contract.id: str) zugreifen. Ebenso ist es möglich, Verweise auf Kontoobjekte zu speichern und ihre IDs abzurufen.

Operatoren

Die meisten in Python verfügbaren Operatoren werden in GCULpy unterstützt und funktionieren wie erwartet.

  • Addition (+) und Subtraktion (-), einschließlich unärer und binärer Formen.
  • Multiplikation (*), Ganzzahldivision (//) und Modulo (%).
  • Potenzierung (**) für positive Exponenten.
  • Vergleiche (<, <=, >, >=, ==, !=).
  • Bitweise Operatoren: AND (&), OR (|), XOR (^), Linksverschiebung (<<), Rechtsverschiebung (>>), Negation (~).
  • Boolesche Operatoren (and, or, not).
  • Roadmap Objektidentität (is).
  • Einschränkung Negative Exponenten sind nicht zulässig und führen zu einem Laufzeit fehler.
  • Einschränkung Die echte Division (/) ist nicht zulässig, da sie den Rückgabetyp float hat, und führt zu einem Kompilierzeitfehler.

Kontrollfluss

Die meisten Kontrollflussanweisungen aus Python funktionieren in GCULpy mit derselben Semantik:

  • pass-Anweisungen.
  • Interne Funktionsaufrufe (derselbe Vertrag, nicht rekursiv).
  • assert-Anweisungen.
  • if ... then .. else ...-Anweisungen.
  • for VAR in CONTAINER-Anweisungen.
  • Roadmap Externe Funktionsaufrufe (an einen anderen Vertrag, nicht rekursiv).
  • Roadmap break und continue Anweisungen.
  • Roadmap raise und try ... except Anweisungen.
  • Roadmap match Anweisungen.
  • Roadmap generators und yield Anweisungen.
  • Roadmap Kontextmanager und with Anweisungen.

Einschränkung GCULpy ist bewusst nicht Turing-vollständig, um Endlosschleifen zu vermeiden, die statische Analyse zu erleichtern und vorhersehbare Kosten für die Transaktionsverarbeitung zu gewährleisten. So wird dies erzwungen:

  • Keine Endlosschleifen:Die Iteration ist nur mit for-Schleifen über endliche Container zulässig. while-Schleifen sind nicht zulässig. Einige Containeraktualisierungen, z. B. das Hinzufügen oder Entfernen von Elementen aus einer Liste oder von Schlüsseln zu einem Wörterbuch, sind während der Iteration nicht zulässig.
  • Keine Rekursion:Eine Funktion kann sich nicht selbst aufrufen, weder direkt noch indirekt. Die Laufzeitumgebung führt sowohl statische als auch Laufzeitprüfungen durch, um die Verwendung von Rekursion zu erkennen und abzulehnen.
  • Kein asynchroner Kontrollfluss:Die Verwendung von async-Primitiven ist nicht zulässig, um die Grundprinzipien von Vorhersehbarkeit, Sicherheit und deterministischer Ausführung beizubehalten. Asynchrone Vorgänge erschweren die Nachvollziehbarkeit des Kontrollflusses eines Programms und führen häufig zu Sicherheitslücken und Race Conditions.

Integrierte Funktionen

Roadmap Hier sehen Sie unsere Roadmap für integrierte Funktionen mit den grundlegendsten und am häufigsten verwendeten Funktionen, mit denen Sie mit Zuversicht entwickeln können.

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()

Versionshinweise

  • 28.Januar 2026 Frühe Version des Compilers gculpyc für Teilnehmer der privaten Vorschau von Universal Ledger verfügbar gemacht. Ein Tutorial zur Verwendung von dem Compiler finden Sie unter Programmierbaren Vertrag bereitstellen.