Guia Definitivo de Smart Contracts para Devs
O que é um smart contract e como ele roda na EVM
Um smart contract é um programa autoexecutável que roda numa blockchain, com código e estado armazenados de forma imutável após o deploy. Ele não depende de um servidor central: a lógica é executada por milhares de nós que validam o resultado de forma determinística.
Na Ethereum, esse ambiente de execução é a EVM (Ethereum Virtual Machine). Todo contrato escrito em Solidity é compilado para bytecode, uma sequência de opcodes que a EVM interpreta instrução por instrução. Cada opcode tem um custo fixo em gas, e é esse custo que conecta a EVM ao terceiro conceito central deste guia: as gas fees. Quanto mais opcodes um contrato executa, mais gas ele consome — e é o pagamento desse gas que remunera os validadores da rede.
A EVM distingue dois tipos de conta. EOAs (Externally Owned Accounts) são controladas por chaves privadas — a carteira de um usuário, por exemplo. Contas de contrato são controladas pelo próprio código armazenado nelas, sem chave privada associada. Uma EOA pode iniciar uma transação; um contrato só executa código em resposta a uma chamada, seja de uma EOA, seja de outro contrato.
A especificação formal desse comportamento está descrita no Yellow Paper e na documentação oficial da Ethereum, que é a referência primária para qualquer dúvida sobre como a EVM processa opcodes e gas.
Solidity: sintaxe, tipos e estrutura de um contrato
Solidity é a linguagem dominante para escrever smart contracts na EVM. Sua sintaxe lembra JavaScript e C++, mas com tipagem estática e conceitos específicos de blockchain, como payable, msg.sender e modifiers de visibilidade.
Um contrato Solidity é organizado em variáveis de estado (persistidas on-chain), funções e modifiers. Cada função declara uma visibilidade — public, external, internal ou private — que determina quem pode chamá-la, e uma mutabilidade de estado — view, pure ou nenhuma — que indica se ela lê, não toca ou altera o armazenamento.
Veja um contrato de armazenamento simples:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SimpleStorage {
uint256 private value;
address public owner;
modifier onlyOwner() {
require(msg.sender == owner, "Somente o owner pode alterar");
_;
}
constructor() {
owner = msg.sender;
}
function setValue(uint256 _value) external onlyOwner {
value = _value;
}
function getValue() external view returns (uint256) {
return value;
}
}
Note o modifier onlyOwner: ele encapsula uma verificação de permissão reutilizável em várias funções, um padrão comum em contratos que gerenciam recursos sensíveis.
Vale mencionar que Solidity não é a única opção. Vyper é uma alternativa com sintaxe inspirada em Python, projetada com um conjunto de features intencionalmente mais restrito para reduzir superfície de bugs. Ela tem menos adoção de mercado e menos bibliotecas de terceiros, mas aparece em projetos que priorizam auditabilidade acima de flexibilidade.
Gas fees: como funcionam e como otimizar
Toda transação na Ethereum consome gas, uma unidade que mede o custo computacional de cada operação. O custo final em ETH depende de três variáveis: o gas usado (quantidade de opcodes executados), o gas price (quanto se paga por unidade de gas) e o gas limit (o teto que o remetente está disposto a pagar antes da transação reverter por falta de gas).
Desde a EIP-1559, o modelo de precificação mudou: cada bloco tem uma base fee calculada algoritmicamente pelo protocolo, que é queimada (removida de circulação), mais uma priority fee opcional paga ao validador como incentivo para priorizar a transação. Antes da EIP-1559, o modelo era um leilão simples de gas price, o que gerava picos de preço mais imprevisíveis. Os detalhes completos estão na especificação oficial da EIP-1559.
Um exemplo hipotético para entender a conta: suponha uma transação que consome 21.000 unidades de gas (o custo mínimo de uma transferência simples), com base fee de 20 gwei e priority fee de 2 gwei. O custo total seria 21.000 × 22 gwei = 462.000 gwei, o equivalente a 0,000462 ETH. Esse número é só ilustrativo — o valor real da base fee varia bloco a bloco conforme a demanda da rede.
Práticas comuns de otimização de gas em Solidity:
- Usar tipos
uint256em vez de tipos menores quando não há necessidade de empacotamento de struct, já que a EVM opera nativamente em palavras de 256 bits. - Evitar loops não limitados sobre arrays que crescem indefinidamente — cada iteração adiciona gas, e um array grande pode tornar a função impossível de executar dentro do block gas limit.
- Marcar variáveis que nunca mudam como
immutableouconstant, o que evita leitura de storage a cada chamada. - Emitir eventos em vez de armazenar dados que só precisam ser consultados off-chain.
Padrões de segurança e vulnerabilidades comuns
A vulnerabilidade mais citada em smart contracts é reentrancy: um contrato externo malicioso chama de volta a função que o invocou antes que ela termine de atualizar seu próprio estado, permitindo drenar fundos repetidamente. O caso histórico mais conhecido é o hack do The DAO em 2016, que motivou o hard fork da rede Ethereum.
A mitigação padrão é o padrão checks-effects-interactions: validar condições primeiro, depois atualizar o estado interno, e só então fazer chamadas externas. Assim, mesmo que o contrato externo tente reentrar, o estado já reflete a operação concluída.
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount, "Saldo insuficiente");
balances[msg.sender] -= amount; // effect antes da interaction
(bool sent, ) = msg.sender.call{value: amount}("");
require(sent, "Falha no envio");
}
Outra classe clássica de bug é overflow/underflow aritmético — quando um número ultrapassa os limites do seu tipo e "vira" silenciosamente. Desde a versão 0.8.x, o compilador Solidity reverte automaticamente essas operações por padrão, o que eliminou a necessidade de bibliotecas como SafeMath na maioria dos casos novos. Os detalhes estão na documentação oficial de checked arithmetic do Solidity.
Para não reinventar padrões já testados, a referência de mercado é a biblioteca OpenZeppelin Contracts, que oferece implementações auditadas de controle de acesso, tokens ERC-20/ERC-721, proxies upgradeáveis e proteção contra reentrancy (ReentrancyGuard).
Nenhum desses padrões substitui uma auditoria de segurança feita por terceiros antes do deploy em mainnet. Um contrato imutável com um bug em produção não tem "hotfix" — a correção normalmente exige migrar todo o estado para um novo contrato, o que é caro e arriscado.
Se o seu produto envolve lógica de negócio crítica rodando on-chain, vale conversar com um time que já lida com arquitetura Web3 de ponta a ponta — conheça os produtos da Hack Tech Farm.
Testando e fazendo deploy de um smart contract
Os dois frameworks mais usados hoje para desenvolvimento Solidity são Hardhat e Foundry. Hardhat é baseado em JavaScript/TypeScript, tem grande ecossistema de plugins e integra bem com scripts de deploy e testes em Mocha/Chai. Foundry é escrito em Rust, roda testes diretamente em Solidity e é notavelmente mais rápido em suítes grandes, além de ter ferramentas nativas de fuzzing.
Independentemente do framework, o fluxo recomendado é sempre o mesmo: escrever testes unitários cobrindo casos de sucesso e de falha, fazer deploy numa testnet pública (como Sepolia), validar o comportamento com transações reais em ambiente sem valor financeiro, e só depois migrar para mainnet.
Um script de deploy simples com Hardhat:
const hre = require("hardhat");
async function main() {
const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage");
const contract = await SimpleStorage.deploy();
await contract.waitForDeployment();
console.log("Contrato deployado em:", await contract.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Após o deploy, é boa prática verificar o contrato no block explorer correspondente (Etherscan, na rede Ethereum). A verificação publica o código-fonte associado ao bytecode deployado, permitindo que qualquer pessoa audite o que está rodando naquele endereço — sem isso, o contrato aparece como bytecode opaco para quem não confia cegamente no time que o publicou.
No caso da NeuroArt DApp optamos pela Base, layer 2 da Ethereum, exatamente pelo custo, "gas fee".
Smart contracts vs backend tradicional: quando usar cada um
Smart contracts não substituem toda a lógica de um sistema — eles resolvem um problema específico: execução verificável e imutável de regras que múltiplas partes não confiam entre si. Para tudo que não exige essa garantia, um backend tradicional é mais barato, mais rápido de iterar e mais fácil de corrigir.
| Critério | Smart contract (EVM) | API REST tradicional |
|---|---|---|
| Mutabilidade do código | Imutável após deploy (salvo padrões de proxy) | Deploy contínuo, rollback fácil |
| Custo por operação | Gas fee variável por transação | Custo de infraestrutura fixo/mensal |
| Confiança exigida | Execução verificável por terceiros, sem intermediário | Depende da confiança no operador do backend |
| Velocidade de iteração | Lenta — cada mudança é um novo deploy e migração de estado | Rápida — deploy contínuo |
| Transparência de dados | Estado público e auditável on-chain | Dados privados, controlados pela empresa |
A imutabilidade é ao mesmo tempo a maior vantagem e o maior custo de um smart contract. Ela garante que ninguém — nem os próprios desenvolvedores — pode alterar unilateralmente as regras depois do deploy. Mas isso também significa que qualquer bug de lógica vira permanente, e qualquer mudança de requisito de negócio exige um processo de migração cuidadoso.
Para a maioria dos SaaS, essa rigidez não compensa. Se o seu produto não precisa de execução sem confiança entre partes desconhecidas — por exemplo, um sistema de assinaturas, um CRM ou um painel administrativo — um backend tradicional resolve com muito menos custo operacional e mais flexibilidade. O nosso guia definitivo de arquitetura SaaS em 2026 detalha como estruturar esse tipo de sistema sem depender de blockchain.
Custos e manutenção de longo prazo
O custo de um smart contract não termina no deploy. Além do gas pago na publicação inicial — que tende a ser mais caro quanto maior e mais complexo o bytecode —, há custo recorrente sempre que o contrato precisa ser chamado, e custo de monitoramento contínuo para detectar comportamento anômalo.
Comparar isso ao custo de manter um SaaS tradicional ajuda a calibrar expectativas: no modelo Web2, o custo de infraestrutura é previsível e escala com uso, como discutimos em quanto custa manter um SaaS pequeno na Vercel. No modelo Web3, o custo é por transação e varia com a congestão da rede — um pico de demanda pode encarecer uma operação em múltiplos de seu custo normal, sem aviso prévio.
Para contornar a rigidez da imutabilidade, existem padrões de upgradability, como o proxy pattern: o usuário interage com um contrato proxy fixo, que delega a lógica para um contrato de implementação que pode ser trocado pelo dono do sistema. Isso resolve o problema de correção de bugs, mas introduz um novo vetor de risco — quem controla a chave que autoriza a troca de implementação tem, na prática, poder de alterar as regras do sistema, o que contraria parcialmente a promessa de imutabilidade que motivou o uso de blockchain em primeiro lugar.
Depois do deploy, um contrato em produção ainda exige monitoramento: acompanhar eventos emitidos, alertar sobre chamadas incomuns e observar métricas de uso, algo estruturalmente parecido com observabilidade de backend, mas com ferramentas específicas do ecossistema Web3.
Continue neste cluster
- Auditoria de Segurança em DApps: Checklist Prático — Checklist prático de auditoria de segurança para DApps: reentrancy, controle de acesso, oráculos, gas, upgradability e ferramentas de análise estática.
Perguntas Frequentes
O que diferencia um smart contract de um programa comum?
Um smart contract roda em uma blockchain, é executado de forma determinística por múltiplos nós independentes e, uma vez deployado, seu código normalmente não pode ser alterado. Um programa comum roda num servidor controlado por uma única parte, que pode alterá-lo a qualquer momento.
Preciso saber Solidity para trabalhar com Web3?
Não necessariamente. É possível construir front-ends e integrações Web3 usando apenas bibliotecas como ethers.js ou viem para interagir com contratos já existentes. Solidity é necessária apenas para quem vai escrever ou auditar a lógica do próprio contrato.
Como funciona o pagamento de gas fees em uma transação?
O remetente define um gas limit e paga com base na base fee do bloco mais uma priority fee opcional, conforme o modelo definido pela EIP-1559. O valor total é gas usado multiplicado pelo preço por unidade de gas.
Smart contracts podem ser alterados depois do deploy?
Por padrão, não — o bytecode publicado é imutável. É possível contornar isso com padrões de proxy que separam a interface fixa da lógica, permitindo trocar a implementação, mas isso exige confiar em quem controla a chave de upgrade.
Vale a pena migrar um SaaS tradicional para smart contracts?
Só se o produto exigir execução verificável entre partes que não confiam umas nas outras, como custódia de ativos ou governança descentralizada. Para a maioria dos SaaS, um backend tradicional é mais barato e flexível.