Explicando sua funcionalidade através do agrupamento de linhas de código
Você provavelmente conhece a fórmula do produto constante (x*y=k) que alimenta a Uniswap. Mas como o contrato inteligente Uniswap realmente funciona nos bastidores?
Neste artigo, vamos entender como a Uniswap é implementada, desmembrando seu contrato inteligente. Vamos examinar algumas centenas de linhas de código Solidity que geram $1.28 bilhão em receita diariamente.
Alerta de spoiler: você verá um código Solidity muito eficiente, elegante e seguro à frente.
Aqui está o esboço deste artigo:
- Como a Uniswap funciona em alto nível
- Como o código Uniswap é organizado
- Funcionalidades Uniswap
- Contratos principais: Par (difícil)
- Contratos principais: Fábrica (fácil)
- Contrato periférico: Roteador (fácil)
- Código totalmente anotado
Sumário
1 . Como a Uniswap funciona em alto nível
2 . Uma nota sobre as versões Uniswap
3 . Como o código Uniswap é organizado
5 . Contratos Principais
..... . Par (difícil)
Como a Uniswap funciona em alto nível
Todo o propósito da Uniswap é permitir que você troque um token ERC20 por outro. Por exemplo, você precisa de Dogecoin, mas só tem Shiba. A Uniswap permite que você venda Shiba e receba Dogecoin em troca. Tudo isso é feito de forma automática e descentralizada. Uniswap é apenas uma corretora descentralizada.
As exchanges podem ser implementadas de duas maneiras.
Modelo de livro de ordem: Compradores e vendedores arquivam ordens. E o sistema centralizado combina as ordens de compra com as ordens de venda. É assim que funciona a bolsa de valores tradicional.
Criadores de mercado automatizados (AMM): Não há um intermediário de combinação centralizado. Existem pessoas que fornecem os dois tokens (Dogecoin e Shiba). Estas pessoas são chamadas de provedoras de liquidez. Estes provedores de liquidez criam uma pool de tokens Dogecoin e Shiba. Agora, os negociantes podem depositar Dogecoin e receber Shiba em troca. Isso é feito automaticamente, sem uma entidade centralizada. Os negociantes pagam uma pequena taxa percentual pela negociação, que vai para os provedores de liquidez por seus serviços.
Uniswap usa a técnica AMM. Como ela determina a taxa de câmbio em uma pool? Ou seja, quantos tokens Shiba valem 1 Dogecoin? Isso é determinado pela fórmula do produto constante (quantidade de Dogecoin
)*(quantidade
de
Shiba
)=k
. Durante as negociações, este produto deve permanecer constante.
De “ Uniswap – uma corretora única ”
Não se preocupe se isto não estiver completamente claro. Aprenderemos mais sobre esta fórmula e dinâmica de mercado ao longo do restante do artigo.
Uma nota sobre as versões Uniswap
A Uniswap tem 3 versões.
- Vamos trabalhar com a v2.
- A v1 é muito simples e não tem todas as características modernas.
- A v3 é essencialmente a v2, mas melhorada e otimizada - seu código é muito mais complicado do que a v2.
Confira meu artigo, se você quiser saber mais sobre as diferenças entre as versões da Uniswap.
Como o código Uniswap é organizado
A Uniswap tem 4 contratos inteligentes no total. Eles estão divididos em principal e periférico.
- O principal é para armazenar os fundos (os tokens) e expor funções para trocar tokens, adicionar fundos, obter recompensas, etc.
- O periférico é para interagir com o principal.
O principal consiste nos seguintes contratos inteligentes:
- Par - um contrato inteligente que implementa a funcionalidade de troca, cunhagem, queima de tokens. Este contrato é criado para cada par de troca como Dogecoin ↔ Shiba.
- Fábrica - cria e mantém o controle de todos os contratos de Pares
- ERC20 - para manter o controle da propriedade da pool. Pense na pool como uma propriedade. Quando os provedores de liquidez fornecem fundos para a pool, eles recebem "tokens de propriedade da pool" em troca. Esses tokens de propriedade ganham recompensas (dos negociantes pagando uma pequena porcentagem por cada negociação). Quando os provedores de liquidez querem seus fundos de volta, eles apenas enviam os tokens de propriedade de volta e recebem seus fundos + as recompensas que foram acumuladas. O contrato ERC20 mantém o registro dos tokens de propriedade.
O Periférico consiste de apenas um contrato inteligente:
- O Roteador é para interagir com o núcleo. Fornece funções como
swapExactETHForTokens
,swapETHForExactTokens
, etc.
Funcionalidade Uniswap
Falamos sobre os 4 contratos inteligentes que a Uniswap tem e como eles são organizados. Mas qual é a principal funcionalidade que estes contratos implementam? A principal funcionalidade é a seguinte:
- Administração dos fundos (como tokens tais como Dogecoin e Shiba são administrados na pool)
- Funções para provedores de liquidez - depositar mais fundos e retirar os fundos junto com as recompensas
- Funções para negociantes - troca
- Gestão de tokens de propriedade da pool
- Taxa de protocolo - A Uniswap v2 introduziu uma taxa de protocolo alternável. Esta taxa de protocolo vai para a equipe da Uniswap por seus esforços na manutenção do protocolo. No momento, esta taxa de protocolo está desligada, mas pode ser ligada no futuro. Quando estiver ligada, os negociantes ainda pagarão a mesma taxa de negociação, mas 1/6 desta taxa agora irá para a equipe da Uniswap e o restante 5/6 irá para os provedores de liquidez como recompensa pelo fornecimento de seus fundos.
Além da principal funcionalidade descrita acima, a Uniswap tem outra que não é essencial, mas é uma ajuda útil para outros contratos no ecossistema Ethereum:
- Oráculo de preço - A Uniswap rastreia os preços dos tokens em relação uns aos outros e pode ser usado como um oráculo de preço para outros contratos inteligentes no ecossistema Ethereum. Devido à arbitragem (que aprenderemos mais adiante no artigo), os preços Uniswap tendem a seguir de perto os preços reais de mercado dos tokens. Portanto, o oráculo de preços Uniswap é uma boa aproximação dos preços reais de mercado.
Estas etiquetas serão utilizadas em todo o código no resto do artigo
Contratos Principais
Par
Vamos agora investigar o código Solidity dos contratos inteligentes da Uniswap. Vamos começar com o contrato Par. Este é o mais complexo dos 4 contratos inteligentes. O resto vai ficar mais fácil.
O contrato Par implementa a troca entre um par de tokens, como Dogecoin e Shiba. O código completo do contrato inteligente Par pode ser encontrado no Github, em v2-core/contracts/UniswapV2Pair.sol
Vamos desmembrá-lo linha por linha.
Em primeiro lugar, as declarações de importação:
Em seguida, a declaração de contrato:
- O nome do contrato é
UniswapV2Pair
- Ele implementa a interface
IUniswapV2Pair
, que é apenas uma interface para este contrato (pode ser encontrada aqui). Também estende o contratoUniswapV2ERC20
. Por quê? Para administrar os tokens de propriedade da pool. Saberemos mais sobre isso mais tarde. -
SafeMath
é uma biblioteca para lidar com overflow/underflow.UQ112x112
é uma biblioteca para suportar números flutuantes. O solidity não suporta números flutuantes por padrão. Esta biblioteca representa os flutuadores usando 224 bits. Os primeiros 112 bits são para os números inteiros e os últimos 112 bits são para a parte fracionária.
A seguir, agruparemos o código pela funcionalidade que ele implementa.
Gerenciando os fundos
Um par Uniswap é uma troca entre um par de tokens como Dogecoin e Shiba. Estes tokens são representados como token0
e token1
no contrato. Eles são os endereços dos contratos inteligentes ERC20 que os implementam.
As variáveis reserve
armazenam quanto do token temos neste par.
O código é codificado por cores, de acordo com a etiqueta ("gerenciar fundos", neste caso)
Você pode se perguntar, onde está armazenado o verdadeiro token? Isto é feito no contrato ERC20 do próprio token. Não é feito no contrato Par. O contrato Par só mantém o controle das reservas. Da perspectiva do ERC20, o contrato Par é apenas um usuário regular que pode transferir e receber tokens, ele tem seu próprio saldo, etc.
É assim que os fundos são administrados através de 3 contratos inteligentes
O contrato Par chama as funções do ERC20, tais como balanceOf
(com owner =
Endereço do contrato Par
) e transfer
para gerenciar os tokens (veja o meu Detalhamento do Contrato Inteligente ERC20 se você estiver confuso). Aqui está um exemplo de como a função transfer
do ERC20 é usada no contrato Par.
O SELECTOR permite que você chame o contrato ERC-20 através de sua ABI
A função _update
abaixo é chamada sempre que há novos fundos depositados ou retirados pelos provedores de liquidez ou tokens são trocados pelos negociantes.
Algumas coisas estão acontecendo nesta função:
-
balance0
ebalance1
são os saldos de tokens no ERC20. Eles são o valor de retorno da funçãobalanceOf
do ERC20. -
_reserve0
e_reserve1
são os saldos anteriormente conhecidos da Uniswap (última vez que obalanceOf
foi verificado). - Tudo o que fazemos nesta função é verificar se há overflow (linha 74), atualizar o oráculo de preço (isto será explicado em uma seção posterior), atualizar reservas, e atualizar um evento
Sync
.
Qual é a diferença entre os argumentos _reserve0
, _reserve1
e as variáveis armazenadas reserve0
, reserve1
(mostradas abaixo)? Elas são essencialmente as mesmas. Os chamadores da função _update
já leram as variáveis reserve
do armazenamento e apenas as passam como argumentos para a função _update
. Esta é apenas uma forma de economizar gás. A leitura a partir do armazenamento é mais cara do que a leitura a partir da memória
_Você notará isto repetidas vezes: A Uniswap ama a eficiência e a economia de gás. Eles extraem todos os pontos de desempenho que podem do Solidity. _reserve0
e _reserve1
são um exemplo disto.
Cunhagem e queima
Agora sobre a próxima funcionalidade - cunhagem e queima. A cunhagem é quando um provedor de liquidez adiciona fundos na pool e como resultado, novos tokens de propriedade da pool _são cunhados (criados a partir do nada) para o provedor de liquidez. Queimar é o oposto - o provedor de liquidez retira fundos (e as recompensas acumuladas) e seus tokens de propriedade da _pool são queimados (destruídos).
Vamos dar uma olhada na função mint
.
- Imediatamente você pode notar a economia de gás novamente:
reserve0
,reserve1
etotalSupply
são transferidos do armazenamento para a memória (linhas 111 e 118) para que seja mais barato ler esses valores. - Lemos os saldos do nosso contrato (o contrato Par) nas linhas 112 e 113 e então calculamos o valor de cada token que foi depositado.
- A parte rosa do código é para a taxa de protocolo opcional. Vamos examiná-lo mais tarde.
-
totalSupply
indica o fornecimento total dos tokens de propriedade da pool e é uma variável armazenada no contratoUniswapV2ERC20
(veja meu detalhamento aqui). O contrato Par estende oUniswapV2ERC20
e é por isso que ele tem acesso à variáveltotalSupply
. - Se
totalSupply
for 0, isso significa que essa pool é nova e precisamos bloquear a quantidadeMINIMIUM_LIQUIDITY
de tokens de propriedade da pool para evitar a divisão por zero nos cálculos de liquidez. A maneira como ela é bloqueada é enviando-a para o endereço zero. (ninguém conhece a chave privada que levará ao endereço zero, portanto, ao enviar fundos para o endereço zero, você basicamente bloqueia os fundos para sempre). - **A variável
liquidity
é a quantidade de novos tokens de propriedade da pool que precisam ser cunhados para o provedor de liquidez. O provedor de liquidez recebe uma quantidade proporcional de tokens de propriedade da pool, dependendo de quantos novos fundos ele fornece (linha 123) - Finalmente, cunhamos novos tokens de propriedade da pool para o endereço
to
(linha 126).to
é o endereço do provedor de liquidez (isso será fornecido pelo contrato Periférico chamado Roteador que chama a funçãomint
)
A maneira como a adição de fundos funciona é: eles são apenas depositados nos contratos ERC20 (chamando transfer
(de: endereço do provedor de liquidez, para: endereço do contrato Par, valor
) para cada token). Em seguida, o contrato Par lerá os saldos (linhas 112 e 113) e os comparará com os últimos saldos conhecidos (linhas 114 e 115). É assim que o contrato Par pode deduzir os valores depositados.
A função burn
é apenas a imagem espelhada da função mint
:
- Vemos novamente economia de_ gás_ nas linhas 135, 136, 137 e 143
-
balance0
ebalance1
são os saldos totais dos tokens nesta pool.Liquidity
é a quantidade de tokens de propriedade da pool que o provedor de liquidez (que deseja sacar) possui. Por que acessar a liquidez como o saldo doaddress
(this
)? Porque a liquidez foi transferida para o contrato Par pelo contrato Periférico antes de chamar a funçãoburn
. - Calculamos os montantes de tokens a serem retirados para o provedor de liquidez proporcionalmente à quantidade de liquidez (tokens de propriedade da pool) que ele possui (linhas 144 e 145)
- Em seguida, queimamos sua liquidez e transferimos os tokens para ele.
- As recompensas para o provedor de liquidez são retiradas automaticamente junto com seus fundos. A matemática garante que as recompensas sejam acumuladas corretamente e que você receba mais do que depositou.
Isso é tudo para a Parte 1! Eu dividi este artigo em 2 partes porque estava ficando muito longo. Eu espero que isto tenha sido útil. Deixe-me saber nos comentários se você tiver alguma dúvida.
Na Parte 2 abordaremos:
- O restante do contrato Par: troca, tokens de propriedade da pool, taxa de protocolo e oráculo de preços.
- Também os contratos Fábrica, ERC20 e Roteador da Uniswap
Estou planejando fazer mais detalhamentos de contratos inteligentes populares como Axie Infinity e BAYC, então me siga no Medium ou no Twitter para obter atualizações.
Você também pode conferir detalhamentos de outros contratos inteligentes e mais coisas para noobs do Solidity em solidnoob.com.
Gostaria de conectar-se?
Siga-me no Twitter.
Este artigo foi escrito por Nazar Ilamanov e sua versão original pode ser encontrada aqui. Traduzido e adaptado por Marcelo Panegali.
Oldest comments (0)