GCULpy est un sous-ensemble strict de Python, typé de manière statique, conçu pour être sûr, lisible et auditable. Sa conception limite intentionnellement certaines fonctionnalités dynamiques de Python pour éviter les failles courantes des contrats intelligents et s'assurer que le comportement d'un contrat est toujours prévisible.
Cette page fournit une documentation de référence sur la spécification du langage GCULpy, qui couvre les concepts de base et le cycle de vie d'un contrat sur un réseau Universal Ledger.
Concepts fondamentaux
Le contrat suivant définit un exemple de jeton ERC20 dans 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
Un contrat GCULpy est une classe qui hérite de gcul.Contract. Elle contient des champs (état de stockage) et des méthodes (logique de traitement qui opère sur les champs).
Champs
L'état d'un contrat GCULpy est stocké dans des champs. Tous les champs doivent être déclarés avec un type statique au niveau de la classe. Il existe deux types de champs :
Les champs de contrat contiennent une seule valeur stockée avec le contrat lui-même. Dans l'exemple
ERC20Token,symbol: strettotal_supply: intsont des champs de contrat.Les champs de compte contiennent une valeur distincte pour chaque compte interagissant avec le contrat. Ils sont déclarés avec
gcul.AccountField[T], oùTest le type de la valeur par compte. Dans l'exempleERC20Token,balance: gcul.AccountField[int]est un champ de compte. Avant qu'un contrat puisse écrire dans l'espace de stockage réservé à ce compte, l'autorisation de stockage doit être explicitement accordée au contrat. Une fois les données stockées, seule l'instance de contrat peut les modifier, pas le compte.
Les champs Restriction sont soumis aux limites de stockage suivantes. Une transaction qui dépasse l'une ou l'autre de ces limites échoue.
- Tous les champs d'un contrat partagent une seule entrée de stockage, qui peut contenir environ 5 Mio de données sérialisées au total.
- Tous les champs de compte qu'un contrat stocke pour chaque compte individuel peuvent contenir au maximum 1 Mio de données sérialisées au total.
Méthodes
Les méthodes définissent la logique exécutable d'un contrat. Elles se comportent comme des méthodes Python et peuvent lire ou modifier les champs du contrat.
__init__: le constructeur n'est appelé qu'une seule fois, lors du premier déploiement du contrat. Chaque champ de contrat doit se voir attribuer une valeur dans le constructeur. Le constructeur n'initialise pas les champs de compte. La valeur de chaque compte est créée lorsque ce compte accorde l'autorisation de stockage.Méthodes privées : les méthodes commençant par un trait de soulignement (par exemple,
_internal_logic) sont privées et ne peuvent être appelées que par d'autres méthodes du même contrat. L'interpréteur Universal Ledger applique cette contrainte.Méthodes publiques : toute méthode ne commençant pas par un trait de soulignement (
_) est publique. Les méthodes publiques peuvent être appelées par n'importe quel utilisateur disposant deROLE_CONTRACT_PARTICIPANTen envoyant une transaction InvokeContractMethod.
Restriction : Les méthodes publiques et toutes les méthodes @gcul.readonly ne peuvent pas utiliser de types modifiables dans leurs signatures, par exemple list ou dict. Utilisez plutôt des déclarations de type en lecture seule telles que Sequence ou Mapping.
Restriction : pendant l'exécution, les méthodes sont limitées à une limite supérieure d'utilisation du calcul à l'échelle du réseau. Le dépassement de cette limite entraîne l'échec de la transaction.
Cycle de vie du contrat
Les sections suivantes décrivent les opérations types impliquées dans le cycle de vie d'un contrat GCULpy.
Déployer un contrat
Tout d'abord, compilez votre code source GCULpy à l'aide du compilateur gculpyc. Ensuite, un utilisateur disposant du rôle ROLE_CONTRACT_CREATOR peut envoyer une transaction CreateContract pour déployer le bytecode compilé sur un réseau Universal Ledger. Pour obtenir des instructions détaillées, consultez le tutoriel Déployer un contrat programmable.
Une telle transaction se présenterait comme suit :
client_transaction {
sender_id: "OWNER_ACCOUNT_ID"
create_contract_transaction {
contract_bytes: "COMPILED_BYTECODE"
init_arguments {
key: "symbol"
value: { string_value: "US02079K1079" }
}
}
}
Lorsque le réseau traite cette transaction :
- Le constructeur, c'est-à-dire la méthode
__init__, est exécuté avec les arguments fournis pour créer une instance de contrat. - L'expéditeur de la transaction devient le propriétaire du contrat.
- L'instance de contrat est stockée de manière permanente dans le registre et se voit attribuer un ID de contrat unique, qui est renvoyé dans le résultat de la transaction.
Octroyer des autorisations
Pour qu'un contrat puisse stocker des données dans un champ de compte au nom d'un compte (qu'il s'agisse d'un compte utilisateur ou d'un autre contrat), l'autorisation de stockage doit être accordée au contrat stockant les données. Il s'agit d'une étape de sécurité essentielle.
Accorder des autorisations pour un compte utilisateur
Un utilisateur disposant d'un ROLE_CONTRACT_PARTICIPANT peut envoyer une transaction GrantContractPermissions pour un ID de contrat spécifique :
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
grant_contract_permissions_transaction {
contract_id: "CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
Lorsque le réseau traite cette transaction :
- Si le contrat ne définit aucun champ de compte, la transaction est refusée.
- Si le contrat définit des champs de compte, ils sont tous renseignés avec des valeurs par défaut (par exemple,
contract.balance[gcul.sender] = 0). Ces valeurs sont ensuite stockées dans l'état mondial dans le cadre des données du compte.
Accorder des autorisations pour un contrat délégué
Un utilisateur disposant d'un ROLE_CONTRACT_PARTICIPANT peut envoyer une transaction GrantContractPermissions pour autoriser un contrat à stocker des données au nom d'un autre contrat qu'il possède :
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
}
}
Lorsque le réseau traite cette transaction :
- L'expéditeur de la transaction, c'est-à-dire
sender_id, doit être le propriétaire du contrat identifié pardelegate_contract_id. - Un contrat ne peut pas s'accorder des autorisations de stockage à lui-même. Par conséquent,
delegate_contract_iddoit être différent decontract_id. - Les autorisations de stockage sont accordées exclusivement au contrat spécifié dans
delegate_contract_id, et non à l'expéditeur de la transaction. - Les champs de compte définis sur le contrat cible,
contract_id, sont initialisés pour le contrat délégué,delegate_contract_id, ce qui permet aux méthodes du contrat cible d'accéder en toute sécurité aux champs de compte associés àgcul.callerougcul.senderlors des appels entre contrats.
Appeler des méthodes de contrat
Une fois qu'un contrat est déployé et que les autorisations nécessaires sont accordées, les utilisateurs peuvent interagir avec lui en appelant ses méthodes publiques. Un utilisateur disposant d'un ROLE_CONTRACT_PARTICIPANT peut envoyer une transaction InvokeContractMethod en spécifiant l'ID du contrat, le nom de la méthode et les valeurs des arguments de la méthode.
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 }
}
}
}
Lorsque le réseau traite cette transaction :
- L'instance de contrat associée au
CONTRACT_IDfourni est récupérée. - La méthode
mint(beneficiary=Account("BENEFICIARY_ID"), value=10)est exécutée. L'objetAccountdu bénéficiaire est créé et validé par l'environnement d'exécution. La logique de la méthode peut supposer que l'ID fourni est valide et fait référence à un compte existant dans le registre. - Si la méthode échoue pour une raison quelconque, la transaction échouera et aucune mise à jour ne sera apportée à l'état du contrat.
- Si la méthode aboutit, l'état mis à jour du contrat est enregistré dans l'état mondial.
Spécification de la langue
GCULpy est conçu pour être sûr et prévisible. Par conséquent, il n'autorise pas plusieurs fonctionnalités Python, qui seront signalées par le libellé Restriction. Ces restrictions sont destinées à être des fonctionnalités de langage permanentes, introduites pour faciliter la lecture, l'audit et l'analyse statique de la logique du contrat, limitant ainsi les comportements surprenants ou dangereux.
Les autres fonctionnalités sont signalées par le libellé Roadmap. Elles figurent dans la feuille de route de mise en œuvre, mais ne sont pas encore compatibles avec le compilateur gculpyc.
Types
GCULpy est compatible avec un éventail de types de variables courants, avec un accent particulier sur le typage statique.
Types de valeurs fondamentales :
int,bool,stretNonesont déjà compatibles.- Feuille de route :
Decimal,bytesetEnumsont sur la feuille de route. - Restriction :
floatetcomplexne sont pas autorisés. - Restriction : les valeurs
intsont des entiers signés de 64 bits, de -263 à 263 - 1. Un littéral entier en dehors de cette plage entraîne une erreur de compilation, et une opération dont le résultat est en dehors de cette plage entraîne une erreur d'exécution. - Restriction : les valeurs
strne peuvent pas dépasser 1 000 000 caractères. Une chaîne littérale plus longue entraînera une erreur de compilation. La transmission d'une chaîne plus longue en tant qu'argument de méthode ou la production d'une chaîne plus longue à la suite d'une opération entraîne une erreur d'exécution.
Types de conteneurs :
listetdict:list[T]: séquence modifiable. Prend en charge l'indexation, par exemplemy_list[0], et la méthodeappend().dict[K, V]: mappage modifiable. Prend en charge l'indexation, par exemplemy_dict[key], et la méthodepop().
collections.abc.Sequenceetcollections.abc.Mapping:collections.abc.Sequence[T]: référence en lecture seule à une séquence. Permet de lire des éléments par index, par exemplemy_seq[0], mais les éléments ne peuvent pas être modifiés ni supprimés.list[T]est un sous-type deSequence[T]. Vous pouvez donc transmettre, renvoyer ou attribuer une valeur ou un littéral de listelist[T]à une cible de typeSequence[T], mais vous ne pouvez pas attribuer unSequence[T]à unlist[T].collections.abc.Mapping[K, V]: référence en lecture seule à un mappage. Permet de lire les entrées par clé, par exemplemy_map[key], mais les entrées ne peuvent pas être modifiées ni supprimées.dict[K, V]est un sous-type deMapping[K, V]. Vous pouvez donc transmettre, renvoyer ou attribuer une valeurdict[K, V]ou un littéral de dictionnaire à une cible de typeMapping[K, V], mais vous ne pouvez pas attribuer unMapping[K, V]à undict[K, V].
- Feuille de route :
tuple,setetdataclasssont sur la feuille de route. - Restriction : Les types concrets doivent être spécifiés pour les valeurs d'un conteneur. Par exemple,
dict[str, int]est autorisé, mais pas un simpledictoudict[str, Any]. - Feuille de route : découpage de liste, par exemple
my_list[1:3]. - Feuille de route Autres méthodes, telles que
.get(),.keys(),.values(),.items(),.clear()ou.extend().
Restriction : toutes les variables, y compris les champs de contrat et de compte, les paramètres de fonction et les types renvoyés, doivent être définies et typées de manière statique. Leur type ne peut pas être modifié au moment de l'exécution, et seuls les types concrets sont acceptés. Les types ne peuvent pas être utilisés comme valeurs. Par exemple, ils ne peuvent pas être stockés dans des variables ni transmis à des fonctions en tant qu'arguments. Toute tentative d'attribution d'une valeur à un champ non déclaré entraîne une erreur de compilation.
Restriction : une variable locale doit être attribuée avant d'être lue. Les variables locales n'ont pas de valeur par défaut. Par conséquent, la lecture d'une variable avant son attribution entraîne une erreur d'exécution.
Classes et héritage
Vous ne pouvez définir que des classes qui sont des sous-classes directes de gcul.Contract ou de gcul.ContractInterface.
Cette règle stricte évite les complexités de l'héritage Python complet, qui peut introduire des bugs difficiles à trouver et rendre le code difficile à comprendre. Pour des raisons de sécurité, toute tentative de remplacement d'une propriété ou d'une méthode d'une classe parente génère une erreur, ce qui offre une protection claire contre les comportements inattendus.
Feuille de route : GCULpy offrira plus de flexibilité tout en conservant ses principes fondamentaux. La feuille de route inclut la compatibilité avec l'héritage unique sur les classes définies par l'utilisateur, avec un remplacement de méthode géré explicitement avec un décorateur @override. De plus, la fonction intégrée super() ne sera compatible que dans sa forme sans argument, afin de garantir des opérations directes et prévisibles.
Le module gcul
GCULpy fournit un module gcul intégré avec des types, des décorateurs et des variables essentiels pour le développement de contrats.
classe gcul.Contract
Classe de base pour tous les contrats. Vous ne pouvez pas créer d'instances directement. Les contrats ne sont instanciés que via des transactions CreateContract. Les méthodes et les propriétés de la classe de base gcul.Contract ne peuvent pas être remplacées dans les sous-classes.
Contract.is_owner(account: Account) -> boolRenvoie
Truesi le compte fourni est le propriétaire du contrat.Contract.call(target: ContractInterface, payment: Amount | None = None) -> ContractMethodsLance une interaction entre contrats avec
target. Au lieu d'appeler directement des méthodes sur la cible (par exemple,target.foo()), vous devez utiliserself.call()pour acheminer explicitement l'appel via l'environnement d'exécution du registre. Ce mécanisme renvoie un objet proxy sécurisé utilisé pour appeler les méthodes publiques correspondantes sur le contrat cible, ce qui garantit que les limites d'exécution sont strictement appliquées et que toutpaymentest transmis de manière sécurisée. Pour en savoir plus, consultez ContractInterface.Un
paymentdoit être fourni lors de l'appel d'une méthode décorée avec@gcul.payable, et doit être omis pour les méthodes non payables.targetdoit être un contrat différent. L'appel du contrat actuel viaself.call()génère une erreur d'exécution. Pour appeler une autre méthode sur le même contrat, appelez-la directement, par exempleself.foo().Contract.native_transfer(beneficiary: Account, amount: Amount) -> NoneTransfère un montant du registre directement du solde du contrat vers
beneficiary.Contract.id: strID de chaîne unique du contrat.
classe gcul.Account
Type intégré représentant un compte utilisateur ou un compte de contrat sur le registre. Vous ne pouvez pas créer d'objets gcul.Account directement. L'environnement d'exécution les crée pour vous et les fournit en tant qu'arguments de fonction ou de méthode. Pour transmettre un compte en tant qu'argument de transaction, fournissez un AccountValue contenant l'ID de compte. L'environnement d'exécution valide l'ID et le convertit en un objet de compte complet. Si l'ID n'appartient pas à un compte enregistré, la transaction échoue. Vous êtes ainsi certain de ne travailler qu'avec des comptes valides.
La définition de la classe est à peu près équivalente à :
@dataclasses.dataclass(frozen=True)
class Account:
"""A valid account on the ledger."""
id: str # The ID of the account as a string.
Account.id: strID de chaîne unique du compte.
classe gcul.Amount
Représente une valeur monétaire libellée dans une devise de comptabilité spécifique.
Instanciez un montant à l'aide de gcul.Amount(value: int, currency: gcul.Currency). La définition de la classe est à peu près équivalente à :
@dataclasses.dataclass(frozen=True)
class Amount:
"""Represents an amount denominated in a ledger currency."""
value: int
currency: Currency
classe gcul.Currency
Représente une devise complète dans le registre. La définition de la classe est à peu près équivalente à :
@dataclasses.dataclass(frozen=True)
class Currency:
"""Represents a fully qualified currency on the ledger."""
code: str
operator_id: str
classe gcul.ContractInterface
Classe de base permettant de déclarer l'interface publique d'un contrat.
Les méthodes et les propriétés de la classe de base gcul.ContractInterface ne peuvent pas être remplacées dans les sous-classes. De plus, les méthodes de la déclaration d'interface ne peuvent inclure aucun code, seul ... est autorisé.
Pour qu'un contrat appelle des méthodes sur un autre contrat déployé sur le registre, déclarez une classe d'interface héritant de gcul.ContractInterface. Les bouchons de méthode doivent correspondre à leur déclaration correspondante dans le contrat cible. Cela inclut les arguments, le type renvoyé et les annotations de décorateur telles que @gcul.readonly ou @gcul.payable.
Dans le code de votre contrat, utilisez self.call(target, payment) pour appeler une méthode d'un autre contrat.
L'exemple suivant illustre l'interaction d'un contrat TokenVendor avec un autre contrat implémentant ERC20Interface. Il utilise self.call() pour transférer des jetons à un acheteur en échange d'un paiement en devise sur le registre :
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)
Les types d'interface peuvent également être utilisés comme champs de contrat, et pas seulement comme paramètres de méthode. Le stockage d'une interface dans un champ permet à un contrat de conserver une référence à un autre contrat d'une transaction à l'autre, et de remplacer cette référence ultérieurement. L'exemple de contrat suivant utilise le ERC20Interface déclaré précédemment :
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
classe gcul.AccountField
Type générique intégré pour déclarer les champs de compte. gcul.AccountField[T] contient une valeur distincte de type T pour chaque compte ayant accordé l'autorisation de stockage au contrat. Il se comporte comme un mappage avec des clés gcul.Account, que vous pouvez utiliser pour lire ou écrire une valeur pour chaque compte spécifique.
Exemple :
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 doit être un type ayant une valeur par défaut. Par conséquent, les types gcul.Account, gcul.Amount, gcul.Currency et d'interface ne peuvent pas être utilisés comme valeurs de champ de compte. Lorsqu'un compte accorde l'autorisation de stockage à un contrat, chaque champ de compte est renseigné avec la valeur par défaut de son type pour ce compte.
Restriction : l'indexation avec un compte est la seule opération compatible avec un champ de compte. Le champ lui-même ne peut pas être attribué à une variable, transmis en tant qu'argument, renvoyé par une fonction ni itéré.
Restriction : Les entrées du champ "Compte" ne peuvent pas être supprimées. Une valeur stockée peut être écrasée, mais l'entrée elle-même est permanente.
Restriction : Les champs de compte pour gcul.sender, gcul.caller et tout gcul.Account transmis en tant qu'argument de méthode (y compris dans des listes ou d'autres conteneurs) sont chargés avant l'exécution de la méthode. Pour les autres comptes, une seule transaction peut charger de manière différée les champs de compte pour au plus 15 paires (contract, account) distinctes. L'accès à un champ de compte pour la même paire plusieurs fois dans une transaction ne compte qu'une seule fois. Une transaction qui dépasse cette limite échoue.
@gcul.readonly
Annote une méthode de contrat en lecture seule. Les déclarations de méthode avec ce décorateur ne peuvent pas modifier l'état du contrat ni effectuer de transferts de devises sur le registre. Elles ne peuvent pas non plus utiliser de types de conteneurs modifiables dans leurs signatures, par exemple list ou dict. Elles doivent utiliser des déclarations de type en lecture seule telles que Sequence ou Mapping à la place.
@gcul.payable
Annote une méthode de contrat comme nécessitant un paiement pour être exécutée. Si une méthode s'attend à recevoir un paiement en devise sur le registre à l'aide de gcul.payment, elle doit être marquée avec ce décorateur, sinon la transaction échouera.
gcul.sender: gcul.Account
Contient une référence au compte qui a signé et envoyé la transaction InvokeContractMethod actuelle. La valeur de gcul.sender ne change pas pendant l'exécution de la transaction, même si elle implique des appels inter-contrats, contrairement à gcul.caller qui fait référence à l'appelant immédiat.
gcul.caller: gcul.Account
Contient une référence à l'appelant immédiat de la méthode actuelle. Pour les transactions utilisateur directes, gcul.caller correspond à gcul.sender, qui pointe vers la personne qui a envoyé la transaction. Pour les appels inter-contrats, gcul.caller correspond à l'ID de compte du contrat appelant. Les appels internes au sein d'un contrat ne modifient pas la valeur de gcul.caller.
gcul.payment: gcul.Amount
Contient le paiement transféré vers une méthode décorée avec @gcul.payable.
L'accès à gcul.payment dans une méthode non payable génère une erreur de compilation.
Opérateurs
La plupart des opérateurs disponibles en Python sont compatibles avec GCULpy et fonctionnent comme prévu.
- Addition (
+) et soustraction (-), y compris les formes unaires et binaires. - Multiplication (
*), division entière (//) et modulo (%). - Exponentiation (
**) pour les exposants positifs. - Comparaisons (
<,<=,>,>=,==,!=). - Opérateurs bit à bit : AND (
&), OR (|), XOR (^), décalage à gauche (<<), décalage à droite (>>), négation (~). - Opérations booléennes (
and,or,not). - Feuille de route : identité de l'objet (
is). - Restriction : les exposants négatifs ne sont pas autorisés et génèrent une erreur d'exécution.
- Restriction : La division réelle (
/), car elle a un type renvoyéfloat, n'est pas autorisée et entraîne une erreur de compilation.
Flux de contrôle
La plupart des instructions de flux de contrôle de Python fonctionnent dans GCULpy avec la même sémantique :
pass.- les appels de fonctions internes (même contrat, non récursifs).
assert.if ... then .. else ....for VAR in CONTAINER.- Appels de fonctions externes (vers n'importe quel autre contrat, non récursifs).
- Instructions
breaketcontinue. raise.- Feuille de route
try ... except - Feuille de route
match - Feuille de route des instructions
generatorsetyield. - Feuille de route : gestionnaires de contexte et instructions
with.
Restriction : GCULpy est intentionnellement incomplet au sens de Turing pour éviter les boucles infinies et faciliter l'analyse statique. Voici comment il applique cette règle :
- Pas de boucles infinies : l'itération n'est autorisée qu'à l'aide de boucles
forsur des conteneurs finis ou des séquences limitées avecrange(...). Les boucleswhilene sont pas autorisées. Certaines mises à jour de conteneurs, par exemple l'ajout ou la suppression d'éléments dans une liste ou de clés dans un dictionnaire, ne sont pas autorisées lors de l'itération. - Pas de récursion : une fonction ne peut pas s'appeler elle-même, directement ou indirectement. L'environnement d'exécution effectue des vérifications statiques et d'exécution pour détecter et rejeter l'utilisation de la récursion.
- Pas de flux de contrôle asynchrone : l'utilisation des primitives
asyncn'est pas autorisée pour maintenir les principes de conception fondamentaux de prévisibilité, de sécurité et d'exécution déterministe. Les opérations asynchrones rendent difficile le raisonnement sur le flux de contrôle d'un programme, ce qui entraîne souvent des failles et des conditions de concurrence.
Fonctions intégrées
GCULpy est compatible avec les fonctions intégrées suivantes :
abs(x)Renvoie la valeur absolue d'un entier
x. Les arguments doivent être de typeint.len(s)Renvoie la longueur, c'est-à-dire le nombre d'éléments, d'une chaîne ou d'un conteneur fini.
max(iterable)
max(arg1, arg2, *args)Renvoie l'élément le plus grand d'un itérable ou le plus grand d'au moins deux arguments. L'appel de
max()sur un itérable vide génère une erreur d'exécution.min(iterable)
min(arg1, arg2, *args)Renvoie l'élément le plus petit d'un itérable ou le plus petit de deux arguments ou plus. L'appel de
min()sur un itérable vide génère une erreur d'exécution.range(stop)
range(start, stop[, step=1])Génère une séquence arithmétique d'entiers avec un entier
stepnon nul.Restriction : ne peut être utilisé qu'en ligne avec des boucles
for, par exemplefor i in range(10): .... Les objets Range ne peuvent pas être attribués à des variables, stockés dans des champs, transmis en tant qu'arguments à des fonctions ni interrogés aveclen().
Feuille de route Les fonctions intégrées suivantes figurent sur la feuille de route de mise en œuvre :
|
A
B
C
D |
E
F
H
I
L |
O
P
R |
S
T
Z |
Notes de version
3 novembre 2026. Lancement de la version en disponibilité générale du compilateur
gculpyc(v0.2.0), y compris la compatibilité avec les appels inter-contrats, les transferts et paiements de devises sur le registre, les méthodes@gcul.readonly,list, les instructions de flux de contrôle supplémentaires et les fonctions intégrées.28 janvier 2026 Une version préliminaire du compilateur
gculpycest mise à la disposition des participants à la preview privée d'Universal Ledger. Pour suivre un tutoriel utilisant le compilateur, consultez Déployer un contrat programmable.