GCULpy è un sottoinsieme di Python con tipi statici rigorosi, progettato per essere sicuro, leggibile e verificabile. Il suo design limita intenzionalmente alcune funzionalità dinamiche di Python per prevenire le vulnerabilità comuni degli smart contract e garantire che il comportamento di un contratto sia sempre prevedibile.
Questa pagina fornisce un riferimento per la specifica del linguaggio GCULpy, che copre i concetti di base e il ciclo di vita di un contratto su una rete Universal Ledger.
Concetti principali
Il seguente contratto definisce un token ERC20 di esempio 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
Un contratto GCULpy è una classe che eredita da gcul.Contract. Contiene campi (che memorizzano lo stato) e metodi (logica di elaborazione che opera sui campi).
Campi
Lo stato di un contratto GCULpy viene memorizzato nei campi. Tutti i campi devono essere dichiarati con un tipo statico a livello di classe. Esistono due tipi di campi:
I campi del contratto contengono un singolo valore memorizzato con il contratto stesso. Nell'esempio
ERC20Token,symbol: stretotal_supply: intsono campi del contratto.I campi dell'account memorizzano un valore separato per ogni account utente che interagisce con un contratto. Vengono sempre dichiarati come dizionario (
dict) congcul.Accountcome chiave, ad esempiobalance: dict[gcul.Account, int]. Prima che un contratto possa scrivere nell'account di un utente, l'utente deve concedere esplicitamente l'autorizzazione di archiviazione al contratto. Una volta memorizzati i dati, solo l'istanza del contratto può modificarli o eliminarli, l'utente no.
Metodi
I metodi definiscono la logica eseguibile di un contratto. Si comportano come i metodi Python e possono leggere o modificare i campi del contratto.
__init__: il costruttore viene chiamato una sola volta quando il contratto viene eseguito il deployment per la prima volta. Viene utilizzato per impostare lo stato iniziale dei campi del contratto. I campi a cui non viene assegnato un valore nel costruttore ricevono un valore predefinito appropriato, ad esempio0per un campointo un dizionario vuoto per un campodict.Metodi privati: i metodi che iniziano con un trattino basso (ad esempio,
_internal_logic) sono privati e possono essere chiamati solo da altri metodi all'interno dello stesso contratto. L'interprete Universal Ledger applica questo vincolo.Metodi pubblici: qualsiasi metodo che non inizia con un trattino basso (
_) è pubblico. I metodi pubblici possono essere chiamati da qualsiasi utente con ilROLE_CONTRACT_PARTICIPANTinviando una InvokeContractMethod transazione.
Ciclo di vita del contratto
Le sezioni che seguono illustrano le operazioni tipiche coinvolte nel ciclo di vita di un contratto GCULpy.
Eseguire il deployment di un contratto
Innanzitutto, compila il codice sorgente GCULpy utilizzando il compilatore gculpyc. Poi, un utente con il ROLE_CONTRACT_CREATOR può inviare una
CreateContract
transazione per eseguire il deployment del bytecode compilato in una rete Universal Ledger. Per
istruzioni dettagliate, consulta il
tutorial Eseguire il deployment di un contratto programmabile.
Una transazione di questo tipo avrebbe il seguente aspetto:
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" }
}
}
}
}
Quando la rete elabora questa transazione:
- Viene eseguito il costruttore, ovvero il metodo
__init__, per creare una nuova istanza del contratto. - Il mittente della transazione diventa il proprietario del contratto.
- L'istanza del contratto viene memorizzata in modo permanente nel ledger e le viene assegnato un ID contratto univoco, che viene restituito come parte dell'output della transazione.
Concedere le autorizzazioni
Prima che un contratto possa memorizzare i dati in un campo dell'account per conto di un utente, l'utente deve prima concedere l'autorizzazione di archiviazione. Si tratta di un passaggio di sicurezza fondamentale. Un utente con il ruolo ROLE_CONTRACT_PARTICIPANT può inviare una
GrantContractPermissions
per un ID contratto specifico.
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
app {
[type.googleapis.com/google.cloud.universalledger.v1.GrantContractPermissions] {
contract_id: "CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
}
Quando la rete elabora questa transazione:
- Se il contratto non definisce alcun campo dell'account, la transazione viene rifiutata.
- Se il contratto definisce i campi dell'account, tutti vengono compilati con i valori predefiniti (ad esempio,
contract.balance[gcul.sender] = 0). Questi valori vengono quindi memorizzati nello stato del mondo come parte dei dati dell'account e il mittente della transazione viene registrato come partecipante a questa istanza di contratto specifica.
Chiamare i metodi del contratto
Una volta eseguito il deployment di un contratto e concesse le autorizzazioni necessarie, gli utenti possono interagire con esso chiamando i suoi metodi pubblici. Un utente con un ROLE_CONTRACT_PARTICIPANT può inviare una
InvokeContractMethod
specificando l'ID contratto, il nome del metodo e i valori degli argomenti.
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 }
}
}
}
}
Quando la rete elabora questa transazione:
- Viene recuperata l'istanza del contratto associata all'
CONTRACT_IDfornito. - Viene eseguito il metodo
mint(beneficiary=Account("BENEFICIARY_ID"), value=10). L'oggettoAccountper il beneficiario viene creato e convalidato dal runtime. La logica del metodo può presupporre in sicurezza che l'ID fornito sia valido e si riferisca a un account esistente nel ledger. - Se il metodo non riesce per qualsiasi motivo, la transazione non andrà a buon fine e non verranno apportati aggiornamenti allo stato del contratto.
- Se il metodo ha esito positivo, lo stato aggiornato del contratto viene registrato nello stato del mondo.
Specifica del linguaggio
GCULpy è progettato per la sicurezza e la prevedibilità e, di conseguenza, non consente diverse funzionalità di Python; queste verranno indicate con l'etichetta Restriction. Queste limitazioni sono intese come funzionalità del linguaggio permanenti, introdotte per rendere la logica del contratto più facile da leggere, controllare e analizzare staticamente, limitando comportamenti sorprendenti o non sicuri.
Altre funzionalità sono indicate con l'etichetta Roadmap, queste sono nella
roadmap di implementazione ma non sono ancora supportate dal compilatore gculpyc.
Tipi
GCULpy supporta una serie di tipi di variabili comuni con una forte enfasi sulla digitazione statica.
Tipi di valori principali:
int,bool,str,Nonesono già supportati.- Roadmap
Decimal,bytes,Enumsono nella roadmap. - Restrizione
floatecomplexnon sono consentiti.
Tipi di container:
dictè già supportato.- Roadmap
list,tuple,set,dataclasssono nella roadmap. - Restrizione I tipi concreti devono essere specificati per i valori in
un container, ad esempio
dict[str, int]è consentito, ma un semplicedictodict[str, Any]non lo sono. - È supportata la nidificazione dei container, ad esempio
dict[str, list[int]].
Restriction Tutte le variabili, inclusi i campi del contratto e dell'account, i parametri delle funzioni e i tipi di restituzione, devono essere definiti e digitati staticamente. Il loro tipo non può essere modificato in fase di runtime e sono supportati solo i tipi concreti. I tipi non possono essere utilizzati come valori, ad esempio non possono essere memorizzati nelle variabili o passati alle funzioni come argomenti. Se provi ad assegnare un valore a un campo non dichiarato, si verifica un errore in fase di compilazione.
Classi ed ereditarietà
Inizialmente, puoi definire solo le classi che sono sottoclassi dirette della classe base gcul.Contract. Questa regola rigorosa impedisce le complessità dell'ereditarietà completa di Python, che può introdurre bug difficili da trovare e rendere il codice difficile da comprendere. Per motivi di sicurezza, se provi a sostituire una proprietà o un metodo da una classe padre, viene generato un errore, fornendo una protezione chiara contro comportamenti imprevisti.
Roadmap GCULpy offrirà maggiore flessibilità mentre
mantenendo i suoi principi fondamentali. La roadmap include il supporto per l'ereditarietà singola nelle classi definite dall'utente con la sostituzione dei metodi gestita esplicitamente con un decoratore @override. Inoltre, l'elemento integrato super() sarà supportato solo nella sua forma senza argomenti, per garantire operazioni dirette e prevedibili.
Il modulo gcul
GCULpy fornisce un modulo gcul integrato con tipi e variabili essenziali per lo sviluppo di contratti.
Classe gcul.Contract
La classe base per tutti i contratti. Non puoi creare istanze direttamente;
i contratti vengono istanziati solo tramite le
CreateContract. I metodi e le proprietà della classe base gcul.Contract non possono essere sostituiti nelle sottoclassi.
Contract.is_owner(account: Account) -> boolRestituisce
Truese l'account fornito è il proprietario del contratto.
Classe gcul.Account
Un tipo integrato che rappresenta un account utente nel ledger. Non puoi creare oggetti gcul.Account direttamente; l'ambiente di runtime li crea per te e li fornisce come argomenti di funzioni o metodi. Quando passi un ID account come argomento di transazione, il runtime lo convalida automaticamente. Se si tratta di un ID valido per un account registrato, viene convertito in un oggetto account completo.
In caso contrario, la transazione non andrà a buon fine. In questo modo, puoi lavorare solo con account validi.
La definizione della classe è approssimativamente equivalente a:
@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
Una variabile speciale, disponibile in qualsiasi metodo, che contiene un riferimento all'account che ha firmato e inviato la transazione corrente.
Roadmap Migliorare la capacità degli sviluppatori di gestire e interagire con
contratti e account: potrai passare i riferimenti agli oggetti contratto
come argomenti, memorizzarli nei campi, accedere al loro ID univoco
(contract.id: str). Allo stesso modo, sarà possibile memorizzare i
riferimenti agli oggetti account e recuperare i relativi ID.
Operatori
La maggior parte degli operatori disponibili in Python è supportata in GCULpy e funziona come previsto.
- Addizione (
+) e sottrazione (-), incluse le forme unarie e binarie. - Moltiplicazione (
*), divisione intera (//) e modulo (%). - Esponenziazione (
**) per esponenti positivi. - Confronti (
<,<=,>,>=,==,!=). - Bitwise and (
&), or (|), xor (^), left shift (<<), right shift (>>), negation (~). - Operazioni booleane (
and,or,not). - Roadmap Identità dell'oggetto (
is). - Restriction Gli esponenti negativi non sono consentiti e generano un errore di runtime error.
- Restrizione La divisione reale (
/), poiché ha un tipo restituitofloat, non è consentita e genera un errore in fase di compilazione.
Flusso di controllo
La maggior parte delle istruzioni di flusso di controllo di Python funziona in GCULpy con la stessa semantica:
- Istruzioni
pass. - Chiamate di funzioni interne (stesso contratto, non ricorsive).
- Istruzioni
assert. - Istruzioni
if ... then .. else .... - Istruzioni
for VAR in CONTAINER. - Roadmap Chiamate di funzioni esterne (a qualsiasi altro contratto, non ricorsive).
- Roadmap
breakecontinueistruzioni. - Roadmap
raiseetry ... exceptistruzioni. - Roadmap
matchistruzioni. - Roadmap
generatorseyieldistruzioni. - Roadmap Gestori di contesto e
withistruzioni.
Restrizione GCULpy è intenzionalmente Turing-incompleto per impedire loop infiniti, facilitare l'analisi statica e garantire costi di elaborazione delle transazioni prevedibili. Ecco come lo applica:
- Nessun loop infinito:l'iterazione è consentita solo utilizzando i loop
forsu container finiti; i loopwhilenon sono consentiti. Alcuni aggiornamenti dei container, ad esempio l'aggiunta o la rimozione di elementi a un elenco o di chiavi a un dizionario, non sono consentiti durante l'iterazione. - Nessuna ricorsione:una funzione non può chiamare se stessa, né direttamente né indirettamente. L'ambiente di runtime esegue controlli statici e di runtime per rilevare e rifiutare l'utilizzo della ricorsione.
- Nessun flusso di controllo asincrono:l'utilizzo di primitive
asyncnon è consentito per mantenere i principi di progettazione fondamentali di prevedibilità, sicurezza ed esecuzione deterministica. Le operazioni asincrone rendono difficile ragionare sul flusso di controllo di un programma, spesso portando a vulnerabilità e condizioni di gara.
Funzioni integrate
Roadmap Ecco uno sguardo alla nostra roadmap per le funzioni integrate con le funzionalità più fondamentali e di uso comune per creare con sicurezza.
|
A
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
Note di rilascio
- 28 gennaio 2026. Versione iniziale del compilatore
gculpycresa disponibile ai partecipanti all'anteprima privata di Universal Ledger. Per un tutorial sull'utilizzo del compilatore, consulta Eseguire il deployment di un contratto programmabile.