El lenguaje GCULpy

GCULpy es un subconjunto de Python con tipos estáticos estrictos, diseñado para ser seguro, legible y auditable. Su diseño limita de forma intencional ciertas funciones dinámicas de Python para evitar vulnerabilidades comunes de los contratos inteligentes y garantizar que el comportamiento de un contrato siempre sea 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

En el siguiente contrato, se 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: 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 contrato de GCULpy es una clase que hereda de gcul.Contract. Contiene campos (estado de almacenamiento) y métodos (lógica de procesamiento que opera en los campos).

Campos

El estado en un contrato de GCULpy se almacena en campos. Todos los campos se deben declarar con un tipo estático a nivel de la clase. Existen dos tipos de campos:

  • Los campos de contrato contienen un solo valor almacenado con el contrato. En el ejemplo de ERC20Token, symbol: str y total_supply: int son campos de contrato.

  • Los campos de cuenta contienen un valor independiente para cada cuenta que interactúa con el contrato. Se declaran con gcul.AccountField[T], en el que T es el tipo del valor por cuenta. En el ejemplo de ERC20Token, balance: gcul.AccountField[int] es un campo de cuenta. Antes de que un contrato pueda escribir en el espacio de almacenamiento reservado para esa cuenta, el permiso de almacenamiento debe otorgarse de forma explícita al contrato. Una vez que se almacenan los datos, solo la instancia del contrato puede modificarlos, no la cuenta.

Los campos de restricción tienen los siguientes límites de almacenamiento. Una transacción que supere cualquiera de los límites fallará.

  • Todos los campos de un contrato comparten una sola entrada de almacenamiento, que puede contener alrededor de 5 MiB de datos serializados en total.
  • Todos los campos de cuenta que un contrato almacena para cada cuenta individual pueden contener, como máximo, 1 MiB de datos serializados en total.

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__: Se llama al constructor solo una vez cuando se implementa el contrato por primera vez. A cada campo del contrato se le debe asignar un valor en el constructor. El constructor no inicializa los campos de la cuenta; el valor de cada cuenta se crea cuando esa cuenta otorga el permiso de almacenamiento.

  • Métodos privados: Los métodos que comienzan con un guion bajo (por ejemplo, _internal_logic) son privados y solo los pueden llamar 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 el rol ROLE_CONTRACT_PARTICIPANT puede llamar a los métodos públicos enviando una transacción InvokeContractMethod.

Restricción Los métodos públicos y todos los métodos @gcul.readonly no pueden usar tipos mutables en sus firmas, por ejemplo, list o dict. En su lugar, usa declaraciones de tipo de solo lectura, como Sequence o Mapping.

Restricción Durante la ejecución, los métodos se limitan a un límite superior de uso de procesamiento en toda la red. Si se supera este límite, se produce un error en la transacción.

Ciclo de vida del contrato

En las siguientes secciones, se explican las operaciones típicas que se realizan 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 transacción CreateContract 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"
  create_contract_transaction {
    contract_bytes: "COMPILED_BYTECODE"
    init_arguments {
      key: "symbol"
      value: { string_value: "US02079K1079" }
    }
  }
}

Cuando la red procesa esta transacción, ocurre lo siguiente:

  • El constructor, es decir, el método __init__, se ejecuta con los argumentos proporcionados para crear una nueva instancia de contrato.
  • 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 mayor y se le asigna un ID de contrato único que se devuelve como parte del resultado de la transacción.

Otorga permisos

Antes de que un contrato pueda almacenar datos en un campo de cuenta en nombre de una cuenta, ya sea una cuenta para un usuario o un contrato diferente, se debe otorgar el permiso de almacenamiento al contrato que almacena los datos. Este es un paso de seguridad fundamental.

Otorga permisos para una cuenta de usuario

Un usuario con un ROLE_CONTRACT_PARTICIPANT puede enviar una transacción GrantContractPermissions para un ID de contrato específico:

client_transaction {
  sender_id: "PARTICIPANT_ACCOUNT_ID"
  grant_contract_permissions_transaction {
    contract_id: "CONTRACT_ID"
    permissions: CONTRACT_PERMISSION_STORAGE
  }
}

Cuando la red procesa esta transacción, ocurre 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 completan 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.

Otorga permisos para un contrato de delegado

Un usuario con un ROLE_CONTRACT_PARTICIPANT puede enviar una transacción GrantContractPermissions para permitir que un contrato almacene datos en nombre de otro contrato de su propiedad:

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
  }
}

Cuando la red procesa esta transacción, ocurre lo siguiente:

  • El remitente de la transacción, es decir, sender_id, debe ser el propietario del contrato identificado por delegate_contract_id.
  • Un contrato no puede otorgarse permisos de almacenamiento a sí mismo, por lo que delegate_contract_id debe ser diferente de contract_id.
  • Los permisos de almacenamiento se otorgan exclusivamente al contrato especificado en delegate_contract_id, en lugar de al remitente de la transacción.
  • Los campos de cuenta definidos en el contrato de destino, contract_id, se inicializan para el contrato delegado, delegate_contract_id, lo que permite que los métodos del contrato de destino accedan de forma segura a los campos de cuenta con clave de gcul.caller o gcul.sender durante las invocaciones entre contratos.

Invocar 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 un ROLE_CONTRACT_PARTICIPANT puede enviar una transacción InvokeContractMethod especificando el ID del contrato, el nombre del método y los valores de los argumentos del método.

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 }
    }
  }
}

Cuando la red procesa esta transacción, ocurre lo siguiente:

  • Se recupera la instancia de contrato asociada con el CONTRACT_ID proporcionado.
  • Se ejecuta el método mint(beneficiary=Account("BENEFICIARY_ID"), value=10). El entorno de ejecución compila y valida el objeto Account para 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 mayor.
  • Si el método falla por cualquier motivo, la transacción fallará y no se realizarán actualizaciones en el estado del contrato.
  • Si el método se ejecuta de forma correcta, el estado actualizado del contrato se registra en el estado mundial.

Especificación del lenguaje

GCULpy está diseñado para brindar seguridad y previsibilidad y, por lo tanto, no permite varias funciones de Python; estas se indicarán con la etiqueta Restricción. Estas restricciones están diseñadas para ser funciones de lenguaje permanentes, introducidas para que la lógica del contrato sea más fácil de leer, auditar y analizar de forma estática, lo que limita los comportamientos sorprendentes o no seguros.

Otras funciones se indican con la etiqueta Roadmap. Estas se encuentran 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 el tipado estático.

Tipos de valores principales:

  • Ya se admiten int, bool, str y None.
  • Hoja de ruta Decimal, bytes, Enum están en la hoja de ruta.
  • Restricción No se permiten float ni complex.
  • Restricción: Los valores int son números enteros de 64 bits con firma, de -263 a 263 - 1. Un literal de número entero fuera de este rango generará un error de tiempo de compilación, y una operación cuyo resultado esté fuera de este rango generará un error de tiempo de ejecución.
  • Restricción: Los valores de str pueden tener un máximo de 1,000,000 de caracteres. Un literal de cadena más largo generará un error de tiempo de compilación. Si pasas una cadena más larga como argumento de método o produces una como resultado de una operación, se producirá un error de tiempo de ejecución.

Tipos de contenedores:

  • list y dict:
    • list[T]: Es una secuencia mutable. Admite la indexación, por ejemplo, my_list[0], y el método append().
    • dict[K, V]: Es una asignación mutable. Admite la indexación, por ejemplo, my_dict[key], y el método pop().
  • collections.abc.Sequence y collections.abc.Mapping:
    • collections.abc.Sequence[T]: Es una referencia de solo lectura a una secuencia. Admite la lectura de elementos por índice, por ejemplo, my_seq[0], pero los elementos no se pueden modificar ni borrar. list[T] es un subtipo de Sequence[T], por lo que puedes pasar, devolver o asignar un valor de list[T] o un literal de lista a un destino con tipo Sequence[T], pero no puedes asignar un Sequence[T] a un list[T].
    • collections.abc.Mapping[K, V]: Es una referencia de solo lectura a una asignación. Admite la lectura de entradas por clave, por ejemplo, my_map[key], pero las entradas no se pueden modificar ni borrar. dict[K, V] es un subtipo de Mapping[K, V], por lo que puedes pasar, devolver o asignar un valor de dict[K, V] o un literal de diccionario a un destino con el tipo Mapping[K, V], pero no puedes asignar un Mapping[K, V] a un dict[K, V].
  • Hoja de ruta tuple, set, dataclass está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 no dict o dict[str, Any] simples.
  • Hoja de ruta: División de listas, por ejemplo, my_list[1:3].
  • Hoja de ruta Otros métodos, como .get(), .keys(), .values(), .items(), .clear() o .extend().

Restricción Todas las variables, incluidos los campos de contrato y 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 durante 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 producirá un error de tiempo de compilación.

Restricción: Se debe asignar una variable local antes de leerla. Las variables locales no tienen un valor predeterminado, por lo que leer una antes de que se asigne genera un error de tiempo de ejecución.

Clases y herencia

Solo puedes definir clases que sean subclases directas de gcul.Contract o gcul.ContractInterface. 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 proporciona una protección clara contra comportamientos inesperados.

Hoja de ruta GCULpy ofrecerá más flexibilidad, pero mantendrá sus principios fundamentales. La hoja de ruta incluye compatibilidad con la herencia única en clases definidas por el usuario con anulación de métodos administrada de forma explícita con un decorador @override. Además, la función integrada super() solo se admitirá en su formato libre argumentos para garantizar operaciones directas y predecibles.

El módulo gcul

GCULpy proporciona un módulo gcul integrado con tipos, decoradores y variables esenciales para el desarrollo de contratos.

clase gcul.Contract

Es la clase base para todos los contratos. No puedes crear instancias de él directamente; los contratos solo se crean a través de transacciones CreateContract. Los métodos y las propiedades de la clase gcul.Contract base no se pueden anular en las subclases.

  • Contract.is_owner(account: Account) -> bool

    Devuelve True si la cuenta proporcionada es la propietaria del contrato.

  • Contract.call(target: ContractInterface, payment: Amount | None = None) -> ContractMethods

    Inicia una interacción entre contratos con target. En lugar de llamar directamente a los métodos en el destino (p.ej., target.foo()), debes usar self.call() para enrutar de forma explícita la invocación a través del entorno de ejecución del libro mayor. Este mecanismo devuelve un objeto de proxy seguro que se usa para invocar los métodos públicos correspondientes en el contrato de destino, lo que garantiza que los límites de ejecución se apliquen de forma estricta y que cualquier payment se entregue de forma segura. Consulta ContractInterface para obtener más detalles.

    Se debe proporcionar un payment cuando se llama a un método decorado con @gcul.payable, y se debe omitir para los métodos no pagaderos.

    target debe ser un contrato diferente. Si se llama al contrato actual a través de self.call(), se genera un error de tiempo de ejecución. Para llamar a otro método en el mismo contrato, llámalo directamente, por ejemplo, self.foo().

  • Contract.native_transfer(beneficiary: Account, amount: Amount) -> None

    Transfiere un importe registrado directamente del saldo del contrato a beneficiary.

  • Contract.id: str

    Es el ID de cadena único del contrato.

clase gcul.Account

Es un tipo integrado que representa una cuenta de usuario o de contrato en el libro mayor. No puedes crear objetos gcul.Account directamente; el entorno de ejecución los crea por ti y los proporciona como argumentos de función o método. Para pasar una cuenta como argumento de transacción, proporciona un AccountValue que contenga el ID de cuenta. El entorno de ejecución valida el ID y lo convierte en un objeto de cuenta completo. Si el ID no pertenece a una cuenta registrada, 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.
  • Account.id: str

    Es el ID de cadena único de la cuenta.

clase gcul.Amount

Representa un valor monetario denominado en una moneda de libro mayor específica. Crea una instancia de un importe con gcul.Amount(value: int, currency: gcul.Currency). La definición de la clase es aproximadamente equivalente a lo siguiente:

@dataclasses.dataclass(frozen=True)
class Amount:
  """Represents an amount denominated in a ledger currency."""

  value: int
  currency: Currency

clase gcul.Currency

Representa una moneda completamente calificada en el libro mayor. La definición de la clase es aproximadamente equivalente a lo siguiente:

@dataclasses.dataclass(frozen=True)
class Currency:
  """Represents a fully qualified currency on the ledger."""

  code: str
  operator_id: str

clase gcul.ContractInterface

Es la clase base para declarar la interfaz pública de un contrato. Los métodos y las propiedades de la clase gcul.ContractInterface base no se pueden anular en las subclases. Además, los métodos de la declaración de la interfaz no pueden incluir ningún código, solo se permite ....

Para que un contrato llame a métodos en otro contrato implementado en el libro mayor, declara una clase de interfaz que herede de gcul.ContractInterface. Los resguardos de métodos deben coincidir con su declaración correspondiente en el contrato de destino. Esto incluye argumentos, tipo de datos que se devuelve y anotaciones de decorador, como @gcul.readonly o @gcul.payable.

Dentro del código de tu contrato, usa self.call(target, payment) para invocar un método de otro contrato.

En el siguiente ejemplo, se ilustra cómo interactúa un contrato TokenVendor con otro contrato que implementa ERC20Interface. Usa self.call() para transferir tokens a un comprador a cambio de un pago en moneda en el libro mayor:

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)

Los tipos de interfaz también se pueden usar como campos de contrato, no solo como parámetros de método. Almacenar una interfaz en un campo permite que un contrato mantenga una referencia a otro contrato en todas las transacciones y que reemplace esa referencia más adelante. En el siguiente contrato de ejemplo, se usa el ERC20Interface declarado antes:

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

clase gcul.AccountField

Es un tipo genérico integrado para declarar campos de cuenta. gcul.AccountField[T] contiene un valor independiente de tipo T para cada cuenta que otorgó al contrato el permiso de almacenamiento. Se comporta como una asignación con claves gcul.Account, que puedes usar para leer o escribir un valor para cada cuenta específica. Por ejemplo:

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 debe ser un tipo que tenga un valor predeterminado, por lo que gcul.Account, gcul.Amount, gcul.Currency y los tipos de interfaz no se pueden usar como valores de campo de cuenta. Cuando una cuenta otorga el permiso de almacenamiento a un contrato, cada campo de la cuenta se propaga con el valor predeterminado de su tipo para esa cuenta.

Restricción: La indexación con una cuenta es la única operación que admite un campo de cuenta. El campo en sí no se puede asignar a una variable, pasar como un argumento, devolver desde una función ni iterar.

Restricción: No se pueden borrar las entradas del campo Cuenta. Se puede reemplazar un valor almacenado, pero la entrada en sí es permanente.

Los campos de cuenta de restricción para gcul.sender, gcul.caller y cualquier gcul.Account que se pase como argumento de método (incluidas las listas internas o cualquier otro contenedor) se cargan antes de la ejecución del método. En el caso de otras cuentas, una sola transacción puede cargar de forma diferida los campos de la cuenta para un máximo de 15 pares (contract, account) distintos. El acceso a un campo de la cuenta para el mismo par varias veces en una transacción se cuenta solo una vez. Una transacción que supera este límite falla.

@gcul.readonly

Anota un método de contrato como de solo lectura. Las declaraciones de métodos con este decorador no pueden mutar el estado del contrato ni realizar transferencias de moneda en el libro mayor. Tampoco pueden usar tipos de contenedores mutables en sus firmas, por ejemplo, list o dict; en su lugar, deben usar declaraciones de tipo de solo lectura, como Sequence o Mapping.

@gcul.payable

Anota un método de contrato que requiere que se ejecute un pago. Si un método espera recibir un pago en moneda registrada con gcul.payment, debe marcarse con este decorador; de lo contrario, la transacción fallará.

gcul.sender: gcul.Account

Contiene una referencia a la cuenta que firmó y envió la transacción InvokeContractMethod actual. El valor de gcul.sender no cambia durante la ejecución de la transacción, incluso si implica llamadas entre contratos, a diferencia de gcul.caller, que hace referencia al emisor inmediato.

gcul.caller: gcul.Account

Contiene una referencia al llamador inmediato del método actual. Para las transacciones directas de los usuarios, gcul.caller coincide con gcul.sender, que apunta a quien envió la transacción. Para las llamadas entre contratos, gcul.caller corresponde al ID de cuenta del contrato que realiza la llamada. Las llamadas internas dentro de un contrato no cambian el valor de gcul.caller.

gcul.payment: gcul.Amount

Contiene el pago transferido a un método decorado con @gcul.payable. Si se accede a gcul.payment dentro de un método no pagadero, se genera un error de tiempo de compilación.

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 de piso (//) y módulo (%).
  • Exponenciación (**) para exponentes positivos.
  • Comparaciones (<, <=, >, >=, ==, !=)
  • AND (&), OR (|), XOR (^), desplazamiento a la izquierda (<<), desplazamiento a la derecha (>>) y negación (~) a nivel de bits.
  • Operaciones booleanas (and, or, not)
  • Hoja de ruta Identidad de objeto (is).
  • Restricción: No se permiten los exponentes negativos y generan un error de tiempo de ejecución.
  • Restricción No se permite la división verdadera (/), ya que tiene un tipo de datos que se devuelve float, y genera un error de tiempo de compilación.

Flujo de control

La mayoría de las declaraciones de flujo de control de Python funcionan en GCULpy con la misma semántica:

  • pass.
  • llamadas a funciones internas (mismo contrato, no recursivas).
  • assert.
  • if ... then .. else ....
  • for VAR in CONTAINER.
  • llamadas a funciones externas (a cualquier otro contrato, no recursivas)
  • Declaraciones break y continue
  • raise.
  • Declaraciones de hoja de ruta try ... except.
  • Declaraciones de hoja de ruta match.
  • Hoja de ruta Declaraciones generators y yield.
  • Administradores de contexto de hoja de ruta y sentencias with.

Restricción: GCULpy es intencionalmente Turing-incompleto para evitar bucles infinitos y facilitar el análisis estático. A continuación, se muestra cómo aplica esta política:

  • Sin bucles infinitos: Solo se permite la iteración con bucles for en contenedores finitos o secuencias limitadas con range(...); no se permiten los bucles while. No se permiten algunas actualizaciones de contenedores, por ejemplo, agregar o quitar elementos de una lista o claves de un diccionario, mientras se iteran.
  • 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 ejecución para detectar y rechazar el uso de la recursión.
  • No hay flujo de control asíncrono: No se permite el uso de primitivas async para mantener los principios de diseño fundamentales de previsibilidad, seguridad y ejecución determinística. Las operaciones asíncronas dificultan el razonamiento sobre el flujo de control de un programa, lo que suele generar vulnerabilidades y condiciones de carrera.

Funciones integradas

GCULpy admite las siguientes funciones integradas:

  • abs(x)

    Devuelve el valor absoluto de un número entero x. Los argumentos deben ser del tipo int.

  • len(s)

    Devuelve la longitud, es decir, la cantidad de elementos, de una cadena o un contenedor finito.

  • max(iterable)
    max(arg1, arg2, *args)

    Devuelve el elemento más grande de un iterable o el más grande de dos o más argumentos. Llamar a max() en un iterable vacío genera un error de tiempo de ejecución.

  • min(iterable)
    min(arg1, arg2, *args)

    Devuelve el elemento más pequeño de un iterable o el más pequeño de dos o más argumentos. Llamar a min() en un iterable vacío genera un error de tiempo de ejecución.

  • range(stop)
    range(start, stop[, step=1])

    Genera una secuencia aritmética de números enteros con un step entero distinto de cero.

    Restricción: Solo se puede usar intercalado con bucles for, por ejemplo, for i in range(10): .... Los objetos de rango no se pueden asignar a variables ni almacenar en campos, pasar como argumentos a funciones ni consultar con len().

Hoja de ruta Las siguientes funciones integradas se encuentran en la hoja de ruta de implementación:

A
all()
any()

B
bin()
bool()
bytes()

C
chr()

D
dict()
divmod()

E
enumerate()

F
format()
frozenset()

H
hash()
hex()

I
id()
int()

L
list()

O
oct()
ord()

P
pow()
property()

R
repr()
reversed()

S
set()
sorted()
staticmethod()
str()
sum()
super()

T
tuple()

Z
zip()

Notas de la versión

  • 3 de noviembre de 2026. Se lanzó la versión de DG del compilador de gculpyc (v0.2.0), que incluye compatibilidad con llamadas entre contratos, transferencias y pagos de monedas en el libro mayor, métodos @gcul.readonly, list, declaraciones de flujo de control adicionales y funciones integradas.

  • 28 de enero de 2026 Se puso a disposición de los participantes de la versión preliminar privada de Universal Ledger una versión inicial del compilador de gculpyc. Para ver un instructivo sobre cómo usar el compilador, consulta Implementa un contrato programable.