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
ERC20Tokensindsymbol: strundtotal_supply: intVertragsfelder.In Kontofeldern wird für jedes Nutzerkonto, das mit einem Vertrag interagiert, ein separater Wert gespeichert. Sie werden immer als Wörterbuch (
dict) mitgcul.Accountals 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.0für einint-Feld oder ein leeres Wörterbuch für eindict-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 derROLE_CONTRACT_PARTICIPANTaufgerufen 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_IDverknüpft ist, wird abgerufen. - Die Methode
mint(beneficiary=Account("BENEFICIARY_ID"), value=10)wird ausgeführt. DasAccount-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,Nonewerden bereits unterstützt.- Roadmap
Decimal,bytes,Enumsind in der Roadmap enthalten. - Einschränkung
floatundcomplexsind nicht zulässig.
Containertypen:
dictwird bereits unterstützt.- Roadmap
list,tuple,set,dataclasssind 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 einfachesdictoderdict[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) -> boolGibt
Truezurü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ückgabetypfloathat, 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
breakundcontinueAnweisungen. - Roadmap
raiseundtry ... exceptAnweisungen. - Roadmap
matchAnweisungen. - Roadmap
generatorsundyieldAnweisungen. - Roadmap Kontextmanager und
withAnweisungen.
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
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
Versionshinweise
- 28.Januar 2026 Frühe Version des Compilers
gculpycfür Teilnehmer der privaten Vorschau von Universal Ledger verfügbar gemacht. Ein Tutorial zur Verwendung von dem Compiler finden Sie unter Programmierbaren Vertrag bereitstellen.