0
0

Berachain報酬ボールトのためのガバナンス提案の作成 [Berachain翻訳]

Posted at

本記事は下記の翻訳となります。
『Creating a Governance Proposal for Berachain Reward Vaults』

image.png

Berachain の Proof of Liquidity コンセンサスメカニズムの最大の利点の 1 つは、プロトコルが Berachain ガバナンストークン($BGT)の発行を通じて流動性を確保できることです。この $BGT を蓄積するプロセスは、RewardsVaultsスマートコントラクトで表現されるリワードボールトを通じて行われます。

注意: テストネット上でBerps リワードボールトスマートコントラクトの動作を確認できます。

リワードボールトに $BGT が蓄積されるプロセスは、バリデータがブロック提案者に選ばれ、選ばれた際に $BGT を受け取り、その大半を BeraChef と呼ばれる配布コントラクトを通じて 1 つ以上のリワードボールトに分配することで行われます。バリデータの報酬は、ブロックを提案する時点で彼らに委任されている $BGT の量によって影響を受けます。

$BGT で何ができるか?

$BGTには主に 3 つの機能があります。それは、$BERAに焼却すること、バリデータに委任して報酬を増やすこと、またはガバナンスで提案や投票に使用することです。

BEX のリワードボールト例

BEXは AMM プロトコルで、Berachain のネイティブな Proof of Liquidity dApp の 1 つであり、複数のリワードボールトを持っています。ユーザーがホワイトリストに登録されたプールに流動性を提供すると、その総貢献度に比例した LP トークンを受け取ります。

例えば、ユーザーはHONEY <> WBTC プールに流動性を追加し、その見返りとして$HONEY-WBTC LP トークンを受け取ることができます。

$HONEY-WBTC Reward Vault

その後、LP トークンを取得し、BEX の$HONEY-WBTCリワードボールトにステーキングすることができます。LP トークンがステーキングされ、バリデータがリワードボールトに向けて$BGTの発行を行っている間、時間とともに$BGTが蓄積されていきます。これが BEX がユーザーにプロトコルへの流動性提供を促すやり方です。

あなたのプロトコルのトークン(仮に$BBT、Black Bera Token とします)の取引流動性のために BEX を活用する場合、それを$WBERAとペアにすると、ユーザーは流動性を提供する際に$BBT-WBERA-LPトークンを受け取ります。
LP トークンがホワイトリストに登録されている場合、つまりそのトークンをステーキングのために受け入れることができるリワードボールトがある場合、$BGT報酬の対象となり、Proof of Liquidity を通じてあなたのプロトコルの流動性を確保することができます。

これらのリワードボールトは、Berachain 上のスマートコントラクトであるリワードボールトファクトリーによって作成されます。このファクトリーがリワードボールトの作成を担当します。

自分でリワードボールトを作成することは誰でも可能ですが、バリデータがあなたのリワードボールトに$BGTを発行するためには、まずガバナンス提案として提出し、承認を得て、BGT ステーションにリワードボールトを追加する必要があります。提案が承認されると、あなたのリワードボールトは「friendOfTheChef」となり、バリデータが$BGTを発行できる適格なリワードボールトのコレクションに承認されます。このプロセスは「ホワイトリスト登録」とも呼ばれています。

Berachain のホワイトリスト登録のためのガバナンスプロセスを理解する

これから構築するものを理解するために、リワードボールトの作成、提案の作成、提案への投票、リワードボールトの有効化というガバナンスプロセスについて説明しましょう。

ガバナンスの要件

成功する提案を作成する前に、考慮すべきいくつかの要件とステップがあります。

  1. 最低 1000 $BGT - すべての提案には最低 1000 $BGTが必要です。これは直接獲得するか、他の人に提案者へ$BGTを委任してもらうことで満たすことができます。

  2. 委任された/自己委任された投票力 - 最低$BGTが満たされている場合、個人所有の$BGTで提案したり既存の提案に投票したりするには、それを提案者(提案者自身の場合も含む)に委任する必要があります。

  3. 定足数の達成 - 「賛成」票の過半数が 20 億の定足数を満たす必要があります。これはメインネットに近づくにつれて変更される可能性がありますが、テストネットでは、Berachain チームに定足数達成の支援を求めることができます。

ガバナンスのライフサイクル

以下は、ガバナンスプロセスのタイムライン全体を通じて提案が経る異なる状態を表しています。


Lifecycle of a Berachain Governance Proposal

1. 提案作成(保留状態)

  • 待機期間:投票開始まで 3 時間
  • この状態では提案をキャンセルすることも可能

2. 投票開始(アクティブ状態)

  • 期間:3 時間
  • BGT 保有者が投票を行う
  • 定足数を満たす必要あり:最低 20 億 BGT

3. 投票終了

  • 提案は成功または否決される
  • 否決された場合:そのように記録され、懸念事項に対処した後に新たな提案を作成可能

4. タイムロック(成功した場合)

  • 3 時間のタイムロック遅延を伴うキューに入る

5. 実行または期限切れ

  • 実行:Berachain に承認されたガバナンス EOA のみが実行可能
  • 期限切れ:期間内に実行されなかった場合

注意: 時間枠はプレースホルダーであり、メインネットローンチに近づくにつれて変更される可能性があります

リワードボールト用の BEX プールの作成

このチュートリアルでは、ユーザーがステーキングすることで$BGTの発行を受け取る資格を得られる LP トークンを受け入れるリワードボールトを作成します。このリワードボールトを LP トークンと共に作成したら、ガバナンスに提出して Berachef の「フレンド」として追加することができます。

まず、BEXプールとそのステーキングトークンを作成します。


Example of Pool Creation on BEX

新しいプールを作成するには、あなたのプロトコルトークン(この場合は新しく作成された$BBTというトークン)と$WBERAトークンを提供して、BEX 上にカスタムプールを作成する必要があります。

プールが作成され、預入が行われると、LP トークンを受け取るはずです。さらに預入を行うと、より多くの LP トークンが発行されます。

デプロイされたプールと LP トークンの例

リワードボールトとガバナンス提案作成のためのプロジェクトセットアップ

$BBT <> $WBERA BEX プールとそれに対応する LP トークン($BBT-WBERA-LP)ができたので、次は Berachef にリワードボールトを追加するガバナンス提案を作成する前に、リワードボールトを作成する必要があります。

以下の作業を行うスクリプトを実行するプロジェクトを作成します:

  1. LP トークン用のリワードボールトを作成する
  2. 新しいリワードボールトを Berachef に追加するための提案を Berachain ガバナンスに提出する
  3. リワードボールトに対して投票を行う
  4. リワードボールトを Berachef に追加して有効化する

プロジェクトの要件

スクリプトを構築するために、以下のものがコンピューターにインストールされているか、これらの要件の一部を満たしていることを確認してください。

  • NVM または NodeJS v20.16.0 以上
  • $BGTが蓄積されているウォレット
  • トランザクションを処理するための$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 ファイルのセットアップ

プロジェクトのルートディレクトリに abi フォルダを作成してください:

# FROM: ./berachain-rewards-vault

mkdir abi

以下の JSON ファイルをダウンロードし、このフォルダに追加する必要があります:

  • ERC20.json - ABI ファイル:LP トークンの ABI です。これは単なる一般的な ERC20 トークンです。
  • BerachainRewardsVaultFactory.json - ABI ファイル:Berachain リワードボールトファクトリーコントラクトの ABI です。
  • BerachainGovernance.json - ABI ファイル:Berachain ガバナンスコントラクトの ABI です。これは、ガバナンス提案の提出、投票、実行を行うメインのコントラクトです。
  • BGT.json - ABI ファイル$BGTの ABI です。
  • BeraChef.json - ABI ファイル:BeraChef の ABI です。これは$BBT <> $WBERA LP トークンのリワードボールトをホワイトリストに登録するコントラクトです。
  • 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 または自分のものに置き換えてください
  • 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. Rewards Vault を作成するためのコードを追加する

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_ADDRESSPRIVATE_KEY.envファイルに追加したら、以下のコードをgovernance.jsファイルに貼り付け、スクリプトを実行してリワードボールトを作成してください:

# FROM: ./berachain-rewards-vault

node governance.js --create-vault

このコマンドは、私たちの LP トークン用の新しいリワードボールトを作成し、作成されたボールトのアドレスをログに記録します。


Output of node governance.js --create-vault command

File: ./.env
この新しい値を .envファイルに追加してください:

REWARDS_VAULT_ADDRESS=value from terminal

注意: あなたのリワードボールトのアドレスは、このスクリーンショットで見られるものとは異なります。

ガバナンス提案の提出

リワードボールトを作成したので、今度はそれをホワイトリストに登録するためのガバナンス提案を提出する時です。このプロセスのために新しいスクリプトを作成します。

1. 環境のセットアップ

このプロセスの時点で、あなたの.envファイルには以下の値が含まれているはずです:
File: ./.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. Proposal 関数を作成する

同じファイルgovernance.jsで、関数getOrCreateVaultの下に次の関数を追加します。

File: ./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. 3. メイン関数にコマンドを追加

次に、governance.js ファイルの main 関数に、以下の switch case を--create-vault ケースの直後に追加してください。これはガバナンス提案の作成を処理します:

File: ./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🚨
  }
}

このmain関数の新しいスイッチケースにより、新しく作成した Rewards Vault を"friends of the chef"に追加するためのガバナンス提案を作成できるようになります。十分な投票力があるかをチェックし、ボールトアドレスを取得(または作成)し、必要なパラメータを使ってcreateProposal関数を呼び出します。

4. create-proposal コマンドを実行する

main関数に--create-proposalケースを追加したので、Rewards Vault を BeraChef に追加するためのガバナンス提案を作成するコマンドを実行できます。ターミナルで以下を実行してください:

# FROM: ./berachain-rewards-vault

node governance.js --create-proposal

このコマンドは、あなたの Rewards Vault をホワイトリストに追加するための新しいガバナンス提案を作成します。スクリプトは提案 ID を出力します。今後のステップのために、この ID を.envファイルに追加する必要があります。

コマンドを実行すると、以下のような出力が表示されるはずです:


Output of the node governance.js --create-proposal command

出力の指示に従って、.envファイルを更新してください。PROPOSAL_IDの値を含む新しい行を追加します:
File: ./.env

PROPOSAL_ID=your_proposal_id

このPROPOSAL_IDは、投票、キューイング、提案の実行など、ガバナンスプロセスの後続のステップで使用されます。これは、あなたの Vault を BeraChef に追加するためのガバナンス提案の ID を表しています。

5. 投票と実行のための関数を作成する

以下の関数をgovernance.jsファイルのcreateProposal関数の下に追加してください:

File: ./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;
  }
}

これらの関数をガバナンスプロセスの異なる段階で処理します:

  • castVote: この関数を使用して提案に投票できます。まず、すでに投票したかどうかをチェックし、まだ投票していない場合は「賛成」票(1で表される)を投じます。

  • executeProposal: 提案がキューに入れられ、タイムロック期間が経過した後、この関数を呼び出して提案を実行できます。これは、ガバナンスプロセスで提案された変更を実施する最終ステップです。

  • cancelProposal: 提案が実行のためにキューに入れられる前にキャンセルすることができます。これは、間違ったアドレスや説明を入力してしまった場合や、新しい提案を作成したい場合に使用します。

次に、governance.jsファイルのmain関数を以下のコードに置き換えてください:

File: ./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;

    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);
});

main関数のこれらの新しいケースにより、.envファイルのPROPOSAL_IDを使用してガバナンス提案に投票し、実行することができます。各ケースは、それぞれのアクションを実行する前にPROPOSAL_IDが設定されているかどうかをチェックするので、.envが更新されていることを確認してください。

提案の進捗をモニタリングする

提案を作成したら、その進捗をモニタリングし、適切なタイミングで行動を起こす必要があります。以下のコマンドを使用して、提案の現在の状態を確認してください:

# FROM: ./berachain-rewards-vault

node governance.js --check-state

提案の状態に応じて、コマンドを実行するか、プロセスの次の段階を待つ必要があります。

  1. Pending: This is the initial state. This stage takes three hours and you cannot vote or execute your proposal in this stage.

  2. Active: This is the voting period. Cast your vote by running:

  3. Pending(保留中): これは初期状態です。この段階は 3 時間続き、この間は提案に投票したり実行したりすることはできません。

  4. Active(アクティブ): これは投票期間です。以下のコマンドを実行して投票してください:

# FROM: ./berachain-rewards-vault

node governance.js --vote
  1. Succeeded(成功): 提案が投票期間を通過しました。これで実行のためにキューに入れられました。

  2. Queued(キュー済み): 提案はタイムロック期間にあります。この期間が終了すると、提案を実行できます。この時点で、あなたの LP Rewards Vault が Berachef に正式に追加されます:

# 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を削除し、新しいものを追加することを忘れないでください。

提案のキャンセル

プロセスのどの段階でも、以下のコマンドを実行することで提案をキャンセルすることができます。

# FROM: ./berachain-rewards-vault

node governance.js --cancel

これは取り消しできないアクションですが、新しい ID でガバナンス提案を作成することはできます。

完全な GitHub コードリポジトリ

最終的なコードを見たい場合は、 Berachain Guides GitHub Repositoryの以下のリンクをご覧ください。

以下のコマンドを実行してください:

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

README に記載されている指示に従って、このスクリプトをダウンロードし実行してください。

次のステップは?

🛠️ さらに開発を進めたいですか?

Berachain でさらに開発を進めたい、より多くの実装例を見たいとお考えですか?私たちBerachain GitHub Guides Repo をぜひご覧ください。このリポジトリには、NextJS、Hardhat、Viem、Foundry など、様々な技術を使用した幅広い実装例が用意されています。

より詳細な情報を探求したい場合は、Berachain ドキュメントをご覧ください。

開発者サポートをお探しですか?

質問をするために、Berachain Discordサーバーに参加し、開発者チャンネルをチェックしてください。



【Sunrise とは】
Sunrise は Proof of Liquidity(PoL)と Fee Abstraction(手数料抽象化)を備えたデータ可用性レイヤーです。 私たちは DA の体験を再構築し、多様なエコシステムからのモジュラー型流動性を活用してロールアップを立ち上げています。

【Social Links】

【お問合せ】
Sunrise へのお問い合わせはこちらから 👉 Google Form

1080x360.jpeg

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0