A linguagem GCULpy

O GCULpy é um subconjunto estrito e estaticamente tipado de Python, projetado para ser seguro, legível e auditável. O design dela limita intencionalmente alguns recursos dinâmicos do Python para evitar vulnerabilidades comuns de contratos inteligentes e garantir que o comportamento de um contrato seja sempre previsível.

Esta página fornece uma referência para a especificação da linguagem GCULpy, abordando os conceitos principais e o ciclo de vida de um contrato em uma rede de razão universal.

Principais conceitos

O contrato a seguir define um token ERC20 de exemplo em 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

Um contrato do GCULpy é uma classe que herda de gcul.Contract. Ele contém campos (armazenamento de estado) e métodos (lógica de processamento que opera em campos).

Campos

O estado em um contrato GCULpy é armazenado em campos. Todos os campos precisam ser declarados com um tipo estático no nível da classe. Há dois tipos de campos:

  • Os campos de contrato contêm um único valor armazenado com o próprio contrato. No exemplo ERC20Token, symbol: str e total_supply: int são campos de contrato.

  • Os campos da conta armazenam um valor separado para cada conta de usuário que interage com um contrato. Elas são sempre declaradas como um dicionário (dict) com gcul.Account como a chave, como balance: dict[gcul.Account, int]. Antes que um contrato possa gravar na conta de um usuário, ele precisa receber permissão explícita de armazenamento. Depois que os dados são armazenados, apenas a instância do contrato pode modificá-los ou excluí-los. O usuário não pode.

Métodos

Os métodos definem a lógica executável de um contrato. Eles se comportam como métodos do Python e podem ler ou modificar os campos do contrato.

  • __init__: o construtor é chamado apenas uma vez quando o contrato é implantado pela primeira vez. Ele é usado para definir o estado inicial dos campos do contrato. Os campos que não recebem um valor no construtor recebem um padrão adequado, por exemplo, 0 para um campo int ou um dicionário vazio para um campo dict.

  • Métodos particulares: métodos que começam com um sublinhado (por exemplo, _internal_logic) são particulares e só podem ser chamados por outros métodos no mesmo contrato. O intérprete do Universal Ledger aplica essa restrição.

  • Métodos públicos: qualquer método que não comece com um sublinhado (_) é público. Os métodos públicos podem ser chamados por qualquer usuário com o ROLE_CONTRACT_PARTICIPANT ao enviar uma transação InvokeContractMethod.

Ciclo de vida do contrato

As seções a seguir descrevem as operações típicas envolvidas no ciclo de vida de um contrato do GCULpy.

Implantar um contrato

Primeiro, compile o código-fonte do GCULpy usando o compilador gculpyc. Em seguida, um usuário com o ROLE_CONTRACT_CREATOR pode enviar uma transação CreateContract para implantar o bytecode compilado em uma rede Universal Ledger. Para instruções detalhadas, consulte o tutorial Implantar um contrato programável.

Uma transação desse tipo seria assim:

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 a rede processa essa transação:

  • O construtor, ou seja, o método __init__, é executado para criar uma nova instância de contrato.
  • O remetente da transação se torna o proprietário do contrato.
  • A instância do contrato é armazenada permanentemente no livro-razão e recebe um ID do contrato exclusivo, que é retornado como parte da saída da transação.

Conceder permissões

Antes que um contrato possa armazenar dados em um campo de conta em nome de um usuário, ele precisa receber a permissão de armazenamento. Essa é uma etapa crítica de segurança. Um usuário com um ROLE_CONTRACT_PARTICIPANT pode enviar uma transação GrantContractPermissions para um 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
    }
  }
}

Quando a rede processa essa transação:

  • Se o contrato não definir nenhum campo de conta, a transação será rejeitada.
  • Se o contrato definir campos de conta, todos eles serão preenchidos com valores padrão (por exemplo, contract.balance[gcul.sender] = 0). Esses valores serão armazenados no estado global como parte dos dados da conta, e o remetente da transação será registrado como participante dessa instância de contrato específica.

Invocar métodos de contrato

Depois que um contrato é implantado e as permissões necessárias são concedidas, os usuários podem interagir com ele chamando os métodos públicos. Um usuário com um ROLE_CONTRACT_PARTICIPANT pode enviar uma transação InvokeContractMethod especificando o ID do contrato, o nome do método e os valores dos 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 }
      }
    }
  }
}

Quando a rede processa essa transação:

  • A instância do contrato associada ao CONTRACT_ID fornecido é recuperada.
  • O método mint(beneficiary=Account("BENEFICIARY_ID"), value=10) é executado. O objeto Account do beneficiário é criado e validado pelo tempo de execução. A lógica do método pode presumir com segurança que o ID fornecido é válido e se refere a uma conta existente no livro razão.
  • Se o método falhar por qualquer motivo, a transação também vai falhar, e nenhuma atualização será feita no estado do contrato.
  • Se o método for bem-sucedido, o estado atualizado do contrato será registrado no estado global.

Especificação de idioma

O GCULpy foi projetado para segurança e previsibilidade e, portanto, não permite vários recursos do Python. Eles serão indicados com o rótulo Restrição. Essas restrições são recursos permanentes da linguagem, introduzidos para facilitar a leitura, a auditoria e a análise estática da lógica do contrato, limitando comportamentos surpreendentes ou inseguros.

Outros recursos são indicados com o rótulo Planejamento. Eles estão no planejamento de implementação, mas ainda não são compatíveis com o compilador gculpyc.

Tipos

O GCULpy é compatível com vários tipos de variáveis comuns, com ênfase em tipagem estática.

Tipos de valor principal:

  • int, bool, str e None já são compatíveis.
  • Roteiro Decimal, bytes e Enum estão no roteiro.
  • Restrição float e complex não são permitidos.

Tipos de contêiner:

  • O dict já é compatível.
  • Roteiro list, tuple, set e dataclass estão no roteiro.
  • Restrição: tipos concretos precisam ser especificados para valores em um contêiner. Por exemplo, dict[str, int] é permitido, mas um dict ou dict[str, Any] simples não são.
  • Há suporte para o aninhamento de contêineres, por exemplo, dict[str, list[int]].

Restrição: todas as variáveis, incluindo campos de contrato e conta, parâmetros de função e tipos de retorno, precisam ser definidas e digitadas de forma estática. O tipo não pode ser mudado durante a execução, e apenas tipos concretos são aceitos. Os tipos não podem ser usados como valores. Por exemplo, não podem ser armazenados em variáveis nem transmitidos para funções como argumentos. A tentativa de atribuir um valor a um campo não declarado resultará em um erro de tempo de compilação.

Classes e herança

Inicialmente, só é possível definir classes que são subclasses diretas da classe base gcul.Contract. Essa regra estrita evita as complexidades da herança completa do Python, que pode introduzir bugs difíceis de encontrar e dificultar o raciocínio sobre o código. Por segurança, tentar substituir uma propriedade ou um método de uma classe pai vai gerar um erro, oferecendo uma proteção clara contra comportamentos inesperados.

Roteiro: o GCULpy vai oferecer mais flexibilidade, mantendo os princípios básicos. O roteiro inclui suporte para herança única em classes definidas pelo usuário com substituição de método gerenciada explicitamente com um decorador @override. Além disso, o super() integrado só será compatível com a formato livre argumentos para garantir operações diretas e previsíveis.

O módulo gcul

O GCULpy oferece um módulo gcul integrado com tipos e variáveis essenciais para o desenvolvimento de contratos.

classe gcul.Contract

A classe de base para todos os contratos. Não é possível criar instâncias diretamente. Os contratos só são instanciados por transações CreateContract. Métodos e propriedades da classe base gcul.Contract não podem ser substituídos em subclasses.

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

    Retorna True se a conta fornecida for a proprietária do contrato.

classe gcul.Account

Um tipo integrado que representa uma conta de usuário no livro razão. Não é possível criar objetos gcul.Account diretamente. O ambiente de execução os cria para você e os fornece como argumentos de função ou método. Quando você transmite um ID de conta como um argumento de transação, o ambiente de execução o valida automaticamente. Se for um ID válido de uma conta registrada, ele será convertido em um objeto de conta completa. Caso contrário, a transação vai falhar. Isso garante que você só trabalhe com contas válidas.

A definição da classe é aproximadamente 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

Uma variável especial, disponível em qualquer método, que contém uma referência à conta que assinou e enviou a transação atual.

Roteiro: melhorar a capacidade dos desenvolvedores de gerenciar e interagir com contratos e contas. Será possível transmitir referências a objetos de contrato como argumentos, armazená-los em campos e acessar o ID exclusivo (contract.id: str). Da mesma forma, será possível armazenar referências a objetos de contas e recuperar os IDs.

Operadores

A maioria dos operadores disponíveis em Python é compatível com GCULpy e funciona como esperado.

  • Adição (+) e subtração (-), incluindo formas unárias e binárias.
  • Multiplicação (*), divisão inteira (//) e módulo (%).
  • Exponenciação (**) para expoentes positivos.
  • Comparações (<, <=, >, >=, ==, !=).
  • Bit a bit e (&), ou (|), xor (^), deslocamento à esquerda (<<), deslocamento à direita (>>), negação (~).
  • Operações booleanas (and, or, not).
  • Identidade do objeto Roadmap (is).
  • Restrição: não são permitidos expoentes negativos, que geram um erro de tempo de execução.
  • Restrição: a divisão real (/), que tem um tipo de retorno float, não é permitida e resulta em um erro de tempo de compilação.

Fluxo de controle

A maioria das instruções de controle de fluxo do Python funciona no GCULpy com a mesma semântica:

  • pass.
  • chamadas de função internas (mesmo contrato, não recursivas).
  • assert.
  • if ... then .. else ....
  • for VAR in CONTAINER.
  • Chamadas de função externa Roadmap (para qualquer outro contrato, não recursivas).
  • Instruções Roadmap break e continue.
  • Instruções Roadmap raise e try ... except.
  • Declarações do roteiro match.
  • Instruções Roadmap generators e yield.
  • Gerenciadores de contexto Roadmap e instruções with.

Restrição: o GCULpy é intencionalmente Turing-incompleto para evitar loops infinitos, facilitar a análise estática e garantir custos previsíveis de processamento de transações. Veja como isso é feito:

  • Sem loops infinitos:a iteração só é permitida usando loops for em contêineres finitos. Loops while não são permitidos. Algumas atualizações de contêineres, como adicionar ou remover elementos de uma lista ou chaves de um dicionário, não são permitidas durante a iteração.
  • Sem recursão:uma função não pode chamar a si mesma, seja direta ou indiretamente. O ambiente de execução realiza verificações estáticas e de tempo de execução para detectar e rejeitar o uso de recursão.
  • Nenhum fluxo de controle assíncrono:o uso de primitivos async não é permitido para manter os princípios de design principais de previsibilidade, segurança e execução determinística. As operações assíncronas dificultam o raciocínio sobre o fluxo de controle de um programa, muitas vezes levando a vulnerabilidades e condições de disputa.

Funções integradas.

Roteiro: confira nosso roteiro de funções integradas com a funcionalidade mais fundamental e usadas com frequência para criar com confiança.

A
abs()
all()
any()

B
bin()
bool()
bytes()

C
chr()

D
dict()
divmod()

E
enumerate()

F
format()
frozenset()

H
hash()
hex()

I
id()
int()

L
len()
list()

M
max()
min()

O
oct()
ord()

P
pow()
property()

R
range()
repr()
reversed()

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

T
tuple()

Z
zip()

Notas de lançamento

  • 28 de janeiro de 2026. Versão inicial do compilador gculpyc disponibilizada para participantes da prévia particular do Universal Ledger. Para um tutorial usando o compilador, consulte Implantar um contrato programável.