Esse é o sétimo publish da série Meu primeiro sensible contract, que tem a intenção de ensinar ao longo de sete semanas alguns conceitos do solidity até construirmos um token baseado no ERC-20 com alguns testes unitários.
Nesse publish vamos terminar de implementar as funções que faltou no token ERC-20 que criamos.
Ferramentas
Nesse publish vamos utilizar o VS Code para editar o código, o Node.js para instalar e executar o código.
Vamos utilizar o mesmo projeto que criamos no publish Subindo meu primeiro sensible contract para blockchain, caso você não tenha visto clique aqui, caso você não tenha mais o código você pode pegar aqui.
CryptoCoin
Nesse publish vamos implementar as funções e eventos que faltaram no token ERC-20 que criamos no publish Criando um token ERC-20.
Vamos começar adicionando mais alguns métodos na nossa interface, a nossa interface atualmente está assim:
interface IERC20 {
operate totalSupply() exterior view returns(uint256);
operate balanceOf(tackle account) exterior view returns(uint256);
operate switch(tackle to, uint256 amount) exterior returns(bool);
occasion Approval(tackle proprietor, tackle spender, uint256 worth);
}
Vamos adicionar as seguintes funções:
-
allowance
: Retorna o número de tokens que alguém pode transferir em nome de outro endereço. -
approve
: Outline uma quantidade de tokens que pode ser transferida em nome de outro endereço. -
transferFrom
: Transfere uma quantidade de tokens para outro endereço utilizando o mecanismo de permissão. -
increaseAllowance
: Aumenta a quantidade de tokens que pode ser transferida em nome de outro endereço. -
decreaseAllowance
: Diminui a quantidade de tokens que pode ser transferida em nome de outro endereço.
E vamos adicionar mais um evento:
-
Approval
: Emite um evento quando um endereço aprova uma quantidade de tokens para outro endereço.
Organização do código
Não existe uma forma certa de estruturar o código dos nossos contratos, mas para facilitar o entendimento vamos utilizar o seguinte padrão:
Enum: Onde criamos nossos Enums, que é um tipo de dado utilizado para armazenar um conjunto de valores constantes que não pode ser modificado.
Properties: Onde criamos nossas variáveis;
Modifiers: Onde criamos nossos modificadores;
Occasions: Onde criamos nossos eventos;
Constructor: Onde criamos nosso construtor;
Public Features: Onde criamos nossas funções públicas;
Enum
Vamos criar um enum
de standing onde vamos definir o estado do nosso contrato.
// Enum
enum Standing { PAUSED, ACTIVE, CANCELLED }
Variáveis
Vamos criar mais algumas variáveis para armazenar o endereço do dono do contrato, o estado do contrato, o valor do token e vamos criar dois mapping
para verificar o saldo de um endereço e um mapping
que tem outro mapping
guardamos que endereço tem permissão de transferir uma quantidade de tokens em nome de outro endereço.
// Properties
string public fixed identify = "CryptoCoin";
string public fixed image = "CRC";
uint8 public fixed decimals = 18;
uint256 non-public totalsupply;
// <-- Adicionado nesse publish
tackle non-public proprietor;
Standing contractState;
uint256 valorToken;
mapping(tackle => uint256) non-public addressToBalance;
mapping(tackle => mapping (tackle => uint256)) allowed;
// -->
Modificadores
Vamos criar alguns modificadores para conseguirmos diferenciar nosso contrato.
O primeiro modificador que iremos criar é o isOwner
, que irá verificar se o endereço que está tentando acessar o contrato é o dono do contrato.
// Modifiers
modifier isOwner() {
require(msg.sender == proprietor , "Sender will not be proprietor!");
_;
}
O segundo modificador será o isActive
, que irá verificar se o contrato está ativo.
modifier isActive() {
require(contractState == Standing.ACTIVE, "Contract will not be Energetic!");
_;
}
Eventos
Vamos criar um evento chamado Mint
que vamos usar mais para frente para ser emitido quando criarmos mais tokens, esse evento vai receber o endereço do dono do contrato, o saldo do endereço do dono, a quantidade de tokens que queremos criar e o complete de tokens que já existem no contrato.
// Occasions
occasion Mint(tackle proprietor, uint256 BalanceOwner, uint256 quantity, uint256 provide);
E vamos criar um evento chamado Burn
que vamos usar mais para frente também para ser emitido quando ‘queimamos’ tokens, esse evento vai receber o endereço do dono do contrato, a quantidade de tokens que queremos queimar e complete de tokens que já existem no contrato.
occasion Burn(tackle proprietor, uint256 worth, uint256 provide);
Construtor
No construtor vamos passar o complete de tokens como parâmetro, definir o dono do contrato como quem realiza o deploy, o totalsupply
recebendo o complete e atribuir todos os tokens inicialmente para carteira do dono do contrato e o standing inicial do contrato como ativo.
// Constructor
constructor(uint256 complete) {
proprietor = msg.sender;
totalsupply = complete;
addressToBalance[msg.sender] = totalsupply;
contractState = Standing.ACTIVE;
}
Agora vamos adicionar mais algumas funções para conseguirmos gerenciar o standing do nosso contrato, criar ou queimar tokens, realizar transferência em nome de um terceiro e matar nosso contrato, os funções do nosso contrato atualmente são essas:
//Public Features
operate totalSupply() public override view returns(uint256) {
return totalsupply;
}
operate balanceOf(tackle account) public override view returns(uint256) {
return addressToBalance[account];
}
operate switch(tackle to, uint256 amount) public override returns(bool) {
require(addressToBalance[msg.sender] >= amount, "Inadequate Steadiness to Switch");
addressToBalance[msg.sender] = addressToBalance[msg.sender] - amount;
addressToBalance[to] = addressToBalance[to] + amount;
emit Switch(msg.sender, to, amount);
return true;
}
Vamos implementar as funções que criamos na nossa interface.
Quantidade restante de tokens
Vamos criar uma função pública chamada allowance
que retorna o número restante de tokens que um terceiro pode transferir em nome de outro endereço. Ela espera dois parâmetros from
endereço da carteira que tem permissão de realizar transferência em nome do spender
.
operate allowance(tackle from, tackle spender) public override view returns (uint) {
return allowed[from][spender];
}
Permissão de transferência em nome de terceiro
Vamos criar uma função pública chamada approve
que permite que um terceiro transfira tokens em nome de outro endereço. Ela espera dois parâmetros: ‘spender’ endereço de quem eu quero dar permissão de realizar a transferência e quantity
quantidade de tokens que eu quero dar permissão. Dentro desta função vamos chamar o evento allowed
passando a carteira de quem está dando permissão, a carteira de quem está recebendo a permissão e a quantidade de tokens que está sendo permitida.
Após isso, vamos emitir o evento Approval
passando a carteira de quem está dando permissão, a carteira de quem está recebendo a permissão e a quantidade de tokens que está sendo permitida.
operate approve(tackle spender, uint256 quantity) public override returns (bool) {
allowed[msg.sender][spender] = quantity;
emit Approval(msg.sender, spender, quantity);
return true;
}
Transferência em nome de terceiro
Vamos criar uma função pública chamada transferFrom
que irá movimentar tokens de uma carteira para outra. Ela espera três parâmetros sender
endereço da carteira que tem permissão de realizar transferência em nome do recipient
, recipient
endereço da carteira que vai receber os tokens e quantity
quantidade de tokens que vai ser transferida.
Dentro desta função vamos verificar se o endereço que está tentando realizar a transferência tem permissão para realizar a transferência, se o valor que está sendo transferido é maior que zero e se o endereço que está transferindo tem saldo suficiente para realizar a transferência. Após isso, vamos emitir o evento Switch
passando a carteira de quem está realizando a transferência, a carteira de quem está recebendo a transferência e a quantidade de tokens que está sendo transferida.
operate transferFrom(tackle sender, tackle recipient, uint256 quantity)public isActive override returns(bool) {
require(quantity > 0, "Tranfer worth invalid will not be zero.");
require(quantity <= balanceOf(sender), "Inadequate Steadiness to Switch");
require(quantity <= allowed[sender][msg.sender], "No allowed");
addressToBalance[sender] -= quantity;
allowed[sender][msg.sender] -= quantity;
addressToBalance[recipient] += quantity;
emit Switch(sender, recipient, quantity);
return true;
}
Aumentar permissão de transferência em nome de terceiro
Vamos criar uma função pública chamada increaseAllowance
que irá aumentar a permissão de transferência em nome de terceiro. Ela espera dois parâmetros: ‘spender’ endereço da carteira que vai receber a permissão e addedValue
quantidade de tokens que vai ser adicionada à permissão.
Dentro desta função vamos verificar se o endereço que está tentando receber a permissão é um endereço válido. Após isso, vamos aumentar a permissão de transferência e emitir o evento Approval
passando a carteira de quem está chamando a função, a carteira de quem está recebendo a permissão e a quantidade de tokens que foram aprovados.
operate increaseAllowance(tackle spender, uint256 addedValue) public override returns (bool){
require(spender != tackle(0), "Invalid tackle!");
allowed[msg.sender][spender] += addedValue;
emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
return true;
}
Diminuir permissão de transferência em nome de terceiro
Vamos criar uma função pública chamada decreaseAllowance
que irá diminuir a permissão de transferência em nome de terceiro. Ela espera dois parâmetros: ‘spender’ endereço da carteira que vai receber a permissão e subtractedValue
quantidade de tokens que vai ser removida da permissão.
Dentro desta função vamos verificar se o endereço que está tentando receber a permissão é um endereço válido. Após isso, vamos diminuir a permissão de transferência e emitir o evento Approval
passando a carteira de quem está chamando a função, a carteira de quem está recebendo a permissão e a quantidade de tokens que foram aprovados.
operate decreaseAllowance(tackle spender, uint256 subtractedValue) public override returns (bool) {
require(spender != tackle(0), "Invalid tackle!");
allowed[msg.sender][spender] -= subtractedValue;
emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
return true;
}
Estado do contrato
Vamos criar uma função pública chamada state
que irá retornar o estado do contrato. Ela não espera nenhum parâmetro e retorna o valor de contractState
.
operate state() public view returns(Standing) {
return contractState;
}
Mudar o estado do contrato
Vamos criar uma função chamada setState
que irá mudar o estado do contrato. Ela espera um parâmetro standing
que é o novo estado do contrato, como contractState
é um enum então devemos passar um numérico de 0 a 2. Essa função só pode ser chamada pelo dono do contrato, por isso vamos passar o nosso modificado ìsOwner
que verifica se o endereço que está chamando a função é o dono do contrato.
Dentro desta função vamos verificar se o novo estado do contrato é diferente do estado atual do contrato, se o novo estado do contrato é um estado válido. Após isso vamos definir o valor de contractState
para o estado passado como parâmetro.
operate setState(uint8 standing) public isOwner {
require(standing <= 1, "Invalid standing");
if(standing == 0) {
require(contractState != Standing.PAUSED, "The standing is already PAUSED");
contractState = Standing.PAUSED;
}else if(standing == 1){
require(contractState != Standing.ACTIVE, "The standing is already ACTIVE");
contractState = Standing.ACTIVE;
}
}
Cunhando mais tokens
Vamos criar uma função pública chamada mint
que irá cunhar mais tokens. Ela espera um parâmetro quantity
que é a quantidade de tokens que vai ser cunhada. Essa função só pode ser chamada pelo dono do contrato, por isso vamos passar o nosso modificado ìsOwner
que verifica se o endereço que está chamando a função é o dono do contrato.
Dentro desta função vamos verificar se a quantidade de tokens que vai ser cunhada é maior que zero, se for maior que zero vamos aumentar o complete de tokens em circulação e aumentar a quantidade de tokens do dono do contrato. Após isso, vamos emitir o evento Mint
passando o endereço do dono do contrato, o saldo da carteira do dono do contrato e a quantidade de tokens existentes.
operate mint(uint256 quantity) public isActive isOwner {
require(quantity > 0, "Invalid mint worth.");
totalsupply += quantity;
addressToBalance[owner] += quantity;
emit Mint(proprietor,addressToBalance[owner], quantity, totalSupply());
}
Queimando tokens
Vamos criar uma função pública chamada burn
que irá queimar tokens. Ela espera um parâmetro quantity
que é a quantidade de tokens que vai ser queimada. Essa função só pode ser chamada pelo dono do contrato, por isso vamos passar o nosso modificado ìsOwner
que verifica se o endereço que está chamando a função é o dono do contrato.
Dentro desta função vamos verificar se a quantidade de tokens que vai ser queimada é maior que zero, se for maior que zero vamos verificar se a quantidade de tokens que vai ser queimada é menor ou igual a quantidade de tokens em circulação, se for menor ou igual vamos verificar se a quantidade de tokens que vai ser queimada é menor ou igual a quantidade de tokens do dono do contrato. Após isso, vamos diminuir o complete de tokens em circulação e diminuir a quantidade de tokens do dono do contrato. Após isso vamos emitir o evento Burn
passando o endereço do dono do contrato, a quantidade de tokens queimados e a quantidade de tokens existentes.
operate burn(uint256 quantity) public isActive isOwner {
require(quantity > 0, "Invalid burn worth.");
require(totalSupply() >= quantity, "The quantity exceeds your stability.");
require(balanceOf(proprietor) >= quantity, "The worth exceeds the proprietor's out there quantity");
totalsupply -= quantity;
addressToBalance[owner] -= quantity;
emit Burn(proprietor, quantity, totalSupply());
}
Matando o contrato
Vamos criar uma função pública chamada kill
que irá matar o contrato. Essa função só pode ser chamada pelo dono do contrato, por isso vamos passar o nosso modificado ìsOwner
que verifica se o endereço que está chamando a função é o dono do contrato.
Dentro desta função vamos mudar o estado do contrato para CANCELLED
e vamos destruir o contrato e enviar todos os Ether do contrato para o dono do contrato.
operate kill() public isOwner {
contractState = Standing.CANCELLED;
selfdestruct(payable(proprietor));
}
Como ficou nosso código
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
interface IERC20 {
operate totalSupply() exterior view returns(uint256);
operate balanceOf(tackle account) exterior view returns(uint256);
operate switch(tackle recipient, uint256 quantity) exterior returns(bool);
operate allowance(tackle proprietor, tackle spender) exterior view returns (uint256);
operate approve(tackle spender, uint256 quantity) exterior returns (bool);
operate transferFrom(tackle sender, tackle recipient, uint256 quantity) exterior returns (bool);
operate increaseAllowance(tackle spender, uint256 addedValue) exterior returns (bool) ;
operate decreaseAllowance(tackle spender, uint256 subtractedValue) exterior returns (bool) ;
occasion Switch(tackle from, tackle to, uint256 worth);
occasion Approval(tackle proprietor, tackle spender, uint256 worth);
}
contract CryptoCoin is IERC20 {
// Enum
enum Standing { PAUSED, ACTIVE, CANCELLED }
//Properties
tackle non-public proprietor;
string public fixed identify = "CryptoCoin";
string public fixed image = "CRC";
uint8 public fixed decimals = 18;
uint256 non-public totalsupply;
Standing contractState;
uint256 valorToken;
mapping(tackle => mapping (tackle => uint256)) allowed;
mapping(tackle => uint256) non-public addressToBalance;
// Modifiers
modifier isOwner() {
require(msg.sender == proprietor , "Sender will not be proprietor!");
_;
}
modifier isActive() {
require(contractState == Standing.ACTIVE, "Contract will not be Energetic!");
_;
}
// Occasions
occasion Mint(tackle proprietor, uint256 BalanceOwner, uint256 quantity, uint256 provide);
occasion Burn(tackle proprietor, uint256 worth, uint256 provide);
//Constructor
constructor(uint256 complete) {
proprietor = msg.sender;
totalsupply = complete;
addressToBalance[msg.sender] = totalsupply;
contractState = Standing.ACTIVE;
}
//Public Features
operate totalSupply() public override view returns(uint256) {
return totalsupply;
}
operate balanceOf(tackle tokenOwner) public override view returns(uint256) {
return addressToBalance[tokenOwner];
}
operate switch(tackle recipient, uint256 quantity) public isActive override returns(bool) {
require(quantity <= addressToBalance[msg.sender], "Inadequate Steadiness to Switch");
addressToBalance[msg.sender] -= quantity;
addressToBalance[recipient] += quantity;
emit Switch(msg.sender, recipient, quantity);
return true;
}
operate allowance(tackle from, tackle spender) public override view returns (uint) {
return allowed[from][spender];
}
operate approve(tackle spender, uint256 quantity) public override returns (bool) {
allowed[msg.sender][spender] = quantity;
emit Approval(msg.sender, spender, quantity);
return true;
}
operate transferFrom(tackle sender, tackle recipient, uint256 quantity)public isActive override returns(bool) {
require(quantity > 0, "Tranfer worth invalid will not be zero.");
require(quantity <= balanceOf(sender), "Inadequate Steadiness to Switch");
require(quantity <= allowed[sender][msg.sender], "No allowed");
addressToBalance[sender] -= quantity;
allowed[sender][msg.sender] -= quantity;
addressToBalance[recipient] += quantity;
emit Switch(sender, recipient, quantity);
return true;
}
operate increaseAllowance(tackle spender, uint256 addedValue) public override returns (bool){
require(spender != tackle(0), "Invalid tackle!");
allowed[msg.sender][spender] += addedValue;
emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
return true;
}
operate decreaseAllowance(tackle spender, uint256 subtractedValue) public override returns (bool) {
require(spender != tackle(0), "Invalid tackle!");
allowed[msg.sender][spender] -= subtractedValue;
emit Approval(msg.sender, spender, allowed[msg.sender][spender]);
return true;
}
operate state() public view returns(Standing) {
return contractState;
}
operate setState(uint8 standing) public isOwner {
require(standing <= 1, "Invalid standing");
if(standing == 0) {
require(contractState != Standing.PAUSED, "The standing is already PAUSED");
contractState = Standing.PAUSED;
}else if(standing == 1){
require(contractState != Standing.ACTIVE, "The standing is already ACTIVE");
contractState = Standing.ACTIVE;
}
}
operate mint(uint256 quantity) public isActive isOwner {
require(quantity > 0, "Invalid mint worth.");
totalsupply += quantity;
addressToBalance[owner] += quantity;
emit Mint(proprietor,addressToBalance[owner], quantity, totalSupply());
}
operate burn(uint256 quantity) public isActive isOwner {
require(quantity > 0, "Invalid burn worth.");
require(totalSupply() >= quantity, "The quantity exceeds your stability.");
require(balanceOf(proprietor) >= quantity, "The worth exceeds the proprietor's out there quantity");
totalsupply -= quantity;
addressToBalance[owner] -= quantity;
emit Burn(proprietor, quantity, totalSupply());
}
// Kill
operate kill() public isOwner {
contractState = Standing.CANCELLED;
selfdestruct(payable(proprietor));
}
}
Deploy
Caso você queira entender com mais detalhes de como realizar o deploy de um sensible contract clique aqui.
Na pasta script
vamos criar um arquivo chamado deploy-cryptoCoin.js
onde vamos escrever nossos códigos para deployar o contrato.
No arquivo deploy-cryptoCoin.js
vamos importar os arquivos do hardhat e criar nossa função assíncrona essential
e capturar o retorno dos erros caso tenha algum.
const hre = require("hardhat");
async operate essential() {}
essential().catch((error) => {
console.error(error);
course of.exitCode = 1;
});
Dentro da função essential
vamos nos conectar ao contrato CryptoCoin, realizar o deploy deste contrato criando mil tokens e escrever no console o endereço do contrato de token.
const hre = require("hardhat");
async operate essential() {
const CryptoCoin = await hre.ethers.getContractFactory("CryptoCoin");
const cryptoCoin = await CryptoCoin.deploy(1000);
await cryptoCoin.deployed();
console.log("Endereço do CryptoCoin", cryptoCoin.tackle);
}
essential().catch((error) => {
console.error(error);
course of.exitCode = 1;
});
Como configuramos o hardhat no publish anterior, no terminal vamos executar o seguinte comando:
npx hardhat run scripts/deploy-cryptoCoin.js --network goerli
Se tudo estiver certo esse irá retornar o endereço do nosso contrato.
Copiando os endereços e entrando no Goerli Etherscan podemos ver nossos contratos na blockchain da Goerli.
Esses são os contratos que subimos nesse publish.
Conclusão
Esse foi o sétimo publish da série “Meu primeiro sensible contract”.
Se tudo deu certo, agora você tem um sensible contract que é capaz criar e queimar tokens, além de ter um sistema de pausa e cancelamento do contrato.
Se você gostou do conteúdo e te ajudou de alguma forma, deixe um like para ajudar o conteúdo a chegar para mais pessoas.
Hyperlink do repositório
https://github.com/viniblack/meu-primeiro-smart-contract
Vamos trocar uma ideia ?
Fique a vontade para me chamar para trocarmos uma ideia, aqui embaixo está meu contato.