WEB3DEV

Cover image for ⚖️ Exchange Mínima Viável
Fatima Lima
Fatima Lima

Posted on • Atualizado em

⚖️ Exchange Mínima Viável

Construindo uma exchange descentralizada simplificada na Ethereum

 

Introdução

Este primeiro aplicativo matador (um programa que é considerado tão necessário e desejável que justifica a compra ou a adoção de um tipo particular de computador) a ser construído na Ethereum foi um token. Um padrão chamado ERC20 surgiu e agora qualquer um pode implantar um token na Ethereum em segundos.

Muitas vezes, um token precisa ser trocado por um valor ou outro token e tradicionalmente isso tem sido facilitado por entidades inseguras e centralizadas.

Em 2017, Vitalik Buterin propôs um método que permitiria que um contrato inteligente realizasse trocas entre dois ativos utilizando um índice de liquidez baseado em reservas. Eventualmente, isto se tornou 🦄Uniswap e agora qualquer um pode usar uniswap.exchange para trocar tokens na Ethereum.

Os contratos Uniswap movimentaram mais de $1,2 BILHÕES de dólares coletivamente. 🤯

Ao longo de 2019, os provedores de liquidez da Uniswap ganharam mais de $1,2 milhões em taxas. — Year in Eth 2019

Neste tutorial, tentaremos reduzir uma troca descentralizada a algumas funções simples de Solidity em um contrato inteligente fácil de digerir. Felizmente, meu amigo Philippe Castonguay

criou uniswap-solidity e este tutorial utilizará uma versão reduzida deste código.

Construiremos uma exchange descentralizada para trocar um token arbitrário por ETH usando um pool de liquidez do qual qualquer pessoa pode participar. Esta construção demonstrará como contratos inteligentes podem criar 🤖 sistemas descentralizados automáticos usando incentivos econômicos em criptos.

🛰 atualização — ago 2022 : Você pode encontrar um repositório atualizado com a última DEX construída aqui:

https://github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-4-dex


Sumário

1 . Introdução

2 . SpeedRun

3 . O que é necessário

4 . Iniciando

5 . Reservas

6 . Preço

7 . Negociação

8 . Liquidez

9 . Interface

10 . Explore

11 . Parabéns

SpeedRun

Assista o vídeo

O que é necessário

Você precisará do NodeJS>=10, Yarn, e Git instalados.

Este tutorial assume que você tem uma compreensão básica de desenvolvimento de app na web e alguma orientação sobre conceitos fundamentais da Ethereum.

Iniciando

Em 🛠 Programando dinheiro descentralizado nós introduzimos 🏗 scaffold-eth. Este tutorial utilizará o ramo dex do scaffolding de desenvolvimento Ethereum:

git clone https://github.com/austintgriffith/scaffold-eth.git dex

cd dex
git checkout dex
yarn install
yarn start
Enter fullscreen mode Exit fullscreen mode

(repositório ATUALIZADO: https://github.com/scaffold-eth/scaffold-eth-challenges/tree/challenge-4-dex)

Também vamos querer trazer nossa blockchain local e implantar nossos contratos. Em uma nova janela de terminal, executar:

yarn run chain
Enter fullscreen mode Exit fullscreen mode

Em uma terceira janela do terminal, podemos compilar e implantar com:

yarn run deploy
Enter fullscreen mode Exit fullscreen mode

O aplicativo deve chegar em http://localhost:3000 e você deverá ver:

Image description

☢️ Se este não é o título que você vê, você provavelmente está no ramo errado.

Também veremos dois contratos inteligentes chamados DEX e Balloons.

Image description

Podemos encontrar esses contratos inteligentes em packages/buidler/contracts:

Balloons.sol é apenas um exemplo de contrato ERC20 que cunha 1000 para qualquer endereço que o implemente.

DEX.sol é o que vamos construir neste tutorial e você pode ver que ele começa com uma biblioteca SafeMath para nos ajudar a evitar overflows e underflows e também rastrear um token (interface ERC20) que definimos no construtor (no deploy):

Image description

☢️ Você encontrará os contratos inteligente em packages/buidler/contracts. Existem outras pastas contracts então certifique-se de que você vai encontrar a correta com DEX.sol.

Reservas

Como mencionado na introdução, queremos criar um mercado automático onde nosso contrato manterá reservas de ambos ETH e 🎈Balloons. Essas reservas proverão liquidity que permite a qualquer pessoa fazer trocas entre os ativos. Vamos adicionar algumas variáveis novas a DEX.sol:

uint256 public totalLiquidity;
mapping (address => uint256) public liquidity;
Enter fullscreen mode Exit fullscreen mode

Essas variáveis rastreiam a liquidez total, mas também por endereços individuais.

Então, vamos criar uma função init() em DEX.sol que é payable e então podemos definir uma quantidade de tokens que vai transferir para si mesma:

function init(uint256 tokens) public payable returns (uint256) {
 require(totalLiquidity==0,"DEX:init - already has liquidity");
 totalLiquidity = address(this).balance;
 liquidity[msg.sender] = totalLiquidity;
 require(token.transferFrom(msg.sender, address(this), tokens));
 return totalLiquidity;
}
Enter fullscreen mode Exit fullscreen mode

Chamando init() nosso contrato será carregado com ambos ETH e 🎈Balloons.

Você pode compilar seus contratos com o comando yarn run compile e por enquanto, basta ignorar quaisquer avisos. Quando você estiver pronto, implante seus contratos:

yarn run deploy
Enter fullscreen mode Exit fullscreen mode

Seu aplicativo deve recarregar automaticamente e o contrato DEX deve estar em um novo endereço. Além disso, nossas novas variáveis de liquidez são automaticamente carregadas no frontend:

Image description

Podemos ver que o DEX começa vazio. Queremos ser capazes de chamar init() para começar com liquidez, mas ainda não temos fundos ou tokens.

🏗 Scaffold-eth inicia cada usuário com uma conta temporária no carregamento da página. Vamos copiar nosso endereço na parte superior direita:

Image description

E envie para nossa conta algum ETH de teste da faucet na parte inferior esquerda:

Image description

Agora precisamos de alguns tokens 🎈Balloon! Encontre o arquivo deploy.js em packages/buidler/scripts e vamos adicionar uma linha que envie 10 tokens (10 vezes 10¹⁸ porque não há decimais) para nossa conta quando o contrato for implantado:

Image description

Agora, reimplante tudo e veremos novos contratos de endereços, mas, mais importante, devemos ter 10 tokens enviados para nós no momento da implantação:

yarn run deploy
Enter fullscreen mode Exit fullscreen mode

Para ver nossos 🎈balloons no frontend você verá que estamos usando o componente <TokenBalance> logo abaixo do componente <Account> em App.js. (Você pode encontrar App.js no diretório packages/react-app/src.)

Image description

Ainda não podemos simplesmente chamar o init() porque o contrato DEX não permite a transferência de tokens da nossa conta. Precisamos approve() o contrato DEX com a UI Balloons. Copie e cole o endereço DEX e então defina o amount para 5000000000000000000 (5 * 10¹⁸):

Image description

Se você pressionar o ícone 💸, deverá disparar uma transação que aprova o DEX para tirar 5 de seus tokens. Você pode testar isso com o formulário allowance:

Image description

Agora estamos prontos para chamar o init() na DEX. Diremos a ela para levar 5 (*10¹⁸) de nossos tokens e também enviaremos 0,01 ETH com a transação. Você pode fazer isso digitando 0.01 como o valor da transação, então pressione ✳️ para fazer *10¹⁸, depois pressione #️⃣ para convertê-lo em hex:

Image description

Uma vez que você pressione o botão 💸 sua transação enviará em ETH e o contrato pegará 5 de seus tokens:

Image description

Você pode verificar quantos balloons 🎈a DEX tem, usando a UI:

Image description

Isto funciona muito bem, mas será muito mais fácil se apenas chamarmos a função init() enquanto implantamos o contrato. No script deploy.js tente descomentar a seção init de forma que a nossa DEX começará com 5 ETH e 5 Balloons de liquidez:

Image description

Agora, quando nós yarn run deploy, nosso contrato deve ser inicializado assim que for executado e devemos ter reservas iguais de ETH e tokens.

Image description

Preço

Agora que nosso contrato mantém reservas tanto de ETH quanto de tokens, queremos usar uma fórmula simples para determinar a taxa de câmbio entre os dois.

Vamos começar com a fórmula x * y = k onde x e y são as reservas:

( amount of ETH in DEX ) * ( amount of tokens in DEX ) = k
Enter fullscreen mode Exit fullscreen mode

O k é chamado de constante porque não muda durante as negociações. (O k só muda quando _liquidity _é adicionada.) Se plotarmos esta fórmula, teremos uma curva que parece algo como:

Image description

💡 Estamos apenas trocando um ativo por outro, o "preço" é basicamente quanto do ativo de saída resultante você receberá se você colocar uma certa quantidade do ativo de entrada.

🤔 OH! Um mercado baseado em uma curva como esta sempre terá liquidez, mas à medida que a relação se torna cada vez mais desequilibrada, você receberá cada vez menos do ativo mais fraco do mesmo valor de negociação. Mais uma vez, se o contrato inteligente tiver muito ETH e não houver tokens suficientes, o preço para trocar tokens por ETH deverá ser mais desejável.

Quando chamamos init() passamos em ETH e tokens a uma proporção de 1:1 e essa proporção deve permanecer constante. Como as reservas de um ativo mudam, o outro ativo também deve mudar inversamente.

Vamos editar nosso contrato inteligente DEX.sol e trazer esta função price:

function price(uint256 input_amount, uint256 input_reserve, uint256 output_reserve) public view returns (uint256) {
 uint256 input_amount_with_fee = input_amount.mul(997);
 uint256 numerator = input_amount_with_fee.mul(output_reserve);
 uint256 denominator = input_reserve.mul(1000).add(input_amount_with_fee);
 return numerator / denominator;
}
Enter fullscreen mode Exit fullscreen mode

Usamos a razão entre a reserva de input e a reserva de output para calcular o preço de troca de um ativo pelo outro. Vamos implantar isto e dar uma olhada:

yarn run deploy
Enter fullscreen mode Exit fullscreen mode

Digamos que temos 1 milhão de ETH e 1 milhão de tokens. Se colocarmos isso em nossa fórmula de preço e perguntarmos o preço de 1000 ETH, será uma proporção de quase 1:1:

Image description

Se pusermos 1000 ETH, receberemos 996 tokens. Se estivermos pagando uma taxa de 0,3%, deveria ser 997 se tudo estivesse perfeito. MAS, há um pequena derrapagem à medida que nosso contrato se afasta da proporção original. Vamos aprofundar mais para entender realmente o que está acontecendo aqui.

Digamos que há 5 milhões de ETH e apenas 1 milhão de tokens. Então, queremos colocar 1.000 tokens. Isso significa que devemos receber cerca de 5.000 ETH:

Image description

Finalmente, vamos dizer que a proporção é a mesma, mas queremos trocar 100.000 tokens em vez de apenas 1000. Vamos notar que a quantidade de derrapagem é muito maior. Em vez de 498.000 de volta, receberemos apenas 453.305 porque estamos fazendo um grande furo nas reservas.

Image description

💡 O contrato ajusta automaticamente o preço à medida que a relação de reservas se afasta do equilíbrio. É chamado de um 🤖 Formador de mercado automatizado.

Negociação

Vamos editar o contrato inteligente DEX.sol e adicionar duas funções novas para trocar cada ativo pelo outro:

function ethToToken() public payable returns (uint256) {
 uint256 token_reserve = token.balanceOf(address(this));
 uint256 tokens_bought = price(msg.value, address(this).balance.sub(msg.value), token_reserve);
 require(token.transfer(msg.sender, tokens_bought));
 return tokens_bought;
}
function tokenToEth(uint256 tokens) public returns (uint256) {
 uint256 token_reserve = token.balanceOf(address(this));
 uint256 eth_bought = price(tokens, token_reserve, address(this).balance);
 msg.sender.transfer(eth_bought);
 require(token.transferFrom(msg.sender, address(this), tokens));
 return eth_bought;
}
Enter fullscreen mode Exit fullscreen mode

Cada uma dessas funções calcula a quantidade resultante do ativo de output usando nossa função de preço que analisa a relação entre as reservas e o ativo de input.

Podemos chamar tokenToEth e ela tomará nossos tokens e nos enviará ETH ou podemos chamar ethToToken com algum ETH na transação e ela nos enviará tokens.

Vamos compilar e implantar nosso contrato e depois passar para o frontend:

yarn run deploy
Enter fullscreen mode Exit fullscreen mode

Sua UI do aplicativo deve fazer recarga automática e mostrar as duas funções novas:

Image description

Image description

Após enviar algum ETH para nossa conta com a faucet, podemos tentar trocar algum ETH por tokens. Vamos começar com 0.001 e depois clicar em ✳️ e depois #️⃣. Então, se clicarmos em 💸, a transação será feita para chamar ethToToken():

Image description

E nossa conta deve receber 0,001 tokens de volta:

Image description

Trocar tokens por ETH é um pouco mais complicado porque temos que fazer uma transação para approve() o DEX para levar nossos tokens primeiro. Vamos aprovar o endereço DEX para levar 1 (* 10¹⁸) tokens (1000000000000000000):

Image description

Então vamos tentar trocar essa quantidade de tokens por ETH:

Image description

Então nosso saldo ETH deve aumentar 0,85 ou mais:

Image description

(Ele mostra seu saldo em USD, mas você pode clicar nele para ver o valor exato:)

Image description

🎉 Estamos trocando ativos! 🎊 Celebre com seus emojis! 🥳 🍾 🥂

Liquidez

Até agora, somente a função init() controla a liquidez. Para tornar isto mais descentralizado, seria melhor se qualquer pessoa contribuisse para o pool de liquidez enviando ao DEX tanto ETH quanto tokens na proporção correta.

Vamos criar duas novas funções que nos permitem depositar e retirar liquidez:

function deposit() public payable returns (uint256) {
 uint256 eth_reserve = address(this).balance.sub(msg.value);
 uint256 token_reserve = token.balanceOf(address(this));
 uint256 token_amount = (msg.value.mul(token_reserve) / eth_reserve).add(1);
 uint256 liquidity_minted = msg.value.mul(totalLiquidity) / eth_reserve;
 liquidity[msg.sender] = liquidity[msg.sender].add(liquidity_minted);
 totalLiquidity = totalLiquidity.add(liquidity_minted);
 require(token.transferFrom(msg.sender, address(this), token_amount));
 return liquidity_minted;
}
function withdraw(uint256 amount) public returns (uint256, uint256) {
 uint256 token_reserve = token.balanceOf(address(this));
 uint256 eth_amount = amount.mul(address(this).balance) / totalLiquidity;
 uint256 token_amount = amount.mul(token_reserve) / totalLiquidity;
 liquidity[msg.sender] = liquidity[msg.sender].sub(eth_amount);
 totalLiquidity = totalLiquidity.sub(eth_amount);
 msg.sender.transfer(eth_amount);
 require(token.transfer(msg.sender, token_amount));
 return (eth_amount, token_amount);
}
Enter fullscreen mode Exit fullscreen mode

Tire um segundo para entender o que essas funções estão fazendo, depois de colá-las no DEX.solem packages/buidler/contracts:

A função deposit() recebe ETH e também transfere tokens do chamador para o contrato na proporção certa. O contrato também rastreia o valor da liquidity que o endereço do depositante possui vs totalLiquidity.

A função withdraw() permite que um usuário tire tanto ETH quanto tokens na proporção correta. A quantidade real de ETH e tokens que um provedor de liquidez retira será maior do que o que eles depositaram, por causa das taxas de 0,3% cobradas de cada negociação. Isto incentiva terceiros a fornecer liquidez.

Compile e implante seus contratos para o frontend:

yarn run deploy
Enter fullscreen mode Exit fullscreen mode

Interface

A UX é muito ruim/feia e ainda é um pouco difícil visualizar toda esta derrapagem. Vamos fazer algum trabalho de frontend para limpar a interface e torná-la mais fácil de entender.

Vamos editar App.js em packages/react-app/src:

Há um componente personalizado <DEX> incluído neste ramo de código. Exclua o componente genérico <Contract> para o DEX e traga o <DEX> como:

<DEX
 address={address}
 injectedProvider={injectedProvider}
 localProvider={localProvider}
 mainnetProvider={mainnetProvider}
 readContracts={readContracts}
 price={price}
/>
Enter fullscreen mode Exit fullscreen mode

Vamos limpar o componente &lt;Contract> do Balloon dando a ele um título e optando por mostrar apenas as ações balanceOf e approve():

<Contract
 title={"🎈 Balloons"}
 name={"Balloons"}
 show={["balanceOf","approve"]}
 provider={localProvider}
 address={address}
/>
Enter fullscreen mode Exit fullscreen mode

Fantástico, nosso frontend está parecendo 🔥 AF:

Image description

Explore

Agora, um usuário pode simplesmente digitar a quantidade de ETH ou tokens que deseja trocar e o gráfico mostrará como o preço é calculado. O usuário também pode visualizar como trocas maiores resultam em mais derrapagens e menos ativos de saída:

Image description

Um usuário também pode depositar e retirar do pool de liquidez, ganhando taxas:

Image description

Parabéns

Juntos, remendamos uma exchange descentralizada viável mínima. Podemos fornecer liquidez para que qualquer pessoa possa trocar ativos e os provedores de liquidez ganharão taxas. Tudo isso acontece on-chain e não pode ser censurado nem adulterado.

É 🤖 um mercado incontrolável** **⚖️!!!

Fale comigo pelo DM no Twitter: @austingriffith

Aqui está nosso contrato de exchange final em toda sua glória:

Image description

Agradecimentos a Cooper Turley e Veronica Zhixing Zheng

Este artigo foi escrito por Austin Thomas Griffith e traduzido por Fátima Lima. O original pode ser lido aqui.

Latest comments (0)