GCULpy ist eine strikte, statisch typisierte Teilmenge von Python, die sicher, lesbar und prüfbar ist. Das Design schränkt bestimmte dynamische Funktionen von Python absichtlich ein, um häufige Sicherheitslücken bei Smart Contracts zu vermeiden und dafür zu sorgen, dass das Verhalten eines Vertrags immer vorhersehbar ist.
Diese Seite enthält eine Referenz für die GCULpy-Sprachspezifikation, die die Kernkonzepte 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: gcul.AccountField[int]
def __init__(self, symbol: str):
self.symbol = symbol
self.total_supply = 0
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. Sie enthält Felder (Speicherstatus) und Methoden (Verarbeitungslogik, die auf Feldern ausgeführt wird).
Felder
Der Status in einem GCULpy-Vertrag wird in Feldern gespeichert. Alle Felder müssen mit einem statischen Typ auf Klassenebene 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.Kontofelder enthalten einen separaten Wert für jedes Konto, das mit dem Vertrag interagiert. Sie werden mit
gcul.AccountField[T]deklariert, wobeiTder Typ des kontospezifischen Werts ist. Im BeispielERC20Tokenistbalance: gcul.AccountField[int]ein Kontofeld. Bevor ein Vertrag in den für dieses Konto reservierten Speicherplatz schreiben kann, muss die Speicherberechtigung explizit dem Vertrag gewährt werden. Sobald die Daten gespeichert sind, können sie nur von der Vertragsinstanz geändert werden, nicht vom Konto.
Für Einschränkungsfelder gelten die folgenden Speicherlimits. Eine Transaktion, die eines der beiden Limits überschreitet, schlägt fehl.
- Alle Vertragsfelder eines Vertrags teilen sich einen einzigen Speichereintrag, der insgesamt etwa 5 MiB serialisierte Daten enthalten kann.
- Alle Kontofelder, die ein Vertrag für jedes einzelne Konto speichert, können insgesamt maximal 1 MiB serialisierte Daten enthalten.
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. Jedem Vertragsfeld muss im Konstruktor ein Wert zugewiesen werden. Der Konstruktor initialisiert keine Kontofelder. Der Wert jedes Kontos wird erstellt, wenn dieses Konto die Speicherberechtigung gewährt.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. Der Universal Ledger-Interpreter erzwingt diese Einschränkung.Ö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 eine InvokeContractMethod-Transaktion gesendet wird.
Einschränkung: Öffentliche Methoden und alle @gcul.readonly-Methoden können keine änderbaren Typen in ihren Signaturen verwenden, z. B. list oder dict. Verwenden Sie stattdessen schreibgeschützte Typdeklarationen wie Sequence oder Mapping.
Einschränkung: Während der Ausführung sind Methoden auf eine netzwerkweite Obergrenze für die Rechennutzung beschränkt. Das Überschreiten dieses Limits führt zu einem Transaktionsfehler.
Vertragslebenszyklus
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 gculpyc-Compiler. Anschließend kann ein Nutzer mit der Rolle ROLE_CONTRACT_CREATOR eine CreateContract-Transaktion senden, um den kompilierten Bytecode in einem Universal Ledger-Netzwerk bereitzustellen. Eine ausführliche Anleitung finden Sie in der Anleitung Programmierbaren Vertrag bereitstellen.
Eine solche Transaktion würde so aussehen:
client_transaction {
sender_id: "OWNER_ACCOUNT_ID"
create_contract_transaction {
contract_bytes: "COMPILED_BYTECODE"
init_arguments {
key: "symbol"
value: { string_value: "US02079K1079" }
}
}
}
Wenn das Netzwerk diese Transaktion verarbeitet:
- Der Konstruktor, also die Methode
__init__, wird mit den angegebenen Argumenten ausgeführt, um eine neue Vertragsinstanz zu erstellen. - Der Absender der Transaktion wird zum Inhaber 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 Kontos (entweder eines Nutzerkontos oder eines anderen Vertrags) Daten in einem Kontofeld speichern kann, muss dem Vertrag, der die Daten speichert, die Speicherberechtigung gewährt werden. Dies ist ein wichtiger Sicherheitsschritt.
Berechtigungen für ein Nutzerkonto gewähren
Ein Nutzer mit einer ROLE_CONTRACT_PARTICIPANT kann eine GrantContractPermissions-Transaktion für eine bestimmte Vertrags-ID senden:
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
grant_contract_permissions_transaction {
contract_id: "CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
Wenn das Netzwerk diese Transaktion verarbeitet:
- Wenn im Vertrag keine Kontofelder definiert sind, wird die Transaktion abgelehnt.
- Wenn der Vertrag Kontofelder definiert, werden alle mit Standardwerten (z. B.
contract.balance[gcul.sender] = 0) gefüllt. Diese Werte werden dann als Teil der Kontodaten im World State gespeichert.
Berechtigungen für einen Delegatvertrag erteilen
Ein Nutzer mit einem ROLE_CONTRACT_PARTICIPANT kann eine GrantContractPermissions-Transaktion senden, um einem Vertrag zu erlauben, Daten im Namen eines anderen Vertrags zu speichern, den er besitzt:
client_transaction {
sender_id: "OWNER_ACCOUNT_ID"
grant_contract_permissions_transaction {
contract_id: "TARGET_CONTRACT_ID"
delegate_contract_id: "DELEGATE_CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
Wenn das Netzwerk diese Transaktion verarbeitet:
- Der Absender der Transaktion, also
sender_id, muss der Inhaber des durchdelegate_contract_ididentifizierten Vertrags sein. - Ein Vertrag kann sich selbst keine Speicherberechtigungen gewähren. Daher muss sich
delegate_contract_idvoncontract_idunterscheiden. - Speicherberechtigungen werden ausschließlich dem in
delegate_contract_idangegebenen Vertrag gewährt, nicht dem Absender der Transaktion. - Im Zielvertrag
contract_iddefinierte Kontofelder werden für den Delegatvertragdelegate_contract_idinitialisiert. Dadurch können Methoden im Zielvertrag bei vertragsübergreifenden Aufrufen sicher auf Kontofelder zugreifen, die mitgcul.callerodergcul.senderverschlüsselt sind.
Vertragsmethoden aufrufen
Sobald ein Vertrag bereitgestellt und alle erforderlichen Berechtigungen gewährt wurden, können Nutzer mit ihm interagieren, indem sie seine öffentlichen Methoden aufrufen. Ein Nutzer mit einem ROLE_CONTRACT_PARTICIPANT kann eine InvokeContractMethod-Transaktion senden und dabei die Vertrags-ID, den Methodennamen und die Werte der Methodenargumente angeben.
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
invoke_contract_method_transaction {
contract_id: "CONTRACT_ID"
method_name: "mint"
method_arguments {
key: "beneficiary"
value: { account_id: "BENEFICIARY_ID" }
}
method_arguments {
key: "value"
value: { int64_value: 10 }
}
}
}
Wenn das Netzwerk diese Transaktion verarbeitet:
- 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 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 World State aufgezeichnet.
Sprachspezifikation
GCULpy ist auf Sicherheit und Vorhersehbarkeit ausgelegt und lässt daher mehrere 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 sind mit dem Label Roadmap gekennzeichnet. Diese sind auf der Implementierungs-Roadmap, werden aber noch nicht vom gculpyc-Compiler unterstützt.
Typen
GCULpy unterstützt eine Reihe gängiger Variablentypen, wobei der Schwerpunkt auf statischer Typisierung liegt.
Zentrale Werttypen:
int,bool,strundNonewerden bereits unterstützt.- Roadmap:
Decimal,bytesundEnumsind auf der Roadmap. - Einschränkung:
floatundcomplexsind nicht zulässig. - Einschränkung:
int-Werte sind vorzeichenbehaftete 64-Bit-Ganzzahlen von -263 bis 263 - 1. Ein Ganzzahlliteral außerhalb dieses Bereichs führt zu einem Fehler bei der Kompilierung und ein Vorgang, dessen Ergebnis außerhalb dieses Bereichs liegt, zu einem Laufzeitfehler. - Einschränkung:
str-Werte dürfen maximal 1.000.000 Zeichen lang sein. Ein längeres Stringliteral führt zu einem Fehler bei der Kompilierung. Wenn Sie einen längeren String als Methodenargument übergeben oder einen längeren String als Ergebnis eines Vorgangs erzeugen, führt dies zu einem Laufzeitfehler.
Containertypen:
listunddict:list[T]: Eine änderbare Sequenz. Unterstützt die Indexierung, z. B.my_list[0], und die Methodeappend().dict[K, V]: Eine veränderliche Zuordnung. Unterstützt die Indexierung, z. B.my_dict[key], und die Methodepop().
collections.abc.Sequenceundcollections.abc.Mapping:collections.abc.Sequence[T]: Ein schreibgeschützter Verweis auf eine Sequenz. Unterstützt das Lesen von Elementen nach Index, z. B.my_seq[0], aber Elemente können nicht geändert oder gelöscht werden.list[T]ist ein Untertyp vonSequence[T]. Sie können also einenlist[T]-Wert oder ein Listenliteral an ein Ziel übergeben, zurückgeben oder zuweisen, das alsSequence[T]typisiert ist. Sie können jedoch keinSequence[T]einemlist[T]zuweisen.collections.abc.Mapping[K, V]: Ein schreibgeschützter Verweis auf eine Zuordnung. Unterstützt das Lesen von Einträgen nach Schlüssel, z. B.my_map[key], aber Einträge können nicht geändert oder gelöscht werden.dict[K, V]ist ein Untertyp vonMapping[K, V]. Sie können also einendict[K, V]-Wert oder ein Wörterbuchliteral an ein Ziel mit dem TypMapping[K, V]übergeben, zurückgeben oder zuweisen. Sie können jedoch keinMapping[K, V]einemdict[K, V]zuweisen.
- Roadmap:
tuple,setunddataclasssind auf der Roadmap. - Einschränkung Für Werte in einem Container müssen konkrete Typen angegeben werden. Beispielsweise ist
dict[str, int]zulässig, ein einfacherdictoderdict[str, Any]jedoch nicht. - Roadmap: Listenslicing, z. B.
my_list[1:3]. - Roadmap: Andere Methoden wie
.get(),.keys(),.values(),.items(),.clear()oder.extend().
Einschränkung: Alle Variablen, einschließlich Vertrags- und Kontofelder, Funktionsparameter 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. Sie können beispielsweise nicht in Variablen gespeichert oder als Argumente an Funktionen übergeben werden. Der Versuch, einem nicht deklarierten Feld einen Wert zuzuweisen, führt zu einem Kompilierungsfehler.
Einschränkung: Eine lokale Variable muss zugewiesen werden, bevor sie gelesen wird. Lokale Variablen haben keinen Standardwert. Wenn Sie eine Variable lesen, bevor sie zugewiesen wurde, führt dies zu einem Laufzeitfehler.
Klassen und Vererbung
Sie können nur Klassen definieren, die direkte Unterklassen von gcul.Contract oder gcul.ContractInterface sind.
Diese strenge Regel verhindert die Komplexität der vollständigen Python-Vererbung, die zu schwer zu findenden Fehlern führen und die Nachvollziehbarkeit des Codes erschweren kann. Aus Sicherheitsgründen wird beim Versuch, ein Attribut oder eine Methode einer übergeordneten Klasse zu überschreiben, ein Fehler ausgegeben. Dies ist eine klare Schutzmaßnahme gegen unerwartetes Verhalten.
Roadmap GCULpy wird mehr Flexibilität bieten, während die Kernprinzipien beibehalten werden. Die Roadmap umfasst die Unterstützung der einfachen 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 ermöglichen.
Das gcul-Modul
GCULpy bietet ein integriertes gcul-Modul mit wichtigen Typen, Decorators und Variablen für die Vertragsentwicklung.
Klasse gcul.Contract
Die Basisklasse für alle Verträge. Sie können keine Instanzen direkt erstellen. Verträge werden nur über CreateContract-Transaktionen instanziiert. Methoden und Attribute der Basisklasse gcul.Contract können in Unterklassen nicht überschrieben werden.
Contract.is_owner(account: Account) -> boolGibt
Truezurück, wenn das angegebene Konto der Vertragsinhaber ist.Contract.call(target: ContractInterface, payment: Amount | None = None) -> ContractMethodsInitiiert eine vertragsübergreifende Interaktion mit
target. Anstatt Methoden direkt für das Ziel aufzurufen (z.B.target.foo()), müssen Sieself.call()verwenden, um den Aufruf explizit über die Ledger-Laufzeit weiterzuleiten. Dieser Mechanismus gibt ein sicheres Proxy-Objekt zurück, mit dem entsprechende öffentliche Methoden für den Zielvertrag aufgerufen werden. So wird sichergestellt, dass die Ausführungsgrenzen strikt eingehalten werden und allepaymentsicher übermittelt werden. Weitere Informationen finden Sie unter ContractInterface.Beim Aufrufen einer mit
@gcul.payableversehenen Methode muss einpaymentangegeben werden. Bei nicht zahlungspflichtigen Methoden muss er weggelassen werden.targetmuss ein anderer Vertrag sein. Wenn Sie den aktuellen Vertrag überself.call()aufrufen, wird ein Laufzeitfehler ausgelöst. Wenn Sie eine andere Methode für denselben Vertrag aufrufen möchten, rufen Sie sie direkt auf, z. B.self.foo().Contract.native_transfer(beneficiary: Account, amount: Amount) -> NoneÜberträgt einen On-Ledger-Betrag direkt vom Guthaben des Vertrags an
beneficiary.Contract.id: strEindeutige String-ID des Vertrags.
Klasse gcul.Account
Ein integrierter Typ, der ein Nutzer- oder Vertragskonto im Ledger darstellt. Sie können gcul.Account-Objekte nicht direkt erstellen. Die Laufzeitumgebung erstellt sie für Sie und stellt sie als Funktions- oder Methodenargumente bereit. Wenn Sie ein Konto als Transaktionsargument übergeben möchten, geben Sie einen AccountValue mit der Konto-ID an. Die Laufzeit validiert die ID und wandelt sie in ein vollständiges Kontoobjekt um. Wenn die ID nicht zu einem registrierten Konto gehört, schlägt die Transaktion fehl. So wird sichergestellt, dass Sie nur mit gültigen Konten arbeiten.
Die Definition der Klasse entspricht in etwa Folgendem:
@dataclasses.dataclass(frozen=True)
class Account:
"""A valid account on the ledger."""
id: str # The ID of the account as a string.
Account.id: strEindeutige String-ID des Kontos.
Klasse gcul.Amount
Stellt einen Geldwert dar, der in einer bestimmten Hauptbuchwährung angegeben ist.
Instanziieren Sie einen Betrag mit gcul.Amount(value: int, currency: gcul.Currency). Die Definition der Klasse entspricht in etwa Folgendem:
@dataclasses.dataclass(frozen=True)
class Amount:
"""Represents an amount denominated in a ledger currency."""
value: int
currency: Currency
Klasse gcul.Currency
Stellt eine vollständig qualifizierte Währung im Ledger dar. Die Definition der Klasse entspricht in etwa Folgendem:
@dataclasses.dataclass(frozen=True)
class Currency:
"""Represents a fully qualified currency on the ledger."""
code: str
operator_id: str
Klasse gcul.ContractInterface
Die Basisklasse zum Deklarieren der öffentlichen Schnittstelle eines Vertrags.
Methoden und Attribute aus der Basisklasse gcul.ContractInterface können in Unterklassen nicht überschrieben werden. Außerdem dürfen die Methoden in der Schnittstellendeklaration keinen Code enthalten, nur ... ist zulässig.
Damit ein Vertrag Methoden für einen anderen im Ledger bereitgestellten Vertrag aufrufen kann, deklarieren Sie eine Schnittstellenklasse, die von gcul.ContractInterface erbt. Methoden-Stubs müssen mit der entsprechenden Deklaration im Zielvertrag übereinstimmen. Dazu gehören Argumente, Rückgabetyp und Decorator-Annotationen wie @gcul.readonly oder @gcul.payable.
Verwenden Sie in Ihrem Vertragscode self.call(target, payment), um eine Methode aus einem anderen Vertrag aufzurufen.
Das folgende Beispiel zeigt, wie ein TokenVendor-Vertrag mit einem anderen Vertrag interagiert, der die ERC20Interface implementiert. Dabei wird self.call() verwendet, um Tokens im Austausch für eine On-Ledger-Währungszahlung an einen Käufer zu übertragen:
class ERC20Interface(gcul.ContractInterface):
"""Interface for an external ERC20 token contract."""
def transfer(self, beneficiary: gcul.Account, value: int) -> int:
...
class TokenVendor(gcul.Contract):
"""A vendor contract that distributes ERC20 tokens."""
price_per_token: int
def __init__(self, price_per_token: int) -> None:
self.price_per_token = price_per_token
@gcul.payable
def purchase_tokens(self, token: ERC20Interface, quantity: int) -> int:
"""Accepts on-ledger currency for a specific quantity of ERC20 tokens."""
# Enforce positive quantity.
assert quantity > 0, "Quantity must be positive"
# Calculate the required payment.
required_payment = quantity * self.price_per_token
assert gcul.payment.value == required_payment, "Incorrect payment amount"
# Invokes `transfer` on the external ERC20 token contract
# to move tokens from the vendor's own balance to the caller.
return self.call(token).transfer(gcul.caller, quantity)
Schnittstellentypen können nicht nur als Methodenparameter, sondern auch als Vertragsfelder verwendet werden. Wenn Sie eine Schnittstelle in einem Feld speichern, kann ein Vertrag über Transaktionen hinweg einen Verweis auf einen anderen Vertrag enthalten und diesen Verweis später ersetzen. Im folgenden Beispielvertrag wird der zuvor deklarierte ERC20Interface verwendet:
class Router(gcul.Contract):
"""Holds a replaceable reference to an ERC20 token contract."""
target: ERC20Interface
def __init__(self, target: ERC20Interface) -> None:
self.target = target
def set_target(self, new_target: ERC20Interface) -> None:
assert self.is_owner(gcul.sender), "Only the owner can set the target"
self.target = new_target
Klasse gcul.AccountField
Ein integrierter generischer Typ zum Deklarieren von Kontofeldern. gcul.AccountField[T] enthält einen separaten Wert vom Typ T für jedes Konto, das dem Vertrag die Speicherberechtigung erteilt hat. Sie verhält sich wie eine Zuordnung mit gcul.Account-Schlüsseln, mit denen Sie einen Wert für jedes spezifische Konto lesen oder schreiben können.
Beispiel:
class Token(gcul.Contract):
balance: gcul.AccountField[int]
def deposit(self, account: gcul.Account, value: int) -> None:
self.balance[account] = value # Writes the value for one account.
def get_balance(self, account: gcul.Account) -> int:
return self.balance[account] # Reads the value for one account.
T muss ein Typ mit einem Standardwert sein. Daher können gcul.Account, gcul.Amount, gcul.Currency und Schnittstellentypen nicht als Werte für Kontofelder verwendet werden. Wenn ein Konto einem Vertrag die Speicherberechtigung gewährt, wird jedes Kontofeld mit dem Standardwert seines Typs für dieses Konto gefüllt.
Einschränkung: Die Indexierung mit einem Konto ist der einzige Vorgang, der von einem Kontofeld unterstützt wird. Das Feld selbst kann keiner Variablen zugewiesen, als Argument übergeben, von einer Funktion zurückgegeben oder iteriert werden.
Einschränkung: Einträge in Kontofeldern können nicht gelöscht werden. Ein gespeicherter Wert kann überschrieben werden, der Eintrag selbst ist jedoch dauerhaft.
Einschränkung: Kontofelder für gcul.sender, gcul.caller und alle gcul.Account, die als Methodenargument übergeben werden (auch innerhalb von Listen oder anderen Containern), werden vor der Methodenausführung geladen. Bei anderen Konten können in einer einzelnen Transaktion Kontofelder für maximal 15 verschiedene (contract, account)-Paare per Lazy Loading geladen werden. Der mehrfache Zugriff auf ein Kontofeld für dasselbe Paar in einer Transaktion wird nur einmal gezählt. Eine Transaktion, die dieses Limit überschreitet, schlägt fehl.
@gcul.readonly
Kennzeichnet eine Vertragsmethode als schreibgeschützt. Methodendeklarationen mit diesem Decorator können den Vertragsstatus nicht ändern oder Währungsübertragungen im Ledger vornehmen. Außerdem können sie in ihren Signaturen keine änderbaren Containertypen wie list oder dict verwenden. Stattdessen müssen sie schreibgeschützte Typdeklarationen wie Sequence oder Mapping verwenden.
@gcul.payable
Kennzeichnet eine Vertragsmethode als zahlungspflichtig. Wenn eine Methode eine On-Ledger-Währungszahlung mit gcul.payment erwartet, muss sie mit diesem Decorator gekennzeichnet werden. Andernfalls schlägt die Transaktion fehl.
gcul.sender: gcul.Account
Enthält einen Verweis auf das Konto, das die aktuelle InvokeContractMethod-Transaktion signiert und gesendet hat. Der Wert von gcul.sender ändert sich während der Transaktionsausführung nicht, auch wenn vertragsübergreifende Aufrufe erfolgen, im Gegensatz zu gcul.caller, das sich auf den unmittelbaren Aufrufer bezieht.
gcul.caller: gcul.Account
Enthält einen Verweis auf den unmittelbaren Aufrufer der aktuellen Methode. Bei direkten Nutzertransaktionen entspricht gcul.caller dem Wert gcul.sender, der auf die Person verweist, die die Transaktion gesendet hat. Bei vertragsübergreifenden Aufrufen entspricht gcul.caller der Konto-ID des aufrufenden Vertrags. Interne Aufrufe innerhalb eines Vertrags ändern den Wert von gcul.caller nicht.
gcul.payment: gcul.Amount
Enthält die Zahlung, die an eine mit @gcul.payable versehene Methode übertragen wurde.
Der Zugriff auf gcul.payment innerhalb einer nicht zahlbaren Methode führt zu einem Fehler bei der Kompilierung.
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 (
*), Floor-Division (//) und Modulo (%). - Potenzierung (
**) für positive Exponenten. - Vergleiche (
<,<=,>,>=,==,!=). - Bitweises 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 Laufzeitfehler.
- Einschränkung: Die echte Division (
/) ist nicht zulässig und führt zu einem Kompilierungsfehler, da sie den Rückgabetypfloathat.
Kontrollfluss
Die meisten Kontrollflussanweisungen aus Python funktionieren in GCULpy mit derselben Semantik:
passändern.- interne Funktionsaufrufe (gleicher Vertrag, nicht rekursiv).
assertändern.if ... then .. else ...ändern.for VAR in CONTAINERändern.- Externe Funktionsaufrufe (an einen anderen Vertrag, nicht rekursiv)
break- undcontinue-Anweisungenraiseändern.- Roadmap-
try ... except-Ankündigungen. - Roadmap-
match-Ankündigungen. - Roadmap
generators- undyield-Anweisungen. - Roadmap: Kontextmanager und
with-Anweisungen.
Einschränkung: GCULpy ist absichtlich nicht Turing-vollständig, um Endlosschleifen zu verhindern und die statische Analyse zu erleichtern. So wird das durchgesetzt:
- Keine Endlosschleifen: Iterationen sind nur mit
for-Schleifen über endliche Container oder begrenzte Sequenzen mitrange(...)zulässig.while-Schleifen sind nicht zulässig. Einige Containeraktualisierungen, z. B. das Hinzufügen oder Entfernen von Elementen zu einer Liste oder von Schlüsseln zu einem Wörterbuch, sind während der Iteration nicht zulässig. - Keine Rekursion: Eine Funktion kann sich weder direkt noch indirekt selbst aufrufen. 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 grundlegenden Designprinzipien der Vorhersehbarkeit, Sicherheit und deterministischen Ausführung beizubehalten. Asynchrone Vorgänge erschweren die Nachvollziehbarkeit des Kontrollflusses eines Programms, was häufig zu Sicherheitslücken und Race-Bedingungen führt.
Integrierte Funktionen
GCULpy unterstützt die folgenden integrierten Funktionen:
abs(x)Gibt den absoluten Wert einer Ganzzahl
xzurück. Argumente müssen vom Typintsein.len(s)Gibt die Länge, d. h. die Anzahl der Elemente, eines Strings oder endlichen Containers zurück.
max(iterable)
max(arg1, arg2, *args)Gibt das größte Element in einem iterierbaren Objekt oder das größte von zwei oder mehr Argumenten zurück. Wenn
max()für ein leeres iterierbares Objekt aufgerufen wird, wird ein Laufzeitfehler ausgelöst.min(iterable)
min(arg1, arg2, *args)Gibt das kleinste Element in einem iterierbaren Objekt oder das kleinste von zwei oder mehr Argumenten zurück. Wenn
min()für ein leeres iterierbares Objekt aufgerufen wird, wird ein Laufzeitfehler ausgelöst.range(stop)
range(start, stop[, step=1])Generiert eine arithmetische Folge von Ganzzahlen mit einer Ganzzahl
stepungleich null.Einschränkung: Kann nur inline mit
for-Schleifen verwendet werden, z. B.for i in range(10): .... Bereichsobjekte können nicht Variablen zugewiesen oder in Feldern gespeichert, als Argumente an Funktionen übergeben oder mitlen()abgefragt werden.
Roadmap Die folgenden integrierten Funktionen stehen auf der Implementierungs-Roadmap:
|
A
B
C
D |
E
F
H
I
L |
O
P
R |
S
T
Z |
Versionshinweise
03.11.2026 Die GA-Version des
gculpyc-Compilers (v0.2.0) wurde veröffentlicht, einschließlich Unterstützung für vertragsübergreifende Aufrufe, On-Ledger-Währungsübertragungen und -Zahlungen,@gcul.readonly-Methoden,list, zusätzliche Kontrollflussanweisungen und integrierte Funktionen.28. Januar 2026 Frühe Version des
gculpyc-Compilers, die Teilnehmern der privaten Vorschau von Universal Ledger zur Verfügung gestellt wird. Eine Anleitung zur Verwendung des Compilers finden Sie unter Programmierbaren Vertrag bereitstellen.