如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程

这篇文章简要介绍了如何在 Berachain 上创建奖励金库并通过治理提案将其列入 BeraChef 白名单。内容包括设置开发环境、提交提案、监控提案进展并进行投票和执行。

导言

本教程由 Black Bera 和 Manny Bera 编写, Rama 翻译。

教程旨在为开发者提供一个清晰的操作指南,帮助大家在 Berachain 上创建并管理奖励金库,推动流动性激励和治理进程。

文章非常详细,不仅涵盖了从创建智能合约到提交治理提案的各个步骤,还详细讲解了如何将激励代币列入白名单,优化用户体验和流动性激励机制。

希望 DAppChaser 编译能让更多中文区的开发者能够快速上手并深入探索 Berachain 的去中心化生态系统。

Rama


Berachain 流动性共识机制的最大优势之一,是协议可以通过获取 Berachain 治理代币 ($BGT) 的发放来启动其流动性。这一累积 $BGT 的过程通过奖励金库(由 RewardsVaults 智能合约代表)完成。

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程

注意:你可以在测试网中看到 Berps 奖励金库智能合约的实际运行情况。

$BGT 在奖励金库中累积的过程涉及选择一个验证人提议一个区块,当该验证人被选中时会获得 $BGT,并通过一个名为 BeraChef 的分发合约将大部分 $BGT 分配到一个或多个奖励金库中。验证人的奖励受他们在提议区块时被委派的 $BGT 数量影响。

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程

$BGT 的用途

$BGT 有三个主要功能,包括:将其销毁以获取 $BERA,将其委派给验证人以提高他们的奖励,或者在治理中使用 $BGT 提出或投票通过提案。

BEX 的奖励金库示例

BEX 是 Berachain 上的一个原生 PoL AMM 协议,同时拥有多个奖励金库。对于用户贡献流动性的每个白名单池,他们会根据其总贡献量获得相应的 LP 代币。

例如,用户可以向 HONEY<>WBTC 池添加流动性,并获得 $HONEY-WBTC LP 代币作为回报。

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程

$HONEY-WBTC Reward Vault

然后,你可以将 LP 代币质押到 BEX 的 $HONEY-WBTC 奖励金库中。当 LP 代币被质押并且验证人将 $BGT 发放至奖励金库时,LP 代币会随着时间累积 $BGT。这就是 BEX 激励用户为其协议增加流动性的方式。

如果你利用 BEX 为你的协议代币(我们称之为 $BBT,即 Black Bera Token)提供交易流动性,并将其与 $WBERA 配对,用户在提供流动性时会收到 $BBT-WBERA-LP 代币。
如果该 LP 代币被白名单认证,意味着它有一个奖励金库可以接受该代币进行质押,那么它有资格获得 $BGT 奖励,从而通过 PoL 启动你协议的流动性。

这些奖励金库由 RewardsVault Factory 创建,这是 Berachain 上的一个智能合约,负责创建奖励金库。

虽然创建自己的奖励金库是无许可的,但要让验证人将他们的 $BGT 发放到你的奖励金库中,必须首先提交并批准治理提案,将你的奖励金库添加到 BGT Station。一旦提案获得通过,你的奖励金库就会成为 “friendOfTheChef”,被列入允许验证人发放 $BGT 的合格奖励金库集合中。这个过程也被称为“白名单认证”。

理解 Berachain 白名单认证的治理流程

为了了解我们即将构建的内容,首先我们需要了解创建奖励金库、提出提案、对提案进行投票、以及激活奖励金库的治理流程。

治理要求

在成功创建提案之前,有一些要求和步骤需要考虑:

  1. 最低 1000 $BGT
    所有提案都需要至少 1000 $BGT,可以通过直接购买或让他人将他们的 $BGT 委派给提案人来满足这一要求。
  2. 委派/自委派投票权
    如果达到了最低的 $BGT 要求,无论是提出提案还是对现有提案进行投票,所拥有的 $BGT 都需要委派给提案人,甚至可以自我委派。
  3. 达到法定人数
    赞成票必须达到至少 20 亿的法定人数(Quorum),这一数字可能在接近主网时发生变化。不过在测试网上,你可以请求 Berachain 团队帮助你达到这一法定人数。

治理生命周期

以下是提案在整个治理流程中将经历的不同状态。

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程

Berachain 治理提案的生命周期

  1. 提案创建(待处理状态)
    等待期:提案需要等待 3 小时才能进入投票
    在此状态下,提案也可以被取消。
  2. 投票开始(活动状态)
    持续时间:3 小时
    $BGT 持有者可以进行投票
    必须达到法定人数:最低 20 亿 $BGT。
  3. 投票结束
    提案会成功或失败。
    如果失败:标记为失败状态,提案人可以在解决问题后重新提交提案。
  4. 时间锁(如果成功)
    提案进入队列,延迟 3 小时时间锁。
  5. 执行或过期
    执行:只能由 Berachain 批准的治理 EOA 执行。
    过期:如果在指定时间内未执行则会过期。

*注意:这些时间段可能会在接近主网启动时更改。

创建 BEX 池以用于你的奖励金库

在本教程中,我们将创建一个奖励金库,该金库接受 LP 代币,使质押的用户有资格获得 $BGT 发放。一旦使用我们的 LP 代币创建了这个奖励金库,我们就可以将其提交给治理,以将其添加为 Berachef 的 “Friend”。

首先,我们将创建 BEX 池及其质押代币。

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程
BEX 上创建池子的示例

要创建一个新的池子,你需要提供你的协议代币,在此例中为新创建的代币 $BBT,以及 $WBERA 代币,以在 BEX 上创建一个自定义池子。

一旦池子创建并完成存款后,你将收到 LP 代币。任何后续存款都会生成更多的 LP 代币。

示例部署的池子和 LP 代币

创建奖励金库和治理提案的项目设置

现在我们已经有了 $BBT <> $WBERA 的 BEX 池及其相应的 LP 代币 ($BBT-WBERA-LP),我们需要先创建一个奖励金库,才能提交治理提案,将我们的奖励金库添加到 BeraChef。

我们将通过创建一个项目,运行脚本来完成以下操作:

  1. 为我们的 LP 代币创建一个奖励金库
  2. 向 Berachain 治理提交提案,将我们的新奖励金库添加到 BeraChef
  3. 对我们的奖励金库进行投票
  4. 通过将奖励金库添加到 BeraChef 来激活它

项目要求

为了构建脚本,请确保你的电脑上已经安装了以下内容并满足部分要求:

  1. NVM 或 NodeJS v20.16.0 或更高版本
  2. 累积了一定数量的 $BGT 的钱包
  3. 处理交易所需的 $BERA 代币

1.初始化一个新的 JavaScript 项目

在为我们的 LP 代币创建奖励金库之前,先设置我们的开发环境。打开终端,按照以下步骤开始操作:

mkdir berachain-rewards-vault
cd berachain-rewards-vault
npm init -y

2.安装所需的依赖项

# FROM: ./berachain-rewards-vault

npm install ethers dotenv yargs

3. 创建一个 .gitignore 文件

# FROM: ./berachain-rewards-vault

echo "node_modules\n.env" > .gitignore

4. 设置 ABI 文件

# FROM: ./berachain-rewards-vault

mkdir abi

你需要下载并将这些 JSON 文件添加到此文件夹中:

  1. ERC20.json – ABI 文件:这是你的 LP 代币的 ABI,实际上就是一个通用的 ERC20 代币。
  2. BerachainRewardsVaultFactory.json – ABI 文件:这是 Berachain 奖励金库工厂合约的 ABI。
  3. BerachainGovernance.json – ABI 文件:这是 Berachain 治理合约的 ABI,这是我们提议、投票和执行治理提案的主要合约。
  4. BGT.json – ABI 文件:这是 $BGT 的 ABI。
  5. BeraChef.json – ABI 文件:这是 BeraChef 的 ABI,这是负责将我们的奖励金库列入白名单的合约,用于 $BBT <> $WBERA LP 代币。
  6. BerachainRewardsVault.json – ABI 文件:这是我们奖励金库的 ABI。

5.设置环境变量

接下来,我们将在项目根目录下创建一个 .env 文件,并添加以下内容:

cat << EOF > .env
RPC=https://bartio.rpc.berachain.com/
PRIVATE_KEY=your_private_key_here
FACTORY_ADDRESS=0x2B6e40f65D82A0cB98795bC7587a71bfa49fBB2B
LP_TOKEN_ADDRESS=insert_your_lp_token_address_here
GOVERNANCE_ADDRESS=0xE3EDa03401Cf32010a9A9967DaBAEe47ed0E1a0b
BERACHEF_ADDRESS=0xfb81E39E3970076ab2693fA5C45A07Cc724C93c2
BGT_ADDRESS=0xbDa130737BDd9618301681329bF2e46A016ff9Ad
EOF

以下是我们添加的值:

  • RPC – 公共 RPC,或替换为你自己的 RPC
  • PRIVATE_KEY – 包含 BGT 的钱包私钥
  • FACTORY_ADDRESS – bArtio 奖励金库工厂地址
  • LP_TOKEN_ADDRESS – 你的 LP 代币地址
  • GOVERNANCE_ADDRESS – Berachain 的治理合约地址,拥有调用 BeraChef 的 updateFriendsOfTheChef 方法的专有权限
  • BERACHEF_ADDRESS – BeraChef 的地址,此合约存储了列入白名单的奖励金库和验证人偏好
  • BGT_ADDRESS – Berachain 治理代币的地址

请确保将 PRIVATE_KEYLP_TOKEN_ADDRESS(如 $BBT-WBERA-LP)替换为你实际的值。

6.创建并设置治理脚本

创建一个名为 governance.js 的新文件到你的项目根目录中,并添加以下代码:

File: ./governance.js

// Import required libraries
const ethers = require('ethers');
require('dotenv').config();

// Import ABI (Application Binary Interface) for various contracts
const BeraChefABI = require('./abi/BeraChef.json');
const BerachainGovernanceABI = require('./abi/BerachainGovernance.json');
const BGTABI = require('./abi/BGT.json');
const BerachainRewardsVaultABI = require('./abi/BerachainRewardsVault.json');
const ERC20ABI = require('./abi/ERC20.json');
const BerachainRewardsVaultFactoryABI = require('./abi/BerachainRewardsVaultFactory.json');

// Set up the Ethereum provider using the RPC URL from the .env file
const provider = new ethers.JsonRpcProvider(`${process.env.RPC}`, {
    chainId: 80084,  // Chain ID for Berachain
    name: 'Berachain',
    ensAddress: null
});

// Initialize the wallet using the private key from the .env file
let wallet;
try {
    wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
} catch (error) {
    console.error('Error creating wallet:', error.message);
    process.exit(1);
}

// Helper function to create contract instances
function createContract(address, abi, signer) {
    return new ethers.Contract(ethers.getAddress(address), abi, signer);
}

// Create instances of various contracts
const governance = createContract(process.env.GOVERNANCE_ADDRESS, BerachainGovernanceABI, wallet);
const beraChef = createContract(process.env.BERACHEF_ADDRESS, BeraChefABI, wallet);
const bgt = createContract(process.env.BGT_ADDRESS, BGTABI, wallet);
const factory = createContract(process.env.FACTORY_ADDRESS, BerachainRewardsVaultFactoryABI, wallet);
const token = createContract(process.env.LP_TOKEN_ADDRESS, ERC20ABI, wallet);
let rewardsVault;  // This will be initialized later when creating or retrieving a vault

7. 添加辅助函数

接下来,将我们的辅助函数复制并粘贴到文件中。

File: ./governance.js

// Function to check the current state of a proposal
async function checkProposalState(proposalId) {
  // Get the numerical state of the proposal
  const state = await governance.state(proposalId);
  // Define an array of state names corresponding to their numerical values
  const stateNames = ['Pending', 'Active', 'Canceled', 'Defeated', 'Succeeded', 'Queued', 'Expired', 'Executed'];
  // Return both the numerical state and its corresponding name
  return { state, stateName: stateNames[state] };
}

// Function to determine the next stage in the governance process
async function getNextStage(currentState) {
  // Define the order of stages in the governance process
  const stageOrder = ['Pending', 'Active', 'Succeeded', 'Queued', 'Executed'];
  // Find the index of the current state in the stage order
  const currentIndex = stageOrder.indexOf(currentState);
  // Return the next stage if it exists, otherwise return 'End'
  return currentIndex < stageOrder.length - 1 ? stageOrder[currentIndex + 1] : 'End';
}

// Function to ensure the user has sufficient voting power to create a proposal
async function ensureSufficientVotingPower() {
  // Get the user's BGT balance
  const balance = await bgt.balanceOf(wallet.address);
  console.log('BGT balance:', balance.toString());

  // Check who the current delegatee is for the user's BGT
  const currentDelegatee = await bgt.delegates(wallet.address);
  console.log('Current delegatee:', currentDelegatee);

  // Get the user's current voting power
  const votingPower = await governance.getVotes(wallet.address, await provider.getBlockNumber() - 1);
  console.log('Your voting power:', votingPower.toString());

  // Get the proposal threshold (minimum voting power required to create a proposal)
  const proposalThreshold = await governance.proposalThreshold();
  console.log('Proposal threshold:', proposalThreshold.toString());

  // If voting power is less than the threshold
  if (votingPower < proposalThreshold) {
    // If BGT is not self-delegated, delegate it to self
    if (currentDelegatee !== wallet.address) {
      console.log('Delegating all BGT to self...');
      await (await bgt.delegate(wallet.address)).wait();
      console.log('Delegation complete');
    } else {
      // If already self-delegated but still not enough voting power
      console.log('Already delegated to self, but still not enough voting power');
      console.log('Please acquire more BGT tokens to meet the proposal threshold');
      return false;
    }
  }

  // Check updated voting power after potential delegation
  const updatedVotingPower = await governance.getVotes(wallet.address, await provider.getBlockNumber() - 1);
  console.log('Updated voting power:', updatedVotingPower.toString());

  // If still not enough voting power, return false
  if (updatedVotingPower < proposalThreshold) {
    console.log('Voting power is still less than proposal threshold, cannot create proposal');
    return false;
  }

  // Sufficient voting power achieved
  return true;
}

// Function to check if a proposal with given parameters already exists
async function checkExistingProposal(targets, values, calldatas, descriptionHash) {
  // Generate a proposal ID based on the given parameters
  const proposalId = await governance.hashProposal(targets, values, calldatas, descriptionHash);
  try {
    // Try to get the state of the proposal
    const state = await governance.state(proposalId);
    // If state is not 3 (Defeated), the proposal exists and is not defeated
    return state !== 3;
  } catch (error) {
    // If the error indicates the proposal doesn't exist, return false
    // Otherwise, propagate the error
    return error.reason === "GovernorNonexistentProposal(uint256)" ? false : Promise.reject(error);
  }
}

8. 添加创建奖励金库的代码

File: ./governance.js

// Function to get an existing rewards vault or create a new one
async function getOrCreateVault() {
    console.log('Checking for existing rewards vault...');
    try {
        // Check if a vault already exists for the token
        const existingVaultAddress = await factory.getVault(process.env.LP_TOKEN_ADDRESS);
        
        // If a vault exists (address is not zero)
        if (existingVaultAddress !== ethers.ZeroAddress) {
            console.log('A rewards vault already exists for this token.');
            console.log(`Existing rewards vault address: ${existingVaultAddress}`);
            
            // Provide instructions to view vault details
            console.log('\nTo view details about the existing vault:');
            console.log('1. Go to https://bartio.beratrail.io');
            console.log(`2. Search for the rewards vault address: ${existingVaultAddress}`);
            console.log('3. Look for the "Create Rewards Vault" method in the transaction history');
            
            console.log('\nUsing the existing vault for this operation.');
            console.log('\nAdd this rewards vault address to your .env file under REWARDS_VAULT_ADDRESS:');
            console.log(`REWARDS_VAULT_ADDRESS=${existingVaultAddress}`);
            
            // Create a contract instance for the existing vault
            rewardsVault = new ethers.Contract(existingVaultAddress, BerachainRewardsVaultABI, wallet);
            return existingVaultAddress;
        }

        // If no existing vault, create a new one
        console.log('No existing vault found. Creating new rewards vault...');
        const tx = await factory.createRewardsVault(process.env.LP_TOKEN_ADDRESS);
        console.log('Transaction sent. Waiting for confirmation...');
        const receipt = await tx.wait();
        console.log('Rewards vault created. Transaction hash:', receipt.transactionHash);
        console.log();

        // Get the address of the newly created vault
        const newVaultAddress = await factory.getVault(process.env.LP_TOKEN_ADDRESS);
        console.log('New rewards vault created at:', newVaultAddress);
        
        // Provide instructions to view new vault details
        console.log('\nTo view details about the new vault:');
        console.log('1. Go to https://bartio.beratrail.io');
        console.log(`2. Search for the rewards vault address: ${newVaultAddress}`);
        console.log('3. Look for the "Create Rewards Vault" method in the transaction history');
        console.log('\nAdd this rewards vault address to your .env file under REWARDS_VAULT_ADDRESS:');
        console.log(`REWARDS_VAULT_ADDRESS=${newVaultAddress}`);

        // Create a contract instance for the new vault
        rewardsVault = new ethers.Contract(newVaultAddress, BerachainRewardsVaultABI, wallet);
        return newVaultAddress;
    } catch (error) {
        console.error('Error getting or creating rewards vault:', error);
        throw error;
    }
}

// Main function to handle command-line arguments
async function main() {
    const args = process.argv.slice(2);
    const flag = args[0];

    switch (flag) {
        case '--create-vault':
            // Call getOrCreateVault when the --create-vault flag is used
            await getOrCreateVault();
            break;
        // ... other cases ...
    }
}

main().catch((error) => {
    console.error(error);
    process.exit(1);
});

一旦你将 LP_TOKEN_ADDRESS 和 PRIVATE_KEY 添加到你的 .env 文件中,将以下代码粘贴到 governance.js 文件中,然后运行脚本以创建你的奖励金库:

# FROM: ./berachain-rewards-vault

node governance.js --create-vault

该命令将为我们的 LP 代币创建一个新的奖励金库,并记录创建的金库地址。

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程

文件路径:./.env
将这个新值添加到你的 .env 文件中

REWARDS_VAULT_ADDRESS=value from terminal

注意:你的奖励金库地址将与此截图中的地址不同。

提交你的治理提案

现在我们已经创建了奖励金库,接下来是提交治理提案,将其列入白名单。我们将为此过程创建一个新的脚本。

1.设置环境

在此步骤中,你的 .env 文件应包含以下值:

文件路径:./.env

RPC=https://bartio.rpc.berachain.com/
PRIVATE_KEY=your_private_key
FACTORY_ADDRESS=0x2B6e40f65D82A0cB98795bC7587a71bfa49fBB2B
LP_TOKEN_ADDRESS=your_lp_token_address
GOVERNANCE_ADDRESS=0xE3EDa03401Cf32010a9A9967DaBAEe47ed0E1a0b
BERACHEF_ADDRESS=0xfb81E39E3970076ab2693fA5C45A07Cc724C93c2
BGT_ADDRESS=0xbDa130737BDd9618301681329bF2e46A016ff9Ad
REWARDS_VAULT_ADDRESS=your_rewards_vault_address

2. 创建提案函数

现在,在同一个文件 governance.js 中,在 getOrCreateVault 函数的下方添加以下函数。

文件路径:./governance.js

async function getOrCreateVault() {
    // previous function we implemented
    // ...
}

//🚨COPY THIS FUNCTION🚨
async function createProposal(targets, values, calldatas, description) {
  // Generate a hash of the proposal description
  const hash = ethers.id(description);
  
  // Check if a proposal with these parameters already exists
  const proposalExists = await checkExistingProposal(targets, values, calldatas, hash);

  if (proposalExists) {
    // If the proposal exists, get its ID
    const proposalId = await governance.hashProposal(targets, values, calldatas, hash);
    // Check the current state of the existing proposal
    const { stateName } = await checkProposalState(proposalId);
    // Determine the next stage in the proposal process
    const nextStage = await getNextStage(stateName);

    // Log information about the existing proposal
    console.log('\nA proposal with these parameters already exists.');
    console.log(`Proposal ID: ${proposalId.toString()}`);
    console.log(`Current state: ${stateName}`);
    
    // Inform about the next stage or if it's the final stage
    if (nextStage !== 'End') {
      console.log(`Next stage: ${nextStage}`);
    } else {
      console.log('This is the final stage of the proposal.');
    }

    // Provide instructions to add the proposal ID to the .env file
    console.log('\nAdd this proposal ID to your .env file under PROPOSAL_ID:');
    console.log(`PROPOSAL_ID=${proposalId.toString()}`);

    return proposalId.toString();
  }

  try {
    // If no existing proposal, create a new one
    console.log('Creating new proposal...');
    const tx = await governance.propose(targets, values, calldatas, description);
    const receipt = await tx.wait();
    console.log('Proposal transaction confirmed. Transaction hash:', receipt.transactionHash);
    console.log();

    // Get the ID of the newly created proposal
    const proposalId = await governance.hashProposal(targets, values, calldatas, hash);
    console.log('New proposal created with ID:', proposalId.toString());
    
    // Provide instructions to add the new proposal ID to the .env file
    console.log('\nAdd this proposal ID to your .env file under PROPOSAL_ID:');
    console.log(`PROPOSAL_ID=${proposalId.toString()}`);
    
    return proposalId.toString();
  } catch (error) {
    // Handle any errors that occur during proposal creation
    console.error('Error creating proposal:', error);
    if (error.error?.data) {
      try {
        console.error('Decoded error:', governance.interface.parseError(error.error.data));
      } catch (parseError) {
        console.error('Could not parse error. Raw error data:', error.error.data);
      }
    }
    throw error;
  }
}

3. 将命令添加到主函数

接下来,在你的 governance.js 文件中的主函数中,--create-vault 之后添加以下 switch case。这将用于处理治理提案的创建:

文件路径:./governance.js

async function main() {
    // Get command-line arguments, skipping the first two (node and script name)
    const args = process.argv.slice(2);
    // The first argument is our flag/command
    const flag = args[0];

    switch (flag) {
        case '--create-vault':
            // If the flag is to create a vault, call the getOrCreateVault function
            await getOrCreateVault();
            break;
        //🚨COPY THIS CASE🚨
        case '--create-proposal':
            // Check if the user has sufficient voting power to create a proposal
            if (!(await ensureSufficientVotingPower())) return;

            // Get or create a rewards vault
            const vaultAddress = await getOrCreateVault();

            // Get the address of the BeraChef contract
            const beraChefAddress = await beraChef.getAddress();

            // Set up the proposal parameters
            const targets = [beraChefAddress]; // The contract to call
            const values = [0]; // No BERA being sent with the call
            // Encode the function call to updateFriendsOfTheChef
            const calldatas = [beraChef.interface.encodeFunctionData('updateFriendsOfTheChef', [vaultAddress, true])];
            const description = "Update friends of the chef"; // Description of the proposal

            // Create the proposal with the specified parameters
            await createProposal(targets, values, calldatas, description);
            break;
        //🚨END COPY HERE🚨
    }
}

这个主函数中的新 switch case 允许你创建一个治理提案,将你新创建的奖励金库更新为 “friends of the chef”。它会检查是否有足够的投票权,获取(或创建)金库地址,然后调用 createProposal 函数并传递必要的参数。

4.运行 create-proposal 命令

现在你已经将 --create-proposal case 添加到主函数中,你可以运行命令来创建治理提案,将你的奖励金库添加到 BeraChef。 在终端中执行以下命令:

# FROM: ./berachain-rewards-vault

node governance.js --create-proposal

该命令将创建一个新的治理提案,以将你的奖励金库列入白名单。脚本将输出一个提案 ID。你需要将此 ID 添加到你的 .env 文件中,以便后续步骤使用。

运行该命令后,你应该会看到类似以下的输出结果:

如何创建 Berachain 奖励金库的治理提案? | 开发者必备教程
node governance.js –create-proposal 命令的输出结果

按照输出中的指示更新你的 .env 文件。添加一行带有 PROPOSAL_ID 值的内容:

文件路径:./.env

PROPOSAL_ID=your_proposal_id

PROPOSAL_ID 将在治理流程的后续步骤中使用,例如投票、排队和执行提案。它代表了你将奖励金库添加到 BeraChef 的治理提案的 ID。

5.创建投票和执行的函数

createProposal 函数下方,将这些函数添加到你的 governance.js 文件中:

文件路径:./governance.js

// Function to cast a vote on a proposal
async function castVote(proposalId) {
  // Check if the wallet has already voted
  const hasVoted = await governance.hasVoted(proposalId, wallet.address);
  if (hasVoted) {
    console.log('Vote already cast for this proposal. Proceeding to next steps.');
    return;
  }

  console.log('Casting vote...');
  try {
    // Cast a vote in favor of the proposal (1 = yes)
    const voteTx = await governance.castVote(proposalId, 1);
    const receipt = await voteTx.wait();
    console.log('Vote cast successfully. Transaction hash:', receipt.transactionHash);
  } catch (error) {
    console.error('Error casting vote:', error);
    if (error.error?.data) {
      try {
        console.error('Decoded error:', governance.interface.parseError(error.error.data));
      } catch (parseError) {
        console.error('Could not parse error. Raw error data:', error.error.data);
      }
    }
    throw error;
  }
}

// Function to execute a queued proposal
async function executeProposal(proposalId) {
  console.log('Executing proposal...');
  try {
    const executeTx = await governance.execute(proposalId);
    const receipt = await executeTx.wait();
    console.log('Proposal executed successfully. Transaction hash:', receipt.transactionHash);
  } catch (error) {
    console.error('Error executing proposal:', error);
    throw error;
  }
}

// Function to cancel a proposal
async function cancelProposal(proposalId) {
    console.log('Cancelling proposal...');
    try {
        const cancelTx = await governance.cancel(proposalId);
        const receipt = await cancelTx.wait();
        console.log('Proposal cancelled successfully. Transaction hash:', receipt.transactionHash);
    } catch (error) {
        console.error('Error cancelling proposal:', error);
        if (error.error?.data) {
            try {
                console.error('Decoded error:', governance.interface.parseError(error.error.data));
            } catch (parseError) {
                console.error('Could not parse error. Raw error data:', error.error.data);
            }
        }
        throw error;
    }
}


async function whitelistIncentiveToken(tokenAddress, minIncentiveRate) {
    console.log('Whitelisting incentive token...');
    try {
        if (!process.env.REWARDS_VAULT_ADDRESS) {
            console.error('Please set the REWARDS_VAULT_ADDRESS in your .env file');
            return;
        }
        const vaultAddress = process.env.REWARDS_VAULT_ADDRESS;
        rewardsVault = new ethers.Contract(vaultAddress, BerachainRewardsVaultABI, wallet);
        const targets = [vaultAddress];
        const values = [0];
        const calldatas = [rewardsVault.interface.encodeFunctionData('whitelistIncentiveToken', [tokenAddress, minIncentiveRate])];
        const description = `Whitelist incentive token ${tokenAddress}`;
        await createProposal(targets, values, calldatas, description);
    } catch (error) {
        console.error('Error whitelisting incentive token:', error);
        throw error;
    }
}

这些函数处理治理流程的不同阶段:

  • castVote:此函数允许你对提案进行投票。它首先检查你是否已经投票,如果没有,则会投出 “yes” 票(以 1 表示)。
  • executeProposal:一旦提案被排队并且时间锁期已过,可以调用此函数来执行提案。这是实施治理流程中提案变更的最后一步。
  • cancelProposal:在提案被排队执行之前,你可以取消提案,例如如果你填写了错误的地址或描述,并希望创建一个新的提案。

现在,将 governance.js 文件中的主函数替换为以下代码:

文件路径:./governance.js

async function main() {
    // Get command-line arguments, skipping the first two (node and script name)
    const args = process.argv.slice(2);
    // The first argument is our flag/command
    const flag = args[0];

    // Get the proposal ID from the environment variables
    const proposalId = process.env.PROPOSAL_ID;

    switch (flag) {
        case '--create-vault':
            // Create or retrieve an existing rewards vault
            await getOrCreateVault();
            break;

        case '--create-proposal':
            // Check if there's an existing proposal
            if (proposalId) {
                const { stateName } = await checkProposalState(proposalId);
                // Only allow creating a new proposal if the current one is defeated
                if (stateName !== 'Defeated') {
                    console.log(`A proposal (ID: ${proposalId}) already exists and is in ${stateName} state.`);
                    console.log('You can only create a new proposal if the current one is defeated.');
                    return;
                }
            }
            // Ensure the user has enough voting power to create a proposal
            if (!(await ensureSufficientVotingPower())) return;
            // Get or create a rewards vault
            const vaultAddress = await getOrCreateVault();
            // Get the BeraChef contract address
            const beraChefAddress = await beraChef.getAddress();
            // Set up proposal parameters
            const targets = [beraChefAddress];
            const values = [0];
            const calldatas = [beraChef.interface.encodeFunctionData('updateFriendsOfTheChef', [vaultAddress, true])];
            const description = "Update friends of the chef";
            // Create the proposal
            await createProposal(targets, values, calldatas, description);
            break;

        case '--vote':
            // Ensure a proposal ID is set
            if (!proposalId) {
                console.error('Please set the PROPOSAL_ID in your .env file');
                return;
            }
            // Check the current state of the proposal
            const voteState = await checkProposalState(proposalId);
            // Only allow voting if the proposal is in the Active state
            if (voteState.stateName !== 'Active') {
                console.log(`Proposal is in ${voteState.stateName} state. Please wait until it reaches Active state to vote.`);
                return;
            }
            // Cast a vote on the proposal
            await castVote(proposalId);
            break;

        case '--execute':
            // Ensure a proposal ID is set
            if (!proposalId) {
                console.error('Please set the PROPOSAL_ID in your .env file');
                return;
            }
            // Check the current state of the proposal
            const executeState = await checkProposalState(proposalId);
            // Only allow execution if the proposal is queued
            if (executeState.stateName !== 'Queued') {
                console.log(`Proposal is in ${executeState.stateName} state. Please wait until it reaches Queued state to execute.`);
                return;
            }
            // Execute the proposal
            await executeProposal(proposalId);
            break;

        case '--check-state':
            // Ensure a proposal ID is set
            if (!proposalId) {
                console.error('Please set the PROPOSAL_ID in your .env file');
                return;
            }
            // Check and display the current state of the proposal
            const { stateName } = await checkProposalState(proposalId);
            console.log(`Current proposal state: ${stateName}`);
            // Get and display the next stage of the proposal
            const nextStage = await getNextStage(stateName);
            if (nextStage !== 'End') {
                console.log(`Next stage: ${nextStage}`);
            } else {
                console.log('This is the final stage of the proposal.');
            }
            break;
            
                case '--cancel':
            // Ensure a proposal ID is set
            if (!proposalId) {
                console.error('Please set the PROPOSAL_ID in your .env file');
                return;
            }
            // Check the current state of the proposal
            const cancelState = await checkProposalState(proposalId);
            // Allow cancellation if the proposal is in a cancellable state
            if (!['Pending', 'Active', 'Succeeded'].includes(cancelState.stateName)) {
                console.log(`Proposal is in ${cancelState.stateName} state and cannot be cancelled.`);
                return;
            }
            // Cancel the proposal
            await cancelProposal(proposalId);
            break;
            case '--whitelist-incentive':
                if (!process.env.INCENTIVE_TOKEN) {
                    console.error('Please set the INCENTIVE_TOKEN address in your .env file');
                    return;
                }
                if (!process.env.REWARDS_VAULT_ADDRESS) {
                    console.error('Please set the REWARDS_VAULT_ADDRESS in your .env file');
                    return;
                }
                // Ensure the user has enough voting power to create a proposal
                if (!(await ensureSufficientVotingPower())) return;
                // Set a default minIncentiveRate (you may want to make this configurable)
                const minIncentiveRate = ethers.parseUnits('0.01', 18); // 0.01 token per BGT emission
                await whitelistIncentiveToken(process.env.INCENTIVE_TOKEN, minIncentiveRate);
                break;

        default:
            // If an invalid flag is provided, show usage instructions
            console.log('Please provide a valid flag:');
            console.log('--create-vault: Create a new rewards vault');
            console.log('--create-proposal: Create a new governance proposal');
            console.log('--vote: Vote on the proposal specified in .env');
            console.log('--queue: Queue the proposal specified in .env');
            console.log('--execute: Execute the proposal specified in .env');
            console.log('--check-state: Check the current state of the proposal');
    }
}

// Run the main function and catch any errors
main().catch((error) => {
    console.error(error);
    process.exit(1);
}); 

这些主函数中的新 case 语句允许你使用 .env 文件中的 PROPOSAL_ID 来对治理提案进行投票和执行。每个 case 在执行相应操作之前都会检查 PROPOSAL_ID 是否已设置,因此请确保你的 .env 文件已更新。

监控提案进展

现在你已经创建了提案,你需要监控其进展并在适当的时间采取行动。使用以下命令来检查你的提案当前的状态:

# FROM: ./berachain-rewards-vault

node governance.js --check-state

根据提案的状态,你可能需要运行一个命令,或等待流程的下一个阶段:

  • Pending(待处理):这是初始状态。此阶段持续三个小时,在此期间你无法投票或执行提案。
  • Active(活跃):这是投票阶段。运行以下命令来投票:
# FROM: ./berachain-rewards-vault

node governance.js --vote
  1. Succeeded(通过):提案已通过投票阶段。现在已进入排队等待执行阶段。
  2. Queued(排队中):提案处于时间锁阶段。一旦该阶段结束,你可以执行提案,此时你的 LP 奖励金库将正式添加到 BeraChef。运行以下命令:
node governance.js --execute
# FROM: ./berachain-rewards-vault

node governance.js --execute
  1. Executed(已执行):提案已成功实施,无需进一步操作。

使用 --check-state 命令定期监控你的提案进展。

⚠️ 警告:由于治理流程中的等待阶段,此过程可能需要数小时。请耐心等待并持续检查提案状态。

如果你的提案进入 Defeated(失败)Expired(过期) 状态,你需要运行以下命令重新创建一个新提案:

# FROM: ./berachain-rewards-vault

node governance.js --create-proposal

如果你创建了一个新提案,记得删除 .env 文件中的旧 PROPOSAL_ID 并添加新的提案 ID。

取消提案

你也可以在流程的任何阶段通过运行以下命令取消提案。

# FROM: ./berachain-rewards-vault

node governance.js --cancel

这是一个不可逆的操作,不过你可以使用新的 ID 创建一个治理提案。

将激励代币列入白名单4o

在创建奖励金库并通过治理获得批准后,你可以将额外的代币列入白名单作为激励。将激励代币列入白名单允许你在奖励金库中与 $BGT 发放一起分配该代币。

要将代币列入白名单,你需要创建另一个治理提案,指定代币地址和最低激励比率。最低激励比率确保分配的激励代币数量相对于 $BGT 发放有一定的意义。4o

要控制列入白名单代币的最低激励比率,你可以在 governance.js 脚本中修改 whitelistIncentiveToken 函数。查找以下这行代码:

const minIncentiveRate = ethers.parseUnits('0.01', 18); // 0.01 token per BGT emission

这一行代码将最低激励比率设置为每次 BGT 发放 0.01 个代币。你可以根据需要调整此值。例如,要将其设置为每次 BGT 发放 0.1 个代币,你可以将其更改为:

const minIncentiveRate = ethers.parseUnits('0.1', 18); // 0.1 token per BGT emission

在调整好激励比率后,若要发起列入激励代币白名单的治理提案,你可以使用以下命令:

node governance.js --whitelist-incentive

在运行此命令之前,确保你的 .env 文件中更新了以下信息:

  • INCENTIVE_TOKEN:将其设置为你想要列入白名单作为激励的代币地址。
  • REWARDS_VAULT_ADDRESS:确保此值设置为你的奖励金库地址。

例如,你的 .env 文件应包含以下内容:

INCENTIVE_TOKEN=0x1234...5678
REWARDS_VAULT_ADDRESS=0xabcd...efgh

运行该命令将创建一个新的治理提案,以将你的激励代币列入白名单。按照之前的流程监控、投票并执行该提案,以完成白名单认证过程。

完整的 GitHub 代码仓库

如果你想查看最终代码,请参考以下链接中的 Berachain Guides GitHub 仓库。

运行以下命令:

git clone https://github.com/berachain/guides.git
cd guides/apps/berachain-governance-proposal
npm install

按照附带的  README  指南下载并运行此脚本。

接下来怎么做?

🛠️ 想 BUIDL 更多?

如果你想在 Berachain 上构建更多项目并查看更多示例,请查看  Berachain GitHub Guides Repo , 里面有各种教程。

如果你想深入了解详情,请查看  Berachain Docs.

寻求开发支持?

确保加入 Berachain Discord ,查看开发者频道,向社区和 DevRels 提问并获取支持。

❤️ 别忘了为这篇文章点个赞 👏🏼

原创文章,作者:Rama Ai,如若转载,请注明出处:https://www.dappchaser.com/creating-a-governance-proposal-for-berachain-reward-vaults/

发表评论

邮箱地址不会被公开。 必填项已用*标注

联系我们

邮件:contact@dappchaser.com

QR code