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: stretotal_supply: intsã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) comgcul.Accountcomo a chave, comobalance: 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,0para um campointou um dicionário vazio para um campodict.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 oROLE_CONTRACT_PARTICIPANTao 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_IDfornecido é recuperada. - O método
mint(beneficiary=Account("BENEFICIARY_ID"), value=10)é executado. O objetoAccountdo 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,streNonejá são compatíveis.- Roteiro
Decimal,byteseEnumestão no roteiro. - Restrição
floatecomplexnão são permitidos.
Tipos de contêiner:
- O
dictjá é compatível. - Roteiro
list,tuple,setedataclassestão no roteiro. - Restrição: tipos concretos precisam ser especificados para valores em um contêiner. Por exemplo,
dict[str, int]é permitido, mas umdictoudict[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) -> boolRetorna
Truese 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 retornofloat, 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
breakecontinue. - Instruções Roadmap
raiseetry ... except. - Declarações do roteiro
match. - Instruções Roadmap
generatorseyield. - 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
forem contêineres finitos. Loopswhilenã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
asyncnã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
B
C
D |
E
F
H
I
L |
M
O
P
R |
S
T
Z |
Notas de lançamento
- 28 de janeiro de 2026. Versão inicial do compilador
gculpycdisponibilizada para participantes da prévia particular do Universal Ledger. Para um tutorial usando o compilador, consulte Implantar um contrato programável.