Se tem uma coisa que tira as noites de sono dos desenvolvedores de smart contracts são as taxas de transações. Isso porque um smart contract com altas taxas tem baixo incentivo aos usuários utilizarem-no de fato. Entender como as taxas funcionam é o primeiro passo para que no futuro você possa aprender como reduzi-las, construindo smart contracts mais eficientes. Essa é uma habilidade que vale ouro neste mercado e muitos serviços de consultoria que já prestei eram justamente sobre isso.
É importante entender que este artigo não deve ser o seu primeiro contato com Solidity, pois ele aborda conceitos intermediários e alguns até avançados. Quando se está iniciando os estudos, foque apenas em fazer funcionar, deixe otimizações para um segundo momento, ok?
Vamos lá!
#1 – Por que existem taxas?
A primeira coisa que você tem de entender é o porque das taxas existirem, caso contrário mais nada vai fazer sentido neste artigo. Você pode ler este trecho ou assistir ao vídeo abaixo.
A rede blockchain é mantida por nodes (nós), que nada mais são do que computadores de pessoas rodando um aplicativo com a implementação de um nó daquela rede, como Geth/Go-Ethereum para rede Ethereum. Quanto mais nós uma blockchain possui, mais descentralizada e forte ela se torna, por isso é importante que exista interesse das pessoas em se tornarem nós.
O problema, é que se tornar um ó é algo que pode sair bem caro. Esses computadores consomem muita energia elétrica para fazer a blockchain funcionar, geralmente se tornando inúteis para outras atividades, portanto gerando custos aos seus proprietários, certo? Então porque razão o dono de uma máquina colocaria ela a serviço da blockchain se não fosse para receber algo por isso? Apenas pelos ideais libertários da web3? Talvez no início de tudo, sim, mas na escala que isso se tornou atualmente, certamente que não.
Quem fornece seu poder computacional para a blockchain o faz para se tornar um minerador, ou validador, dependendo da blockchain. Mineradores/validadores são pagos pelo seu trabalho de processamento das transações que os usuários desejam que sejam persistidas na blockchain. Esse valor é pago através das taxas cobradas junto às transações dos usuários e ele é proporcional ao “esforço computacional” daquela transação em si (CPU, memória, disco, etc).
Assim, entendemos que as taxas de transação existem porque são elas que financiam a própria existência da blockchain. Não apenas isso, mas a cobrança de taxas pelo seu uso também inibe muitos tipos de ataques comuns na web2, já que um atacante “burro” pagaria horrores para poder fazer um ataque de força bruta na blockchain, possivelmente tornando o ataque inviável.
Agora a pergunta que você deve ter é, como essas taxas são calculadas?
#2 – Como as taxas são calculadas?
Se preferir, pode assistir ao vídeo abaixo que explica a mesma coisa.
Antes de falar do cálculo em si você precisa entender que nem todas chamadas à blockchain são cobradas. Operações de leitura de dados, as chamadas “calls” são gratuitas e atendidas imediatamente pelo nó que você consultar, sem passar por mempool e sem taxas. Somente as transactios (os “sends”) é que são cobradas, mesmo que 99% do código da função seja leitura e processamento, se uma instrução salvar dados no disco, ela exigirá uma transação para ser executada, sem exceção.
Então a pergunta mais correta agora é “como as taxas de transações são calculadas?” e uma outra pergunta que você pode pensar é “porque algumas transações são mais caras que outras?”. Vamos responder essa segunda primeiro e isso irá nos levar à resposta da primeira também.
Quando a transação de um usuário é apenas uma transferência de fundos, algo super corriqueiro e simples de ser feito, as taxas cobradas ao usuário e pagas ao minerador (ou validador, vou resumir sempre como minerador, ok?) são pequenas. Agora quando a transação de um usuário é algo complexo, que envolve muitos ciclos de CPU e escreve megabytes no disco, as taxas serão significativamente mais altas, por exemplo.
Indo mais no detalhe, mas sem entrar no bit e no byte, o que acontece é o seguinte: todo código Solidity, após compilado, é convertido em opcodes (operation codes) com comandos que serão convertidos em bytescodes (binário em hexadecimal) que serão interpretados pela EVM (Ethereum Virtual Machine) presente em cada node da blockchain. Cada comando consome uma quantidade de memória e de CPU para ser executado, fora os bytes a serem persistidos no disco, se for o caso. Quanto mais comandos e mais memória e disco, mais caro fica. Mas o quanto mais caro?
Para calcular as taxas de uma transação, a EVM usa uma tabela onde para cada comando, tem um custo em gás associado, como abaixo (tabela parcial).
Repare como fazer um POP na stack (uma das áreas de memória da EVM), tem um custo de 64 gás por KB. Repare que isso é muito mais barato do que escrever algo no disco (STORAGE), onde um SSTORE custa 640.000 gás por KB de dados. Sendo assim, o primeiro passo na estimativa de custos é justamente bater todas instruções com a tabela completa e somar tudo. O resultado final será quanto gás a sua transação irá consumir. Não sabe o que é gás? Já falarei sobre isso, espere um pouco.
Vamos pegar primeiro um exemplo. Considere o código Solidity abaixo, bem simples.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract MyContract { uint256 i = 0; function test(uint256 x) public { i = x; } } |
Quando compilado, isso vai virar os opcodes abaixo.
1 2 3 |
PUSH1 0x80 PUSH1 0x40 MSTORE PUSH0 DUP1 SSTORE CALLVALUE DUP1 ISZERO PUSH2 0x12 JUMPI PUSH0 DUP1 REVERT JUMPDEST POP PUSH1 0xD9 DUP1 PUSH2 0x1F PUSH0 CODECOPY PUSH0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH1 0xE JUMPI PUSH0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH1 0x26 JUMPI PUSH0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x29E99F07 EQ PUSH1 0x2A JUMPI JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH1 0x40 PUSH1 0x4 DUP1 CALLDATASIZE SUB DUP2 ADD SWAP1 PUSH1 0x3C SWAP2 SWAP1 PUSH1 0x7D JUMP JUMPDEST PUSH1 0x42 JUMP JUMPDEST STOP JUMPDEST DUP1 PUSH0 DUP2 SWAP1 SSTORE POP POP JUMP JUMPDEST PUSH0 DUP1 REVERT JUMPDEST PUSH0 DUP2 SWAP1 POP SWAP2 SWAP1 POP JUMP JUMPDEST PUSH1 0x5F DUP2 PUSH1 0x4F JUMP JUMPDEST DUP2 EQ PUSH1 0x68 JUMPI PUSH0 DUP1 REVERT JUMPDEST POP JUMP JUMPDEST PUSH0 DUP2 CALLDATALOAD SWAP1 POP PUSH1 0x77 DUP2 PUSH1 0x58 JUMP JUMPDEST SWAP3 SWAP2 POP POP JUMP JUMPDEST PUSH0 PUSH1 0x20 DUP3 DUP5 SUB SLT ISZERO PUSH1 0x8F JUMPI PUSH1 0x8E PUSH1 0x4B JUMP JUMPDEST JUMPDEST PUSH0 PUSH1 0x9A DUP5 DUP3 DUP6 ADD PUSH1 0x6B JUMP JUMPDEST SWAP2 POP POP SWAP3 SWAP2 POP POP JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 DUP2 SLOAD 0x25 SMOD PUSH13 0x1E160D5126DD1F2FCC8F68C320 TIMESTAMP SELFDESTRUCT 0xB1 0xD3 EXTCODESIZE 0xBA 0xC4 EXP PUSH30 0xAE5B94B764736F6C634300081800330000000000000000000000000000 |
Os primeiros 3 comandos (2 PUSH1 e 1 MSTORE) são de inicialização do contrato na stack da EVM. Já o PUSH0 seguinte é justamente para subir uma variável de estado zerada para a memória (espaço para nosso i do contrato) e depois duplica ela (DUP1) no disco (SSTORE). Na sequência temos um CALLVALUE DUP1 ISZERO que se não me engano indica um constructor vazio (não declaramos um, mas ele sempre existe) e depois a coisa começa a complicar pois entramos na forma “opcode” de descrever as funções e seus comportamentos.
O objetivo aqui não é entender o funcionamento deste contrato em um baixo nível com exatidão, mas sim entender que Solidity vira esses opcodes e que eles são comparados com a tabela para entender o custo de processamento de uma função em gás.
#3 – O que é Gás?
Gás é uma unidade de medida de esforço computacional fixa, que portanto não sofre ação direta da variação cambial da criptomoeda da rede (ETH na Ethereum, por exemplo). Assim, o custo de 1 gás é sempre 1 gás, independente se o ETH está valendo $1000 ou $10.000 e permitindo com isso que a rede se autoregule conforme sua demanda e inovações futuras do algoritmo. Se estivesse atrelada a wei/ETH, hoje poderia estar ok, mas no futuro teriam de rever o algoritmo conforme atualizações da tabela.
Mas como saber o custo final, em ETH/wei?
Cada unidade de gás possui um preço atualizado regularmente pela rede. Quanto mais lotado está o mempool de transações esperando para serem processadas, mais alta é a cotação do gás. Agora se a rede estiver com pouco uso, o custo de gás baixa conforme algoritmo pré-determinado, para estimular mais seu uso. Também existe a possibilidade de você pagar gás extra para ganhar prioridade na mempool. Você pode acompanhar o preço atualizado do gás no explorador de blocos da sua blockchain.
Assim, se sua transação vai consumir 20.000 gás e o custo da unidade de gás é 200 gwei atualmente (geralmente usamos gwei no gás) o cálculo seria 20.000 * 200 = 4.000.000 gwei ou 0.004 ETH. Aí se quiser saber em dólares, basta fazer a conversão usando a cotação atual do ETH.
Outro ponto importante sobre o gás é que sua estimativa é determinística, ou seja, você pode inclusive simular os gastos em gás antes mesmo de enviar sua transação para a blockchain, desde que conheçamos as variáveis envolvidas e o algoritmo que será executado, que é exatamente o que as carteiras cripto fazem para lhe dar uma ideia das taxas a serem pagas. Assim, caso você não possua saldo suficiente para pagar pelas taxas, elas já lhe avisam que não é uma boa ideia enviar a transação. Isso porque caso você envie a transação mesmo assim, o contrato tentará executar a mesma com o gás enviado e quando o mesmo for esgotado, ela irá falhar e a quantidade gasta não será devolvida (este é o comportamento padrão para qualquer situação de erro em transações).
O custo de gás de uma transação inclusive pode ser estimado por você, durante a fase de desenvolvimento, usando libs web3 como Ethers, como no código abaixo, retirado de um projeto HardHat.
1 2 3 4 5 6 7 8 9 10 11 |
import { ethers } from "hardhat"; const ethAmount = ethers.utils.parseEther('0.5'); const gas = await ethers.provider.estimateGas({ from: from.address, to: to.address, value: ethAmount, }); console.log('gas', gas.toNumber()) |
E com isso finalizamos este artigo, espero ter ajudado a clarificar esse assunto e se quiser aprender a reduzir o consumo de gás nos seus smart contracts, este é o artigo certo.
Até a próxima!
Olá, tudo bem?
O que você achou deste conteúdo? Conte nos comentários.