SwiftHTML & CSSSolidityDesenvolvimento de JogosSolana/Rust
06.12.2024

Lição 241: Implementando Mecanismos de Staking

Nesta lição, vamos nos aprofundar no mundo dos mecanismos de staking implementados em contratos inteligentes Ethereum usando Solidity. O staking é um recurso popular em aplicações de finanças descentralizadas (DeFi), onde os usuários podem bloquear seus tokens para ganhar recompensas.

Compreendendo o Staking

O staking geralmente envolve o bloqueio de uma quantidade específica de criptomoeda em um contrato inteligente para apoiar as operações de uma rede blockchain. Em troca, os participantes recebem recompensas, que podem ser na forma de tokens ou uma parte das taxas de transação.

Conceitos Básicos

  • Stakeholder: Um usuário que bloqueia seus tokens em um contrato inteligente por um determinado período.
  • Recompensa: Tokens dados aos stakeholders como compensação por sua participação na rede.
  • Duração do Bloqueio: O período durante o qual os tokens permanecem bloqueados.
  • Desbloqueio: O processo de retirada dos tokens bloqueados e de quaisquer recompensas associadas.

Exemplo de Contrato de Staking

Vamos implementar um contrato de staking simples usando Solidity que permite aos usuários bloquear tokens ERC20 e ganhar recompensas.

1. Configurando o Contrato

Primeiro, certifique-se de ter importado a interface ERC20 da OpenZeppelin. Você pode instalar os contratos da OpenZeppelin se ainda não o fez.

npm install @openzeppelin/contracts

2. Implementando o Contrato de Staking

Aqui está um contrato de staking simples:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract Staking {
    IERC20 public stakingToken;  // O token a ser bloqueado
    mapping(address => uint256) public balances; // Saldo dos usuários
    mapping(address => uint256) public rewards;   // Recompensas dos usuários
    mapping(address => uint256) public stakingStart; // Tempo de início do staking
    uint256 public rewardRate = 100; // Taxa de recompensa por bloco ou unidade de tempo

    constructor(address _stakingToken) {
        stakingToken = IERC20(_stakingToken);
    }

    function stake(uint256 amount) external {
        require(amount > 0, "Você precisa bloquear mais de 0");

        // Transferir tokens de staking para o contrato
        stakingToken.transferFrom(msg.sender, address(this), amount);

        // Atualizar estados
        balances[msg.sender] += amount;
        stakingStart[msg.sender] = block.timestamp; // Registrar tempo de início do staking
    }

    function unstake(uint256 amount) external {
        require(amount <= balances[msg.sender], "Saldo bloqueado insuficiente");

        // Atualizar saldo e calcular recompensas
        _calculateReward(msg.sender);

        // Atualizar saldos
        balances[msg.sender] -= amount;

        // Transferir tokens de volta para o usuário
        stakingToken.transfer(msg.sender, amount);
    }

    function claimRewards() external {
        _calculateReward(msg.sender);
        uint256 reward = rewards[msg.sender];
        require(reward > 0, "Sem recompensas disponíveis");

        // Redefinir recompensas do usuário
        rewards[msg.sender] = 0;

        // Transferir recompensas para o usuário (também poderia ser um token diferente)
        stakingToken.transfer(msg.sender, reward);
    }

    function _calculateReward(address user) internal {
        uint256 stakedTime = block.timestamp - stakingStart[user];
        rewards[user] += (balances[user] * rewardRate * stakedTime) / 1 days; // Exemplo de cálculo de recompensas
    }

    function getStakeBalance() external view returns (uint256) {
        return balances[msg.sender];
    }

    function getRewardBalance() external view returns (uint256) {
        return rewards[msg.sender];
    }
}

3. Explicação do Código

  • Variáveis: O contrato mantém o controle do token de staking, saldos, recompensas e o tempo de início das operações de staking para cada usuário.
  • Função Stakes: Os usuários podem bloquear tokens chamando essa função, que transfere tokens do usuário para o contrato e atualiza seu saldo.
  • Função Unstake: Permite que os usuários retirem seus tokens bloqueados. Antes de fazer isso, calcula suas recompensas com base na duração do staking.
  • Função Claim Rewards: Os usuários podem chamar essa função para reivindicar suas recompensas acumuladas, que redefine seu saldo de recompensas.
  • Cálculo de Recompensas: As recompensas são calculadas com base na duração em que os tokens foram bloqueados.

4. Testando o Contrato

Para testar este contrato, você pode usar ferramentas como Truffle ou Hardhat e interagir com ele usando um arquivo de teste em JavaScript ou diretamente em um console JavaScript.

Exemplo de Script de Teste

Aqui está um exemplo de como você pode testar o contrato inteligente usando Hardhat:

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("Contrato de Staking", function () {
    let staking;
    let stakingToken;
    let owner, addr1, addr2;

    beforeEach(async function () {
        // Implantando o contrato do token
        const Token = await ethers.getContractFactory("ERC20Token");
        stakingToken = await Token.deploy("Token de Staking", "STK", ethers.utils.parseEther("1000"));

        const Staking = await ethers.getContractFactory("Staking");
        staking = await Staking.deploy(stakingToken.address);

        [owner, addr1, addr2] = await ethers.getSigners();

        // Transferir alguns tokens para addr1
        await stakingToken.transfer(addr1.address, ethers.utils.parseEther("100"));
    });

    it("Deve permitir o staking", async function () {
        await stakingToken.connect(addr1).approve(staking.address, ethers.utils.parseEther("50"));
        await staking.connect(addr1).stake(ethers.utils.parseEther("50"));

        const balance = await staking.getStakeBalance();
        expect(balance).to.equal(ethers.utils.parseEther("50"));
    });

    it("Deve permitir o unstake", async function () {
        await stakingToken.connect(addr1).approve(staking.address, ethers.utils.parseEther("50"));
        await staking.connect(addr1).stake(ethers.utils.parseEther("50"));
        await staking.connect(addr1).unstake(ethers.utils.parseEther("50"));

        const balance = await staking.getStakeBalance();
        expect(balance).to.equal(ethers.utils.parseEther("0"));
    });

    it("Deve permitir a reivindicação de recompensas", async function () {
        await stakingToken.connect(addr1).approve(staking.address, ethers.utils.parseEther("50"));
        await staking.connect(addr1).stake(ethers.utils.parseEther("50"));
        // Aumentar o tempo para o cálculo da recompensa
        await ethers.provider.send("evm_increaseTime", [86400]); // 1 dia = 86400 segundos
        await ethers.provider.send("evm_mine"); // Minera um novo bloco para refletir a mudança de tempo

        await staking.connect(addr1).claimRewards();
        const rewardBalance = await staking.getRewardBalance();
        expect(rewardBalance).to.be.greaterThan(0);
    });
});

Conclusão

Nesta lição, exploramos os conceitos básicos da implementação de um mecanismo de staking no Ethereum usando Solidity. Criamos um contrato de staking simples que permite aos usuários bloquear tokens, ganhar recompensas e retirar seus tokens quando desejado. Você pode expandir esse exemplo ainda mais, adicionando recursos como taxas de recompensa variáveis, suporte a múltiplos tokens e recursos de governança para um mecanismo de staking mais robusto. Boa codificação!

Video

Did you like this article? Rate it from 1 to 5:

Thank you for voting!