WEB3DEV

Cover image for Implantando um Contrato de Staking de NFT na Gnosis Chain
Paulo Gio
Paulo Gio

Posted on

Implantando um Contrato de Staking de NFT na Gnosis Chain

https://chainstack.com/wp-content/uploads/2022/11/nft-staking-gnosis-1024x542.jpg

Introdução

Gnosis é uma blockchain baseada na EVM que atualmente é protegida por mais de 100 mil validadores. Ela visa tempos de transação rápidos e taxas de transação baixas. A rede principal da Gnosis e sua respectiva rede de testes, Chiado, usam xDai e o Chiado xDai como tokens nativos para suas respectivas redes blockchain.

Neste tutorial, implantaremos um contrato inteligente de staking de NFT na Gnosis Chain. Nosso projeto consiste em dois contratos:

  • Fuel.sol, representará nosso contrato inteligente NFT (ERC-721)
  • Rewards.sol, será nosso contrato de staking que receberá novos NFTs e emitirá recompensas na forma de tokens ERC-20.

Pré-requisitos

  • Qualquer editor de código de sua escolha em uma máquina que tenha NodeJs instalado.
  • Uma compreensão dos conceitos básicos de Solidity e Javascript, já que todo o nosso código será escrito nessas duas linguagens.
  • Usaremos o Truffle para compilar e implantar nossos contratos inteligentes. Será útil, mas não necessário estar familiarizado com o framework.

Configurando um ambiente de desenvolvimento com o Truffle

Antes de começarmos a escrever nossos contratos inteligentes, precisamos ter um ambiente de desenvolvimento configurado que nos permita compilar e testar nossos contratos inteligentes antes de implantá-los em uma rede em atividade. Embora existam excelentes frameworks de contratos inteligentes disponíveis, vamos escolher o Truffle neste tutorial. Para configurar um ambiente de desenvolvimento com o Truffle:

  1. Crie uma pasta chamada nftGnosis (ou qualquer outro nome que você queira).
  2. Abra a pasta no seu terminal e execute o comando npm init -y. Este comando inicializará um arquivo package.json vazio no seu diretório.
  3. Para instalar o Truffle em seu sistema, execute npm install -g truffle. Este comando instalará a versão mais recente do Truffle em seu sistema.
  4. Vamos verificar se o Truffle foi instalado corretamente em nosso sistema. Para fazer isso, execute truffle version. Este comando deve retornar a versão do Truffle instalada em seu sistema.
  5. Para inicializar um projeto básico do Truffle, primeiro, certifique-se de que seu terminal está apontando corretamente para a raiz do diretório do seu projeto. Agora, no terminal, execute truffle init. Este comando instalará um projeto Truffle básico em seu diretório local.
  6. É assim que seu terminal e diretório de projeto devem se parecer agora:

https://chainstack.com/wp-content/uploads/2022/11/Screenshot-2022-11-24-135426-1024x532.png

Estamos usando o VS Code como nosso editor, mas qualquer editor de código decente servirá.

Visualizando a estrutura do projeto Truffle

Depois de ter inicializado um projeto boilerplate do Truffle, seu diretório deve parecer com isso:

├── contracts 
  └── .gitkeep 
├── migrations 
  └── .gitkeep 
├── test 
  └── .gitkeep 
├── package.json 
├── truffle-config.js
Enter fullscreen mode Exit fullscreen mode

Vamos revisar isso rapidamente:

  • O arquivo .gitkeep é simplesmente usado como um espaço reservado em diretórios vazios, pois o Git não nos permite enviar diretórios vazios para repositórios remotos. Este arquivo não afeta nosso projeto de forma alguma.
  • O diretório de contratos (contracts) contém todos os nossos contratos inteligentes.
  • O diretório de migrações (migrations) contém arquivos JavaScript que são usados para implantar nossos contratos em redes ao vivo.
  • O diretório de testes (test) contém código que testa nossos contratos inteligentes. O Truffle suporta testes de contratos inteligentes em JavaScript, TypeScript e Solidity. Você pode ler mais sobre isso em sua documentação oficial.
  • O arquivo package.json contém uma referência a todos os pacotes npm que instalamos em nosso projeto.
  • O arquivo truffle-config, como o nome sugere, contém todas as configurações para o nosso projeto Truffle. Este é um arquivo JavaScript que exporta um objeto que contém todas as nossas configurações. Podemos definir uma ampla variedade de parâmetros dentro deste objeto, incluindo as redes, a versão Solidity e os URLs do provedor RPC, entre muitos outros. Vamos examinar isso com mais detalhes abaixo.

Escrevendo e compilando contratos inteligentes no Truffle

Antes de começarmos a escrever o código Solidity, vamos aprender um pouco sobre os contratos OpenZeppelin.

O OpenZeppelin nos oferece uma variedade de produtos, mas sua biblioteca de contratos é a que usaremos neste tutorial.

O Solidity, como linguagem, suporta herança. Isso significa que podemos usar componentes Solidity pré-construídos para aumentar nosso próprio código. O OpenZeppelin, por sua vez, tem uma enorme biblioteca de contratos e interfaces Solidity que podemos usar para construir contratos inteligentes ERC-20 e ERC-721 de maneira segura. Claro, poderíamos fazer o mesmo sem usar o OpenZeppelin, mas por que não usar uma biblioteca que foi completamente auditada e otimizada, economizando assim tempo e dinheiro?

Para começar com os contratos do OpenZeppelin, abra seu terminal e execute:

npm i @openzeppelin/contracts
Enter fullscreen mode Exit fullscreen mode

Este comando instalará a biblioteca @openzeppelin/contracts no seu diretório local, e você pode conferir seu package.json para garantir que foi instalado corretamente. Agora podemos herdar contratos OpenZeppelin em nosso próprio código Solidity.

Estamos prontos para começar a escrever um pouco de código no Solidity. Na pasta contracts, crie 2 arquivos:

  • Fuel.sol: Este será nosso contrato ERC-721;
  • Rewards.sol: Este contrato conterá a lógica de nosso sistema de staking.

Fuel.sol

Em um Fuel.sol vazio, cole o seguinte código:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract Fuel is ERC721, ERC721Burnable, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    constructor() ERC721("Fuel", "FUEL") {}
    function safeMint(address to) public onlyOwner {
        uint256 tokenId = _tokenIdCounter.current();
        _tokenIdCounter.increment();
        _safeMint(to, tokenId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Este é um pequeno, mas bastante eficaz contrato ERC-721 que faz o seguinte:

  • O construtor inicializa o nome do contrato e o símbolo no momento do lançamento.
  • O contrato counters.sol nos ajuda a acompanhar todos os NFTs que foram cunhados a partir de nosso contrato. Toda vez que um novo NFT é cunhado, o ID do token é incrementado. Isso significa que cada NFT cunhado de nosso contrato tem um ID de token único que pode ser usado para identificá-lo de maneira única.
  • A função safeMint() usa a função _safeMint() do contrato ERC-721, que nos permite convenientemente cunhar um novo NFT para um endereço específico. Por favor, note que enquanto definimos a função safeMint(), a função _safeMint() é herdada do contrato ERC-721. Você pode ler mais sobre o template ERC-721 na documentação oficial do OpenZeppelin. O modificador onlyOwner nos ajuda a prevenir que pessoas não autorizadas chamem funções sensíveis.
  • ERC721Burnable.sol nos dá acesso à função burn, que pode ser usada para 'queimar' um NFT específico, basicamente transferindo-o para o endereço 'zero', que é basicamente um endereço nulo genérico que não tem dono.

Rewards.sol

Agora abra Rewards.sol, e cole o seguinte código dentro dele:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract Rewards is ERC20, ERC721Holder, Ownable {
    IERC721 public nft;
    mapping(uint256 => address) public tokenOwnerOf;
    mapping(uint256 => uint256) public tokenStakedAt;
    mapping(uint256 => bool) public isStaked;
    uint256 public rewardsPerHour = (1 * 10 ** decimals()) / 1 hours;
    constructor(address _nft) ERC20("Reward", "RWD") {
        nft = IERC721(_nft);
    }
    function stake(uint256 tokenId) external {
        nft.safeTransferFrom(msg.sender, address(this), tokenId);
        tokenOwnerOf[tokenId] = msg.sender;
        tokenStakedAt[tokenId] = block.timestamp;
        isStaked[tokenId] = true;
    }
    function calculateRewards(uint256 tokenId) public view returns (uint256) {
        require(isStaked[tokenId], "This Token id was never staked");
        uint timeElapsed = block.timestamp - tokenStakedAt[tokenId];
        return timeElapsed * rewardsPerHour;
    }
    function unstake(uint256 tokenId) external {
        uint timeElapsed = block.timestamp - tokenStakedAt[tokenId];
        uint minimumTime = 7 days;
        require(tokenOwnerOf[tokenId] == msg.sender, "You can't unstake because you are not the owner");
        require(timeElapsed >= minimumTime, "You need to stake for at least 7 days");
        _mint(msg.sender, calculateRewards(tokenId));
        nft.transferFrom(address(this), msg.sender, tokenId);
        delete tokenOwnerOf[tokenId];
        delete tokenStakedAt[tokenId];
        delete isStaked[tokenId];
    }
}
Enter fullscreen mode Exit fullscreen mode

Este é o nosso principal contrato de staking. Vamos revisar os principais conceitos deste contrato cuidadosamente:

  • O contrato utiliza quatro templates de contratos do OpenZeppelin, que são:
    • ERC20.sol: O contrato ERC-20, nos permite criar um contrato inteligente para tokens fungíveis. O construtor do contrato especifica o nome, símbolo e quantidade inicial de cunhagem de nosso token.
    • Ownable.sol: O contrato ownable nos permite gerenciar o acesso às funções do nosso contrato de maneira simplificada. Sempre que um contrato inteligente herda o contrato ownable, quem implanta o contrato é definido como seu dono.
    • ERC721Holder.sol: Este é um contrato interessante. Quando finalmente implantamos nossos contratos inteligentes, precisaremos aprovar o contrato Rewards.sol para poder transferir NFTs do Fuel.sol para seu próprio endereço. Isso é o que chamamos de 'staking de um NFT'. Mas para um contrato poder chamar a função de transferência em um NFT a partir de outro contrato, ele deve suportar a interface IERC721Receiver. O ERC721Holder.sol é simplesmente uma implementação conveniente da interface. Nós apenas precisamos herdar o contrato em nosso contrato Rewards principal. Se isso foi um pouco confuso, recomendamos que você dê uma olhada no que as interfaces fazem no Solidity. Uma olhada na documentação oficial do OpenZeppelin também será útil.
    • IERC721.sol: Este contrato é simplesmente a interface na qual o contrato ERC721 é baseado. Uma vez que nosso contrato Fuel é uma implementação ERC721, podemos iniciar uma instância do contrato Fuel em nosso contrato Rewards envolvendo seu endereço dentro da interface IERC721. Como você pode ver no construtor, estaremos fornecendo o endereço do contrato Fuel para o contrato Rewards durante a implantação.
  • O construtor simplesmente aceita um argumento de endereço e inicializa um token ERC20 com o símbolo "RWD".
  • A função de staking é muito simples. Chamamos a função safeTransferFrom() na instância do contrato Fuel, que transfere um NFT de um ID de token específico para o contrato Rewards. Uma vez feito isso, atualizamos três mapeamentos em nosso contrato. Esses mapeamentos acompanham os donos dos NFTs, as marcas temporais (timestamps) específicas quando eles foram colocados em stake, e uma lista de IDs de token válidos.
  • Por favor, note que a função só funcionará se duas condições forem cumpridas:
    • O ID do token sendo passado como parâmetro deve realmente ser um ID de token válido e cunhado.
    • E, o endereço chamando a função deve realmente possuir o NFT daquele ID de token específico.
  • A função calculateRewards() simplesmente calcula a quantidade de tokens que precisamos dar ao dono de um NFT que foi colocado em stake com base na quantidade de tempo que o NFT foi colocado em stake. A função calcula a quantidade apenas para IDs de token válidos, que acompanhamos com a ajuda do mapeamento isStaked[].
  • A função unstake permitirá que um dono desfaça o stake de um NFT após um mínimo de 7 dias. A função cunha uma quantidade exata de tokens RWD e os transfere para o dono do NFT, após o qual o NFT é devolvido ao dono original. Todos os dados correspondentes a esse ID de token são então apagados.
  • Sinta-se à vontade para mexer na lógica do contrato. Você pode calcular recompensas com base em outros parâmetros. Você pode mudar o tempo mínimo de staking ou talvez removê-lo completamente.

Isso foi bastante para digerir, mas agora temos nosso contrato de staking pronto para começar.

Para compilar contratos inteligentes no Truffle, precisamos rodar o comando de compilação em nosso terminal:

truffle compile
Enter fullscreen mode Exit fullscreen mode

Este comando compila todos os contratos inteligentes no diretório de contratos e gera artefatos para eles no diretório build/contracts. Invocações subsequentes do comando só irão compilar aqueles contratos inteligentes que sofreram alguma alteração desde o último comando de compilação.

Configurando um arquivo dotenv

Em breve iremos implantar e verificar o contrato inteligente de staking de NFT. Para fazer isso, precisamos de três valores principais:

  • RPC_URL: O Truffle precisa se conectar a um nó de blockchain para implantar nosso contrato. Podemos nos conectar a uma blockchain usando um ponto de extremidade HTTPS para uma blockchain em particular. Para uma conexão rápida e confiável com uma rede em atividade, recomendamos que você use um ponto de extremidade de um provedor como o Chainstack que é executado e mantido por uma equipe dedicada nos bastidores. Se você nunca configurou um nó usando o Chainstack antes, sinta-se à vontade para seguir este tutorial em nosso blog.
  • PRIVATE_KEY: O Truffle precisa acessar a chave privada de uma carteira para poder assinar transações e enviá-las para à blockchain. Neste tutorial, vamos implantar nosso contrato inteligente na rede principal da Gnosis, então certifique-se de usar a chave privada de uma carteira que tenha alguns tokens xDAI da Gnosis Chain.
  • API_KEY: Vamos verificar nosso contrato inteligente implantado a partir da linha de comando do Truffle. Para fazer isso, precisamos de uma chave de API da GnosisScan. Você pode começar se inscrevendo aqui.

É possível conectar esses valores diretamente no arquivo truffle-config para começar a trabalhar, mas nós desaconselhamos que você faça isso. Colocar esses valores diretamente no arquivo de configuração pode levar a você carregá-los para um diretório remoto que poderia potencialmente comprometer seus fundos. Podemos exportar nossos dados sensíveis de forma segura usando o pacote dotenv. É isso que faremos agora.

Assim que tiver essas três variáveis, abra o terminal, certifique-se de que está apontando para o diretório raiz, e execute:

npm i dotenv
Enter fullscreen mode Exit fullscreen mode

Isso instalará o pacote dotenv em seu diretório local. Novamente, em seu terminal execute:

touch .env
Enter fullscreen mode Exit fullscreen mode

Isso criará um arquivo .env vazio dentro de seu projeto. Cole o seguinte código dentro do arquivo:

PRIVATE_KEY="0x0000000000000000000" CHIADO_RPC_URL=https://123-456-789 GNOSIS_RPC_URL=https://987-654-321 API_KEY=ABCDEFGHIJKLMNOPQRSTUVWXYZ
Enter fullscreen mode Exit fullscreen mode

Simplesmente substitua os dados fictícios por suas chaves e URLs reais. Note que estamos colocando dois URLs RPC diferentes em nosso arquivo dotenv. Isso é porque vamos definir duas redes diferentes em nosso arquivo truffle-config, uma para a rede principal da Gnosis e outra para a rede de testes Chiado.

Uma vez que você esteja pronto, salve o arquivo dotenv.

Configurando o truffle-config

Precisamos instalar dois pacotes para alimentar nosso arquivo truffle-config:

  • HD wallet provider: Precisamos definir um provedor para assinar transações através do truffle. Um provedor no Truffle é basicamente uma combinação de uma chave privada/mnemônica com um URL RPC. Esses dois valores usados juntos podem assinar transações para uma determinada blockchain a partir de um determinado endereço de carteira.
  • truffle-plugin-verify: O Truffle nos permite instalar uma variedade de plugins oficialmente suportados em nosso projeto. Este plugin pode ser usado para verificar contratos inteligentes em certas blockchains.

Para instalar esses plugins, execute os seguintes comandos em seu terminal:

npm i @truffle/hdwallet-provider 
npm i truffle-plugin-verify
Enter fullscreen mode Exit fullscreen mode

Agora que você tem todos os plugins necessários, vá para o seu arquivo truffle-config e apague tudo dentro dele. Cole o seguinte código dentro do arquivo:

require('dotenv').config();
const HDWalletProvider = require("@truffle/hdwallet-provider");
module.exports = {
  networks: {
    GnosisMainnet: {
      provider: () => {
        return new HDWalletProvider([process.env.PRIVATE_KEY], `${process.env.GNOSIS_RPC_URL}`);
      },
      network_id: 100,
    },
    ChiadoTestnet: {
      provider: () =>
        new HDWalletProvider([process.env.PRIVATE_KEY], `${process.env.CHIADO_RPC_URL}`),
      network_id: 10200,
    },
  },
  compilers: {
    solc: {
      version: "0.8.17",
      settings: {
        optimizer: {
          enabled: true,
          runs: 50,
        },
      },
    },
  },
  plugins: ['truffle-plugin-verify'],
  api_keys: {
    gnosisscan: `${process.env.API_KEY}`
  },

};
Enter fullscreen mode Exit fullscreen mode

Vamos analisar as principais configurações que definimos aqui:

  • Networks: Podemos definir várias redes dentro do truffle-config. Cada rede deve conter pelo menos um provedor, juntamente com um ID de cadeia. Um novo provedor pode ser declarado como uma nova instância do objeto hdwallet-provider que contém pelo menos uma chave privada e um URL HTTPS ou WSS correspondente a uma determinada blockchain.
  • O objeto compilers suporta várias alterações no compilador Solidity. Aqui simplesmente definimos a versão do Solidity e pedimos ao compilador para otimizar a implantação do contrato para um limite esperado de 50 chamadas durante sua vida útil. Você pode ler mais sobre as configurações suportadas na página de referência do Truffle.
  • plugins: O Truffle nos permite usar uma variedade de plugins que podem ser instalados como dependências do NPM. Neste tutorial, estamos usando apenas um plugin, como explicado anteriormente.

Implantando e verificando nosso contrato inteligente

Antes de prosseguir, observe que a rede principal da Gnosis passou recentemente por uma fusão. Se você não sabe o que é a fusão, ou o que isso significa para a Gnosis Chain, você pode se referir a este artigo em nosso blog.

https://chainstack.com/wp-content/uploads/2022/12/Gnosis_Merge_60-1024x542.png

Fusão Gnosis

Agora configuramos tudo em nosso arquivo truffle-config. Para realmente implantar um contrato no Truffle, no entanto, precisamos escrever um script de implantação básico. Vá para o diretório de migrações e crie um arquivo chamado 1_deploy_contract.js. Dentro do arquivo, cole o seguinte código:

const Fuel = artifacts.require("Fuel");
const Rewards = artifacts.require("Rewards");
module.exports = async function (deployer) {
  await deployer.deploy(Fuel);
  await deployer.deploy(Rewards, Fuel.address);
  await Fuel.setApprovalForAll(Rewards.address, true);
};
Enter fullscreen mode Exit fullscreen mode

Este é um script de implantação muito simples. Importamos as ABIs de nossos contratos inteligentes da pasta artifacts. Depois disso, chamamos a função de implantação em todos os três contratos em uma função assíncrona. Observe que o contrato NFTstake requer os endereços de ambos Fuel.sol e Rewards.sol em seu construtor. Portanto, eles não apenas precisam ser implantados primeiro, mas também precisamos passar seus respectivos endereços nos parâmetros do NFTstake.

Observe que o Truffle tem uma convenção de nomenclatura muito específica para seus arquivos de migração. Recomendamos fortemente que você siga esta convenção de nomenclatura ou o script de implantação pode não funcionar conforme esperado. Você pode ler mais sobre essas convenções na documentação do Truffle.

Para implantar nossos contratos inteligentes em uma rede em atividade, abra o terminal e execute:

truffle migrate --network GnosisMainnet
Enter fullscreen mode Exit fullscreen mode

Isso acionará 1_deploy_Contract.js e implantará o contrato na rede principal da Gnosis conforme especificado em truffle-config.js.

Agora, para a parte de verificação. Observe que a chave da API GnosisScan que adicionamos ao nosso projeto não suporta a verificação de contrato na rede de testes Chiado. Podemos usá-la apenas para verificar contratos na rede principal Gnosis.

Para verificar um contrato inteligente na rede principal Gnosis usando uma chave da API GnosisScan no Truffle:

  1. Implante um contrato inteligente na rede principal Gnosis usando o comando de migração com a respectiva flag de rede, conforme mostrado acima.
  2. Execute o seguinte comando no seu terminal depois que seu contrato for implantado com sucesso.
truffle run verify <CONTRACT_NAME>@<CONTRACT_ADDRESS> --network GnosisMainnet
Enter fullscreen mode Exit fullscreen mode

Isso verificará seu contrato inteligente na rede Gnosis e retornará uma mensagem de sucesso. Seu contrato inteligente agora está verificado!

Verifique os dois contratos inteligentes na rede principal, e agora você pode interagir com eles diretamente do explorador GnosisScan.

Se você não tiver tokens xDAI da rede principal, ainda poderá seguir o tutorial implantando os contratos na rede de testes Chiado, mas você não será capaz de usar o Truffle para verificar seus contratos. Você ainda pode verificá-los indo ao explorador Chiado. Se você precisar de alguns tokens xDAI de teste, pode obtê-los na torneira (faucet) oficial.

Fluxo lógico para colocar um NFT em stake

Ambos os nossos contratos inteligentes estão agora implantados e verificados na rede principal da Gnosis. Nesta última seção, revisaremos brevemente o fluxo lógico para o mecanismo de staking:

  1. Implante Fuel.sol. Agora copie o endereço do contrato Fuel.sol e passe-o para o construtor de Rewards.sol. Agora implante o segundo contrato inteligente. Já concluímos esta etapa através do script de migrações.
  2. Chame a função safeMint e cunhe um NFT para pelo menos 2 endereços únicos. Você pode verificar se os NFTs foram cunhados corretamente chamando as funções currentTokenID() e ownerOf().
  3. Agora vá para Rewards.sol e tente colocar um NFT em stake nele chamando a função de staking com um parâmetro de ID de token. Não vai funcionar. Por quê? Porque nunca aprovamos o Rewards.sol como um destinatário elegível de um NFT Fuel. Quando você tem um NFT ERC721, como proprietário, você pode entregar a custódia de seu NFT chamando a função setApprovalForAll(), aprovando assim um endereço específico como um manipulador autorizado de seus NFTs. Vá para Fuel.sol e aprove o contrato Rewards como um destinatário chamando a função.
  4. Agora todos os NFTs de uma carteira específica podem ser colocados em stake em Rewards.sol. Observe que apenas os NFTs daqueles endereços podem ser colocados em stake no contrato Rewards, ou seja, aqueles que autorizaram o contrato Rewards como um manipulador aprovado chamando a função setApprovalForAll().
  5. Se você quiser colocar em stake NFTs de 10 endereços diferentes em Rewards.sol, terá que chamar setApprovalForAll() desses 10 endereços diferentes antes de colocar em stake seus NFTs com o contrato de recompensas.

Conclusão

Neste tutorial, você aprendeu como compilar, testar e implantar contratos inteligentes usando o framework Truffle.

Nós implantamos um contrato inteligente de staking NFT na rede principal da Gnosis através de um script de migrações no Truffle e, em seguida, verificamos usando uma chave de API do GnosiSscan. Você pode ir ao nosso blog para mais tutoriais como este.

Artigo original publicado por Chainstack. Traduzido por Paulinho Giovannini.

Top comments (0)