Explorando um Smart Contract Pool de Liquidez
Conforme abordado na nossa série de artigos sobre Finanças Descentralizadas, carinhosamente denominadas “DeFis”, falamos de algumas possibilidades de ganhos passivos com suas criptomoedas, tipo uma poupança descentralizadas, onde deixamos nossos ativos “bloqueados” enquanto recebemos uma recompensa, sendo essas recompensas definidas por cada um dos protocolos.
Diferente da poupança que estamos acostumados, no mundo DeFi o bloqueio de ativos são fundamentais para a existência e descentralização dos protocolos, e são essenciais para a manutenção e escalabilidade do ecossistema.
Sendo suas principais funções ser um provedor de liquidez em uma Dex ou contribuindo para o mecanismos de consenso, Proof-of-Stake, bloqueando suas moedas em um Staking Pool. Ambas as formas nos trazem ganhos passivos enquanto deixamos nossas moedas bloqueadas, legal né, mas como isso funciona na prática?
Nessa série de artigos vamos explorar os smart contract da Uniswap, vamos mostrar como o código que viabiliza e assegura o stake em um pool de liquidez, como também a criação do pool é a importância das taxas em um protocolo descentralizado, ao final dessa série de artigos vocês vão entender o por que acreditamos tanto nesse modelo de finanças descentralizados, então vem com a gente nessa jornada!
E no caso de ainda não conhecer a Uniswap, recomendo a leitura desse artigo: O que é Uniswap?
Interface de um Pool de Liquidez
Quando falamos de um pool de liquidez é assim que ele se apresenta aos usuários da rede, vamos introduzir alguns conceitos da interface do pool para ajudar no entendimento do código por trás deles. Os números na imagem correspondem a legenda:
- Par de Tokens: WBTC/ETH onde 1 WBTC = 12.0933 ETH e 1ETH = 0.0827 WBTC (no momento do screenshot).
- Total de tokens bloqueados no contrato: lembrando que a proporção entre as moedas alocadas é sempre 50/50.
- TVL: Valor total bloqueado em USD
- 24h Fees: Total pago em taxas nas últimas 24h em USD
- Últimas transações realizadas: nesse caso as transações apresentadas são relativas aos swaps, estilo uma casa de câmbio online descentralizada, onde a disponibilidade das moedas é promovida pelos usuários que bloquearam o fundo neste pool de liquidez.

ScreenShot: Par de Liquidez WBTC/ETH na uniswap.
Essa é a interface que normalmente o usuário irá interagir, agora vamos entrar em uma camada abaixo, vamos ver como essas funções são escritas no smart contract.
Termos principais do contrato
Para auxiliar a nossa jornada, segue alguns termos que vamos falar nesse artigo. A linguagem de programação utilizada no contrato é a Solidity, não vamos entrar em detalhe de cada função apenas na funcionalidade de cada estrutura do contrato.
Pool: O endereço ao qual as moedas ficarão bloqueadas, para promover liquidez.
- Ticks: esse conceito é um pouco abstrato de se entender, mas vamos imaginar que o preço do ativo é uma linha e essa linha foi fracionada em ticks. Onde dentro desse pequeno pedaço de ticks, temos uma área discreta do preço do ativo e o tick representa sua barreira. Lembrando um pouco do conceito matemático para discreto, o nome refere-se a funções ao qual sua imagem não varia de forma gradual. Podemos dizer que o acréscimo/decréscimo de 1 Tick representa 0.0001% acréscimo/decréscimo no preço em qualquer ponto da nossa linha de preço, complexo não. No contrato, assume-se que preço é sempre expresso como o preço do token0. Resumindo, o Tick representa preços em que a liquidez virtual do contrato pode mudar.
- Initialized Ticks: Quando liquidez é adicionada no Pool, se os ticks que representam os atuais limites superior e inferior não contém o tick da posição de liquidez adicionado, dizemos que o tick é inicializado. Quando o preço do ativo cruza um initialized tick, tem-se que ajustar a liquidez virtual do pool, tirando ou colocando ticks.
- Tick Interval: O espaço de preço entre dois ticks adjacentes.
- TickSpacing: Parâmetro definido no contrato, apenas Ticks com index divisíveis pelo TickSpacing podem ser inicializados, ex: TickSpacing = 2, apenas ticks (…-4,2,0, 2, 4…) podem ser inicializados. Como o valor da transação é função dos Ticks, quanto menor o Tickspacing maior a taxa pela transação efetuada.
Para quem ficou curioso sobre a matemática por trás do tick, o white paper da uniswap explica detalhadamente na seção 6.1, um pouco abstrata essas informações mas elas são fundamentais para o entendimento do valor dos ativos e taxas no pool da Uniswap.
- Liquidez concentrada: O conceito de Tick possibilitou a criação de diferentes ranges de pool de liquidez, ou seja, o provedor de liquidez pode escolher em qual “range” ele que bloquear seu par de ativos e prover a liquidez, única restrição é prover liquidez suficiente para suportar o trade. Os provedores de liquidez podem prover quantas posições desejarem e com isso pode-se criar pool de liquidez em todo o range de preço do par.
Taxas Flexíveis: Taxas para o Swap (trocas) de ativos, ao longo do artigo vamos ver que essas taxas serão definidas na construção do contrato sendo que inicialmente já são suportadas as taxas de 0,05%, 0.3% e 1%. A governança da Uni tem a flexibilidade de incluir outras frações de taxas para o swap que serão coletadas pelo protocolo.

Screnshot Uniswap V3 White PAper: Representação gráfica Liquidez concentrada
Smart Contract - Uniswap V3
Vamos utilizar o contrato da Uniswap, sendo este composto de contratos e bibliotecas, onde estão separados em duas categorias: Core e Periférico. O diagrama de bolha abaixo representa visualmente como esses contratos estão associados, onde o core aparece nas cores azuis, os contratos de interface em roxo e as bibliotecas em amarelo.
Contratos denominados como Core garantem a segurança entre todas as partes que interagem com a Uniswap, onde eles definem a lógica por trás da criação dos Pools e também a lógica por trás de toda interação envolvendo os ativos dentro do pool. Contratos Core: UniswapV3Factory, NodelegateCall, UniswapV3Pool e o UniswapV3PoolDeployer.
Os contratos Periféricos é uma constelação de smart contracts desenhados para suportar e interagir com os contratos core. Como vamos ver adiante, existem diversos contratos periféricos para auxiliar na criação dos pool de liquidez. Como também existem as bibliotecas periféricas, que provém diversas funções para auxiliar o desenvolvedor na criação dos pools, como funções de segurança para transações, cálculo de recompensas, entre outras mais.

Neste primeiro artigo vamos focar nos contratos UniswapV3Factory e no Contrato NoDelegateCall
UniswapV3Factory.sol
O papel principal do contrato Factory é facilitar a criação do Pool, onde o pool é definido por dois tokens, o que virá o par de ativos, e as taxas. Podem existir mais de um pool para o mesmo par de ativos, onde a única diferença será a taxa de troca dos ativos, SwapFee.
Na programação em Solidity, após termos nosso smart contract disponível na rede esse torna-se imutável, isso quer dizer que ele não poderá sofrer modificações e/ou ser melhorado. O código implementado no seu smart contract estará permanentemente, para sempre, na blockchain.
Por isso, em solidity a segurança é uma preocupação central, por que estou trazendo essa informação? Para introduzir a importância das dependências externas.
Nas primeiras linhas do contrato Factory, vemos a importação dos smart contracts de interface e core, conforme representado no nosso diagrama de bolha.
Para ilustrar a importância dessas dependências externas, imagine a situação em que temos um bug no contrato IUniswapV3factory, se esse contrato ao invés de ter sido importado como interface fosse programado diretamente dentro do smart contract V3factory, qualquer bug nele irá inutilizar o uso do contrato core UniswapV3Factory, e consequentemente inutiliza a construção de novos pools na Uniswap. Por essa razão, é importante termos algumas ferramentas que permitam atualizar partes chaves dos nossos smarts contract core.
No caso de contratos muito grandes, uma maneira de organizar a programação é fazendo diferentes contratos com responsabilidades específicas, e utilizar a função “import” para importar um contrato dentro do outro.

Outro recurso utilizado na assinatura do contrato é a herança, onde o contrato UniswapV3factory “herda” os contratos IUniswapV3factory, V3poColDeployer, NodelegateCall. Isso nos informa que ao colocarmos na rede o contrato V3factory, compile & deploy, todos os demais contratos herdados serão acessados também. Podendo ser a herança utilizada como uma forma de organizar o smart contract e/ou como ferramenta para definir contratos de subclasses, por exemplo, Gato é um Animal (Cat is an Animal).

A função constructor é responsável por iniciar nosso contrato e atribuir os valores iniciais a algumas variáveis, nesse caso estamos definindo o valor para feeAmountTickSpacing, no caso de dúvidas sobre as nomenclaturas e variáveis checar o início do artigo. Lembra que na seção que explicamos sobre os termos, falamos das taxas flexíveis, pois então, é assim que ela é declarada no código, linha 23 a 28, respectivamente as taxas de swap 0,05%, 0,3% e 1%.
Dentro dessa função, vemos também um teste, onde avaliasse se o “owner” do contrato é igual ao endereço que está fazendo o deploy, no caso contrário, ele chama uma função de OwnerChanged que vamos explorar mais no contrato IUniswapV3factory.
Lembrando que o conceito de “owner” para protocolos descentralizados está ligado a governança e não a um único dono, como estamos acostumados.

A primeira linha desse bloco de código já nos indica que essa função é herdada do IUniswapV3Factory, o “//” possibilita ao desenvolver deixar comentários no código para auxiliar o entendimento de terceiros, nós no caso! A função denominada como createPool é responsável por criar o Pool, no código vemos que para execução os tokens necessitam ser diferentes, vemos também a declaração dos Token0 / Token1 usando como entrada o token A / Token B.
Nesse código também aparece o conceito de tickSpacking, que como falado anteriormente, apresenta relação direta com o valor das taxas que serão pagas pelos swaps dos ativos e futuramente serão as dívidas entre os provedor de liquidez do pool. Se voltarmos à função constructor, vemos a declaração dos valores mas até o momento não sabíamos a qual variável esse valor estava associado, na linha 40 vemos int24 tickSpacing = feeAmountTickSpacing[fee]; Portanto, no constructor declaramos os valores para int24 tickSpacking e para o fee.
Lembra que no início deste capítulo falamos que o pool é definido pelo par de tokens e as taxas, na linha 43 vemos isso escrito no código.
Ao final dessa função emitirá os token0, token1, fee, tickSpacing e o pool, ou seja, temos nosso pool criado!!! Se voltarmos no screenshot da Pool WBTC/ETH essa função representa o item 1

A última função do contrato do Factory, mas não menos importante, é a enableFeeAmount. Essa função é responsável por habilitar o valor da taxa de swap entre os ativos do Pool, conforme citado anteriormente após habilitada o valor da taxa, esse não pode ser alterado sendo esse valor correlacionada diretamente ao tickSpacing, lembrando que dentro da função construct definimos os valores para o fee e para o tickSpacing, sendo essas duas variáveis correlacionadas, exemplo quando o fee é 500 o tickSpacing é 10, a taxa do swap seria 0,05%.

NoDelegateCall.sol
O smart contract NoDelegateCall tem como função principal evitar a utilização delegatecall em algum contrato interligado aos contratos core, antes de seguirmos, precisamos entender o que significa delegatecall.
Quando utilizado “delegatecall” em um contrato dizemos que o contexto do contrato target é igual ao contexto do contrato que está “chamando” o delegatecall, vamos nomear essa contrato como caller. E quando dizemos que os contratos operam com base no mesmo contexto, isso abre uma porta para o contrato target acessar o storage e o balanço do contrato “caller”, se você ainda não sabe como funciona o conceito de storage em solidity, recomendo ler nosso artigo sobre EVM.
Você deve estar se perguntando o que tudo isso significa? Isso significa que quando temos um contrato target e um contrato caller, chamando a função delegatecall as variáveis associadas a ele, quando alteradas no contrato target serão salvas (storage) no contrato caller, ou seja, vai subscrever(overriding) o nosso contrato caller, resumindo, o contrato target consegue alterar as variáveis do nosso contrato caller.
Na EVM as variáveis são associadas a slots e a ordem dos slots é dada pela ordem das variáveis definidas no contrato, quando temos a alteração de uma variável, a EVM olha para os slot e ler/escrever a data neles.
No caso do delegatecall, o contrato target acessa os mesmo storage slots do contrato caller, sendo que a ordem das variáveis do contrato target define a ordem em que as variáveis serão salvas no storage.

screenshot: Blockchain-academy DelegateCall()
Existe um risco inerente ao utilizar DelegateCall em um smart contract, já que isso abre acesso para o contrato target subscrever o contrato caller podendo até mesmo “sequestrar” o contrato caller, alterando o owner do contrato.
Agora que estamos munidos com essa informação, voltamos ao NoDelagateCall, esse contrato tem como função prevenir a utilização do delegatecall para um contrato target.
A declaração do endereço do contrato como immutable o computa no init code do contrato, sendo que variáveis immutable só podem ser declaradas uma única vez no construct, linha 8 a 10, do smart contract, e a partir do ponto em que foram declaradas essa variável pode ser lida mesmo durante a construção do contrato.
A última função que vemos nesse contrato é a modifier, onde aqui ela aparece para prevenir que delegatecall seja utilizando através do modified method.
No fim esse smart contract existe para assegurar a integridade dos contratos Core, conforme falamos antes a segurança dos smart contracts é um dos pilares centrais do desenvolvimento, já que uma vez que o contrato foi disponibilizado na rede (deploy), não podemos alterá-lo.

Conclusão
Tivemos uma longa jornada até aqui, aos que chegaram ao fim: Parabéns! Introduzimos conceitos fundamentais na criação do pool de liquidez, conceitos exclusivos do pool da uniswap, como a divisão entre contratos cores e periféricos, e também conceitos importantes como “ticks”.
Exploramos o smart contract Factory, que apresenta o papel principal para a construção de novos pools de liquidez, vimos como são definidos os valores das taxas de swap conforme o ticks e vimos o código para criação do Pool, ou seja, o endereço de ETH onde o par de tokens de ativos ficará alocado e todas os swaps referentes ao par de ativos ocorrerão.
Falamos também sobre o contrato NodelegateCall, sobre sua importância para a segurança do contrato, e também aproveitamos para revisar alguns conceitos de storage e slots na EVM.
No próximo artigo vamos falar sobre o Smart Contract UniswapV3pool, sendo o seu papel principal servir como o automated market makers (AMM), ou seja, tem a função de ser o “livro de ordens” para qualquer transação do par de ativos, além disso expõem os dados de preço dos ativos do pool e também auxilia nas transações de flash (flash swap) dos ativos.
Caso tenha interesse em se aprofundar ainda mais nesse assunto, não deixe de ler nossos outros artigos. Também nos siga em nossas redes sociais que estão no rodapé do site.
- Whitepaper – Uniswap:
- /UniswapV3Factory.sol
https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Factory.sol
- Documentos UniSwap:
- docs.soliditylang definição immutable:
https://docs.soliditylang.org/en/latest/contracts.html?highlight=immutable#immutable