Implementando um flash swap com a Uniswap V3
Neste artigo, vamos escrever um contrato inteligente que implementa uma troca rápida (flash swap), integrando contratos Uniswap V3. Este guia é apenas para fins de aprendizagem e o código abaixo não está totalmente pronto para a produção.
A ideia de uma troca rápida é ter tokens antes de ter que pagar por eles. Imagine que existe um pool de um tokenA/tokenB. Então você percebeu que pode obter mais tokens trocando o tokenB com o tokenA em outro nível de taxas. O que você pode fazer é obter o tokenB do primeiro pool antes de pagar o tokenA equivalente e ir executar a troca em outro nível de taxa onde você pode obter mais tokenA. Mais tarde, volte e compense a quantidade de tokenB que você retirou do pool original com o tokenA e mantenha o resto dos tokens como lucro. Mais concreto, digamos que você tenha um pool de USDC/USDT que pode ser trocado a 0,99-1 (quero dizer 0,99USDC = 1USDT) em um determinado nível de taxa. Mas, então você percebe que há outro nível de taxa onde você poderia obter 1USDT = 1,01USDT. Neste caso, você poderia obter USDT do primeiro pool, depois ir e fazer a troca no segundo pool, depois voltar ao primeiro pool com o USDC que você trocou e pagar o valor equivalente a USDC no pool um. Para manter o pool estável, a Uniswap utiliza a fórmula x.y=k.
Pool 1: 0.99USDC = 1 USDT
Pool 2: 1 USDT = 1.01 USDC
Isto significa que se você executar uma troca rápida de 10000000 USDT no pool1, então você vai e executa a troca no pool2, 10000000 USDT = 10100000 USDC.
Finalmente, você volta para o pool1 e paga o USDT que pegou antes.
Depois de pagar todas as taxas do protocolo, você pode manter os tokens restantes.
Este é apenas um exemplo para mostrar exatamente como uma troca rápida pode funcionar em teoria.
Vamos agora implementar o contrato inteligente.
Primeiro, em sua linha de comando (terminal), crie um diretório e nomeie como quiser, depois navegue para esse diretório.
Dependendo se você estiver utilizando npm ou yarn, você pode rodar npm i hardhat
ou yarn add hardhat
. Isto irá instalar todas as dependências que você precisará. Quando terminar a instalação, execute o npx hardhat
para criar um novo layout de projeto.
Antes de fazermos qualquer codificação, precisaremos instalar as dependências da Uniswap que contenham todos os contratos necessários para nosso projeto. Vamos fazer isso executando npm i @uniswap/v3-core @uniswap/v3-periphery
.
Agora que temos essas dependências instaladas, vamos criar um contrato chamado FlashSwap na pasta 'contratos'. Agora que o contrato foi criado, vamos importar uma série de contratos das dependências da Uniswap que acabamos de instalar.
É assim que deve ser o seu contrato com todos os contratos importados.
No próximo passo, faremos nosso contrato herdar de IUniswapV3FlashCallback e PeripheryPayments. Estes contratos também herdam de outros contratos, tais como LowGasSafeMath debaixo do capô. Isto significa que nosso contrato também terá acesso a funções desses contratos dos antecessores.
Agora, vamos implementar a LowGasSafeMath para todos os nossos uint256 e int256. Também vamos declarar uma variável imutável e pública chamada swapRouter que será do tipo ISwapRouter (das interfaces Uniswap).
Vamos declarar o construtor e passar em todos os valores que precisamos especificar no momento da implantação.
constructor(ISwapRouter _swapRouter,address _factory,address _WETH) PeripheryImmutableState(_factory, _WETH9) {
swapRouter = _swapRouter;
}
No construtor, estamos passando uma série de parâmetros como _swapRouter que é o endereço do roteador swap v3, estamos passando o endereço da fábrica da qual vamos executar a troca rápida, e finalmente passando no endereço de WETH9 que é o envoltório ERC20 para Ether.
Antes de chamar a função flash, precisaremos definir todos os parâmetros necessários para nossa troca rápida, bem como os dados para passar para a função de callback. Definiremos ambos em estruturas.
Vamos chamar a primeira estrutura FlashParams que conterá os parâmetros necessários para a chamada inicial.
struct FlashParams {
address token0;
address token1;
uint24 fee1;
uint256 amount0;
uint256 amount1;
uint24 fee2;
uint24 fee3;
}
A segunda estrutura será chamada FlashCallbackData que conterá os dados necessários a serem passados na callback
struct FlashCallbackData {
uint256 amount0;
uint256 amount1;
address payer;
PoolAddress.PoolKey poolKey;
uint24 poolFee2;
uint24 poolFee3;
}
Vamos agora trabalhar em nossa função de troca.
Vamos nomear nossa função initFlash
e passar em um parâmetro chamado params do tipo FlashParams. Nossa função é uma função external
.
Dentro do corpo da nossa função, vamos começar atribuindo os parâmetros necessários à nossa variável poolKey
.
Em seguida, declararemos outra variável chamada pool
do tipo IUniswapV3Pool que nos permitirá chamar flash
em nosso pool desejado.
Agora que temos nosso pool inicializado, podemos chamar flash
nesse pool. No último parâmetro de nossa função de pool(), codificaremos FlashCallbackData - a estrutura que definimos anteriormente - que será decodificada na callback e informada sobre os próximos passos da transação.
Veja como é nossa função "initFlash".
Vamos trabalhar em nossa função de callback.
Vamos nomear esta função uniswapV3FlashCallback
e override
para criar nossa própria lógica. Nossa função terá 3 parâmetros que são: uint256 fee0, uint256 fee1, e bytes de dados que são os dados que codificamos com abi.encode() na função anterior.
No corpo da função, vamos decodificar data
e armazená-la em uma variável chamada decodedData que é do tipo FlashCallbacData(struct).
FlashCallbackData memory decodedData = abi.decode(data, (FlashCallbackData));
Antes de continuar, precisamos validar que a chamada veio de um V3Pool válido. Fazemos isso da seguinte forma
CallbackValidation.verifyCallback(factory, decoded.poolKey);
Depois disso, vamos criar 2 variáveis token0 e token1 do tipo "address", atribuindo-lhes valores diferentes para aprovar o roteador para interagir com os tokens do flash.
address token0 = decoded.poolKey.token0;
address token1 = decoded.poolKey.token1;
TransferHelper.safeApprove(token0, address(swapRouter), decoded.amount0);
TransferHelper.safeApprove(token1, address(swapRouter), decoded.amount1);
Vamos adicionar também valores mínimos para que a troca possa falhar/reverter se não obtivermos nenhum lucro
uint256 amount1Min = LowGasSafeMath.add(decoded.amount1, fee1);
uint256 amount0Min = LowGasSafeMath.add(decoded.amount0, fee0);
Agora, podemos executar a troca. Este processo acontecerá em 2 etapas.
No primeiro passo, vamos chamar de "ExactInputSingle" no contrato de interface de roteador. Vamos utilizar nossa variável 'amount0Min' como nossa variável mínima e armazenar o valor da troca em uma variável chamada 'amountOutput0'.
Prosseguiremos com a segunda troca, bem como com a última taxa e o valor inicial "montante0" que obtivemos do pool inicial (original). Este processo é o mesmo que o primeiro, mas com parâmetros diferentes.
Agora que ambas as trocas estão feitas, precisamos pagar o pool original.
Fazemos isso calculando a quantia de dinheiro que devemos ao pool, bem como as taxas de troca, então aprovamos o "swapRouter" para transferir esses tokens de nosso contrato de volta ao pool original.
A implementação completa da função é a seguinte
Isso é tudo para toda a implementação da troca rápida.
Com isto, você tem uma ideia de como você pode implementar uma troca rápida usando contratos Uniswap V3 em seu próprio contrato. Você pode personalizar este contrato como quiser. Talvez você possa alterar o valor mínimo para permitir apenas um valor de lucro que esteja entre um determinado intervalo. Apenas uma ideia minha.
Você pode encontrar o contrato completo aqui.
Até a próxima vez, continue #CONSTRUINDO.
Artigo escrito no por Merkim . A versão original pode ser encontrada aqui. Traduzido e adaptado por Dimitris Calixto.
Top comments (0)