WEB3DEV

Cover image for Swaps de Caminhos Únicos - Uniswap
Paulo Gio
Paulo Gio

Posted on • Atualizado em

Swaps de Caminhos Únicos - Uniswap

O swap (troca) é a interação mais comum com o protocolo Uniswap. O exemplo a seguir mostra como implementar um contrato de swap de caminho único que usa duas funções que você cria:

  • swapExactInputSingle
  • swapExactOutputSingle

A função swapExactInputSingle existe para realizar swaps de inputs exatos, que trocam uma quantidade fixa de um token por uma quantidade máxima possível de outro token. Essa função usa a estrutura ExactInputSingleParams e a função exactInputSingle da interface ISwapRouter.

A função swapExactOutputSingle é para realizar swaps de outputs exatos, que trocam uma quantidade mínima possível de um token por uma quantidade fixa de outro token. Essa função usa a estrutura ExactOutputSingleParams e a função exactOutputSingle da interface ISwapRouter.

Para simplificar, o exemplo codifica rigidamente os endereços do contrato do token, mas, conforme explicado mais abaixo, o contrato pode ser modificado para alterar pools e tokens de acordo com a transação.

Ao negociar a partir de um contrato inteligente, o mais importante a ter em mente é que é necessário ter acesso a uma fonte de preços externa. Sem isso, os negócios podem estar sujeitos a perdas consideráveis.

Observação: Os exemplos de swaps não são códigos prontos para a fase de implantação. São implementados de maneira simplista para fins de aprendizado.

Configure o contrato

Declare a versão utilizada do Solidity para compilar o contrato, e também declare o abicoder v2, para permitir que arrays e structs aninhados arbitrariamente sejam codificados e decodificados nos dados da chamada do contrato, um recurso usado ao executar um swap.

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
Enter fullscreen mode Exit fullscreen mode

Importe os dois contratos relevantes da instalação do pacote npm.

import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';
import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
Enter fullscreen mode Exit fullscreen mode

Crie um contrato chamado SwapExamples e declare uma variável pública imutável swapRouter do tipo ISwapRouter. Isso nos permite chamar funções na interface ISwapRouter.

contract SwapExamples {

    /// Para o escopo desses exemplos de swap, detalharemos as considerações de design ao usar `exactInput`, `exactInputSingle`, `exactOutput` e `exactOutputSingle`.
    /// Deve-se notar que, por causa desses exemplos, passamos o roteador do swap como um argumento de construtor em vez de herdá-lo.
    /// Contratos de exemplo mais avançados detalharão como herdar o roteador do swap com segurança.
    /// Este exemplo faz o swap DAI/WETH9 para swaps de caminho único e DAI/USDC/WETH9 para swaps de múltiplos caminhos.

    ISwapRouter public immutable swapRouter;
Enter fullscreen mode Exit fullscreen mode

Para o exemplo, codifique rigidamente os endereços do contrato do token e os níveis das taxas do pool. Na fase de implantação, você provavelmente usaria um parâmetro de input para isso e passaria esse input para uma variável de memória, permitindo que o contrato alterasse os pools e tokens com os quais interage em cada transação, mas para uma simplicidade conceitual, estamos codificando-os aqui.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    // Para este exemplo, definiremos a taxa do pool para 0.3%.
    uint24 public constant poolFee = 3000;

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }
Enter fullscreen mode Exit fullscreen mode

Swaps de inputs exatos

Quem chama deve executar approve no contrato para retirar os tokens da conta do endereço chamado para executar um swap. Lembre-se disso, porque nosso contrato é um contrato em si e não uma extensão de quem chama (nós); também devemos aprovar o contrato do roteador do protocolo Uniswap para usar os tokens que nosso contrato possuirá depois que eles forem retirados do endereço de quem chamou (nós).

Em seguida, transfira a quantidade (amount) de Dai do endereço de quem chama para o nosso contrato, e use amount como o valor passado para a segunda execução do approve.

      /// @nota swapExactInputSingle troca uma quantidade fixa de DAI por uma quantidade máxima possível de WETH9
    /// usando o pool de 0,3% DAI/WETH9 chamando `exactInputSingle` no roteador da troca.
    /// @desenvolvimento O endereço de chamada deve aprovar este contrato para gastar pelo menos o valor de `amountIn` de suas DAI para que esta função seja bem-sucedida.
    /// @parâmetro amountIn A quantidade exata de DAI que será trocada por WETH9.
    /// @retornar amountOut A quantidade de WETH9 recebida.
    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
        // msg.sender deve aprovar este contrato

        // Transferir a quantidade especificada de DAI para este contrato.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Permitir o roteador a gastar DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);
Enter fullscreen mode Exit fullscreen mode

Parâmetros de Input do Swap

Para executar a função de swap, precisamos preencher ExactInputSingleParams com os dados necessários do swap. Esses parâmetros são encontrados nas interfaces de contrato inteligente, que podem ser navegadas aqui.

Uma breve visão geral dos parâmetros:

  • tokenIn: O endereço do contrato do token de entrada;
  • tokenOut: O endereço do contrato do token de saída;
  • fee: O nível de taxa do pool, usado para determinar o contrato correto do pool no qual será executada a troca;
  • recipient: o endereço de destino do token de saída;
  • deadline: o tempo unix após o qual um swap falhará, para proteger contra transações pendentes e grandes oscilações nos preços;
  • amountOutMinimum: estamos definindo como zero, mas esse é um risco significativo na fase de implantação. Para uma implantação real, esse valor deve ser calculado usando nosso SDK ou um oráculo de preços on-chain - isso ajuda a proteger contra a obtenção de um preço excepcionalmente ruim para uma negociação, devido a um bot de front-running ou outro tipo de manipulação de preço;
  • sqrtPriceLimitX96: Definimos isso como zero - o que torna esse parâmetro inativo. Na fase de implantação, esse valor pode ser usado para definir o limite para o preço para o qual o swap empurrará o pool, o que pode ajudar a proteger contra o impacto do preço ou para configurar a lógica em uma variedade de mecanismos relevantes para o preço.

Chamar a função

      // Definimos amountOutMinimum como 0. Na fase de implantação, use um oráculo ou outra fonte de dados para escolher um valor mais seguro para amountOutMinimum.
         // Também definimos sqrtPriceLimitx96 como 0 para garantir que fazemos o swap do nosso valor exato de input.
        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        // Chamar `exactInputSingle` executa o swap.
        amountOut = swapRouter.exactInputSingle(params);
    }
Enter fullscreen mode Exit fullscreen mode

Swaps de outputs exatos

O Output Exato troca uma quantidade mínima possível do token de input por uma quantidade fixa do token de output. Este é o estilo de swap menos comum - mas útil em várias circunstâncias.

Como este exemplo transfere o ativo de input em antecipação à troca - é possível que algum token de input seja deixado após a execução do swap, e é por isso que o pagamos de volta ao endereço de chamada no final da troca.

/// @nota swapExactOutputSingle troca uma quantidade mínima possível de DAI por uma quantidade fixa de WETH. 
/// @desenvolvimento O endereço de chamada deve aprovar este contrato para gastar sua DAI para que essa função seja bem-sucedida. Como a quantidade de entrada DAI é variável, o endereço de chamada precisará aprovar um valor um pouco maior, antecipando alguma variação. 
/// @parâmetro amountOut A quantidade exata de WETH9 a receber do swap. 
/// @parâmetro amountInMaximum A quantidade de DAI que estamos dispostos a gastar para receber a quantidade especificada de WETH9. 
/// @retornar amountIn A quantidade de DAI realmente gasta na troca. 
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
// Transferir a quantidade especificada de DAI para este contrato.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), valorInMaximum);

      // Aprova o roteador para gastar o `amountInMaximum` especificado de DAI. 
      // Na fase de implantação, você deve escolher o valor máximo a ser gasto com base em oráculos ou outras fontes de dados para obter uma troca melhor.
     TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        ISwapRouter.ExactOutputSingleParams memory params =
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum,
                sqrtPriceLimitX96: 0
            });

      // Executa o swap retornando o amountIn necessário para gastar para receber o valor desejado.
      amountIn = swapRouter.exactOutputSingle(params);

      // Para trocas de outputs exatos, o valorInMaximum pode não ter sido todo gasto.
       /// Se o valor real gasto (amountIn) for menor que o valor máximo especificado, devemos reembolsar o msg.sender e aprovar o swapRouter para gastar 0.
       if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
       }
}
Enter fullscreen mode Exit fullscreen mode

Contrato Completo:

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;

import '@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol';
import '@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol';

contract SwapExamples {
    // Para o escopo desses exemplos de swap, detalharemos as considerações de design ao usar `exactInput`, `exactInputSingle`, `exactOutput` e `exactOutputSingle`.
    // Deve-se notar que, por causa desses exemplos, passamos o roteador do swap como um argumento de construtor em vez de herdá-lo.
    // Contratos de exemplo mais avançados detalharão como herdar o roteador do swap com segurança.

    ISwapRouter public immutable swapRouter;

    // Este exemplo faz o swap DAI/WETH9 por swaps de caminho único e DAI/USDC/WETH9 por swaps de múltiplos caminhos.

    address public constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address public constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

    // Para este exemplo, definiremos a taxa do pool para 0.3%.
    uint24 public constant poolFee = 3000;

    constructor(ISwapRouter _swapRouter) {
        swapRouter = _swapRouter;
    }

     /// @nota swapExactInputSingle troca uma quantidade fixa de DAI por uma quantidade máxima possível de WETH9
    /// usando o pool de 0,3% DAI/WETH9 chamando `exactInputSingle` no roteador da troca.
    /// @desenvolvimento O endereço de chamada deve aprovar este contrato para gastar pelo menos o valor de `amountIn` de sua DAI para que esta função seja bem-sucedida.
    /// @parâmetro amountIn A quantidade exata de DAI que será trocada por WETH9.
    /// @retornar amountOut A quantidade de WETH9 recebida.
    function swapExactInputSingle(uint256 amountIn) external returns (uint256 amountOut) {
        // msg.sender deve aprovar este contrato

        // Transferir a quantidade especificada de DAI para este contrato.
        TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), amountIn);

        // Permitir o roteador a gastar DAI.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountIn);

        // Definimos amountOutMinimum como 0. Na fase de implantação, use um oráculo ou outra fonte de dados para escolher um valor mais seguro para amountOutMinimum.
         // Também definimos sqrtPriceLimitx96 como 0 para garantir que fazemos o swap do nosso valor exato de input.
        ISwapRouter.ExactInputSingleParams memory params =
            ISwapRouter.ExactInputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountIn: amountIn,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        // Chamar `exactInputSingle` executa o swap.
        amountOut = swapRouter.exactInputSingle(params);
    }

    /// @nota swapExactOutputSingle troca uma quantidade mínima possível de DAI por uma quantidade fixa de WETH. 
    /// @desenvolvimento O endereço de chamada deve aprovar este contrato para gastar sua DAI para que essa função seja bem-sucedida. Como a quantidade de entrada DAI é variável, o endereço de chamada precisará aprovar um valor um pouco maior, antecipando alguma variação. 
    /// @parâmetro amountOut A quantidade exata de WETH9 a receber do swap. 
    /// @parâmetro amountInMaximum A quantidade de DAI que estamos dispostos a gastar para receber a quantidade especificada de WETH9. 
    /// @retornar amountIn A quantidade de DAI realmente gasta na troca. 
function swapExactOutputSingle(uint256 amountOut, uint256 amountInMaximum) external returns (uint256 amountIn) {
    /// Transferir a quantidade especificada de DAI para este contrato.
TransferHelper.safeTransferFrom(DAI, msg.sender, address(this), valorInMaximum);


        // Aprovar o roteador para gastar o `amountInMaximum` especificado de DAI. 
      // Na fase de implantação, você deve escolher o valor máximo a ser gasto com base em oráculos ou outras fontes de dados para obter uma troca melhor.
        TransferHelper.safeApprove(DAI, address(swapRouter), amountInMaximum);

        ISwapRouter.ExactOutputSingleParams memory params =
            ISwapRouter.ExactOutputSingleParams({
                tokenIn: DAI,
                tokenOut: WETH9,
                fee: poolFee,
                recipient: msg.sender,
                deadline: block.timestamp,
                amountOut: amountOut,
                amountInMaximum: amountInMaximum,
                sqrtPriceLimitX96: 0
            });

        // Executa o swap retornando o amountIn necessário para gastar para receber o valor desejado.
        amountIn = swapRouter.exactOutputSingle(params);

        // Para trocas de outputs exatos, o valorInMaximum pode não ter sido todo gasto.
     // Se o valor real gasto (amountIn) for menor que o valor máximo especificado, devemos reembolsar o msg.sender e aprovar o swapRouter para gastar 0.
        if (amountIn < amountInMaximum) {
            TransferHelper.safeApprove(DAI, address(swapRouter), 0);
            TransferHelper.safeTransfer(DAI, msg.sender, amountInMaximum - amountIn);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Este guia foi publicado no Uniswap Docs. Tradução por Paulinho Giovannini.

Latest comments (0)