Você sabia que a EIP170 define um limite de 24kb de tamanho máximo por smart contract? Isso é devido a performance e também segurança, uma vez que contratos muito grandes poderiam ser usados para alguns ataques hipotéticos específicos. O fato é que essa limitação nos dá de 300 a 500 linhas de código por contrato o que pode ser o suficiente para use cases comuns, como tokens, coleções NFT e outros, mas que é deveras limitado para projetos maiores como dex (como Uniswap) e DAOs.
Então o que devemos fazer quando nosso contrato se torna maior que essa limitação? Abandonar o projeto? Claro que não!
No artigo de hoje eu vou te apresentar três alternativas que você pode usar para contornar esta limitação e permitir projetos de smart contracts do tamanho da sua necessidade (ou criatividade).
Vamos lá!

#1 – Use o otimizador do compilador
Essa dica eu já passei aqui no blog antes, em minha série sobre como economizar nas taxas dos smart contracts e consiste em utilizar o otimizador do compilador para diminuir o tamanho. O Solidity Compiler (solc) possui um parâmetro de otimização que por padrão vem desabilitado. Esse parâmetro, quando ligado, permite dizer ao compilador que otimize o seu código para taxas menores nas transações ou para tamanho menor no deploy. Mas é um ou outro. Ou deixa desligado. Isso não é sempre possível, mas geralmente provoca diferenças sim em contratos grandes e é possível graças às diferentes formas que o compilador pode combinar de opcodes para chegar no resultado que você deseja com seu algoritmo. Sabe o lance de usar i++ vs ++i, pois é, é esse tipo de otimização, mas de maneira automática.
Se estiver utilizando o Remix, essa opção pode ser habilitada no menu Solidity Compiler, seção Advanced Configurations, como mostra a imagem abaixo onde marquei o checkbox “Enable optimization”.
Se estiver usando o HardHat, esta configuração você pode habilitar no hardhat.config, sendo abaixo um exemplo com esta configuração nos valores default (se você não preencher, é dessa forma que vai estar):
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const config: HardhatUserConfig = { solidity: { version: "0.8.20", settings: { optimizer: { enabled: false, runs: 200 } } } }; |
Agora no momento que você setar o enabled do optimizer para true, a propriedade runs será usada para aumentar ou diminuir a otimização, sendo 200 o valor comum (“otimiza pouco”). Procurando na Internet encontrei o valor de 1000 para otimizar bastante focando em transações mais baratas, mas aumentando o custo de deploy e o valor de 20 para otimizar bastante para deploy barato, aumentando o custo de transações. Nos testes que fiz, o valor padrão de 200 foi um bom meio-termo, reduzindo o custo do deploy e às vezes das transações também.

#2 – Divida em diferentes contratos
Assim como nos projetos orientados a objetos nós dividimos o domínio da aplicação em diversas classes, em projetos de smart contracts podemos fazer o mesmo, dividindo o protocolos que estamos construindo em diversos contratos. Claro, não é uma regra 1×1 com a POO então não recomendo sair criando contratos como se fossem classes ou você terá outros problemas, mas sim que pense em modularizar a sua aplicação considerando responsabilidades. Comece simples, com apenas um contrato, e vá aumentando conforme ele for crescendo e for se tornando nítida a necessidade do “split”.
Por exemplo, em um projeto de protocolo de staking que tenha emita um token de stake para os participantes que investiram no projeto poderem sacar seu colateral mais tarde. Temos duas responsabilidades bem distintas aqui e que podem (ou até devem, sob ótica de arquitetura) se tornar contratos separados: stake e token. Caso você nunca tenha criado um contrato de token antes, eu já falei sobre o padrão mais usado, ERC20, em outro tutorial aqui do blog. Abaixo a interface usada para construção de projetos deste tipo:
1 2 3 4 5 6 7 8 9 10 11 12 |
interface IERC20 { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); function transfer(address recipient, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); } |
Uma vez que você faça o desenvolvimento e o deploy do seu projeto de token ERC20, você irá receber o endereço dele, certo? Tendo o endereço em mãos, você pode escrever o seu segundo contrato, de staking neste exemplo, usando a interface IERC20 acima e o endereço para se comunicar corretamente com ele. Eu falei mais sobre protocolo de staking em outro tutorial aqui do blog, mas segue um trecho de exemplo onde carrego o contrato de token ERC20 que já foi deployado e uso uma função dele no meu segundo contrato.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import "./IERC20.sol"; contract ProtocoloDefi { IERC20 public token; constructor(address tokenAddress){ token = IERC20(tokenAddress); } function liquidityPool() external view returns(uint) { return token.balanceOf(address(this)); } } |
E isso pode escalar em complexidade e número de contratos conforme necessário, veja abaixo um diagrama antigo, da Uniswap v2 para ter uma ideia de arquitetura mais profissional.
Reforço no entanto para não fazer esse tipo de abordagem a menos que estritamente necessário pois o custo de gás em transações envolvendo funções em contratos diferentes é de 21000 gás (opcode CALLCODE), enquanto que chamadas de funções no mesmo contrato custam apenas 8 gás (opcode JUMP)!!!
#3 – Crie e/ou use libraries
Essa dica é uma variação da dica anterior mas ao invés de quebrar seu contrato “grande” em contratos menores, você pode criar libraries (bibliotecas) auxiliares. Libraries são arquivos .sol que não são smart contracts, mas apenas “pedaços de código” reutilizáveis em um ou mais contratos. Funções, structs, etc mas sem estado e sem endereço próprio na blockchain. Assim, você pode remover funções mais genéricas do seu contrato, como funções de controle de acesso, de matemática ou mesmo estruturas de dados (structs) são fortes candidatos a estarem em libraries se estiver com problemas de espaço no contrato principal.
Se você já usou a biblioteca OpenZeppelin, que já falei aqui antes, já está se beneficiando desse recurso pois seu contrato não precisa repetir todos aqueles códigos que eles oferecem pronto pra gente. Independente disso, abaixo tem um exemplo de como criar e usar uma library, valendo salientar que o deploy é feito junto com o contrato que usa a biblioteca, então não há uma economia real aqui no deploy.
1 2 3 4 5 6 7 8 |
library JKPLibrary { struct Player { address wallet; uint32 wins; } } |
E abaixo um exemplo de uso da struct presente na library anterior:
1 2 3 4 5 6 7 8 |
import "./JKPLibrary.sol"; contract JoKenPo { JKPLibrary.Player[] public players; //... |
Agora sobre o deploy, primeiro você faz o deploy da sua lib normalmente, como se ela fosse um contrato. Depois, quando fizer o deploy do contrato que usa ela, deve linká-la dessa forma.
1 2 3 4 5 6 7 8 |
const JoKenPo = await ethers.getContractFactory("JoKenPo", { libraries: { JKPLibrary: "0x...", }, }); const myContract = await JoKenPo.deploy(); |
E com isso finalizo este artigo e espero ter lhe entregado três ferramentas que sejam úteis para você diminuir o tamanho dos seus smart contract Solidity caso necessário.
Até a próxima!

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