25个版本 (12个破坏性)

0.17.1 2023年1月26日
0.16.0 2022年12月16日
0.15.1 2022年9月22日
0.13.0 2022年7月26日
0.8.1 2022年3月29日

#397 in 魔法豆

Download history 42/week @ 2024-03-29 52/week @ 2024-04-05 6/week @ 2024-04-12

每月57次下载

Apache-2.0

305KB
6K SLoC

Tgrade验证者集

这使用了Tgrade特定绑定,允许特权合约将可信cw4合约映射到运行链的Tendermint验证者集。指向cw4-group合约将实现PoA,指向cw4-stake合约将创建一个纯(未委托)PoS链。

(惩罚和奖励分配是其他合约的后续工作)

奖励计算

Tgrade::EndBlock sudo消息中,此合约执行通过epoch的活跃验证者的奖励计算和分配。

累积奖励值包含

  • 每个epoch的奖励 - 每个epoch新铸造的代币
  • 验证的块中的交易费用

每个epoch的奖励在实例化消息中可配置,在epoch_reward字段中。费用累积在合约本身上。

epoch奖励不是固定的 - epoch_reward是其基本值,但它根据累积的费用数量而修改。最终的奖励公式是

cumulative_reward = max(0, epoch_rewards - fee_percentage * fees) + fees

该想法是,在早期时期,预计交易不会太多,因此会铸造奖励以使验证有利可图。然而,在后期,当交易增多时,费用足以作为验证的奖励,因此不需要再铸造新代币,所以实际上没有必要引入代币通货膨胀。

可以通过将 fee_percentage 设置为 0(这实际上使 fee_percentage * fees 始终等于 0)来轻松禁用奖励减少功能。将其设置为大于 1(或 100%)会导致随着费用的增长,累计奖励会减少,直到 fees 达到 epoch_reward / fee_percentage 阈值(从这一点开始,不再铸造新代币,只有费用在验证者之间分配)。在 (0; 1] 范围内设置 fee_percentage 会导致累计奖励增长减少 - 基本上,当 fees 达到 epoch_reward / fee_percentage 时,所有费用都值得 (1 - fee_percentage) * fees(它们被削减)。

下一步是将 cumulative_reward 分成几个部分。对于每个 分配合约,接受一个地址和一个比例。使用 distribute_funds 消息将 distribution_contract_ratio * cumulative_reward 发送到每个此类合约,其目的是根据非验证者的参与度将这部分奖励分配给他们。剩余的奖励代币作为 validators_reward 发送到上一个周期的验证者。需要在 InstantiateMsg 中配置的 distribution_contracts 向量中包含分配比率。这些比率的总和需要适合 [0, 1] 范围。该向量可以为空,在这种情况下,所有奖励都将归验证者所有。

当计算 validators_reward 时,它将在活跃验证者之间分配。活跃验证者是前 max_validators 个得分最高的验证者,但至少有 min_pointsscaling 是一个可选字段,它允许根据 Tendermint 的目的缩放积分(它不应影响奖励分配)。选择验证者后,cumulative_reward 将按验证者 points 的比例在他们之间分配。所有 max_validatorsmin_pointsscaling 都可以在实例化期间进行配置。通过外部合约实现 validators_reward 的分配。

fee_percentage 的默认值为 0(因此,当在消息中未指定时,奖励减少将被禁用)。在 Tgrade 创世时,fee_percentage 预计将被设置为 0.5

奖励分配合约

如前文所述,奖励分配是通过由 tgrade-valset 管理的外部合约实现的。它假定是 tg4-engagement 合约,但在现实中它只需支持适当的 API,然后 tgrade-valset(它只是 tg4-engagement 的一个子集)就可以使用。

在 valset 实例化过程中,奖励分配合约,称为 validator_group,使用以下消息进行实例化:

{
  "admin": "tgrade_valset_addr",
  "denom": "epoch_reward_denom",
  "members": []
}

存储的验证者组合约的代码 ID 通过其实例化消息发送到 valset(validator_group_code_id 字段)。验证者组合约分配的地址将以 wasm 事件的形式发出。

{
  "_contract_addr": "valset_addr",
  "action": "tgrade-valset_instantiation",
  "validator_group": "validator_group_addr"
}

在每一个纪末,将使用执行消息将奖励发送到验证者组合约。

{
  "distribute_funds": {}
}

之后,将发送另一个消息来更新验证者和他们的积分。

{
  "update_members": {
    "remove": ["validator_to_be_removed"],
    "add": [{
      "addr": "validator_with_points_updated",
      "points": 10
    }]
  }
}

监禁

监禁是一种暂时禁止操作者验证区块的机制。

只有一个地址允许监禁成员,并且它在 InstantiateMsg 中配置为 admin。想法是,管理员是某种投票合约,它将通过投票共识来决定禁止。

监禁成员将禁止他在未来的纪中作为验证者,除非他被解禁。有三种解禁成员的方法:

  • 管理员可以始终通过投票解禁被监禁的成员。
  • 任何成员都可以在监禁期届满时自行解禁。
  • 成员可以在监禁期届满后自动解禁(这可以通过 InstantiateMsg::auto_unjail 标志启用)。

可以通过正常验证者查询查询监禁状态 - 如果验证者被监禁,则响应将包含一个 jailed_until 字段,该字段包含一个单独的 forever 字段(如果此成员永远不允许自行解禁),或者一个包含时间戳的 until 字段,指示成员何时可以解禁。

减薪

该合约实现了减薪语义,但实际上并没有实现完整的减薪接口。它对减薪消息做出适当的反应。

{
  "slash": {
    "addr": "contract_to_slash",
    "portion": portion_to_slash
  }
}

减薪通过只是将 Slash 消息转发到 membership 合约(在实例化时设置 - 这被假定为混合合约,但从技术上讲,可以是任何实现 tg4Slashing 接口的合约)。显然,要能够在 membership 合约上减薪,tgrade-valset 必须将其自身注册为减薪者,因此在实例化时,它会向 membership 合约发送 AddSlasher 消息,整个实例化必须成功。因此,必须在 membership 合约上设置适当的 slashing_preauths

tgrade-valsetAddSlasherRemoveSlasher 消息没有反应,因为它不支持多个减薪者。只有 tgrade-valset 的管理员才能永远在该合约上减薪(他也可以永远这样做)。

由于只有 membership 合约通过此 Slash 实现被减薪,因此该 membership 合约本身负责确保验证者和参与合约上的积分保持一致。然而,由于奖励分配直到下一个纪才会重新计算,减薪不会影响当前纪。

初始化

pub struct InstantiateMsg {
    /// Address allowed to jail, meant to be a OC voting contract. If `None`, then jailing is
    /// impossible in this contract.
    pub admin: Option<String>,
    /// Address of a cw4 contract with the raw membership used to feed the validator set
    pub membership: String,
    /// Minimum points needed by an address in `membership` to be considered for the validator set.
    /// 0-points members are always filtered out.
    /// TODO: if we allow sub-1 scaling factors, determine if this is pre-/post- scaling
    /// (use points for cw4, power for Tendermint)
    pub min_points: u64,
    /// The maximum number of validators that can be included in the Tendermint validator set.
    /// If there are more validators than slots, we select the top N by membership points
    /// descending. (In case of ties at the last slot, select by "first" Tendermint pubkey,
    /// lexicographically sorted).
    pub max_validators: u32,
    /// Number of seconds in one epoch. We update the Tendermint validator set only once per epoch.
    /// Epoch # is env.block.time/epoch_length (round down). The first block with a new epoch number
    /// will trigger a new validator calculation.
    pub epoch_length: u64,
    /// Total reward paid out at each epoch. This will be split among all validators during the last
    /// epoch.
    /// (epoch_reward.amount * 86_400 * 30 / epoch_length) is the amount of reward tokens to mint
    /// each month.
    /// Ensure this is sensible in relation to the total token supply.
    pub epoch_reward: Coin,

    /// Initial operators and validator keys registered.
    /// If you do not set this, the validators need to register themselves before
    /// making this privileged/calling the EndBlockers, so that we have a non-empty validator set
    pub initial_keys: Vec<OperatorInitInfo>,

    /// A scaling factor to multiply cw4-group points to produce the Tendermint validator power
    /// (TODO: should we allow this to reduce points? Like 1/1000?)
    pub scaling: Option<u32>,

    /// Percentage of total accumulated fees that is subtracted from tokens minted as rewards.
    /// 50% by default. To disable this feature just set it to 0 (which effectively means that fees
    /// don't affect the per-epoch reward).
    #[serde(default = "default_fee_percentage")]
    pub fee_percentage: Decimal,

    /// Flag determining if validators should be automatically unjailed after the jailing period;
    /// false by default.
    #[serde(default)]
    pub auto_unjail: bool,

    /// Validators who are caught double signing are jailed forever and their bonded tokens are
    /// slashed based on this value.
    #[serde(default = "default_double_sign_slash")]
    pub double_sign_slash_ratio: Decimal,

    /// Addresses where part of the reward for non-validators is sent for further distribution. These are
    /// required to handle the `Distribute {}` message (eg. tg4-engagement contract) which would
    /// distribute the funds sent with this message.
    ///
    /// The sum of ratios here has to be in the [0, 1] range. The remainder is sent to validators via the
    /// rewards contract.
    ///
    /// Note that the particular algorithm this contract uses calculates token rewards for distribution
    /// contracts by applying decimal division to the pool of reward tokens, and then passes the remainder
    /// to validators via the contract instantiated from `rewards_code_is`. This will cause edge cases where
    /// indivisible tokens end up with the validators. For example if the reward pool for an epoch is 1 token
    /// and there are two distribution contracts with 50% ratio each, that token will end up with the
    /// validators.
    pub distribution_contracts: UnvalidatedDistributionContracts,

    /// Code id of the contract which would be used to distribute the rewards of this token, assuming
    /// `tg4-engagement`. The contract will be initialized with the message:
    /// ```json
    /// {
    ///     "admin": "valset_addr",
    ///     "denom": "reward_denom",
    /// }
    /// ```
    ///
    /// This contract has to support all the `RewardsDistribution` messages
    pub validator_group_id: u64,
}

消息

pub enum ExecuteMsg {
    /// Change the admin
    UpdateAdmin {
        admin: Option<String>,
    },
    /// Links info.sender (operator) to this Tendermint consensus key.
    /// The operator cannot re-register another key.
    /// No two operators may have the same consensus_key.
    RegisterValidatorKey {
        pubkey: Pubkey,
        /// Additional metadata assigned to this validator
        metadata: ValidatorMetadata,
    },
    UpdateMetadata(ValidatorMetadata),
    /// Jails validator. Can be executed only by the admin.
    Jail {
        /// Operator which should be jailed
        operator: String,
        /// Duration for how long validator is jailed, `None` for jailing forever
        duration: Option<Duration>,
    },
    /// Unjails validator. Admin can unjail anyone anytime, others can unjail only themselves and
    /// only if the jail period passed.
    Unjail {
        /// Address to unjail. Optional, as if not provided it is assumed to be the sender of the
        /// message (for convenience when unjailing self after the jail period).
        operator: Option<String>,
    },
    /// To be called by admin only. Slashes a given address (by forwarding slash to both rewards
    /// contract and engagement contract)
    Slash {
        addr: String,
        portion: Decimal,
    },
}

pub struct ValidatorMetadata {
    /// The validator's name (required)
    pub moniker: String,

    /// The optional identity signature (ex. UPort or Keybase)
    pub identity: Option<String>,

    /// The validator's (optional) website
    pub website: Option<String>,

    /// The validator's (optional) security contact email
    pub security_contact: Option<String>,

    /// The validator's (optional) details
    pub details: Option<String>,
}

查询

pub enum QueryMsg {
    /// Returns ConfigResponse - static contract data
    Config {},
    /// Returns EpochResponse - get info on current and next epochs
    Epoch {},

    /// Returns the validator key and associated metadata (if present) for the given operator.
    /// Returns ValidatorResponse
    Validator { operator: String },
    /// Paginate over all operators, using operator address as pagination.
    /// Returns ListValidatorsResponse
    ListValidators {
        start_after: Option<String>,
        limit: Option<u32>,
    },

    /// List the current validator set, sorted by power descending
    /// (no pagination - reasonable limit from max_validators)
    ListActiveValidators {},

    /// This will calculate who the new validators would be if
    /// we recalculated end block right now.
    /// Also returns ListActiveValidatorsResponse
    SimulateActiveValidators {},

    /// Returns a list of validator slashing events.
    /// Returns ListValidatorSlashingResponse
    ListValidatorSlashing { operator: String },
}

依赖

~4–16MB
~232K SLoC