Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

kaiax/staking,reward: Introduce consensus liquidity #176

Merged
merged 3 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
hyunsooda marked this conversation as resolved.
Show resolved Hide resolved
}

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
Loading