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!