Meu primeiro smart contract: Criando airdrop de tokens ERC-20

viniblack

ViniBlack

Posted on August 17, 2022

Meu primeiro smart contract: Criando airdrop de tokens ERC-20

Esse é o sexto post da série Meu primeiro smart 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 post vamos criar um contrato de airdrop de tokens utilizando o hardhat.

Ferramentas

Nesse post vamos utilizar o VS Code para editar o código, o Node.js para instalar e executar o código.

Vamos continuar usando o mesmo projeto do post anterior, caso você não tenha visto o post anterior clique aqui.

Airdrop

Os airdrops de criptomoedas são uma estratégia de marketing usada pelas empresas para fornecer tokens a traders de criptomoedas existentes gratuitamente ou em troca de um trabalho promocional mínimo.

No VS Code, vamos criar dois novos arquivos dentro da pasta contracts um chamado 03-tokens.sol e outro chamado 05-airdrop.sol, dentro do 03-tokens.sol vamos copiar o código que criamos no post Criando um token ERC-20, caso você não tenho o código clique aqui. E colar dentro de 03-tokens.sol, e no 05-airdrop.sol vamos declarar as licenças do nosso contrato, a versão do contrato e dar um nome ao contrato como já fizemos antes, mas agora vamos importar 03-tokens.sol também.

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

import "./03-token.sol";

contract Airdrop  {

}
Enter fullscreen mode Exit fullscreen mode

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:

Estrutura do código

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;
Events: Onde criamos nossos eventos;
Constructor: Onde criamos nosso construtor;
Public Functions: Onde criamos nossas funções públicas;
Private Functions: Onde criamos nossas funções privadas;

Enum

Para criar um enum precisamos declarar o nome e entre as chaves os valores que ele pode ter.

// Enum
enum Status { PAUSED, ACTIVE, CANCELLED }
Enter fullscreen mode Exit fullscreen mode

Clique aqui para ver mais sobre o enum.

Variáveis

Para fazer o nosso contrato de airdrop, precisamos criar algumas variáveis.

  • owner: Que vai ser uma variável privada do tipo address;
  • subscribers: Que vai ser um array de endereços privada do tipo address;
  • tokenAddress: Que vai ser do tipo address;
  • Status: Que vai receber contractState, quando criarmos o nosso construtor isso vai fazer mais sentido;
  • subscribersMapping: Que vai ser um mapping que irá receber address como "chave" e bool como "valor";
// Properties
address private owner;
address[] private subscribers;
address public tokenAddress;

Status contractState;
mapping(address => bool) subscribersMapping;
Enter fullscreen mode Exit fullscreen mode

Modifiers

Vamos criar alguns modifiers para conseguirmos gerenciar nosso contrato.

O primeiro modifier é o isOwner, que vai ser responsável por verificar se o endereço que está tentando acessar o contrato é o dono do contrato.

modifier isOwner() {
  require(msg.sender == owner , "Sender is not owner!");
  _;
}
Enter fullscreen mode Exit fullscreen mode

O segundo modifier é o isActived, que vai ser responsável por verificar se o nosso contrato está ativo.

modifier isActived() {
  require(contractState == Status.ACTIVE, "The contract is not active!");
  _;
}
Enter fullscreen mode Exit fullscreen mode

Construtor

Como vamos fazer um contrato de airdrop de token, precisamos passar o endereço do contrato do token como parâmetro, então vamos definir que tokenAddress irá receber o parâmetro token que é o endereço do contrato do token, e vamos definir que owner vai ser o endereço que realizar o deploy do contrato de airdrop e definimos que contractState vai iniciar com Status.PAUSED.

// Constructor
constructor(address token) {
  tokenAddress = token;
  owner = msg.sender;
  contractState = Status.PAUSED;
}
Enter fullscreen mode Exit fullscreen mode

Funções privadas

Verifica inscrição

Vamos criar uma função privada chamada hasSubscribed que vai verificar se o endereço que está tentando acessar o contrato já está inscrito, para isso irá receber um endereço como parâmetro e irá retornar um bool.

function hasSubscribed(address subscriber) private view returns(bool) {

}
Enter fullscreen mode Exit fullscreen mode

Dentro da função vamos verificar se o endereço que está tentando acessar o contrato já está inscrito, se estiver inscrito vamos retornar uma mensagem de erro, se não estiver inscrito vamos retornar true.

function hasSubscribed(address subscriber) private view returns(bool) {
  require(subscribersMapping[subscriber] != true, "You already registered");

  return true;
}
Enter fullscreen mode Exit fullscreen mode

Funções públicas

Inscrever-se

Vamos criar uma função pública chamada subscribe, só podemos chamar subscribe caso o contrato esteja ativo, por isso vamos utilizar nosso modifier isActived e essa função irá retornar um bool.

function subscribe() public isActived returns(bool) {

}
Enter fullscreen mode Exit fullscreen mode

Dentro da função subscribe vamos chamar a função hasSubscribed para verificar se o endereço que está tentando acessar o contrato já está inscrito, se não estiver inscrito vamos inscrever esse endereço no array de endereços subscribers e vamos adicionar o endereço como chave no mapping subscribersMapping e true como valor.

function subscribe() public isActived returns(bool) {
  hasSubscribed(msg.sender);
  subscribers.push(msg.sender);
  subscribersMapping[msg.sender] = true;
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Verificando status do contrato

Vamos criar uma função pública chamada state que vai retornar o status do contrato.

function state() public view returns(Status) {
  return contractState;
}
Enter fullscreen mode Exit fullscreen mode

Mudando o status do contrato

Vamos criar uma função pública chamada changeState que irá receber um número como parâmetro, esse número pode ser 0-PAUSED e 1-ACTIVE, só podemos definir status cancelado quando o contrato estiver "morto", essa função só pode ser chamado pelo dono do contrato.

function changeState(uint8 status) public isOwner {

}
Enter fullscreen mode Exit fullscreen mode

Agora vamos realizar uma validação para verificar se o status que está tentando definir é válido, se for válido verificamos se o contrato já não está com o status que estamos tentando definir se não estiver mudamos o status do contrato.

function changeState(uint8 status) public isOwner {
  require(status <= 1, "Invalid status");

  if(status == 0) {
    require(contractState != Status.PAUSED, "The status is already PAUSED");
    contractState = Status.PAUSED;
  }else {
    require(contractState != Status.ACTIVE, "The status is already ACTIVE");
    contractState = Status.ACTIVE;
  }
}
Enter fullscreen mode Exit fullscreen mode

Executando o airdrop

Vamos criar uma função pública chamada execute que irá retornar um bool que só vai poder ser chamada quando o contrato estiver ativo e só o dono do contrato pode executar a função.

function execute() public isOwner isActived returns(bool) {

}
Enter fullscreen mode Exit fullscreen mode

Dentro da função vamos criar uma variável chamada balance que irá receber o saldo de tokens do contrato de airdrop no contrato CryptoToken.

function execute() public isOwner isActived returns(bool) {
  uint256 balance = CryptoToken(tokenAddress).balanceOf(address(this));
}
Enter fullscreen mode Exit fullscreen mode

A lógica que vamos utilizar para distribuir nossos tokens é pegar o total de tokens e dividir pelo total de endereços que se inscreveram no nosso contrato de airdrop .
Então vamos criar uma variável chamada amountToTransfer que irá receber a quantidade total de tokens e dividir pela quantidade total de endereços inscritos no contrato de airdrop.

function execute() public isOwner isActived returns(bool) {
  uint256 balance = CryptoToken(tokenAddress).balanceOf(address(this));
  uint256 amountToTransfer = balance / subscribers.length;
}
Enter fullscreen mode Exit fullscreen mode

Para realizarmos o pagamento para todas as pessoas inscritas, vamos criar um looping para percorrer todos os endereços inscritos no contrato de airdrop, vamos verificar se o endereço cadastrado é um endereço válido e depois vamos utilizar o CryptoToken passando o endereço do contrato de token e realizar uma transferência passando o endereço da pessoa e a quantidade de tokens que vamos transferir e no final vamos retornar true.

function execute() public isOwner isActived returns(bool) {
  uint256 balance = CryptoToken(tokenAddress).balanceOf(address(this));
  uint256 amountToTransfer = balance / subscribers.length;
  for (uint i = 0; i < subscribers.length; i++) {
    require(subscribers[i] != address(0));
    require(CryptoToken(tokenAddress).transfer(subscribers[i], amountToTransfer));
  }

  return true;
}
Enter fullscreen mode Exit fullscreen mode

Matando o contrato

Como as coisas que são escritas na blockchain não podem ser alteradas após realizarmos o deploy para ela, o solidity criou uma forma de conseguirmos "matar" nosso contrato ele irá continuar na blockchain, mas não será mais possível executar nenhuma função deste contrato.
Para isso, vamos criar uma função pública chamada kill que só o dono do contrato poderá executar essa função.

function kill() public isOwner {

}
Enter fullscreen mode Exit fullscreen mode

Dentro desta função vamos definir que o status do contrato será cancelado e depois vamos chamar uma função chamada selfdestruct para matar o contrato passando o endereço do dono do contrato, com isso antes de matar o contrato vamos enviar todos os Ethers para o endereço do dono do contrato caso tiver algum.

function kill() public isOwner {
  contractState = Status.CANCELLED;
  selfdestruct(payable(owner));
}
Enter fullscreen mode Exit fullscreen mode

Caso queira entender um pouco mais sobre selfdestruct clique aqui

Como ficou nosso código

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

import "./03-token.sol";

contract Airdrop  {
  // Enum
  enum Status { PAUSED, ACTIVE, CANCELLED } // mesmo que uint8

  // Properties
  address private owner;
  address[] private subscribers;
  address public tokenAddress;

  Status contractState;
  mapping(address => bool) subscribersMapping;

  // Modifiers
  modifier isOwner() {
    require(msg.sender == owner , "Sender is not owner!");
    _;
  }

  modifier isActived() {
    require(contractState == Status.ACTIVE, "The contract is not active!");
    _;
  }

  // Constructor
  constructor(address token) {
    owner = msg.sender;
    tokenAddress = token;
    contractState = Status.PAUSED;
  }


  // Public Functions
  function subscribe() public isActived returns(bool) {
    hasSubscribed(msg.sender);
    subscribers.push(msg.sender);
    subscribersMapping[msg.sender] = true;
    return true;
  }

  function execute() public isOwner isActived returns(bool) {
    uint256 balance = CryptoToken(tokenAddress).balanceOf(address(this));
    uint256 amountToTransfer = balance / subscribers.length;
    for (uint i = 0; i < subscribers.length; i++) {
      require(subscribers[i] != address(0));
      require(CryptoToken(tokenAddress).transfer(subscribers[i], amountToTransfer));
    }

    return true;
  }

  function state() public view returns(Status) {
    return contractState;
  }

  function changeState(uint8 status) public isOwner {
    require(status <= 1, "Invalid status");

    if(status == 0) {
      require(contractState != Status.PAUSED, "The status is already PAUSED");
      contractState = Status.PAUSED;
    }else if(status == 1){
      require(contractState != Status.ACTIVE, "The status is already ACTIVE");
      contractState = Status.ACTIVE;
    }
  }

  // Private Functions
  function hasSubscribed(address subscriber) private view returns(bool) {
    require(subscribersMapping[subscriber] != true, "You already registered");

    return true;
  }

  function kill() public isOwner {
    contractState = Status.CANCELLED;
    selfdestruct(payable(owner));
  }
}
Enter fullscreen mode Exit fullscreen mode

Deploy

Caso você não tenha visto o post anterior clique aqui onde eu explico com mais detalhes o que é fazer um deploy e como configurar o hardhat para subir nosso contrato para blockchain.
Na pasta script vamos criar um arquivo chamado deploy-airdrop.js onde vamos escrever nossos códigos para deployar o contrato.

No arquivo deploy-airdrop.js vamos importar os arquivos do hardhat e criar nossa função assíncrona main e capturar o retorno dos erros caso tenha algum.

const hre = require("hardhat");

async function main() {

}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Dentro da função main vamos nos conectar ao contrato CryptoToken, realizar o deploy deste contrato criando mil tokens e escrever no console o endereço do contrato de token.

const hre = require("hardhat");

async function main() {
  const CryptoToken = await hre.ethers.getContractFactory("CryptoToken");
  const cryptoToken = await CryptoToken.deploy(1000);
  await cryptoToken.deployed();
  console.log("Endereço do CryptoToken", cryptoToken.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Depois disso vamos se conectar ao contrato Airdrop, realizar o deploy passando o endereço do contrato CryptoToken como parâmetro e escrever no console o endereço do contrato de airdrop.

const hre = require("hardhat");

async function main() {
  const CryptoToken = await hre.ethers.getContractFactory("CryptoToken");
  const cryptoToken = await CryptoToken.deploy(1000);
  await cryptoToken.deployed();
  console.log("Endereço do CryptoToken", cryptoToken.address);

  const Airdrop = await hre.ethers.getContractFactory("Airdrop");
  const airdrop = await Airdrop.deploy(cryptoToken.address);
  await airdrop.deployed();
  console.log("Endereço do Airdrop", airdrop.address);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
Enter fullscreen mode Exit fullscreen mode

Como configuramos o hardhat no post anterior, no terminal vamos executar o seguinte comando:

npx hardhat run scripts/deploy-airdrop.js --network goerli
Enter fullscreen mode Exit fullscreen mode

Se tudo estiver certo esse irá retornar o endereço do nosso contrato.

Endereço dos nossos contratos

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 post.

Conclusão

Esse foi o sexto post da série "Meu primeiro smart contract".
Se tudo deu certo, agora você tem um smart contract que é capaz de realizar um airdrop de token ERC-20 rodando na testnet Goerli.

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.

deixa um like


Link 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.

https://www.linkedin.com/in/viniblack/

💖 💪 🙅 🚩
viniblack
ViniBlack

Posted on August 17, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related