Números aleatórios em smart contracts Solidity

Web3 e Blockchain

Números aleatórios em smart contracts Solidity

Luiz Duarte
Escrito por Luiz Duarte em 22/04/2025
Junte-se a mais de 34 mil devs

Entre para minha lista e receba conteúdos exclusivos e com prioridade

Uma das principais características de toda blockchain é que ela precisa ser transparente e portanto auditável. Essa auditabilidade inclui a possibilidade de poder revisitar todo seu histórico a qualquer momento, refazendo todas as transações que geraram os blocos e chegar sempre exatamente no mesmo resultado atual, de maneira determinística. Ou seja, não importa quando ou quem auditar a cadeia de blocos, o resultado tem de ser sempre o mesmo.

Devido a essa necessidade, fatores externos ou aleatórios não são permitidos nativamente no algoritmo das blockchains, o que impede que elas consigam acessar dados offchain, como APIs de terceiros, ou de gerar números aleatórios, já que em ambos os casos poderiam ocorrer resultados diferentes a cada execução das transações do histórico. Claro que considerando a necessidade de tais funcionalidades em certos projetos é crucial, existem soluções inteligentes que permitem, por exemplo, gerar números aleatórios ou pseudo-aleatórios sem prejudicar o determinismo da rede e é sobre elas que vou falar no artigo de hoje.

Se preferir, você pode assistir ao vídeo abaixo, o conteúdo é o mesmo.

Vamos lá!

Curso Web3 para Iniciantes
Curso Web3 para Iniciantes

#1 – Números pseudo-aleatórios

Em aplicações que não exigem aleatoriedade real, mas onde pseudo-aleatoriedade já resolve o problema, podemos criar um gerador que apesar de ainda ser determinístico (ou seja, para os mesmos inputs sempre sai o mesmo output) torna complexa a “adivinhação” do próximo número, a exemplo do código abaixo.

Esse código usa uma seed, que pode ser um dado do contexto da chamada original ou junção de vários, em conjunto do timestamp do bloco que está sendo minerado, encoda e criptografa tudo, gerando um inteiro sem sinal de 256 bits, ou seja, um número aleatório bem grande como 110781994263618744142372905065980595618648537530936725380920130567338079250175. Como números tão grandes geralmente não são nada práticos, um parâmetro max define o maior número possível para a geração, usando o operador de módulo (%) para transformar o número original no menor.

Obviamente esta abordagem simplista tem várias brechas de segurança, como por exemplo se você precisar chamar mais de uma vez com a mesma seed no mesmo bloco, vai retornar sempre o mesmo resultado. Outra brecha é que mineradores podem manipular o block.timestamp mexendo no horário de suas máquinas, a fim de garantir que saia um resultado específico que lhes seja mais conveniente.

Assim, você pode usar um gerador como esse em situações onde não haja repetição de seed no mesmo bloco e onde também a manipulação por parte dos mineradores não quebre o seu modelo de negócio. Coleções NFT, projetos de estudos, aleatoriedade em jogos e outros cenários simples se enquadram nessa categoria.

Roadmap Web3
Roadmap Web3

#2 – Números aleatórios

Agora quando você precisa de aleatoriedade real, ela só pode ser obtido com a ajuda de oráculos. Eu já expliquei aqui no blog sobre o que são oráculos e até mesmo já ensinei a construir um. Resumidamente é uma aplicação fora da blockchain que insere valores na blockchain conforme uma necessidade de um contrato, permitindo que dados nativamente offchain sejam acessados onchain, como preços de commodities, informações de outros sistemas ou em nosso caso, números aleatórios. Se você quiser, pode criar o seu oráculo de números aleatórios com os tutoriais que linkei acima, ou se preferir, pode usar uma solução de mercado como os oráculos da Chainlink.

A Chainlink é uma empresa web3 focada em soluções de oráculos de vários tipos. As vantagens de usar os oráculos da Chainlink incluem:

  • infraestrutura robusta: os oráculos estão sempre no ar para você usar;
  • variedade: eles tem muitos oráculos diferentes e soluções customizáveis, que se adequam a qualquer cenários;
  • confiabilidade: os oráculos são auditáveis, para garantir que não sejam manipulados para fins de terceiros;

Já como desvantagens temos um fator óbvio mas importante de ser mencionado: a Chainlink é uma empresa e empresas visam lucro para sobreviverem. Logo, os seus oráculos são pagos utilizando a moeda nativa da rede ou a moeda deles, a LINK. Você não precisará pagar nada durante o desenvolvimento, bastando usar testnets para isso e usar o faucet oficial da Chainlink para obter LINK de teste, gratuitamente.

Independente da abordagem, por uma questão de custos x usabilidade, todos os oráculos de números aleatórios trabalham com uma arquitetura assíncrona, baseada em callbacks. Isso quer dizer que toda vez que você quiser um novo número aleatório, terá de fazer um pedido onchain, ou seja, registrar sua necessidade em um contrato do oráculo, para o oráculo offchain, que está monitorando o seu contrato, posso gerar o número aleatório e escrever no seu contrato para você, como na arquitetura Request-response abaixo.

Arquitetura Oracle
Arquitetura Oracle

Isso obviamente torna a usabilidade de contratos que necessitem de oráculos para números aleatórios algo que tem de ser pensado com mais carinho, caso contrário torna a experiência do usuário um desastre. Dito isso, a seguir vamos ver como fazer o setup e utilizar o oráculo de números aleatórios da Chainlink em um projeto, bem, aleatório. 🙂

O produto da Chainlink para números aleatórios é o Chainlink VRF ou Verifiable Random Function e é ele que vamos usar para um exemplo de contrato que usa números aleatórios reais. O Verifiable no nome é porque em cada request você tem acesso a um comprovante criptográfico que permite atestar a aleatoriedade do número, garantindo que não foi manipulado de forma alguma, algo extremamente importante dependendo do projeto em que optou por usá-lo.

Vamos em frente!

#3 – Aleatoriedade com Chainlink VRF

Antes de começar a codificar, você precisa criar uma assinatura na Chainlink e adicionar fundos, para garantir que terá como pagar pelas requisições. Você pode fazê-lo neste link. Você deve conectar a sua carteira (eu uso MetaMask, ensino a usar aqui) e clicar no botão de “create subscription”. Preencha os dados da assinatura e avance pelos passos que incluem pagar a transação de assinatura e assinar uma mensagem de confirmação, tudo com a carteira cripto que você conectou. Note que a carteira que você usar para criação dessa assinatura via de regra é a única autorizada a fazer chamadas na sua conta, qualquer outra carteira dará erro de permissão.

Uma vez com a subscription criada (às vezes não conclui de primeira e tem que tentar de novo), é hora de adicionar fundos na sua conta. Basta transferir LINK ou a moeda nativa da rede para garantir que você tenha grana para pagar as taxas de uso do oráculo, lembrando que você pode receber LINK gratuitamente no faucet oficial.

Adicionado os fundos, o próximo passo é adicionar o endereço dos contratos autorizado a usar a sua conta na Chainlink, ou seja, que irão gastar os seus fundos com chamadas. Além disso vai ser fornecido um subscription id, que você precisará usar para fazer chamadas ao oráculo, conforme mostrarei como fazer a seguir. Deixe essa tela aberta, precisará voltar aqui depois que dizer o seu deploy, para adicionar o endereço do contrato.

Depois de garantir que você fez o passo a passo citado anteriormente, de setup da sua subscription, é hora de começar a codificar a integração do seu smart contract com o do VRF. Para isso, comece importando essas duas dependências. Você também tem de garantir que seu contrato herde de VRFConsumerBaseV2Plus, como fiz abaixo.

Na sequência você vai precisar de algumas variáveis de estado no seu contrato, necessárias para configuração das suas chamadas ao VRF, como abaixo.

A saber:

  • s_subscriptionId: seu subcription id recebido após a etapa final de setup da subscription que fizemos anteriormente, vamos definir ele no constructor;
  • vrfCoordinator: o endereço do contrato VRF, neste caso é o endereço da rede Sepolia, mude de acordo com sua rede;
  • s_keyHash: o id do job offchain que vai rodar sua requisição. Diferentes job runners possuem diferentes preços;
  • callbackGasLimit: o limite de gás para execução da sua função de callback, ajuste conforme quantas palavras aleatórias serão geradas de cada vez;
  • requestConfirmations: quantas confirmações você deseja esperar antes que a palavra aleatória lhe seja entregue. Quanto mais, maior a segurança de que ela é idônea, mas também mais lento o retorno;
  • numWords: quantas palavras aleatórias você quer na request. Se for precisar mais de uma, ajuste de acordo, pois gera economia de taxas;

Agora é hora de definirmos o contructor do nosso contrato. É uma exigência da herança com VRFConsumerBaseV2Plus que a gente chame o construtor original passando o endereço do contrato VRF, mas também vamos usar o constructor para inicializar o subscription id, já que é mais seguro fazer assim do que deixar fixo no código.

Agora vou definir mais algumas variáveis que dizem respeito à minha aplicação em si e não tanto à integração com o VRF. É aqui que mais vai variar da sua implementação.

Neste exemplo eu defini uma constante para o valor máximo gerado como aleatório, além de um mapping para guardar quem fez cada request (requestId => requester) e outro mapping para guardar os resultados das requests, ou seja, os números aleatórios em si. Como a geração se dá em “duas pernas”, na primeira (ida) vamos guardar quem pediu o valor junto com id que será gerado pela Chainlink como recibo da request. Na segunda (volta), vamos guardar o valor aleatório gerado para aquele pedido. Isso ficará mais claro com a implementação a seguir, da função que solicita o número aleatório.

Aqui temos uma função que somente pode ser chamada pelo administrador do contrato, algo comum nesse tipo de integração já que atacantes poderiam querer ficar chamando essa função para esgotar os recursos da sua conta na Chainlink. Assim, lembre-se: na hora de testar seu contrato, sempre use a carteira com a qual você criou a sua subscription na Chainlink. Para sabermos de quem é a solicitação, passamos o endereço da carteira por parâmetro, mas você pode usar qualquer informação única que julgar adequada, este é apenas um exemplo.

No corpo da função temos uma chamada ao contrato do VRF pedindo o número aleatório. Essa chamada espera um objeto por parâmetro que montamos usando o VRFV2PlusClient que importamos anteriormente e passamos todos os parâmetros que configuramos no início do contrato. Atenção especial aqui à última propriedade que defini como nativePayment: false para usar o saldo em LINK que adicionei na minha subscription, mas que você pode usar como true se quiser usar saldo na moeda nativa da rede.

Como resultado dessa chamada receberemos um requestId que usamos como chave no mapping de controle das requests.

Quando chamarmos esta função uma requisição será cadastrada no contrato do VRF que está sendo monitorado pelo oráculo offchain da Chainlink. Ele vai pegar essa transação e agendar um job para gerar o número aleatório, verificá-lo e executar o callback, que também deve estar programado no seu contrato, da seguinte maneira.

O conteúdo em si pouco importa, você pode colocar a lógica que quiser nele, mas a assinatura da função é uma exigência do VRF para ele conseguir se integrar ao seu contrato no callback. Por parâmetro ele vai informar qual request está respondendo, enquanto que no array randomWords ele vai devolver todos números aleatórios que você pediu (apenas 1 no meu caso).

Assim como fiz no exemplo pseudo-aleatório, é comum transformarmos o número gigante que vem em algo mais razoável, conforme o range que você precisa. Aqui no meu caso eu defini o MAX para isso, lembra? Por fim, guardamos o valor transformado como resultado no respectivo mapping. Este é todo meu processamento, mas você pode fazer muito mais, apenas tome cuidado porque o custo de execução desse callback recai sobre as taxas que a Chainlink cobra de você e também é impactado pelo limite de gás de callback que você definiu nas configurações da request.

Agora é hora de testar o seu contrato. Faça o deploy dele na mesma rede que configurou as variáveis do VRF e pegue o endereço do mesmo para cadastrar lá na sua subscription. Depois, chame a função random primeiro (com a carteira admin da subscription), verifique se foi cadastrado o retorno no mapping e depois de um tempo verifique se já veio o resultado no outro mapping (demora um bocado na testnet). Uma última verificação interessante é com relação ao custo dessa transação na Chainlink, que no meu caso ficou em menos de 0.03 LINK. Isso ajuda a dimensionar os fundos necessários para seu projeto rodar usando esse oráculo.

Até a próxima!

Curso Web23

TAGS:

Olá, tudo bem?

O que você achou deste conteúdo? Conte nos comentários.

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *