Otimização de gas em Solidity: 12 técnicas para deixar seus smart contracts mais baratos e eficientes
Escrito por Usman Asim

As taxas de gas do Ethereum sempre foram um ponto de dor histórico para o ecossistema: que usuário quer pagar $20+ em gas por uma transação onchain simples? Embora os upgrades recentes do Ethereum tenham reduzido substancialmente os custos de gas para os usuários nos últimos anos, otimizar gas no seu código Solidity ainda é a principal forma de viabilizar ações onchain complexas no seu app sem pesar no bolso dos seus usuários.
Seja para minimizar riscos em revisões de código ou apenas escrever contratos mais limpos, acertar na otimização de gas significa viabilizar apps seguros e acessíveis que escalam para milhões de usuários. Neste guia, vamos explicar o básico de gas, por que a otimização importa (spoiler: pode reduzir custos em 20-50% em comparação com código não otimizado), e compartilhar 12 técnicas de otimização com exemplos de código.
O que é gas e otimização de gas em Solidity?
Gas é a unidade de medida do esforço computacional necessário para realizar operações específicas no Ethereum, e otimização de gas em Solidity é o processo de tornar seu código de smart contract mais barato de executar.
Ao transacionar no Ethereum, toda transação tem um custo, o custo de escrever dados em storage ou processar uma transação, e esse custo é chamado, adequadamente, de "gas". Se você não paga as taxas de gas, nada acontece, assim como um carro precisa de gasolina para se mover.
Você também precisa desse gas para contratos. Veja o que acontece quando você faz deploy e executa um smart contract:
- Você escreve código Solidity. Essa é a linguagem de programação de alto nível, legível por humanos, para smart contracts do Ethereum.
- O compilador converte isso em bytecode. Ao compilar seu código Solidity, o compilador o traduz em bytecode, uma representação hexadecimal de instruções de baixo nível. Esse bytecode é o que efetivamente fica armazenado na blockchain.
- O bytecode contém opcodes. Esse bytecode é composto por opcodes (operation codes), que são o conjunto de instruções da EVM: pense neles como a linguagem assembly do Ethereum. Cada opcode representa uma operação específica, como "somar dois números", "ler do storage" ou "pular para outra instrução".
- A EVM executa os opcodes. Quando alguém chama seu smart contract, a Ethereum Virtual Machine (EVM) rodando nos nós da rede lê o bytecode, o decodifica em opcodes individuais e os executa um por um. Cada opcode tem um custo de gas fixo.
Por exemplo, o opcode ADD (que soma dois números) custa 3 gas, enquanto SSTORE (que escreve no storage) custa pelo menos 20.000 gas. A EVM soma o custo de gas de cada opcode executado durante sua transação.
Para mais detalhes sobre o modelo de gas do Ethereum, veja a documentação oficial.
Otimização de gas é ajustar seu código para fazer o mesmo trabalho, mas com menos operações, reduzindo os custos de execução. Como mencionamos acima, toda transação precisa de gas (pago em ETH ou equivalentes em outras chains). Depois do Dencun, a disponibilidade de dados ficou mais barata graças aos blobs, mas o gas de execução (os custos de computação) ainda pode se acumular. Contratos otimizados não apenas economizam dinheiro dos usuários, como também protegem contra ataques de DoS por serem codebases mais enxutas. Aprofunde-se em opcodes na documentação do "Gas Optimizer".
Por que a otimização de gas é importante para desenvolvedores?
Gas alto = UX frustrante que pode fazer usuários pararem de usar seu app, especialmente durante picos de tráfego, quando as taxas de gas podem disparar.
Otimizar seu código para baixo consumo de gas pode reduzir as taxas para seus usuários, oferecer uma UX melhor e viabilizar transações de baixo valor que, de outra forma, seriam inviáveis por causa das taxas, além de lidar com alto uso sem esbarrar nos limites de bloco.
Contratos não otimizados podem queimar 20-50% de gas extra, elevando custos e abrindo brechas para exploits. Com o TVL de DeFi chegando perto de $150 bilhões em outubro de 2025, contratos eficientes em gas não são apenas desejáveis, são uma vantagem competitiva que pode determinar o sucesso ou fracasso da adoção pelos usuários.
Ou você é o app com lógica de smart contract complexa que faz os usuários pagarem as altas taxas associadas a essa complexidade, ou você otimiza seu código para tornar a experiência final dos seus usuários melhor e mais barata.
As 12 principais técnicas de otimização de gas em Solidity
Aqui estão formas testadas na prática de otimizar o uso de gas no seu código. Vamos explicar cada exemplo, como ele economiza gas, e mostrar o código. Recomendamos testar isso no Remix ou Hardhat para ver as diferenças por conta própria.
1. Use mappings em vez de arrays
Solidity oferece duas estruturas de dados principais para armazenar listas de dados: arrays e mappings. Embora a sintaxe pareça similar, elas servem a propósitos muito diferentes e têm custos de gas drasticamente diferentes.
Arrays são coleções ordenadas e iteráveis que armazenam elementos sequencialmente em memory ou storage. São úteis quando você precisa percorrer todos os itens ou manter uma ordem específica. No entanto, encontrar um item específico em um array requer iteração: a EVM precisa checar cada elemento até encontrar uma correspondência. Isso significa que operações de busca têm complexidade O(n), ficando mais caras à medida que seu array cresce.
Mappings (também chamados de hash tables) funcionam de forma completamente diferente. Eles usam uma estrutura chave-valor onde você pode recuperar instantaneamente qualquer valor pela sua chave, com buscas O(1) de tempo constante, independentemente de quantos itens estão armazenados. Isso acontece porque Solidity calcula o storage slot diretamente a partir da chave usando uma função hash, eliminando a necessidade de buscar entre os dados. A contrapartida é que mappings não são iteráveis: você não consegue percorrer todas as entradas ou saber quais chaves existem sem rastreá-las separadamente.
O impacto no gas: criar e acessar entradas de mapping é significativamente mais barato do que operações com array porque não há overhead de iteração. Use arrays apenas quando precisar especificamente iterar por todos os itens ou manter a ordem de inserção. Para todos os outros casos, especialmente saldos de usuários, registros de propriedade ou qualquer busca baseada em chave: mappings são a escolha clara.
Aqui está um exemplo usando um array (mais caro para acesso):
string[] public cars = ["ford", "audi", "chevrolet"];
// To find "audi", you'd need to loop through the array
function findCar(string memory target) public view returns (bool) {
for (uint i = 0; i < cars.length; i++) {
if (keccak256(bytes(cars[i])) == keccak256(bytes(target))) {
return true; // Cost increases with array size
}
}
return false;
}Aqui está o mesmo dado usando um mapping (muito mais barato para buscas):
mapping(uint => string) public cars;
constructor() {
cars[101] = "Ford";
cars[102] = "Audi";
cars[103] = "Chevrolet";
}
// Direct access - O(1) constant time regardless of data size
function getCar(uint id) public view returns (string memory) {
return cars[id]; // Single storage read, minimal gas
}Usar chaves inteiras permite imitar listas ordenadas sem os custos de iteração dos arrays. Isso é especialmente útil para dados de usuário como saldos ou registros de propriedade, onde você precisa de acesso rápido e direto por ID. Para mais detalhes sobre como os mappings funcionam internamente, veja a documentação de mappings do Solidity.
2. Habilite o otimizador do compilador Solidity
O otimizador do compilador Solidity é uma ferramenta poderosa que pode reduzir significativamente os custos de gas, mas requer configuração compatível com seu caso de uso específico. O otimizador funciona analisando seu código e aplicando várias transformações: simplifica expressões, remove código morto, faz inline de funções pequenas para eliminar operações de jump caras, e reaproveita segmentos de código duplicados. Todas essas mudanças reduzem o número de opcodes que a EVM precisa executar.
No entanto, há um trade-off importante controlado pelo parâmetro "runs". Esse número informa ao otimizador quantas vezes você espera que cada opcode do seu contrato seja executado ao longo de sua vida útil. O otimizador usa isso para equilibrar dois objetivos concorrentes: minimizar custos de deploy (que acontecem uma vez) versus minimizar custos de execução em runtime (que acontecem repetidamente a cada chamada de função).
Como o parâmetro runs funciona:
- Runs baixo (ex.: 200): o otimizador prioriza bytecode menor, resultando em deploy mais barato. Isso significa menos duplicação de código e mais jumps entre segmentos de código reutilizáveis. Ideal para contratos que você fará deploy com frequência, mas chamará com pouca frequência, como factory contracts ou scripts de deploy de uso único.
- Runs alto (ex.: 10.000+): o otimizador prioriza eficiência em runtime, duplicando código para evitar jumps e fazendo inline agressivo de funções. Isso cria um bytecode maior (deploy mais caro), mas execução de função mais rápida e barata. Ideal para contratos com alto volume de transações: como routers de DEX, contratos de staking ou marketplaces de NFT.
Aqui está um exemplo para apps com deploy frequente e runs baixo (200):
module.exports = { solidity: { version: "0.8.9", settings: { optimizer: { enabled: true, // Set to true for opt runs: 200, }, }, },};E aqui está um exemplo para apps com deploy pouco frequente, otimizados para runs alto (10000):
module.exports = { solidity: { version: "0.8.9", settings: { optimizer: { enabled: true, runs: 10000, }, }, },};Escolhendo o valor de runs certo
Há trade-offs em ambas as abordagens. Pense no ciclo de vida do seu contrato. Um token de governança que faz deploy uma vez, mas tem milhões de transações de transferência, deve usar runs alto. Uma factory de deploy que cria novos contratos constantemente, mas esses contratos raramente são chamados, deve usar runs baixo. Na dúvida, 200 é um padrão seguro que equilibra razoavelmente bem as duas preocupações. Sinta-se à vontade para testar com o perfil do seu contrato, e você pode habilitar qualquer um deles no Hardhat config ou Remix.
3. Minimize dados on-chain
Storage on-chain é a operação individual mais cara em Solidity. Cada opcode SSTORE (escrita em storage) pode custar 20.000+ gas (medido em Gwei), e modificar storage slots existentes ainda custa entre 2.900 e 5.000 gas. Compare isso com operações de memory, que custam apenas 3 gas, e você rapidamente entende por que a otimização de storage é crítica. O princípio fundamental aqui é simples: armazene apenas o que é absolutamente essencial on-chain e trate tudo o mais off-chain, por meio de APIs, oráculos ou serviços de indexação.
Além de reduzir escritas em storage, você também deve agrupar operações de forma inteligente para evitar custos redundantes. Isso mantém seu contrato enxuto, reduzindo tanto as taxas de deploy quanto de runtime, além de dificultar a exploração por atacantes de funções computacionalmente pesadas que poderiam ser usadas em ataques de DoS.
Salvando dados em variáveis de storage
Seja implacável quanto ao que merece storage. Por exemplo, saldos de usuários, registros de propriedade e estado de contrato que precisam ser à prova de adulteração e acessíveis globalmente: isso pertence on-chain. Se você está otimizando para gas, tudo o mais deveria ficar off-chain. Por exemplo, se você precisa de dados externos de preço, use oráculos como Chainlink para buscá-los durante a execução, em vez de armazenar preços históricos. Se você precisa rastrear histórico de transações para um frontend, emita eventos em vez de armazenar arrays.
Um detalhe importante sobre eventos: embora eventos sejam baratos de emitir (cerca de 375 gas base + 375 gas por topic), contratos não conseguem ler seus próprios eventos. Eventos existem puramente para consumo off-chain por indexadores e frontends. Nunca use eventos como substituto de storage que a lógica do seu contrato precisa acessar.
Agrupando operações (batching)
Em vez de exigir que usuários enviem várias transações separadas, agrupe ações relacionadas em uma única transação. Isso economiza a taxa base de 21.000 gas por transação (paga em toda transação, independentemente do que ela faz) e também reduz operações redundantes, como checar msg.sender várias vezes, carregar as mesmas variáveis de storage repetidamente ou pagar custos de calldata para múltiplos envios de transação.
Esse padrão é especialmente útil para processos com múltiplas etapas, como aprovações de token seguidas de transferências, ou executar várias operações DeFi de forma atômica (swap, depois stake, depois claim de recompensas).
Aqui está um exemplo de função de batch send:
Struct Call {
address recipient;
uint256 gas;
uint256 value;
bytes data;
}
function batchSend(Call[] memory _calls) public payable {
for(uint256 i = 0; i < _calls.length; i++) {
(bool _success, bytes memory _data) = _calls[i].recipient.call{
gas: _calls[i].gas,
value: _calls[i].value
}(_calls[i].data);
if (!_success) {
assembly {
revert(add(0x20, _data), mload(_data))
}
}
}
}Esse padrão economiza gas de forma significativa ao eliminar verificações repetidas de msg.sender, reduzir o overhead de calldata (você passa o function selector apenas uma vez), e pagar a taxa base de transação apenas uma vez em vez de uma vez por operação.
Loops
Loops são multiplicadores de gas: cada iteração repete as mesmas operações, e os custos se acumulam linearmente. Um loop sobre 100 itens fazendo operações de storage pode facilmente consumir 500.000+ gas, e loops sobre arrays não limitados podem até exceder os limites de gas por bloco, tornando sua função permanentemente impossível de chamar.
A solução quase sempre é eliminar o loop por completo. Use mappings para buscas O(1) de tempo constante em vez de iteração O(n) sobre arrays. Se você realmente precisar iterar, limite estritamente o tamanho dos arrays, ou, melhor ainda, mova a iteração para off-chain e faça com que os usuários enviem índices ou chaves específicos.
Uma nota sobre reembolsos de gas
Solidity costumava oferecer reembolsos de gas para limpar storage (zerando valores), mas o EIP-3529 reduziu significativamente esses reembolsos. Embora você ainda receba um pequeno reembolso ao limpar storage, isso não é mais uma estratégia de otimização importante. Para detalhes sobre a mecânica atual de reembolsos, veja a proposta de reembolsos de gas do Ethereum.
4. Use eventos indexados
Eventos são um mecanismo de logging leve que custa uma fração das operações de storage, cerca de 375 gas base mais 375 gas por parâmetro indexado, comparado a 20.000+ gas para escrever em storage. Eventos são gravados na receipt trie da transação, que é separada do storage de estado do contrato, tornando-os perfeitos para registrar informações que aplicações off-chain precisam rastrear.
A limitação crítica: eventos são somente-escrita do ponto de vista do contrato. Uma vez emitidos, o código do seu contrato não consegue lê-los de volta. Eventos existem puramente para consumo externo por frontends, indexadores e ferramentas de monitoramento. Use eventos para notificações e registros históricos que sistemas off-chain precisam, mas nunca para dados dos quais a lógica do seu contrato depende.
Isso descarrega uma quantidade enorme de gas. Em vez de armazenar cada transação em um array de storage caro, emita um evento e deixe que indexadores off-chain (como o The Graph ou as APIs da Alchemy) construam esse histórico para o seu frontend.
Veja como declarar e emitir um evento:
event MyFirstEvent(address indexed sender, uint256 indexed amount, string message);
function doSomething(uint256 _amount, string memory _message) public {
// Your contract logic here
// Emit the event - cheap logging instead of expensive storage
emit MyFirstEvent(msg.sender, _amount, _message);
}Parâmetros indexados
Você pode marcar até 3 parâmetros como indexed, o que os torna pesquisáveis em consultas de log. Por exemplo, com sender e amount indexados, você pode filtrar rapidamente "todos os eventos onde sender = 0x123..." sem escanear cada evento. Parâmetros não indexados como message ainda são registrados, mas não são diretamente pesquisáveis.
Casos de uso comuns incluem transferências de token, mudanças de propriedade, transições de estado e rastreamento de atividade de usuário: qualquer lugar onde você precisa de um registro para consumo off-chain, mas não precisa de acesso on-chain. Para mais detalhes, veja a documentação de eventos do Solidity.
5. Empacote suas variáveis
A EVM armazena dados em slots de 32 bytes, e cada storage slot custa gas para escrever (20.000+ gas para novos slots, 2.900-5.000 gas para atualizações). Ao agrupar estrategicamente variáveis pequenas, você pode encaixar várias variáveis em um único slot, reduzindo drasticamente o número de operações SSTORE que seu contrato precisa.
Pense nisso como arrumar uma mala de forma eficiente: a ordem em que você organiza os itens determina quanto espaço você desperdiça. Variáveis são empacotadas na ordem em que você as declara, então um arranjo cuidadoso é fundamental.
Antes (desperdiça espaço em 3 slots):
contract MyContract {
uint128 c; // Slot 0 (uses 16 bytes, wastes 16 bytes)
uint256 b; // Slot 1 (uses full 32 bytes)
uint128 a; // Slot 2 (uses 16 bytes, wastes 16 bytes)
}Isso usa 3 storage slots, mesmo que os dados só precisem de 2,5 slots de espaço.
Depois (compacta em 2 slots):
contract MyContract {
uint128 a; // Slot 0 (first 16 bytes)
uint128 c; // Slot 0 (second 16 bytes) - packed together!
uint256 b; // Slot 1 (full 32 bytes)
}Ao declarar as duas variáveis uint128 consecutivamente, elas compartilham um único slot, economizando uma operação SSTORE inteira toda vez que você escreve em ambas as variáveis.
Regras principais de empacotamento:
- Variáveis são empacotadas na ordem de declaração
- Um novo slot começa quando a próxima variável não cabe no slot atual
- Tipos menores como
uint8,uint128,address(20 bytes) são ótimos candidatos para empacotamento - Mesmo que um tipo pequeno fique sozinho, ele ainda consome um slot completo de 32 bytes, então sempre tente emparelhá-los
Por exemplo, duas variáveis address (20 bytes cada) não se encaixam em um slot, já que 40 bytes excedem 32 bytes. Mas um address (20 bytes) mais um uint96 (12 bytes) se encaixam perfeitamente em um slot de 32 bytes.
Para mais detalhes sobre como o Solidity organiza o storage, veja a documentação de storage layout.
6. Libere storage não utilizado
Quando você limpa variáveis de storage, restaurando-as aos valores padrão (0 para inteiros, address\(0\) para addresses, false para booleanos), a EVM fornece um reembolso de gas. Embora o EIP-3529 tenha reduzido significativamente esses reembolsos em relação aos valores originais, você ainda recebe de volta 4.800 gas por storage slot limpo: uma recuperação significativa ao limpar dados obsoletos.
Isso funciona porque resetar storage slots reduz o tamanho do estado da blockchain, então o Ethereum incentiva esse comportamento de limpeza. É uma forma de recuperar alguns custos quando dados se tornam obsoletos, mantendo o estado do seu contrato enxuto e eficiente.
Veja como limpar variáveis:
delete myVariable; // Resets to default value and triggers refund// Or explicitly:
myInt = 0;
myAddress = address(0);
myBool = false;Nota importante sobre mappings: a palavra-chave delete não funciona em mappings inteiros, porque mappings não rastreiam quais chaves existem. Em vez disso, você precisa deletar entradas individuais do mapping:
mapping(address => uint256) public balances;
// This won't work - can't delete entire mapping// delete balances;// Instead, delete specific keys:
delete balances[msg.sender]; // Clears this specific entryCasos de uso práticos
Reembolsos de gas são mais úteis em contratos onde os dados têm um ciclo de vida claro, pense em contratos de escrow que podem ser limpos após a conclusão, autorizações temporárias que expiram, ou dados em cache que ficam desatualizados. Não force a lógica do seu contrato apenas para perseguir reembolsos, mas quando dados naturalmente se tornam obsoletos, limpá-los é vantajoso para todos.
Para a mecânica atual de reembolsos e limitações, veja a documentação do EIP-3529 sobre reembolsos de gas.
7. Armazene dados em calldata em vez de memory para certos parâmetros de função
Ao declarar parâmetros de função, você tem uma escolha entre memory e calldata para tipos de referência como arrays, strings e structs. Entender a diferença pode economizar gas significativo, especialmente para funções externas com parâmetros grandes.
Calldata é um storage somente-leitura que fica diretamente nos dados da transação. Quando você usa calldata, a função lê os argumentos diretamente da transação sem copiá-los para lugar nenhum. Essa é a opção mais barata porque evita completamente a alocação de memory e operações de cópia.
Memory, por outro lado, exige que a EVM aloque espaço e copie os dados de calldata para memory, executando várias operações MLOAD e MSTORE. Esse overhead de cópia se torna caro com arrays ou strings grandes: cada elemento copiado custa gas adicional.
A regra: use calldata para parâmetros de função externa que você só precisa ler. Use memory apenas quando precisar modificar os dados dentro da sua função.
Aqui está um exemplo usando calldata (mais barato para acesso somente-leitura):
function processNumbers(uint[] calldata nums) external {
for (uint i = 0; i < nums.length; i++) {
// Read-only operations - no copy needed
uint value = nums[i];
// Do something with value
}
}Compare com memory (mais caro devido à cópia):
function processNumbers(uint[] memory nums) external {
// Data gets copied from calldata to memory first (costs gas)
for (uint i = 0; i < nums.length; i++) {
uint value = nums[i];
}
}A economia de gas escala com o tamanho dos dados: um array de 100 elementos passado como calldata pode economizar milhares de gas em comparação com memory.
Quando você precisa usar memory: se sua função precisa modificar o array, adicionar itens a ele ou construir novas estruturas de dados, então memory é necessário, já que calldata é imutável. Mas para operações puramente de leitura, calldata é sempre a melhor escolha.
Para mais detalhes sobre as diferenças entre locais de armazenamento, veja a documentação sobre calldata vs memory.
8. Use valores fixos immutable e constant
Variáveis marcadas como constant ou immutable não usam storage slots de forma alguma: seus valores são incorporados diretamente no bytecode do contrato no momento do deploy. Isso elimina operações caras de SLOAD (2.100 gas cada) toda vez que você as acessa, substituindo leituras de storage por leituras baratas de bytecode.
Constant: o valor precisa ser definido em tempo de compilação e não pode mudar. Use para valores fixos que nunca vão variar entre deploys.
Immutable: o valor é definido uma vez no constructor e não pode mudar depois. Use para valores que diferem entre deploys (como endereços de token ou de owner), mas permanecem fixos após o deploy.
Aqui está como usar ambos:
contract MyContract {
uint256 constant FEE_RATE = 10; // Set at compile time
address immutable owner; // Set once at deployment
constructor(address _owner) {
owner = _owner; // Can only set in constructor
}
function calculateFee(uint256 amount) public pure returns (uint256) {
return amount * FEE_RATE / 100; // No SLOAD - reads from bytecode
}
}Exemplo de economia de gas: se você lê uma variável de storage normal 10 vezes em uma função, isso são 21.000 gas em operações SLOAD. Com constant ou immutable, essas leituras custam essencialmente nada: apenas o gas para executar operações aritméticas básicas.
Casos de uso comuns:
- Taxas de protocolo ou percentuais (
constant) - Constantes matemáticas como decimais ou fatores de escala (
constant) - Endereços de token vindos de argumentos do constructor (
immutable) - Endereços de owner ou admin do contrato (
immutable) - Endereços de contratos externos que não vão mudar (
immutable)
Tanto variáveis constant quanto immutable também podem ser declaradas no nível do arquivo, fora de contratos, tornando-as reutilizáveis em múltiplos contratos no mesmo arquivo.
Para mais detalhes, veja a documentação sobre constantes e immutables.
9. Use o modificador de visibilidade external
Modificadores de visibilidade de função afetam como a EVM lida com chamadas de função e podem impactar os custos de gas. A diferença principal: funções external são otimizadas especificamente para chamadas de fora do contrato, enquanto funções public precisam lidar tanto com chamadas externas quanto internas, adicionando overhead.
Funções external só podem ser chamadas de fora do contrato (via transações ou outros contratos). Quando chamadas externamente, elas leem parâmetros diretamente de calldata sem copiá-los, tornando-as ligeiramente mais eficientes. Você não pode chamar uma função external internamente usando functionName\(\), você precisaria usar this.functionName\(\), o que cria uma chamada externa cara.
Funções public podem ser chamadas tanto externa quanto internamente. Essa flexibilidade exige que o compilador gere código adicional para lidar com ambos os tipos de chamada, adicionando um pequeno overhead de gas mesmo quando chamadas externamente.
Uma regra geral: use external para funções que fazem parte da API pública do seu contrato e que só serão chamadas por usuários ou outros contratos. Use internal ou private para funções auxiliares que só as próprias funções do seu contrato precisam chamar.
Aqui está um exemplo de função external (otimizada para chamadas externas):
function updateMessage(string calldata _newMessage) external returns (string memory) {
message = _newMessage;
return message;
}Compare com public (lida com chamadas internas e externas):
function updateMessage(string memory _newMessage) public returns (string memory) {
message = _newMessage;
return message;
}A economia de gas é modesta: tipicamente em torno de 20-50 gas por chamada, mas isso se acumula ao longo de milhares de transações. Mais importante, usar external sinaliza claramente a intenção: essa função é feita para ser chamada de fora do contrato.
Dica bônus: percebeu como a versão external usa calldata para o parâmetro string? Funções external combinam perfeitamente com argumentos calldata, já que ambos são otimizados para chamadas externas.
Para mais sobre visibilidade de função e boas práticas, veja a documentação de visibilidade.
10. Use aritmética unchecked com segurança
A partir do Solidity 0.8.0, o compilador adiciona automaticamente verificações de overflow e underflow a todas as operações aritméticas. Embora isso previna bugs, custa aproximadamente 30-40 gas por operação. Quando você tem certeza de que overflow/underflow é matematicamente impossível, envolva as operações em blocos unchecked para pular essas verificações e economizar gas.
Quando é seguro: contadores de loop com limites conhecidos, aritmética onde você validou os inputs, ou operações onde overflow é matematicamente impossível.
Quando evitar: valores fornecidos pelo usuário sem validação, cálculos financeiros, ou qualquer lugar onde overflow poderia criar vulnerabilidades.
Aqui está um exemplo básico:
function add(uint x, uint y) external pure returns (uint) {
unchecked {
return x + y; // Skips overflow check - saves ~30 gas
}
}Aqui está outro exemplo mostrando contadores de loop, o caso de uso mais comum dessa técnica:
function processArray(uint[] calldata items) external {
for (uint i = 0; i < items.length;) {
// Process item
unchecked {
++i; // i can never realistically overflow
}
}
}Nesse loop, i começa em 0 e incrementa em 1 a cada iteração. Para que i dê overflow, o array precisaria de 2^256 elementos, o que é fisicamente impossível dadas as restrições da blockchain. Esse é um candidato perfeito para unchecked.
Nota importante de segurança: se você usar unchecked, adicione statements manuais de require para validar inputs que poderiam causar problemas:
function subtract(uint x, uint y) external pure returns (uint) {
require(x >= y, "Underflow prevented");
unchecked {
return x - y; // Safe because validated above
}
}A economia de gas dos blocos unchecked se acumula rapidamente em loops ou funções chamadas com frequência. Para mais detalhes e casos extremos, veja a documentação de unchecked.
11. Minimize chamadas externas
Cada chamada para outro contrato custa pelo menos 100 gas para o opcode CALL, mais custos adicionais de calldata e quaisquer mudanças de estado no contrato chamado. Essas chamadas também podem falhar de forma imprevisível se o contrato externo reverter, tornando-as caras e arriscadas ao mesmo tempo. Quando você precisa de dados de contratos externos, agrupe várias chamadas juntas ou faça cache dos resultados para evitar chamadas repetidas dentro da mesma transação.
Abordagem eficiente em gas:
interface IExternal {
function getData() external view returns (uint);
}
function processData(address addr) external {
// Call once and cache the result
uint data = IExternal(addr).getData();
// Reuse cached value multiple times - no additional calls
uint result1 = data * 2;
uint result2 = data + 100;
uint result3 = data / 5;
}Abordagem ineficiente (evite isso):
function processData(address addr) external {
// Calling three separate times - wastes ~300 gas
uint result1 = IExternal(addr).getData() * 2;
uint result2 = IExternal(addr).getData() + 100;
uint result3 = IExternal(addr).getData() / 5;
}Se você precisa dos mesmos dados várias vezes em uma transação, sempre faça cache em uma variável local. Se você precisa de dados externos em várias transações, considere armazená-los (embora seja necessário pesar o custo de 20.000 gas de SSTORE contra a frequência das chamadas externas).
Para padrões e considerações de segurança em torno de chamadas externas, veja as melhores práticas para chamadas externas.
12. Use assembly em caminhos críticos
Para código crítico em termos de performance, como loops apertados ou funções chamadas com frequência, você pode descer para assembly Yul para otimizar manualmente além do que o compilador Solidity consegue alcançar. Assembly te dá controle direto sobre gerenciamento de memory, permite pular verificações de segurança e elimina overhead de abstração. No entanto, é uma faca de dois gumes, assembly ignora todos os recursos de segurança do Solidity, tornando o código mais difícil de ler e extremamente sujeito a erros.
Quando considerar assembly: operações de alta frequência, manipulação complexa de bits, layouts de memory customizados, ou loops que executam milhares de vezes, onde cada unidade de gas conta.
Quando evitar assembly: em qualquer outro lugar. A economia de gas raramente justifica o risco aumentado de bugs, vulnerabilidades de segurança e o ônus de manutenção.
Aqui está um exemplo de soma de um array usando assembly:
function sum(uint[] memory arr) public pure returns (uint s) {
assembly {
let len := mload(arr) // Load array length
let data := add(arr, 0x20) // Skip length, point to data
for { let i := 0 } lt(i, len) { i := add(i, 1) } {
s := add(s, mload(add(data, mul(i, 0x20)))) // Load and sum each element
}
}
}Essa versão em assembly economiza gas manipulando diretamente ponteiros de memory e pulando verificações de limites, mas é significativamente mais difícil de entender e auditar em comparação com código Solidity equivalente.
Práticas críticas de segurança
- Teste minuciosamente o código assembly com casos extremos
- Adicione comentários extensos explicando cada operação
- Faça auditoria das seções de assembly por especialistas em segurança
- Use assembly apenas como último recurso, depois de esgotar as otimizações do Solidity
Para a maioria dos desenvolvedores e casos de uso, as outras 11 técnicas deste guia oferecerão melhor economia de gas com muito menos risco. Só recorra a assembly depois de fazer o profiling do seu contrato, identificar gargalos específicos e confirmar que a economia de gas justifica a complexidade adicional.
Para aprender a sintaxe e as capacidades do Yul, veja a documentação do Yul.
Teste seu smart contract antes do deploy
Antes de fazer deploy na mainnet, teste rigorosamente o uso de gas usando ferramentas de desenvolvimento. O Hardhat oferece plugins de gas reporter que geram detalhamentos precisos dos custos de gas por função. O Remix IDE mostra estimativas de gas em tempo real enquanto você testa. Concentre seus esforços de otimização em funções voltadas ao usuário, como mints, transfers e swaps: essas são chamadas com mais frequência e têm o maior impacto na experiência do usuário.
Para testes mais aprofundados, use as capacidades de fuzzing do Foundry para fazer benchmark das suas otimizações em milhares de inputs randomizados, garantindo que sua economia de gas se sustente em condições reais e casos extremos.
Encerrando: comece a otimizar
Agora você tem 12 técnicas testadas na prática para tornar seus contratos Solidity mais enxutos e baratos de executar. Otimização de gas não é só sobre economizar dinheiro: é sobre construir experiências melhores para seus usuários e criar aplicações com as quais eles realmente querem interagir.
Comece implementando o que é mais fácil de ganhar: habilite o otimizador do compilador, use mappings em vez de arrays e marque valores fixos como constant ou immutable. Depois, faça o profiling dos seus contratos para encontrar gargalos e aplique as técnicas mais avançadas onde elas terão mais impacto.
Recursos para continuar aprendendo:
- Guias de Solidity da Alchemy
- Contratos padronizados da OpenZeppelin
- Guia de campo sobre segurança de smart contracts
Agora vá lá e comece a otimizar, seus usuários (e as carteiras deles) vão agradecer!
Perguntas frequentes
Quais são algumas das formas mais eficazes de reduzir custos de gas em smart contracts Solidity?
As técnicas mais impactantes incluem usar mappings em vez de arrays para buscas, habilitar o otimizador do compilador Solidity com configurações de runs apropriadas, marcar valores fixos como constant ou immutable, minimizar operações de storage, e usar calldata em vez de memory para parâmetros de função somente-leitura.
Como usar variáveis constant e immutable ajuda na otimização de gas?
Valores constant e immutable são incorporados diretamente no bytecode do contrato em vez de armazenados em storage slots caros, eliminando operações caras de SLOAD (2.100 gas cada) e substituindo-as por leituras baratas de bytecode.
Por que devo usar mappings em vez de arrays para buscas de dados?
Mappings oferecem buscas O(1) de tempo constante, independentemente do tamanho dos dados, enquanto arrays exigem iteração O(n) que se torna mais cara à medida que o array cresce. Mappings são significativamente mais baratos para padrões de acesso baseados em chave, como saldos de usuários ou registros de propriedade.
Como o otimizador do compilador Solidity funciona e qual configuração de runs devo usar?
O otimizador reduz gas simplificando expressões, removendo código morto e fazendo inline de funções. Use runs baixo (200) para contratos que você fará deploy com frequência, mas chamará raramente, e runs alto (10.000+) para contratos com alto volume de transações, onde a eficiência em runtime importa mais do que o custo de deploy.
Qual é a diferença entre usar calldata, memory e storage para otimização de gas?
Use calldata para parâmetros de função externa que você só lê (evita custos de cópia), memory para manipulação temporária de dados, e storage apenas quando precisar de estado persistente. calldata é sempre mais barato do que memory para operações somente-leitura.
Quando devo usar blocos de aritmética unchecked com segurança?
Use unchecked para operações onde overflow é matematicamente impossível, como contadores de loop com limites conhecidos ou operações aritméticas validadas. Isso economiza cerca de 30-40 gas por operação ao pular as verificações automáticas de overflow introduzidas no Solidity 0.8.0.
Como posso otimizar operações de storage para reduzir custos de gas?
Empacote múltiplas variáveis pequenas em storage slots únicos de 32 bytes, delete storage não utilizado para receber reembolsos de gas (4.800 gas por slot limpo), e minimize escritas em storage fazendo cache de valores em memory durante a execução da função.
Quais são os benefícios de usar o modificador de visibilidade external em vez de public?
Funções external são otimizadas especificamente para chamadas de fora do contrato e funcionam de forma eficiente com parâmetros calldata, economizando 20-50 gas por chamada em comparação com funções public, que precisam lidar tanto com chamadas internas quanto externas.
Visões gerais relacionadas
Solidity3 de dezembro de 2025
As 6 melhores IDEs de Solidity para desenvolvedores (2026)
Conheça as melhores IDEs para desenvolvimento em Solidity.
Solidity13 de novembro de 2025
12 boas práticas de segurança para smart contracts em Solidity
Proteja seus smart contracts com essas dicas e ferramentas de segurança de especialistas.
Solidity25 de setembro de 2023
O que é selfdestruct em Solidity?
O que é, como funciona e como começar

Construa magia blockchain
A Alchemy combina os produtos e ferramentas de desenvolvimento Web3 mais poderosos com recursos, comunidade e suporte lendário.