WEB3DEV

Cover image for Mergulhando no UniSwap
Dimitris Carvalho Calixto
Dimitris Carvalho Calixto

Posted on

Mergulhando no UniSwap

Unicornio UniSwap

Antes de lê-lo, você deve assistir a este vídeo e entender totalmente tudo o que eles dizem – LINK

Vamos trabalhar com este repositório – LINK

E você precisa apertar os botões no próprio UniSwap o máximo possível.

mini-esquema

P.S.
Este artigo foi escrito apenas como uma revisão do código uniswap v3 githab, tudo foi escrito em paralelo com o estudo da própria tecnologia, devido a isso pode haver algumas imprecisões e grosserias

Fábrica

Pools são criados aqui

Image

Aqui nos são dados os contratos das duas moedas entre as quais o par será formado, assim como a comissão que será fixada neste par. A função nos retorna o endereço do novo pool que formamos

  • A primeira coisa que esta função faz é verificar se nossos tokens são diferentes.
  • Em seguida, calcula o que tem um endereço menor e nomeia o menor como token0 e o maior como token1
  • Agora ele verifica que o token0 não é zero (token1 obviamente não precisa ser verificado)
  • Atribuímos ao tickSpacing um valor armazenado na variável feeAmountTickSpacing no endereço da taxa
  • Verifica se tickSpacing não é zero.
  • Verifica se não há nenhum pool com nossos tokens e nossas comissões (array 3D getPool retorna o endereço do pool usando os dados fornecidos).
  • Chama a função deploy passando para lá o endereço da fábrica, ambos os tokens, comissões e tickSpacing e depois passa os dados retornados (o novo endereço do pool) para a variável do pool que é então passada para a matriz getPool com ambas as posições dos tokens, salvando-a assim para que ninguém mais possa fazer um pool desse tipo

feeAmountTickSpacing

Ela armazena a distância entre os ticks, que corresponde a cada valor particular da comissão (inicialmente definida no construtor, mas também pode haver distâncias personalizadas, que são definidas pelo proprietário do contrato separadamente para cada valor da comissão).

Deploy

Código

Passamos para esta função o endereço da fábrica, tanto os tokens, a comissão e o tickSpacing, e a saída é o endereço do novo pool. Na própria função criamos parâmetros de estrutura, depois criamos um novo contrato usando o construtor UniswapV3Pool e passamos o endereço deste contrato em pool variável (que retornamos), depois retornamos os parâmetros aos valores iniciais com exclusão, economizando assim gás

Tick

Para obter liquidez concentrada, o espectro, outrora contínuo, da área de preços foi dividido por ticks

Ticks são as fronteiras entre as áreas no espaço de preços. Os ticks são dispostos de tal forma que um aumento ou diminuição de 1 tick representa um aumento ou diminuição de 0,01% (1 ponto base) no preço em qualquer ponto do espaço de preços.

Ticks servem como limites para as posições de liquidez. Quando uma posição é criada, o provedor deve selecionar os ticks inferiores e superiores, que representarão os limites de sua posição

Como o preço à vista muda durante a troca, o contrato de pool trocará continuamente o ativo de saída pelo de entrada, gradualmente usando toda a liquidez disponível durante o intervalo atual do tick, até que o tick seguinte seja alcançado. Nesse momento, o contrato muda para um novo tick e ativa qualquer liquidez latente dentro da posição que tem um limite no novo tick ativo.

Embora cada pool tenha o mesmo número de ticks subjacentes, na prática apenas alguns deles podem servir como ticks ativos. Devido à natureza dos contratos inteligentes v3, a distância entre ticks está diretamente relacionada com a comissão de troca. Níveis de comissão mais baixos proporcionam ticks ativos potenciais mais próximos, e comissões mais altas permitem um espaçamento relativamente maior entre os ticks ativos potenciais

Enquanto ticks inativos não têm efeito sobre o valor da transação durante as trocas, cruzar um tick ativo aumenta o valor da transação na qual ele é cruzado porque cruzar um tick ativa a liquidez em quaisquer novas posições usando esse tick como um limite

Em áreas onde a eficiência do capital é primordial, como os pares de stable coins, uma distância mais estreita do tick aumenta a granularidade da provisão de liquidez e é provável que reduza o impacto no preço durante as trocas, resultando em preços muito melhores para as trocas de stable coins.

Pool

Essa coisa que a fábrica cria, mas em que ela mesma se consiste?

IMPORTANTE: O pool é um contrato inteligente completo, e a Fábrica cria um contrato inteligente completo a partir de variáveis de entrada

A biblioteca de posições armazena todas as informações sobre cada posição específica, ou seja, para cada caso de depósito de liquidez no Pool.

Mint

Image

Esta função é responsável por adicionar liquidez ao pool

Damos o seguinte conjunto de variáveis como entrada:

  • recipient - endereço do pool ao qual vamos acrescentar liquidez
  • tickLower - tick inferior
  • tickUpper - tick superior
  • amount - a quantidade de liquidez, que estamos adicionando
  • data - qualquer dado a ser passado para o callback

Com a saída desta função, obtemos:

  • - amount0 - a quantidade de token0 que foi paga pela adição deste montante
  • - liquidity. Corresponde ao valor no callback
  • - amount1 - a quantia de token1 que foi paga pela adição desta quantia
  • - liquidity. Corresponde ao valor no callback

O que esta função faz?

  • - Verifica se estamos adicionando pelo menos alguma liquidez e não zero
  • - Depois disso, definimos as proporções dos tokens a serem adicionados (isso é feito na função _modifyPosition, enquanto ModifyPositionParams é necessário para o ajuste ao tipo de dados necessários)
  • - Depois disso, atribuímos os valores obtidos de _modifyPosition para aqueles que queremos que a função principal mint retorne (amount0 e amount1)
  • - No final, atualizamos os valores dos pools da moeda

_modifyPosition

Esta função calcula as proporções de moedas que precisamos adicionar ao nosso pool

Código

Código

Na entrada desta função damos a estrutura ModifyPositionParams, e a saída que nos dá a posição, amount0 e amount1

O que a função faz:

  • Verifica se os ticks estão dentro dos limites permitidos
  • Cria uma variável local _Slot0 do tipo Slot0 para economizar gás
  • Atribui a posição variável à saída da função _updatePosition
  • Verifica se há uma mudança na LiquidityDelta:

Compara o tick atual e os limites do tick definido, e se o tick atual estiver abaixo de nossos limites, então fornecemos mais token0 porque se torna mais valioso o tick atual está dentro de nossos limites, então fornecemos uma quantidade igual de moedas, o tick atual está acima de nossos limites, então fornecemos mais token1 porque se torna mais valioso

_updatePosition

Com base nos dados de entrada, ele devolve esta estrutura:

Código

Slot0

Código

ModifyPositionParams

Image

Collect

Esta função nos permite retirar comissões do pool

Image

Damos o seguinte conjunto de variáveis como entrada:

  • recipient - endereço do pool do qual tomaremos o liquor
  • tickLower - tick inferior
  • tickUpper - tick superior
  • amount0Requested - quanto deve ser retirado das
  • comissões
  • amout1Requestedd - quanto de token1 deve ser retirado das comissões

Na saída, a função nos dá:

  • - amount0 - o montante das comissões cobradas em token0
  • - amount1 - valor das comissões cobradas no token1

O que a função faz?

  • Primeiro obtemos a posição inicial do msg.sender e o tick superior e inferior
  • Depois disso, obtemos a quantidade de liquor que pode ser retirada de cada moeda do par (com base nas comissões)
  • No final, subtraímos as moedas que retiramos de nossa posição total

Swap

Esta característica nos permite trocar moedas

Image

Image

Image

Image

Image

Image

Damos o seguinte conjunto de variáveis como entrada:

  • - destinatário - endereço para receber fundos da troca
  • - zeroForOne - direção da troca, verdadeiro para token0 -> token1, falso para token1 -> token0
  • - amountSpecified - Quantidade de swap, que implicitamente define swap como entrada exata (positiva) ou saída exata (negativa).
  • - sqrtPriceLimitX96 - O limite de preço de Q64,96 (um número com vírgula exatamente definida, onde 64 bits são responsáveis pela parte inteira e 96 pela parte fracionária) sqrt. Se zero for para um, o preço não pode ser inferior a este valor após a troca. Se um é para zero, o preço não pode ser maior do que este valor após a troca.
  • - data - quaisquer dados que devem ser passados para a ligação de retorno

A saída desta função é:

  • amount0 - saldo delta do pool do token0, exato quando negativo, mínimo quando positivo
  • amount1 - o delta do saldo do pool do token1, exato com um valor negativo, mínimo com um valor positivo

O que essa função faz:

  • Verifica se swap não é zero
  • Cria uma variável local slot0Start como slot0, passa slot0 nele, e verificar se o pool está ativo
  • Verifica em que direção a troca ocorrerá (token0 -> token1 ou token1 -> token0)
  • Na estrutura do slot0 , escreve falso no campo "desbloqueado" para bloquear o pool para o momento da troca
  • Cria uma variável de cache do tipo SwapCache e passa para ela a liquidez do par, timestamp do bloco, coms para moeda de entrada, também zero tickCumulatives (o total de ticks por segundo do timestamp do bloco) e segundosPerLiquidityCumulativeX128s (total de segundos por unidade de liquidez na faixa para cada segundo do timestamp do bloco), e no final atribuímos falso para computedLatestObservation (verdadeiro se pudéssemos contar os dois valores acima, falso caso contrário)
  • Agora, finalmente, atribuímos à exactInput a moeda que queremos trocar
  • Criamos um estado variável do tipo SwapState e lhe damos o volume de nosso swap, quanto trocamos (assim 0), o preço atual no par, o preço do tick, o kmsa do token de entrada, a quantidade de token de entrada que pagamos como kmsa (inicialmente definido como 0), a liquidez no tick no momento
  • Agora começamos a fazer trocas condicionais até termos trocado a quantia inteira
  • Inicialize uma estrutura de passos vazia do tipo StepComputations
  • Passe o valor do estado do campo sqrtPriceX96 para a variável Step no campo sqrtPriceStartX96
  • Em seguida, chame a próxima função InitializedTickWithinOneWord da biblioteca tickBitmap e passe aí os valores state.tick (o tick atual), tickSpacing (a distância entre os ticks), zeroForOne, que retorna dois valores - o tick seguinte e se é inicializado
  • Nós nos certificamos de não exceder o tick mínimo/máximo, já que o tick do bitmap não conhece os limites e passa o resultado (o tick seguinte) para a estrutura de passos no campo tickNext
  • Então atribuímos o preço do próximo tick à estrutura de passos no campo sqrtPriceNextX96
  • Calcula o valor para passar para o tick alvo, limite de preço ou o ponto onde a quantidade de E/S está esgotada, tudo isso é feito através desta função de biblioteca. Na saída, obtemos 4 valores e os escrevemos em state.sqrtPriceX96 (o preço atual no par), step.amountIn (o valor de swap a ser feito neste passo), step.amountOut (o valor de swap que foi feito neste passo), step.feeAmount (quanto dinheiro foi pago). No final, calculamos qual valor do pool deve ser subtraído de qual quantia de swap, e qual deve ser adicionada.
  • Se a comsa do pool estiver ligada, calculamos o tamanho da comsa, diminuímos a step.feeAmount e aumentamos a state.protocolFee
  • Depois disso, atualizamos a comissão no pool, chamando a função FullMath.mulDiv
  • A seguir vem um grande bloco, que pode ser descrito como um "mudança de tick se atingirmos o próximo preço".
  • - Então simplesmente atualizamos o tick e registramos o resultado do oráculo (acho que haverá um artigo separado sobre ele), se o tick mudou, caso contrário, simplesmente atualizamos o preço, equiparando-o a state.sqrtPriceX96
  • - Depois disso, atualizamos a liquidez, se ela tiver mudado
  • - Agora mudamos a comissão no pool de moedas, dependendo de que forma tivemos a troca
  • - E finalmente, fazemos a transferência em si, chamando a função safeTransfer, que todos conhecemos, e mudando os saldos simbólicos no pool
  • - E no final, desbloqueamos o pool novamente!

SwapCache

Image

SwapState

Image

StepComputations

Image

tickBitmap.nextInitializedTickWithinOneWord

Image

Obrigado por ler! Espero que o artigo tenha sido interessante e claro!

Artigo escrito por Alexandr Kumancev. A versão original pode ser encontrada aqui. Traduzido e adaptado por Dimitris Calixto.

Oldest comments (0)