GCULpy es un subconjunto de Python estrictamente tipado de forma estática, diseñado para ser seguro, legible y auditable. Su diseño limita intencionalmente ciertas funciones dinámicas de Python para evitar vulnerabilidades comunes de contratos inteligentes y garantizar que el comportamiento de un contrato sea siempre predecible.
En esta página, se proporciona una referencia para la especificación del lenguaje GCULpy, que abarca los conceptos básicos y el ciclo de vida de un contrato en una red de Universal Ledger.
Conceptos básicos
El siguiente contrato define un token ERC20 de muestra en 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 contrato de GCULpy es una clase que hereda de gcul.Contract. Contiene campos (que almacenan el estado) y métodos (lógica de procesamiento que opera en campos).
Campos
El estado de un contrato de GCULpy se almacena en campos. Todos los campos se deben declarar con un tipo estático en el nivel de clase. Existen dos tipos de campos:
Los campos de contrato contienen un solo valor almacenado con el contrato. En el ejemplo de
ERC20Token,symbol: strytotal_supply: intson campos de contrato.Los campos de cuenta almacenan un valor independiente para cada cuenta de usuario que interactúa con un contrato. Siempre se declaran como un diccionario (
dict) congcul.Accountcomo clave, comobalance: dict[gcul.Account, int]. Antes de que un contrato pueda escribir en la cuenta de un usuario, este debe otorgar explícitamente el permiso de almacenamiento al contrato. Una vez que se almacenan los datos, solo la instancia del contrato puede modificarlos o borrarlos, no el usuario.
Métodos
Los métodos definen la lógica ejecutable de un contrato. Se comportan como métodos de Python y pueden leer o modificar los campos del contrato.
__init__: El constructor se llama solo una vez cuando se implementa el contrato por primera vez. Se usa para establecer el estado inicial de los campos del contrato. Los campos a los que no se les asigna un valor en el constructor obtienen un valor predeterminado adecuado, por ejemplo,0para un campointo un diccionario vacío para un campodict.Métodos privados: Los métodos que comienzan con un guion bajo (por ejemplo,
_internal_logic) son privados y solo pueden ser llamados por otros métodos dentro del mismo contrato. El intérprete de Universal Ledger aplica esta restricción.Métodos públicos: Cualquier método que no comience con un guion bajo (
_) es público. Cualquier usuario con elROLE_CONTRACT_PARTICIPANTpuede llamar a los métodos públicos enviando una InvokeContractMethod transacción.
Ciclo de vida del contrato
En las siguientes secciones, se describen las operaciones típicas que participan en el ciclo de vida de un contrato de GCULpy.
Implementa un contrato
Primero, compila tu código fuente de GCULpy con el compilador gculpyc. Luego, un usuario con el ROLE_CONTRACT_CREATOR puede enviar una
CreateContract
transacción para implementar el código de bytes compilado en una red de Universal Ledger. Para
obtener instrucciones detalladas, consulta el
instructivo Implementa un contrato programable.
Una transacción de este tipo se vería de la siguiente manera:
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" }
}
}
}
}
Cuando la red procesa esta transacción, sucede lo siguiente:
- Se ejecuta el constructor, es decir, el método
__init__, para crear una instancia de contrato nueva. - El remitente de la transacción se convierte en el propietario del contrato.
- La instancia del contrato se almacena de forma permanente en el libro de contabilidad y se le asigna un ID de contrato único que se muestra como parte del resultado de la transacción.
Otorgar permisos
Antes de que un contrato pueda almacenar datos en un campo de cuenta en nombre de un usuario, este debe otorgarle primero el permiso de almacenamiento. Este es un paso de seguridad fundamental. Un usuario con el rol ROLE_CONTRACT_PARTICIPANT puede enviar una
GrantContractPermissions
transacción para un ID de contrato específico.
client_transaction {
sender_id: "PARTICIPANT_ACCOUNT_ID"
app {
[type.googleapis.com/google.cloud.universalledger.v1.GrantContractPermissions] {
contract_id: "CONTRACT_ID"
permissions: CONTRACT_PERMISSION_STORAGE
}
}
}
Cuando la red procesa esta transacción, sucede lo siguiente:
- Si el contrato no define ningún campo de cuenta, se rechaza la transacción.
- Si el contrato define campos de cuenta, todos se propagan con valores predeterminados (por ejemplo,
contract.balance[gcul.sender] = 0). Luego, estos valores se almacenan en el estado mundial como parte de los datos de la cuenta, y el remitente de la transacción se registra como participante con esta instancia de contrato específica.
Invoca métodos de contrato
Una vez que se implementa un contrato y se otorgan los permisos necesarios, los usuarios pueden interactuar con él llamando a sus métodos públicos. Un usuario con el rol ROLE_CONTRACT_PARTICIPANT puede enviar una transacción
InvokeContractMethod
en la que se especifiquen el ID del contrato, el nombre del método y los valores de los argumentos.
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 }
}
}
}
}
Cuando la red procesa esta transacción, sucede lo siguiente:
- Se recupera la instancia del contrato asociada con el
CONTRACT_IDproporcionado. - Se ejecuta el método
mint(beneficiary=Account("BENEFICIARY_ID"), value=10). El entorno de ejecución compila y valida el objetoAccountpara el beneficiario. La lógica del método puede suponer de forma segura que el ID proporcionado es válido y hace referencia a una cuenta existente en el libro de contabilidad. - Si el método falla por algún motivo, la transacción fallará y no se realizarán actualizaciones en el estado del contrato.
- Si el método tiene éxito, el estado actualizado del contrato se registra en el estado mundial.
Especificación del lenguaje
GCULpy está diseñado para la seguridad y la previsibilidad y, como tal, no permite varias funciones de Python; estas se indicarán con la Restriction etiqueta. Estas restricciones están diseñadas para ser funciones de lenguaje permanentes, introducidas para facilitar la lectura, la auditoría y el análisis estático de la lógica del contrato, lo que limita los comportamientos sorprendentes o inseguros.
Otras funciones se indican con la etiqueta Roadmap, que se encuentra en la
hoja de ruta de implementación, pero aún no son compatibles con el compilador gculpyc.
Tipos
GCULpy admite una variedad de tipos de variables comunes con un fuerte énfasis en la escritura estática.
Tipos de valores principales:
int,bool,stryNoneya son compatibles.- Hoja de ruta
Decimal,bytes,Enumestán en la hoja de ruta. - Restricción No se permiten
floatnicomplex.
Tipos de contenedores:
dictya es compatible.- Hoja de ruta
list,tuple,set,dataclassestán en la hoja de ruta. - Restricción Se deben especificar tipos concretos para los valores de
un contenedor. Por ejemplo, se permite
dict[str, int], pero nodictsimple nidict[str, Any]. - Se admite el anidamiento de contenedores, por ejemplo,
dict[str, list[int]].
Restricción Todas las variables, incluidos los campos de contrato y de cuenta, los parámetros de función y los tipos de datos que se devuelven, se deben definir y escribir de forma estática. Su tipo no se puede cambiar en el tiempo de ejecución, y solo se admiten tipos concretos. Los tipos no se pueden usar como valores. Por ejemplo, no se pueden almacenar en variables ni pasar a funciones como argumentos. Si intentas asignar un valor a un campo no declarado, se generará un error en el tiempo de compilación.
Clases y herencia
Inicialmente, solo puedes definir clases que sean subclases directas de la clase base gcul.Contract. Esta regla estricta evita las complejidades de la herencia completa de Python, que puede introducir errores difíciles de encontrar y dificultar la comprensión del código. Por motivos de seguridad, si intentas anular una propiedad o un método de una clase superior, se generará un error, lo que proporcionará una protección clara contra el comportamiento inesperado.
Roadmap GCULpy ofrecerá más flexibilidad y, al mismo tiempo,
mantendrá sus principios básicos. La hoja de ruta incluye compatibilidad con la herencia única en clases definidas por el usuario con la anulación de métodos administrada de forma explícita con un decorador @override. Además, el elemento integrado super() solo será compatible en su formato libre argumentos para garantizar operaciones directas y predecibles.
El módulo gcul
GCULpy proporciona un módulo gcul integrado con tipos y variables esenciales para el desarrollo de contratos.
clase gcul.Contract
Es la clase base para todos los contratos. No puedes crear instancias de forma directa;
los contratos solo se instancian a través de
CreateContract. Los métodos y las propiedades de la clase base gcul.Contract no se pueden anular en las subclases.
Contract.is_owner(account: Account) -> boolMuestra
Truesi la cuenta proporcionada es el propietario del contrato.
clase gcul.Account
Es un tipo integrado que representa una cuenta de usuario en el libro de contabilidad. No puedes crear objetos gcul.Account de forma directa; el entorno de ejecución los crea por ti y los proporciona como argumentos de función o método. Cuando pasas un ID de cuenta como argumento de transacción, el entorno de ejecución lo valida automáticamente. Si es un ID válido para una cuenta registrada, se convierte en un objeto de cuenta completo.
De lo contrario, la transacción falla. Esto garantiza que solo trabajes con cuentas válidas.
La definición de la clase es aproximadamente equivalente a lo siguiente:
@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
Es una variable especial, disponible en cualquier método, que contiene una referencia a la cuenta que firmó y envió la transacción actual.
Roadmap Mejora la capacidad de los desarrolladores para administrar contratos y cuentas, e interactuar con ellos. Podrás pasar referencias a objetos de contrato como argumentos, almacenarlos en campos y acceder a su ID único (contract.id: str). Del mismo modo, será posible almacenar referencias a objetos de cuentas y recuperar sus IDs.
Operadores
La mayoría de los operadores disponibles en Python son compatibles con GCULpy y funcionan como se espera.
- Suma (
+) y resta (-), incluidas las formas unarias y binarias. - Multiplicación (
*), división entera (//) y módulo (%). - Exponenciación (
**) para exponentes positivos. - Comparaciones (
<,<=,>,>=,==,!=). - Operaciones bit a bit y (
&), o (|), xor (^), desplazamiento a la izquierda (<<), desplazamiento a la derecha (>>) y negación (~). - Operaciones booleanas (
and,or,not). - Roadmap Identidad del objeto (
is). - Restricción No se permiten exponentes negativos y se genera un error en el tiempo de ejecución.
- Restricción No se permite la división verdadera (
/), ya que tiene un tipo de datos que se devuelvefloat, y genera un error en el tiempo de compilación.
Flujo de control
La mayoría de las instrucciones de flujo de control de Python funcionan en GCULpy con la misma semántica:
- Instrucciones
pass. - Llamadas a funciones internas (mismo contrato, no recursivas).
- Instrucciones
assert. - Instrucciones
if ... then .. else .... - Instrucciones
for VAR in CONTAINER. - Roadmap Llamadas a funciones externas (a cualquier otro contrato, no recursivas).
- Roadmap
breakycontinueInstrucciones. - Roadmap
raiseytry ... exceptinstrucciones. - Roadmap
matchInstrucciones. - Roadmap
generatorsyyieldstatements. - Roadmap Administradores de contexto e
withinstrucciones.
Restricción GCULpy es intencionalmente incompleto de Turing para evitar bucles infinitos, facilitar el análisis estático y garantizar costos predecibles de procesamiento de transacciones. A continuación, se muestra cómo se aplica esto:
- Sin bucles infinitos: La iteración solo se permite con bucles
forsobre contenedores finitos; no se permiten bucleswhile. No se permiten algunas actualizaciones de contenedores, por ejemplo, agregar o quitar elementos a una lista o claves a un diccionario, mientras se itera sobre ellos. - Sin recursión: Una función no puede llamarse a sí misma, ya sea de forma directa o indirecta. El entorno de ejecución realiza verificaciones estáticas y de tiempo de ejecución para detectar y rechazar el uso de la recursión.
- Sin flujo de control asíncrono: No se permite el uso de primitivas
asyncpara mantener los principios básicos de diseño de previsibilidad, seguridad y ejecución determinista. Las operaciones asíncronas dificultan la comprensión del flujo de control de un programa, lo que suele generar vulnerabilidades y condiciones de carrera.
Funciones integradas
Hoja de ruta Aquí puedes ver nuestra hoja de ruta para las funciones integradas con la funcionalidad más fundamental y de uso más frecuente para compilar con confianza.
|
A
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
Notas de la versión
- 28 de enero de 2026: Se puso a disposición de los participantes de la vista previa privada de Universal Ledger una versión inicial del compilador
gculpyc. Para obtener un instructivo sobre el uso de l compilador, consulta Implementa un contrato programable.