Transferências e aprovação de tokens ERC-20 de um contrato inteligente Solidity
No tutorial anterior, estudamos a estrutura de um token ERC-20 no Solidityusado na blockchain Ethereum. Nesse artigo, veremos como usar um contrato inteligente para interagir com um token usando a linguagem Solidity.
Para este contrato inteligente, nós criaremos uma corretora descentralizada realmente fictícia, na qual um usuário pode trocar ether por nosso token ERC-20 recém-implantado.
Para este tutorial, usaremos o código que escrevemos no tutorial anterior como uma base. Nosso DEX instanciará um contrato em seu construtor e realizará as operações de:
- trocando tokens por ether
- trocando ether por tokens
Iniciaremos nosso código de intercâmbio descentralizado adicionando a nossa simples base de código ERC20:
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 function transfer(address receiver, uint256 numTokens) public override returns (bool) {46 require(numTokens <= balances[msg.sender]);47 balances[msg.sender] = balances[msg.sender]-numTokens;48 balances[receiver] = balances[receiver]+numTokens;49 emit Transfer(msg.sender, receiver, numTokens);50 return true;51 }5253 function approve(address delegate, uint256 numTokens) public override returns (bool) {54 allowed[msg.sender][delegate] = numTokens;55 emit Approval(msg.sender, delegate, numTokens);56 return true;57 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 balances[owner] = balances[owner]-numTokens;68 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;69 balances[buyer] = balances[buyer]+numTokens;70 emit Transfer(owner, buyer, numTokens);71 return true;72 }73}7475Exibir tudoCopiar
Nosso novo contrato inteligente DEX implantará o ERC-20 e obter todos os fornecidos:
1contract DEX {23 IERC20 public token;45 event Bought(uint256 amount);6 event Sold(uint256 amount);78 constructor() {9 token = new ERC20Basic();10 }1112 function buy() payable public {13 // TODO14 }1516 function sell(uint256 amount) public {17 // TODO18 }1920}Exibir tudoCopiar
Agora temos nosso DEX, que tem seu próprio token reserva disponível. O contrato tem duas funções:
buy
: o usuário pode enviar ether e obter tokens em trocasell
: o usuário pode decidir enviar tokens para recuperar ether
A função de compra
Vamos programar a função de compra. Primeiro, precisaremos verificar a quantidade de ether que a mensagem contém e verificar se os contratos possuem tokens suficientes e se a mensagem tem algum ether. Se o contrato possui tokens suficientes, enviará o número de tokens para o usuário e emitirá o evento Bought
.
Observe que, se chamarmos a função require
em caso de um erro, o ether enviado será diretamente revertido e retornado para o usuário.
Para simplificar, apenas trocamos 1 "token" por 1 "Wei".
1function buy() payable public {2 uint256 amountTobuy = msg.value;3 uint256 dexBalance = token.balanceOf(address(this));4 require(amountTobuy > 0, "You need to send some ether");5 require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");6 token.transfer(msg.sender, amountTobuy);7 emit Bought(amountTobuy);8}Copiar
No caso de a compra ser bem-sucedida, devemos ver dois eventos na transação: o token Transfer
e o evento Bought
.
A função de venda
A função responsável pela venda primeiro exigirá que o usuário tenha aprovado o valor, chamando a função approve
antecipadamente. Aprovar a transferência requer que o token ERC20Basic instanciado pelo DEX seja chamado pelo usuário. Isso pode ser feito chamando a função token()
do contrato da DEX para recuperar o endereço onde a DEX implantou o contrato ERC20Basic chamado token
. Em seguida, criamos uma instância desse contrato em nossa sessão e chamamos sua função approve
. Então podemos chamar a função sell
da DEX e trocar nossos tokens de volta por ether. Por exemplo, é assim que fica em uma sessão interativa usando brownie:
1#### Python in interactive brownie console...23# deploy the DEX4dex = DEX.deploy({'from':account1})56# call the buy function to swap ether for token7# 1e18 is 1 ether denominated in wei8dex.buy({'from': account2, 1e18})910# get the deployment address for the ERC20 token11# that was deployed during DEX contract creation12# dex.token() returns the deployed address for token13token = ERC20Basic.at(dex.token())1415# call the token's approve function16# approve the dex address as spender17# and how many of your tokens it is allowed to spend18token.approve(dex.address, 3e18, {'from':account2})19Exibir tudoCopiar
Então quando a função sell é chamada, verificamos se a transferência do endereço do remetente para o endereço do contrato foi bem-sucedida e depois enviamos os Ethers para o endereço de chamada.
1function sell(uint256 amount) public {2 require(amount > 0, "You need to sell at least some tokens");3 uint256 allowance = token.allowance(msg.sender, address(this));4 require(allowance >= amount, "Check the token allowance");5 token.transferFrom(msg.sender, address(this), amount);6 payable(msg.sender).transfer(amount);7 emit Sold(amount);8}Copiar
Se tudo funcionar, você deverá ver 2 eventos (uma Transferência
e Vendido
) na transação e seu saldo de token e saldo de ether atualizados.
Neste tutorial, vimos como verificar o saldo e a dedução de um token ERC-20 e também como chamar a Transfer
e TransferFrom
de um contrato inteligente ERC20 usando a interface.
Uma vez feita a transação, temos um tutorial JavaScript esperando e pegando detalhes de uma transação(opens in a new tab) feitas para seu contrato e um tutorial para decodificar eventos gerados em transferências de tokens e outros eventos(opens in a new tab) desde que você tenha um ABI.
Aqui está o código completo para o tutorial:
1pragma solidity ^0.8.0;23interface IERC20 {45 function totalSupply() external view returns (uint256);6 function balanceOf(address account) external view returns (uint256);7 function allowance(address owner, address spender) external view returns (uint256);89 function transfer(address recipient, uint256 amount) external returns (bool);10 function approve(address spender, uint256 amount) external returns (bool);11 function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);121314 event Transfer(address indexed from, address indexed to, uint256 value);15 event Approval(address indexed owner, address indexed spender, uint256 value);16}171819contract ERC20Basic is IERC20 {2021 string public constant name = "ERC20Basic";22 string public constant symbol = "ERC";23 uint8 public constant decimals = 18;242526 mapping(address => uint256) balances;2728 mapping(address => mapping (address => uint256)) allowed;2930 uint256 totalSupply_ = 10 ether;313233 constructor() {34 balances[msg.sender] = totalSupply_;35 }3637 function totalSupply() public override view returns (uint256) {38 return totalSupply_;39 }4041 function balanceOf(address tokenOwner) public override view returns (uint256) {42 return balances[tokenOwner];43 }4445 function transfer(address receiver, uint256 numTokens) public override returns (bool) {46 require(numTokens <= balances[msg.sender]);47 balances[msg.sender] = balances[msg.sender]-numTokens;48 balances[receiver] = balances[receiver]+numTokens;49 emit Transfer(msg.sender, receiver, numTokens);50 return true;51 }5253 function approve(address delegate, uint256 numTokens) public override returns (bool) {54 allowed[msg.sender][delegate] = numTokens;55 emit Approval(msg.sender, delegate, numTokens);56 return true;57 }5859 function allowance(address owner, address delegate) public override view returns (uint) {60 return allowed[owner][delegate];61 }6263 function transferFrom(address owner, address buyer, uint256 numTokens) public override returns (bool) {64 require(numTokens <= balances[owner]);65 require(numTokens <= allowed[owner][msg.sender]);6667 balances[owner] = balances[owner]-numTokens;68 allowed[owner][msg.sender] = allowed[owner][msg.sender]-numTokens;69 balances[buyer] = balances[buyer]+numTokens;70 emit Transfer(owner, buyer, numTokens);71 return true;72 }73}747576contract DEX {7778 event Bought(uint256 amount);79 event Sold(uint256 amount);808182 IERC20 public token;8384 constructor() {85 token = new ERC20Basic();86 }8788 function buy() payable public {89 uint256 amountTobuy = msg.value;90 uint256 dexBalance = token.balanceOf(address(this));91 require(amountTobuy > 0, "You need to send some ether");92 require(amountTobuy <= dexBalance, "Not enough tokens in the reserve");93 token.transfer(msg.sender, amountTobuy);94 emit Bought(amountTobuy);95 }9697 function sell(uint256 amount) public {98 require(amount > 0, "You need to sell at least some tokens");99 uint256 allowance = token.allowance(msg.sender, address(this));100 require(allowance >= amount, "Check the token allowance");101 token.transferFrom(msg.sender, address(this), amount);102 payable(msg.sender).transfer(amount);103 emit Sold(amount);104 }105106}Exibir tudoCopiar
Última edição: @nhsz(opens in a new tab), 15 de agosto de 2023