Skip to content

Commit

Permalink
kaiax/staking,reward: Introduce consensus liquidity
Browse files Browse the repository at this point in the history
  • Loading branch information
hyeonLewis committed Dec 12, 2024
1 parent d43c6c1 commit c2e0fab
Show file tree
Hide file tree
Showing 17 changed files with 2,241 additions and 81 deletions.
12 changes: 12 additions & 0 deletions blockchain/system/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var (
Kip103Name = "KIP103"
Kip113Name = "KIP113"
Kip160Name = "KIP160"
CLRegistryName = "CLRegistry"

AllContractNames = []string{
AddressBookName,
Expand All @@ -64,6 +65,9 @@ var (
Kip113ProxyAddrMock = common.HexToAddress("0x0000000000000000000000000000000000000402")
Kip113LogicAddrMock = common.HexToAddress("0x0000000000000000000000000000000000000403")

// Registry will return zero address for non-existent system contract.
NonExistentAddress = common.HexToAddress("0x0000000000000000000000000000000000000000")

// System contract binaries to be injected at hardfork or used in testing.
MainnetCreditCode = hexutil.MustDecode("0x" + misccontract.CypressCreditBinRuntime)
MainnetCreditV2Code = hexutil.MustDecode("0x" + misccontract.CypressCreditV2BinRuntime)
Expand All @@ -81,6 +85,14 @@ var (
MultiCallCode = hexutil.MustDecode("0x" + multicall.MultiCallContractBinRuntime)
MultiCallMockCode = hexutil.MustDecode("0x" + testcontract.MultiCallContractMockBinRuntime)

// Mock for CLRegistry testing
CLRegistryMockThreeCLAddr = common.HexToAddress("0x0000000000000000000000000000000000000Ff0")
WrappedKaiaMockAddr = common.HexToAddress("0x0000000000000000000000000000000000000Ff1")
RegistryMockForCLCode = hexutil.MustDecode("0x" + reward.RegistryMockForCLBinRuntime)
RegistryMockZero = hexutil.MustDecode("0x" + reward.RegistryMockZeroBinRuntime)
CLRegistryMockThreeCLCode = hexutil.MustDecode("0x" + reward.CLRegistryMockThreeCLBinRuntime)
WrappedKaiaMockCode = hexutil.MustDecode("0x" + reward.WrappedKaiaMockBinRuntime)

// Errors
ErrRegistryNotInstalled = errors.New("Registry contract not installed")
ErrRebalanceIncorrectBlock = errors.New("cannot find a proper target block number")
Expand Down
12 changes: 9 additions & 3 deletions contracts/contracts/testing/reward/AddressBookMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -388,11 +388,17 @@ contract AddressBookMockOneCN {
}
}

contract MockValues {
address public constant nodeId0 = 0x0000000000000000000000000000000000000F00;
address public constant nodeId1 = 0x0000000000000000000000000000000000000F03;
address public constant nodeId2 = 0x0000000000000000000000000000000000000F06;
}

/**
* @title AddressBookMockTwoCN
*/

contract AddressBookMockTwoCN {
contract AddressBookMockTwoCN is MockValues {

function getAllAddress() external view returns (uint8[] memory typeList, address[] memory addressList) {
typeList = new uint8[](8);
Expand All @@ -407,10 +413,10 @@ contract AddressBookMockTwoCN {
typeList[6] = 3; // POC address
typeList[7] = 4; // KIR address

addressList[0] = 0x0000000000000000000000000000000000000F00;
addressList[0] = nodeId0;
addressList[1] = 0x0000000000000000000000000000000000000F01;
addressList[2] = 0x0000000000000000000000000000000000000f02;
addressList[3] = 0x0000000000000000000000000000000000000F03;
addressList[3] = nodeId1;
addressList[4] = 0x0000000000000000000000000000000000000f04;
addressList[5] = 0x0000000000000000000000000000000000000f05;
addressList[6] = 0x0000000000000000000000000000000000000F06;
Expand Down
84 changes: 84 additions & 0 deletions contracts/contracts/testing/reward/CLRegistryMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2024 The klaytn Authors
// This file is part of the klaytn library.
//
// The klaytn library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The klaytn library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.

pragma solidity ^0.4.24;

import "./AddressBookMock.sol";

/**
* @title CLRegistryMockTwoCL
*/

contract CLRegistryMockThreeCL is MockValues {
function getAllCLs()
external
view
returns (address[] memory, uint256[] memory, address[] memory, address[] memory)
{
address[] memory nodeIds = new address[](3);
uint256[] memory gcIds = new uint256[](3);
address[] memory clPools = new address[](3);
address[] memory clStakings = new address[](3);

nodeIds[0] = nodeId0;
nodeIds[1] = nodeId1;
nodeIds[2] = nodeId2; // Doesn't exist in AddressBookMockTwoCN

gcIds[0] = 1;
gcIds[1] = 2;
gcIds[2] = 3;

clPools[0] = 0x0000000000000000000000000000000000000e00;
clPools[1] = 0x0000000000000000000000000000000000000e01;
clPools[2] = 0x0000000000000000000000000000000000000e02;

clStakings[0] = 0x0000000000000000000000000000000000000e03;
clStakings[1] = 0x0000000000000000000000000000000000000e04;
clStakings[2] = 0x0000000000000000000000000000000000000e05;

return (nodeIds, gcIds, clPools, clStakings);
}
}

contract RegistryMockForCL {
// This is a mock implementation of the Registry contract
// It returns a fixed address for the CLRegistryMockThreeCL address
function getActiveAddr(string name) external view returns (address) {
address clRegistryAddr = 0x0000000000000000000000000000000000000Ff0;
address wrappedKaiaAddr = 0x0000000000000000000000000000000000000Ff1;

if (keccak256(name) == keccak256("CLRegistry")) {
return clRegistryAddr;
} else if (keccak256(name) == keccak256("WrappedKaia")) {
return wrappedKaiaAddr;
}
return address(0);
}
}

contract RegistryMockZero {
// This is a mock implementation of the Registry contract
// It returns a fixed address for the CLRegistryMockThreeCL address
function getActiveAddr(string name) external view returns (address) {
return address(0);
}
}

contract WrappedKaiaMock {
function balanceOf(address account) external view returns (uint256) {
return account.balance;
}
}
1,325 changes: 1,314 additions & 11 deletions contracts/contracts/testing/reward/all.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions contracts/contracts/testing/reward/all.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ pragma solidity ^0.4.24;
// If each file is abigen separately, the resulting Go files may have duplicate symbols.
import "./AddressBookMock.sol";
import "./KlaytnReward.sol";
import "./CLRegistryMock.sol";
10 changes: 10 additions & 0 deletions kaiax/reward/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Block rewards are granted in native tokens (KAIA) every block. At high level, th
The rules have changed over hardforks.
- [Magma hardfork](https://github.com/klaytn/klaytn/releases/tag/v1.9.0) implements [KIP-71](https://kips.kaia.io/KIPs/kip-71) which burns the half of the transaction fees.
- [Kore hardfork](https://github.com/klaytn/klaytn/releases/tag/v1.10.0) implements [KIP-82](https://kips.kaia.io/KIPs/kip-82) which stipulates the reward distribution to the stakers and proposer.
- [Prague hardfork]() implements [KIP-226](https://kips.kaia.io/KIPs/kip-226) which introduces consensus liquidity.

![reward_scheme](./reward_scheme.png)

Expand Down Expand Up @@ -71,6 +72,15 @@ The rules have changed over hardforks.

![kip82_reward](./kip82_reward.png)

- **Prague rule (KIP-226)**: The rule since the KIP-226 hardfork with `istanbul.policy == 2` and `reward.deferredtxfee = true`.
- Since Prague hardfork, the consensus liquidity is introduced. The validator's total staking amount is summed up with the KAIA staked in consensus liquidity.
- If the validator has staked more than `reward.minstake` in staking-only (CNStaking) contract, the validator's total staking amount will be summed up with the consensus liquidity.
- In this case, the reward between staking-only and consensus liquidity will be distributed proportionally to their staking amounts.
- Otherwise, validator will not be eligible for rewards.
- Other rules are the same as the Kore rule.

![kip226_reward](./kip226_reward.png)

## Persistent schema

This module does not persist any data.
Expand Down
63 changes: 51 additions & 12 deletions kaiax/reward/impl/getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,8 @@ func getDeferredRewardFullKore(config *reward.RewardConfig, totalFee, burntFee *
kif.Add(kif, ratioRemainder)

// Further distribute using Kip82Ratio. By the way, remainder goes to proposer.
// After Prague, if the CLStaking is not nil, the proposer and staking rewards are proportionally distributed to both CN and CL.
// For proposer rewards, see `specWithProposerAndFunds`.
stakersAlloc, kip82Remainder := assignStakingRewards(config, stakers, si)
proposer.Add(proposer, kip82Remainder)
stakers.Sub(stakers, kip82Remainder)
Expand Down Expand Up @@ -357,13 +359,21 @@ func calcRemainder(total *big.Int, parts ...*big.Int) *big.Int {
// Returns the allocation and the remainder.
func assignStakingRewards(config *reward.RewardConfig, stakersReward *big.Int, si *staking.StakingInfo) (map[common.Address]*big.Int, *big.Int) {
var (
cns = si.ConsolidatedNodes()
minStake = config.MinimumStake.Uint64()
totalExcessInt = uint64(0) // sum of excess stakes (the amount over minStake) over all stakers
cns = si.ConsolidatedNodes()
minStake = config.MinimumStake.Uint64()
totalExcessInt = uint64(0) // sum of excess stakes (the amount over minStake) over all stakers
cnTotalStakingMap = make(map[common.Address]uint64)
)
for _, cn := range cns {
if cn.StakingAmount > minStake {
totalExcessInt += cn.StakingAmount - minStake
// If the CNStaking is less than minStake, skip it.
if cn.StakingAmount >= minStake {
// Calculate total staking amount once
cnTotalStakingAmount := cn.StakingAmount
if cn.CLStakingInfo != nil {
cnTotalStakingAmount += cn.CLStakingInfo.CLStakingAmount
}
totalExcessInt += cnTotalStakingAmount - minStake
cnTotalStakingMap[cn.RewardAddr] = cnTotalStakingAmount
}
}

Expand All @@ -373,16 +383,22 @@ func assignStakingRewards(config *reward.RewardConfig, stakersReward *big.Int, s
alloc = make(map[common.Address]*big.Int)
)
for _, cn := range cns {
if cn.StakingAmount > minStake {
cnTotalStakingAmount := cnTotalStakingMap[cn.RewardAddr]
if cnTotalStakingAmount > minStake {
// The KAIA unit will cancel out:
// reward (kei) = excess (KAIA) * stakersReward (kei) / totalExcess (KAIA)
excess := new(big.Int).SetUint64(cn.StakingAmount - minStake)
reward := new(big.Int).Mul(excess, stakersReward)
reward.Div(reward, totalExcess)
if reward.Sign() > 0 {
alloc[cn.RewardAddr] = reward
excess := new(big.Int).SetUint64(cnTotalStakingAmount - minStake)
if reward := new(big.Int).Div(new(big.Int).Mul(excess, stakersReward), totalExcess); reward.Sign() > 0 {
if cn.CLStakingInfo != nil {
// The remaining amount will be added to the cnAmount.
cnAmount, clAmount := cn.Split(reward)
alloc[cn.RewardAddr] = cnAmount
alloc[cn.CLStakingInfo.CLRewardAddr] = clAmount
} else {
alloc[cn.RewardAddr] = reward
}
remaining.Sub(remaining, reward)
}
remaining.Sub(remaining, reward)
}
}
return alloc, remaining
Expand Down Expand Up @@ -411,6 +427,29 @@ func specWithProposerAndFunds(spec *reward.RewardSpec, config *reward.RewardConf
}

newSpec.Proposer = proposer
if si.CLStakingInfos == nil {
newSpec.IncRecipient(config.Rewardbase, proposer)
return newSpec
}

// Handle CLStakingInfo for proposer if not nil
cns := si.ConsolidatedNodes()
for _, cn := range cns {
if cn.RewardAddr != config.Rewardbase {
continue
}
if cn.CLStakingInfo == nil {
// Early exit if there's no CL for proposer
break
}

cnAmount, clAmount := cn.Split(proposer)

newSpec.IncRecipient(cn.RewardAddr, cnAmount)
newSpec.IncRecipient(cn.CLStakingInfo.CLRewardAddr, clAmount)
return newSpec
}

newSpec.IncRecipient(config.Rewardbase, proposer)
return newSpec
}
Loading

0 comments on commit c2e0fab

Please sign in to comment.