投票
VotesComponent 提供了一个灵活的系统来跟踪和委托投票权。该系统允许用户将其投票权委托给其他地址,从而实现更积极地参与治理。
默认情况下,token 余额不计入投票权。这使得转账更便宜。缺点是它需要用户委托给自己以激活检查点并跟踪他们的投票权。 |
投票单位的转移必须由实施合约处理。在 ERC20 和 ERC721 的情况下,这通常通过 hooks 完成。您可以查看 用法 部分,了解如何实现此目的的示例。
|
主要特性
-
委托: 用户可以将其投票权委托给任何地址,包括他们自己。投票权可以直接通过调用 delegate 函数来委托,也可以通过提供一个签名与 delegate_by_sig 一起使用来委托。
-
历史查询: 该系统跟踪每个账户的历史快照,这允许在特定时间戳查询账户的投票权。+ 例如,这可以用于确定账户在提案创建时的投票权,而不是使用当前的余额。
用法
当集成 VotesComponent
时,必须实现 VotingUnitsTrait,以作为实现合约的函数获取给定账户的投票单位。+
为了简单起见,此模块已经为 ERC20
和 ERC721
token 提供了两个实现,如果集成了相应的组件,它们将开箱即用。+
此外,您必须实现 NoncesComponent 和 SNIP12Metadata trait 才能通过签名进行委托。
以下是如何构建一个简单的 ERC20Votes 合约的示例:
#[starknet::contract]
mod ERC20VotesContract {
use openzeppelin_governance::votes::VotesComponent;
use openzeppelin_token::erc20::{ERC20Component, DefaultConfig};
use openzeppelin_utils::cryptography::nonces::NoncesComponent;
use openzeppelin_utils::cryptography::snip12::SNIP12Metadata;
use starknet::ContractAddress;
component!(path: VotesComponent, storage: erc20_votes, event: ERC20VotesEvent);
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);
// Votes
#[abi(embed_v0)]
impl VotesImpl = VotesComponent::VotesImpl<ContractState>;
impl VotesInternalImpl = VotesComponent::InternalImpl<ContractState>;
// ERC20
#[abi(embed_v0)]
impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl<ContractState>;
impl ERC20InternalImpl = ERC20Component::InternalImpl<ContractState>;
// Nonces
#[abi(embed_v0)]
impl NoncesImpl = NoncesComponent::NoncesImpl<ContractState>;
#[storage]
pub struct Storage {
#[substorage(v0)]
pub erc20_votes: VotesComponent::Storage,
#[substorage(v0)]
pub erc20: ERC20Component::Storage,
#[substorage(v0)]
pub nonces: NoncesComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20VotesEvent: VotesComponent::Event,
#[flat]
ERC20Event: ERC20Component::Event,
#[flat]
NoncesEvent: NoncesComponent::Event
}
// Required for hash computation.
// 哈希计算所需。
pub impl SNIP12MetadataImpl of SNIP12Metadata {
fn name() -> felt252 {
'DAPP_NAME'
}
fn version() -> felt252 {
'DAPP_VERSION'
}
}
// We need to call the `transfer_voting_units` function after
// 我们需要在每次 mint、burn 和 transfer 之后调用 `transfer_voting_units` 函数。
// every mint, burn and transfer.
// 为此,我们使用 `ERC20Component::ERC20HooksTrait` 的 `after_update` hook。
// For this, we use the `after_update` hook of the `ERC20Component::ERC20HooksTrait`.
impl ERC20VotesHooksImpl of ERC20Component::ERC20HooksTrait<ContractState> {
fn after_update(
ref self: ERC20Component::ComponentState<ContractState>,
from: ContractAddress,
recipient: ContractAddress,
amount: u256
) {
let mut contract_state = self.get_contract_mut();
contract_state.erc20_votes.transfer_voting_units(from, recipient, amount);
}
}
#[constructor]
fn constructor(ref self: ContractState) {
self.erc20.initializer("MyToken", "MTK");
}
}
以下是如何构建一个简单的 ERC721Votes 合约的示例:
#[starknet::contract]
pub mod ERC721VotesContract {
use openzeppelin_governance::votes::VotesComponent;
use openzeppelin_introspection::src5::SRC5Component;
use openzeppelin_token::erc721::ERC721Component;
use openzeppelin_utils::cryptography::nonces::NoncesComponent;
use openzeppelin_utils::cryptography::snip12::SNIP12Metadata;
use starknet::ContractAddress;
component!(path: VotesComponent, storage: erc721_votes, event: ERC721VotesEvent);
component!(path: ERC721Component, storage: erc721, event: ERC721Event);
component!(path: SRC5Component, storage: src5, event: SRC5Event);
component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);
// Votes
#[abi(embed_v0)]
impl VotesImpl = VotesComponent::VotesImpl<ContractState>;
impl VotesInternalImpl = VotesComponent::InternalImpl<ContractState>;
// ERC721
#[abi(embed_v0)]
impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl<ContractState>;
impl ERC721InternalImpl = ERC721Component::InternalImpl<ContractState>;
// Nonces
#[abi(embed_v0)]
impl NoncesImpl = NoncesComponent::NoncesImpl<ContractState>;
#[storage]
pub struct Storage {
#[substorage(v0)]
pub erc721_votes: VotesComponent::Storage,
#[substorage(v0)]
pub erc721: ERC721Component::Storage,
#[substorage(v0)]
pub src5: SRC5Component::Storage,
#[substorage(v0)]
pub nonces: NoncesComponent::Storage
}
#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC721VotesEvent: VotesComponent::Event,
#[flat]
ERC721Event: ERC721Component::Event,
#[flat]
SRC5Event: SRC5Component::Event,
#[flat]
NoncesEvent: NoncesComponent::Event
}
/// Required for hash computation.
// 哈希计算所需。
pub impl SNIP12MetadataImpl of SNIP12Metadata {
fn name() -> felt252 {
'DAPP_NAME'
}
fn version() -> felt252 {
'DAPP_VERSION'
}
}
// We need to call the `transfer_voting_units` function after
// 我们需要在每次 mint、burn 和 transfer 之后调用 `transfer_voting_units` 函数。
// every mint, burn and transfer.
// 为此,我们使用 `ERC721Component::ERC721HooksTrait` 的 `before_update` hook。
// For this, we use the `before_update` hook of the
// 这个 hook 在 transfer 执行之前被调用。
//`ERC721Component::ERC721HooksTrait`.
// This hook is called before the transfer is executed.
// 这使我们可以访问先前的所有者。
// This gives us access to the previous owner.
impl ERC721VotesHooksImpl of ERC721Component::ERC721HooksTrait<ContractState> {
fn before_update(
ref self: ERC721Component::ComponentState<ContractState>,
to: ContractAddress,
token_id: u256,
auth: ContractAddress
) {
let mut contract_state = self.get_contract_mut();
// We use the internal function here since it does not check if the token
// 我们在这里使用内部函数,因为它不检查 token id 是否存在,这对于 mint 是必要的
// id exists which is necessary for mints
let previous_owner = self._owner_of(token_id);
contract_state.erc721_votes.transfer_voting_units(previous_owner, to, 1);
}
}
#[constructor]
fn constructor(ref self: ContractState) {
self.erc721.initializer("MyToken", "MTK", "");
}
}
接口
这是 VotesImpl
实现的完整接口:
#[starknet::interface]
pub trait VotesABI<TState> {
// IVotes
fn get_votes(self: @TState, account: ContractAddress) -> u256;
fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256;
fn get_past_total_supply(self: @TState, timepoint: u64) -> u256;
fn delegates(self: @TState, account: ContractAddress) -> ContractAddress;
fn delegate(ref self: TState, delegatee: ContractAddress);
fn delegate_by_sig(ref self: TState, delegator: ContractAddress, delegatee: ContractAddress, nonce: felt252, expiry: u64, signature: Span<felt252>);
// INonces
fn nonces(self: @TState, owner: ContractAddress) -> felt252;
}