diff --git a/doc/icon_chainscore_api.md b/doc/icon_chainscore_api.md
index ec1a3991c..0db729790 100644
--- a/doc/icon_chainscore_api.md
+++ b/doc/icon_chainscore_api.md
@@ -70,6 +70,7 @@
+ [requestUnjail](#requestunjail)
+ [setPRepCountConfig](#setprepcountconfig)
+ [handleDoubleSignReport](#handledoublesignreport)
+ + [setBondRequirementRate](#setbondrequirementrate)
- [BTP](#btp)
* ReadOnly APIs
+ [getBTPNetworkTypeID](#getbtpnetworktypeid)
@@ -770,7 +771,7 @@ def estimateUnstakeLockPeriod() -> dict:
### getPRepTerm
-Returns information for the current term.
+Returns the information about the current term.
```
def getPRepTerm() -> dict:
@@ -778,26 +779,31 @@ def getPRepTerm() -> dict:
*Returns:*
-| Key | Value Type | Description |
-|:-----------------|:--------------------------|:------------------------------------------------------------|
-| blockHeight | int | latest block height when this request was processed |
-| sequence | int | sequence number |
-| startBlockHeight | int | start block height of the term |
-| endBlockHeight | int | end block height of the term |
-| totalSupply | int | total supply amount at `startBlockHeight` |
-| preps | List\[[PRep](#prep)\] | Main/Sub P-Rep list at `startBlockHeight` |
-| totalDelegated | int | total delegation amount of `preps` |
-| totalPower | int | total power amount of `preps` |
-| period | int | term period |
-| rewardFund | [RewardFund](#rewardfund) | reward fund information for the term |
-| bondRequirement | int | bondRequirement for the term |
-| revision | int | revision for the term |
-| isDecentralized | bool | `true` if network is decentralized |
-| mainPRepCount | int | Main P-Reps count for the term |
-| iissVersion | int | IISS version for the term |
-| irep | int | (Optional. revision < 25) Irep for the term |
-| rrep | int | (Optional. revision < 25) Rrep for the term |
-| minimumBond | int | (Optional. revision >= 25) minimum bond amount for the term |
+| Key | Value Type | Description |
+|:--------------------|:--------------------------|:------------------------------------------------------------------------------------------------------------|
+| blockHeight | int | latest block height when this request was processed |
+| sequence | int | sequence number |
+| startBlockHeight | int | start block height of the term |
+| endBlockHeight | int | end block height of the term |
+| totalSupply | int | total supply amount at `startBlockHeight` |
+| preps | List\[[PRep](#prep)\] | Main/Sub P-Rep list at `startBlockHeight` |
+| totalDelegated | int | total delegation amount of `preps` |
+| totalPower | int | total power amount of `preps` |
+| period | int | term period |
+| rewardFund | [RewardFund](#rewardfund) | reward fund information for the term |
+| bondRequirement | int | bond requirement rate ranging from 0 (0%) to 100 (100%) for the current term
removed after revision 28 |
+| bondRequirementRate | int | bond requirement rate ranging from 0 (0%) to 10,000 (100%) for the current term
added after revision 28 |
+| revision | int | revision for the term |
+| isDecentralized | bool | `true` if network is decentralized |
+| mainPRepCount | int | Main P-Reps count for the term |
+| iissVersion | int | IISS version for the term |
+| irep | int | (Optional. revision < 25) Irep for the term |
+| rrep | int | (Optional. revision < 25) Rrep for the term |
+| minimumBond | int | (Optional. revision >= 25) minimum bond amount for the term |
+
+* `bondRequirement` field is replaced with `bondRequirementRate` field after revision 28
+* 0 <= `bondRequirement` <= 100 (0: 0%, 100: 100%)
+* 0 <= `bondRequirementRate` <= 10,000 (0: 0%, 10,000: 100%)
*Revision:* 5 ~
@@ -850,24 +856,29 @@ def getNetworkInfo() -> dict:
*Returns:*
-| Key | Value Type | Description |
-|:------------------|:--------------------------|:-------------------------------------------|
-| mainPRepCount | int | Main P-Reps count |
-| extraPRepCount | int | Extra Main P-Reps count |
-| subPRepCount | int | Sub Main P-Reps count |
-| iissVersion | int | IISS version |
-| termPeriod | int | period of term |
-| bondRequirement | int | bond requirement |
-| lockMinMultiplier | int | multiplier for minimum unstake lock period |
-| lockMaxMultiplier | int | multiplier for maximum unstake lock period |
-| unstakeSlotMax | int | maximum unstakes count of a account |
-| delegationSlotMax | int | maximum delegation count of a account |
-| rewardFund | [RewardFund](#rewardfund) | reward fund information |
-| totalStake | int | total stakes of ICONist |
-| totalBonded | int | total bonded amount of P-Rep |
-| totalDelegated | int | total delegated amount of P-Rep |
-| totalPower | int | total power amount of P-Rep |
-| preps | int | count of all P-Reps |
+| Key | Value Type | Description |
+|:--------------------|:--------------------------|:---------------------------------------------------------------------------------------|
+| mainPRepCount | int | Main P-Reps count |
+| extraPRepCount | int | Extra Main P-Reps count |
+| subPRepCount | int | Sub Main P-Reps count |
+| iissVersion | int | IISS version |
+| termPeriod | int | period of term |
+| bondRequirement | int | bond requirement rate ranging from 0 (0%) to 100 (100%)
removed after revision 28 |
+| bondRequirementRate | int | bond requirement rate ranging from 0 (0%) to 10,000 (100%)
added after revision 28 |
+| lockMinMultiplier | int | multiplier for minimum unstake lock period |
+| lockMaxMultiplier | int | multiplier for maximum unstake lock period |
+| unstakeSlotMax | int | maximum unstakes count of a account |
+| delegationSlotMax | int | maximum delegation count of a account |
+| rewardFund | [RewardFund](#rewardfund) | reward fund information |
+| totalStake | int | total stakes of ICONist |
+| totalBonded | int | total bonded amount of P-Rep |
+| totalDelegated | int | total delegated amount of P-Rep |
+| totalPower | int | total power amount of P-Rep |
+| preps | int | count of all P-Reps |
+
+* `bondRequirement` field is replaced with `bondRequirementRate` field after revision 28
+* 0 <= `bondRequirement` <= 100 (0: 0%, 100: 100%)
+* 0 <= `bondRequirementRate` <= 10,000 (0: 0%, 10,000: 100%)
*Revision:* 13 ~
@@ -1546,6 +1557,36 @@ def DoubleSignReported(owner Address, blockHeight int, type: str)
*Revision:* 25 ~
+### setBondRequirementRate
+
+* Update bondRequirementRate
+* Governance Only
+
+```
+def setBondRequirementRate(rate: int) -> None:
+```
+
+*Parameters:*
+
+| Name | Type | Description |
+|:-----|:-----|:--------------------------|
+| rate | int | new bond requirement rate |
+
+* 0 <= `rate` <= 10,000 (0: 0%, 10,000: 100%)
+
+*Event Log:*
+
+```
+@eventlog(indexed=0)
+def BondRequirementRateSet(rate: int) -> None:
+```
+
+| Name | Type | Description |
+|:------|:--------|:--------------------------|
+| rate | int | new bond requirement rate |
+
+*Revision:* 28 ~
+
# BTP
## ReadOnly APIs
@@ -1806,7 +1847,7 @@ The list of fields below is subject to change based on revisions
| nodeAddress | Address | node Key for only consensus |
| p2pEndpoint | str | network information used for connecting among P-Rep nodes |
| penalty | int | [PENALTY_TYPE_ID](#penalty_type_id) |
-| power | int | amount of power that a P-Rep receives from ICONist. (= min(`bonded`+`delegated`, `bonded` * 20)) |
+| power | int | amount of power that a P-Rep receives from ICONist. See [Power](#power) section for further information |
| status | int | [PREP_STATUS](#prep_status) |
| totalBlocks | int | number of blocks that a P-Rep received when running as a Main P-Rep |
| validatedBlocks | int | number of blocks that a P-Rep validated when running as a Main P-Rep |
@@ -1826,7 +1867,7 @@ The list of fields below is subject to change based on revisions
| name | str | P-Rep name |
| address | Address | P-Rep address |
| delegated | int | delegation amount that a P-Rep receives from ICONist |
-| power | int | amount of power that a P-Rep receives from ICONist. (= min(`bonded`+`delegated`, `bonded` * 20)) |
+| power | int | amount of power that a P-Rep receives from ICONist. See [Power](#power) section for more details |
## PRepStats
@@ -1894,6 +1935,30 @@ The list of fields below is subject to change based on revisions
| name | str | name |
| value | int | value |
+## Power
+
+* `power` is also known as `votingPower`.
+* `power` is calculated according to the following formular:
+
+```
+power = min(bonded + delegated, bonded * 10000 / bondRequirementRate)
+```
+
+| Name | Type | Description |
+|:--------------------|:-----|:-----------------------------------------------------------------------------------------------------------|
+| bonded | int | bond amount that a P-Rep receives from ICONist |
+| delegated | int | delegation amount that a P-Rep receives from ICONist |
+| bondRequirementRate | int | Network configuration value that can be adjusted via [setBondRequirementRate](#setbondrequirementrate) API |
+
+* bondRequirementRate contains the value ranging from 0 to 10000.
+ - 0: 0%
+ - 1: 0.01%
+ - 10: 0.1%
+ - 100: 1%
+ - 1000: 10%
+ - 5000: 50%
+ - 10000: 100%
+
# Event logs
## PenaltyImposed(Address,int,int)
diff --git a/icon/chainscore.go b/icon/chainscore.go
index 8ec758264..971c2eb9f 100644
--- a/icon/chainscore.go
+++ b/icon/chainscore.go
@@ -870,6 +870,14 @@ var chainMethods = []*chainMethod{
scoreapi.Dict,
},
}, icmodule.RevisionIISS4R0, 0},
+ {scoreapi.Method{
+ scoreapi.Function, "setBondRequirementRate",
+ scoreapi.FlagExternal, 1,
+ []scoreapi.Parameter{
+ {"rate", scoreapi.Integer, nil, nil},
+ },
+ nil,
+ }, icmodule.RevisionSetBondRequirementRate, 0},
}
func applyStepLimits(fee *FeeConfig, as state.AccountState) error {
diff --git a/icon/chainscore_iiss.go b/icon/chainscore_iiss.go
index 29f131320..ea32b93d6 100644
--- a/icon/chainscore_iiss.go
+++ b/icon/chainscore_iiss.go
@@ -1048,3 +1048,14 @@ func (s *chainScore) Ex_getPRepCountConfig() (map[string]interface{}, error) {
}
return es.GetPRepCountConfig()
}
+
+func (s *chainScore) Ex_setBondRequirementRate(rate int64) error {
+ if err := s.checkGovernance(true); err != nil {
+ return err
+ }
+ es, err := s.getExtensionState()
+ if err != nil {
+ return err
+ }
+ return es.SetBondRequirementRate(s.newCallContext(s.cc), icmodule.Rate(rate))
+}
diff --git a/icon/icmodule/revision.go b/icon/icmodule/revision.go
index 3de0d9626..605e4e319 100644
--- a/icon/icmodule/revision.go
+++ b/icon/icmodule/revision.go
@@ -47,6 +47,7 @@ const (
Revision25
Revision26
Revision27
+ Revision28
RevisionReserved
)
@@ -126,6 +127,8 @@ const (
RevisionFixIssueRegulator = Revision26
RevisionRecoverUnderIssuance = Revision27
+
+ RevisionSetBondRequirementRate = Revision28
)
var revisionFlags []module.Revision
diff --git a/icon/icsim/revision.go b/icon/icsim/revision.go
index 057ac473b..83837a06a 100644
--- a/icon/icsim/revision.go
+++ b/icon/icsim/revision.go
@@ -29,6 +29,7 @@ var revHandlerTable = []revHandlerItem{
//{icmodule.RevisionBlockAccounts2, onRevBlockAccounts2},
{icmodule.RevisionIISS4R0, onRevIISS4R0},
{icmodule.RevisionIISS4R1, onRevIISS4R1},
+ {icmodule.RevisionSetBondRequirementRate, onRevSetBondRequirementRate},
}
// DO NOT update revHandlerMap manually
@@ -99,7 +100,7 @@ func onRevIISS(sim *simulatorImpl, wc WorldContext, rev, _ int) error {
if err := es.State.SetSubPRepCount(cfg.SubPRepCount); err != nil {
return err
}
- if err := es.State.SetBondRequirement(cfg.BondRequirement); err != nil {
+ if err := es.State.SetBondRequirement(rev, cfg.BondRequirement); err != nil {
return err
}
if err := es.State.SetLockVariables(big.NewInt(cfg.LockMinMultiplier), big.NewInt(cfg.LockMaxMultiplier)); err != nil {
@@ -194,7 +195,7 @@ func onRevIISS2(_ *simulatorImpl, wc WorldContext, _, _ int) error {
return nil
}
-func onRevICON2R1(_ *simulatorImpl, wc WorldContext, _, _ int) error {
+func onRevICON2R1(_ *simulatorImpl, wc WorldContext, rev, _ int) error {
es := getExtensionState(wc)
as := wc.GetAccountState(state.SystemID)
@@ -209,8 +210,8 @@ func onRevICON2R1(_ *simulatorImpl, wc WorldContext, _, _ int) error {
if err := scoredb.NewVarDB(as, state.VarNextBlockVersion).Set(module.BlockVersion2); err != nil {
return err
}
- if es.State.GetBondRequirement() == icmodule.ToRate(icmodule.IISS2BondRequirement) {
- if err := es.State.SetBondRequirement(icmodule.ToRate(icmodule.DefaultBondRequirement)); err != nil {
+ if es.State.GetBondRequirement(rev) == icmodule.ToRate(icmodule.IISS2BondRequirement) {
+ if err := es.State.SetBondRequirement(rev, icmodule.ToRate(icmodule.DefaultBondRequirement)); err != nil {
return err
}
}
@@ -267,3 +268,8 @@ func onRevIISS4R1(_ *simulatorImpl, wc WorldContext, _, _ int) error {
es := getExtensionState(wc)
return es.State.SetIISSVersion(icstate.IISSVersion4)
}
+
+func onRevSetBondRequirementRate(sim *simulatorImpl, wc WorldContext, rev, _ int) error {
+ es := getExtensionState(wc)
+ return es.State.MigrateBondRequirement(rev)
+}
diff --git a/icon/icsim/setbondrequirementrate_test.go b/icon/icsim/setbondrequirementrate_test.go
new file mode 100644
index 000000000..5875689f2
--- /dev/null
+++ b/icon/icsim/setbondrequirementrate_test.go
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2024 ICON Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package icsim
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/icon-project/goloop/icon/icmodule"
+ "github.com/icon-project/goloop/icon/iiss"
+ "github.com/icon-project/goloop/module"
+ "github.com/icon-project/goloop/service/state"
+ "github.com/icon-project/goloop/service/txresult"
+)
+
+func assertBondRequirement(t *testing.T, sim Simulator, br, nextBr icmodule.Rate) {
+ term := sim.GetPRepTermInJSON()
+ networkInfo := sim.GetNetworkInfoInJSON()
+
+ if term["revision"].(int) < icmodule.RevisionSetBondRequirementRate {
+ assert.Equal(t, br.Percent(), term["bondRequirement"])
+ assert.NotContains(t, term, "bondRequirementRate")
+ } else {
+ assert.NotContains(t, term, "bondRequirement")
+ assert.Equal(t, br.NumInt64(), term["bondRequirementRate"])
+ }
+
+ if sim.Revision().Value() < icmodule.RevisionSetBondRequirementRate {
+ assert.Equal(t, nextBr.Percent(), networkInfo["bondRequirement"])
+ assert.NotContains(t, networkInfo, "bondRequirementRate")
+ } else {
+ assert.NotContains(t, networkInfo, "bondRequirement")
+ assert.Equal(t, nextBr.NumInt64(), networkInfo["bondRequirementRate"])
+ }
+}
+
+func assertEventSetBondRequirementRate(t *testing.T, ev *txresult.TestEventLog, rate icmodule.Rate) {
+ err := ev.Assert(state.SystemAddress, iiss.EventBondRequirementRateSet, nil, []any{rate.NumInt64()})
+ assert.NoError(t, err)
+}
+
+func TestSimulatorImpl_SetBondRequirementRate(t *testing.T) {
+ const (
+ termPeriod = int64(10)
+ )
+ var err error
+ var csi module.ConsensusInfo
+ var receipts []Receipt
+ var nextBr icmodule.Rate
+ br := icmodule.ToRate(5)
+ rev := icmodule.ValueToRevision(icmodule.RevisionSetBondRequirementRate - 1)
+
+ cfg := NewSimConfigWithParams(map[SimConfigOption]interface{}{
+ SCOTermPeriod: termPeriod,
+ SCOBondRequirement: br,
+ })
+ env, err := NewEnv(cfg, rev)
+ sim := env.Simulator()
+ assert.NoError(t, err)
+ assert.NotNil(t, sim)
+ assert.Equal(t, sim.Revision().Value(), rev.Value())
+
+ // T(0)
+ assertBondRequirement(t, sim, br, br)
+ assert.NoError(t, sim.Go(csi, 1))
+ assertBondRequirement(t, sim, br, br)
+ assert.NoError(t, sim.GoToTermEnd(csi))
+
+ // T(1)
+ // SetBondRequirementRate is forbidden before icmodule.RevisionSetBondRequirementRate
+ receipts, err = sim.GoBySetBondRequirementRate(csi, env.Governance(), icmodule.ToRate(3))
+ assert.NoError(t, err)
+ assert.Equal(t, Failure, receipts[1].Status())
+ assertBondRequirement(t, sim, br, br)
+
+ // Revision update to RevisionSetBondRequirementRate
+ rev = icmodule.ValueToRevision(icmodule.RevisionSetBondRequirementRate)
+ receipts, err = sim.GoBySetRevision(csi, env.Governance(), rev)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(receipts))
+ assert.Equal(t, Success, receipts[1].Status())
+ assert.Equal(t, sim.Revision().Value(), rev.Value())
+
+ // GetBondRequirementRate() works after RevisionSetBondRequirementRate
+ assertBondRequirement(t, sim, br, br)
+
+ // Ensure that calling setBondRequirementRate() more than once during the same term works well
+ for i := 0; i < 2; i++ {
+ nextBr = icmodule.ToRate(int64(i))
+ receipts, err = sim.GoBySetBondRequirementRate(csi, env.Governance(), nextBr)
+ rcpt := receipts[1]
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(receipts))
+ assert.Equal(t, Success, rcpt.Status())
+ assert.Equal(t, 1, len(rcpt.Events()))
+ assertEventSetBondRequirementRate(t, rcpt.Events()[0], nextBr)
+ assertBondRequirement(t, sim, br, nextBr)
+ }
+
+ // When calling setBondRequirementRate() with the new rate that is the same as the existing one,
+ // the transaction succeeds without any event logs
+ receipts, err = sim.GoBySetBondRequirementRate(csi, env.Governance(), nextBr)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(receipts))
+ rcpt := receipts[1]
+ assert.Equal(t, Success, rcpt.Status())
+ assert.Zero(t, len(rcpt.Events()))
+ assertBondRequirement(t, sim, br, nextBr)
+
+ assert.NoError(t, sim.GoToTermEnd(csi))
+
+ // T(2)
+ assertBondRequirement(t, sim, nextBr, nextBr)
+ assert.NoError(t, sim.Go(csi, 1))
+ assertBondRequirement(t, sim, nextBr, nextBr)
+ assert.NoError(t, sim.GoToTermEnd(csi))
+
+ // T(3)
+ assertBondRequirement(t, sim, nextBr, nextBr)
+ assert.NoError(t, sim.Go(csi, 1))
+ assertBondRequirement(t, sim, nextBr, nextBr)
+
+ // Ensure that only valid BondRequirementRates are allowed
+ for _, rate := range []icmodule.Rate{-1, icmodule.ToRate(101)} {
+ receipts, err = sim.GoBySetBondRequirementRate(csi, env.Governance(), rate)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, len(receipts))
+ assert.Equal(t, Failure, receipts[1].Status())
+ assert.Zero(t, len(receipts[1].Events()))
+ assertBondRequirement(t, sim, nextBr, nextBr)
+ }
+}
diff --git a/icon/icsim/simulator.go b/icon/icsim/simulator.go
index a5f373bb3..68d61bd68 100644
--- a/icon/icsim/simulator.go
+++ b/icon/icsim/simulator.go
@@ -26,6 +26,7 @@ import (
)
type TxType int
+
const (
TypeTransfer TxType = iota
TypeSetStake
@@ -46,6 +47,7 @@ const (
TypeHandleDoubleSignReport
TypeSetPRepCountConfig
TypeSetRewardFundAllocation2
+ TypeSetBondRequirementRate
)
type Transaction interface {
@@ -182,4 +184,8 @@ type Simulator interface {
SetRewardFundAllocation2(from module.Address, values map[icstate.RFundKey]icmodule.Rate) Transaction
GoBySetRewardFundAllocation2(
csi module.ConsensusInfo, from module.Address, values map[icstate.RFundKey]icmodule.Rate) ([]Receipt, error)
+
+ // After RevisionSetBondRequirementRate
+ SetBondRequirementRate(from module.Address, rate icmodule.Rate) Transaction
+ GoBySetBondRequirementRate(csi module.ConsensusInfo, from module.Address, rate icmodule.Rate) ([]Receipt, error)
}
diff --git a/icon/icsim/simulatorimpl.go b/icon/icsim/simulatorimpl.go
index 7e1d176bf..b2ec85cb8 100644
--- a/icon/icsim/simulatorimpl.go
+++ b/icon/icsim/simulatorimpl.go
@@ -448,6 +448,8 @@ func (sim *simulatorImpl) executeTx(cc *callContext, tx Transaction) error {
err = sim.setPRepCountConfig(es, cc, tx)
case TypeSetRewardFundAllocation2:
err = sim.setRewardFundAllocation2(es, cc, tx)
+ case TypeSetBondRequirementRate:
+ err = sim.setBondRequirementRate(es, cc, tx)
default:
return errors.Errorf("Unexpected transaction: %v", tx.Type())
}
@@ -877,6 +879,21 @@ func (sim *simulatorImpl) setRewardFundAllocation2(
return es.State.SetRewardFund(rf)
}
+func (sim *simulatorImpl) SetBondRequirementRate(from module.Address, rate icmodule.Rate) Transaction {
+ return NewTransaction(TypeSetBondRequirementRate, from, rate)
+}
+
+func (sim *simulatorImpl) GoBySetBondRequirementRate(
+ csi module.ConsensusInfo, from module.Address, rate icmodule.Rate) ([]Receipt, error) {
+ return sim.goByOneTransaction(csi, TypeSetBondRequirementRate, from, rate)
+}
+
+func (sim *simulatorImpl) setBondRequirementRate(es *iiss.ExtensionStateImpl, cc *callContext, tx Transaction) error {
+ args := tx.Args()
+ rate := args[0].(icmodule.Rate)
+ return es.SetBondRequirementRate(cc, rate)
+}
+
func NewSimulator(
revision module.Revision, initValidators []module.Validator, initBalances map[string]*big.Int, config *SimConfig,
) (Simulator, error) {
diff --git a/icon/iiss/eventlog.go b/icon/iiss/eventlog.go
index 95e6cb121..2238fe8a8 100644
--- a/icon/iiss/eventlog.go
+++ b/icon/iiss/eventlog.go
@@ -58,6 +58,7 @@ const (
EventRewardFundSet = "RewardFundSet(int)"
EventRewardFundAllocationSet = "RewardFundAllocationSet(str,int)"
EventNetworkScoreSet = "NetworkScoreSet(str,Address)"
+ EventBondRequirementRateSet = "BondRequirementRateSet(int)"
)
func EmitSlashingRateSetEvent(cc icmodule.CallContext, penaltyType icmodule.PenaltyType, rate icmodule.Rate) {
@@ -393,3 +394,10 @@ func EmitNetworkScoreSetEvent(cc icmodule.CallContext, role string, address modu
[][]byte{[]byte(role), addrBytes},
)
}
+
+func EmitBondRequirementRateSetEvent(cc icmodule.CallContext, rate icmodule.Rate) {
+ cc.OnEvent(state.SystemAddress,
+ [][]byte{[]byte(EventBondRequirementRateSet)},
+ [][]byte{intconv.Int64ToBytes(rate.NumInt64())},
+ )
+}
diff --git a/icon/iiss/extension.go b/icon/iiss/extension.go
index 0eb554b39..8f2b4af06 100644
--- a/icon/iiss/extension.go
+++ b/icon/iiss/extension.go
@@ -2073,3 +2073,22 @@ func (es *ExtensionStateImpl) SetMinimumBond(cc icmodule.CallContext, nBond *big
EmitMinimumBondSetEvent(cc, nBond)
return nil
}
+
+func (es *ExtensionStateImpl) SetBondRequirementRate(cc icmodule.CallContext, rate icmodule.Rate) error {
+ revision := cc.Revision().Value()
+ if revision < icmodule.RevisionSetBondRequirementRate {
+ return scoreresult.AccessDeniedError.Errorf("SetBondRequirementRateNotAllowed(rev=%d)", revision)
+ }
+ if !rate.IsValid() {
+ return scoreresult.InvalidParameterError.Errorf("InvalidBondRequirementRate(%d)", rate)
+ }
+
+ var err error
+ oldRate := es.State.GetBondRequirement(revision)
+ if rate != oldRate {
+ if err = es.State.SetBondRequirement(revision, rate); err == nil {
+ EmitBondRequirementRateSetEvent(cc, rate)
+ }
+ }
+ return err
+}
diff --git a/icon/iiss/icstate/networkvalue.go b/icon/iiss/icstate/networkvalue.go
index dc9a360e4..d36bb6e14 100644
--- a/icon/iiss/icstate/networkvalue.go
+++ b/icon/iiss/icstate/networkvalue.go
@@ -242,16 +242,34 @@ func (s *State) SetTotalStake(value *big.Int) error {
return setValue(s.store, VarTotalStake, value)
}
-func (s *State) GetBondRequirement() icmodule.Rate {
+func (s *State) GetBondRequirement(revision int) icmodule.Rate {
v := getValue(s.store, VarBondRequirement).Int64()
- return icmodule.ToRate(v)
+ if revision < icmodule.RevisionSetBondRequirementRate {
+ return icmodule.ToRate(v)
+ } else {
+ return icmodule.Rate(v)
+ }
}
-func (s *State) SetBondRequirement(br icmodule.Rate) error {
+func (s *State) SetBondRequirement(revision int, br icmodule.Rate) error {
if !br.IsValid() {
return errors.IllegalArgumentError.New("Bond Requirement should range from 0% to 100%")
}
- return setValue(s.store, VarBondRequirement, br.Percent())
+
+ if revision < icmodule.RevisionSetBondRequirementRate {
+ return setValue(s.store, VarBondRequirement, br.Percent())
+ } else {
+ return setValue(s.store, VarBondRequirement, br.NumInt64())
+ }
+}
+
+func (s *State) MigrateBondRequirement(revision int) error {
+ if revision != icmodule.RevisionSetBondRequirementRate {
+ return scoreresult.AccessDeniedError.Errorf("MigrateBondRequirementNotAllowed(rev=%d)", revision)
+ }
+ // Change BondRequirementRate unit from 1% to 0.01%
+ rate := s.GetBondRequirement(revision - 1)
+ return s.SetBondRequirement(revision, rate)
}
func (s *State) SetUnbondingPeriodMultiplier(value int64) error {
@@ -482,7 +500,7 @@ func (s *State) SetMinimumBond(bond *big.Int) error {
}
func (s *State) GetNetworkInfoInJSON(revision int) (map[string]interface{}, error) {
- br := s.GetBondRequirement()
+ br := s.GetBondRequirement(revision)
jso := make(map[string]interface{})
jso["mainPRepCount"] = s.GetMainPRepCount()
jso["extraMainPRepCount"] = s.GetExtraMainPRepCount()
@@ -490,7 +508,11 @@ func (s *State) GetNetworkInfoInJSON(revision int) (map[string]interface{}, erro
jso["totalStake"] = s.GetTotalStake()
jso["iissVersion"] = int64(s.GetIISSVersion())
jso["termPeriod"] = s.GetTermPeriod()
- jso["bondRequirement"] = br.Percent()
+ if revision < icmodule.RevisionSetBondRequirementRate {
+ jso["bondRequirement"] = br.Percent()
+ } else {
+ jso["bondRequirementRate"] = br.NumInt64()
+ }
jso["lockMinMultiplier"] = s.GetLockMinMultiplier()
jso["lockMaxMultiplier"] = s.GetLockMaxMultiplier()
jso["rewardFund"] = s.GetRewardFund(revision).ToJSON()
diff --git a/icon/iiss/icstate/networkvalue_test.go b/icon/iiss/icstate/networkvalue_test.go
index f25b75ff9..02d3448c2 100644
--- a/icon/iiss/icstate/networkvalue_test.go
+++ b/icon/iiss/icstate/networkvalue_test.go
@@ -173,29 +173,30 @@ func setTotalStakeTest(t *testing.T, s *State) {
}
func setBondRequirementTest(t *testing.T, s *State) {
+ revision := icmodule.RevisionSetBondRequirementRate - 1
br := icmodule.ToRate(0)
- actual := s.GetBondRequirement()
+ actual := s.GetBondRequirement(revision)
assert.Equal(t, br, actual)
br = icmodule.ToRate(5)
- assert.NoError(t, s.SetBondRequirement(br))
- actual = s.GetBondRequirement()
+ assert.NoError(t, s.SetBondRequirement(revision, br))
+ actual = s.GetBondRequirement(revision)
assert.Equal(t, br, actual)
br = icmodule.ToRate(0)
- err := s.SetBondRequirement(br)
+ err := s.SetBondRequirement(revision, br)
assert.NoError(t, err)
- actual = s.GetBondRequirement()
+ actual = s.GetBondRequirement(revision)
assert.Equal(t, br, actual)
- err = s.SetBondRequirement(icmodule.ToRate(101))
+ err = s.SetBondRequirement(revision, icmodule.ToRate(101))
assert.Error(t, err)
- actual = s.GetBondRequirement()
+ actual = s.GetBondRequirement(revision)
assert.Equal(t, br, actual)
- err = s.SetBondRequirement(icmodule.ToRate(-1))
+ err = s.SetBondRequirement(revision, icmodule.ToRate(-1))
assert.Error(t, err)
- actual = s.GetBondRequirement()
+ actual = s.GetBondRequirement(revision)
assert.Equal(t, br, actual)
}
@@ -753,3 +754,39 @@ func TestState_GetNetworkInfoInJSON(t *testing.T) {
}
}
}
+
+func TestState_SetBondRequirement(t *testing.T) {
+ nextRate := icmodule.ToRate(5)
+
+ rev := icmodule.RevisionSetBondRequirementRate - 1
+ s := newDummyState(false)
+
+ assert.NoError(t, s.SetBondRequirement(rev, nextRate))
+ assert.Equal(t, nextRate, s.GetBondRequirement(rev))
+
+ rev = icmodule.RevisionSetBondRequirementRate
+
+ assert.NoError(t, s.MigrateBondRequirement(rev))
+ assert.Equal(t, nextRate, s.GetBondRequirement(rev))
+
+ nextRate = icmodule.ToRate(3)
+ assert.NoError(t, s.SetBondRequirement(rev, nextRate))
+ assert.Equal(t, nextRate, s.GetBondRequirement(rev))
+
+ assert.NoError(t, s.SetBondRequirement(rev, nextRate))
+ assert.Equal(t, nextRate, s.GetBondRequirement(rev))
+}
+
+func TestState_MigrateBondRequirement(t *testing.T) {
+ s := newDummyState(false)
+
+ rev := icmodule.RevisionSetBondRequirementRate - 1
+ rate := icmodule.ToRate(10)
+ assert.NoError(t, s.SetBondRequirement(rev, rate))
+ assert.Equal(t, rate, s.GetBondRequirement(rev))
+ assert.Error(t, s.MigrateBondRequirement(rev))
+
+ rev = icmodule.RevisionSetBondRequirementRate
+ assert.NoError(t, s.MigrateBondRequirement(rev))
+ assert.Equal(t, rate, s.GetBondRequirement(rev))
+}
diff --git a/icon/iiss/icstate/state.go b/icon/iiss/icstate/state.go
index 48f8d79e9..69b21d4db 100644
--- a/icon/iiss/icstate/state.go
+++ b/icon/iiss/icstate/state.go
@@ -429,7 +429,7 @@ func (s *State) OnValidatorOut(sc icmodule.StateContext, owner module.Address) e
func (s *State) GetPRepStatuses() ([]*PRepStatusState, error) {
size := s.allPRepCache.Size()
- pss := make([]*PRepStatusState, 0, size / 2)
+ pss := make([]*PRepStatusState, 0, size/2)
for i := 0; i < size; i++ {
owner := s.allPRepCache.Get(i)
@@ -544,7 +544,7 @@ func (s *State) SetLastBlockVotersSnapshot(value *BlockVotersSnapshot) error {
func (s *State) IsDecentralizationConditionMet(revision int, totalSupply *big.Int, preps PRepSet) bool {
predefinedMainPRepCount := int(s.GetMainPRepCount())
- br := s.GetBondRequirement()
+ br := s.GetBondRequirement(revision)
if revision >= icmodule.RevisionDecentralize && preps.Size() >= predefinedMainPRepCount {
prep := preps.GetByIndex(predefinedMainPRepCount - 1)
diff --git a/icon/iiss/icstate/term.go b/icon/iiss/icstate/term.go
index 4272ed7fe..bf435c162 100644
--- a/icon/iiss/icstate/term.go
+++ b/icon/iiss/icstate/term.go
@@ -124,8 +124,8 @@ type termDataCommon struct {
period int64
revision int
isDecentralized bool
- totalSupply *big.Int // not nil
- totalDelegated *big.Int // total delegated amount of all active P-Reps. not nil
+ totalSupply *big.Int // not nil
+ totalDelegated *big.Int // total delegated amount of all active P-Reps. not nil
rewardFund *RewardFund // not nil
bondRequirement icmodule.Rate
mainPRepCount int
@@ -245,7 +245,7 @@ func (term *termDataCommon) clone() termDataCommon {
}
func (term *termDataCommon) ToJSON(sc icmodule.StateContext, state *State) map[string]interface{} {
- return map[string]interface{}{
+ jso := map[string]interface{}{
"sequence": term.sequence,
"startBlockHeight": term.startHeight,
"endBlockHeight": term.GetEndHeight(),
@@ -254,13 +254,18 @@ func (term *termDataCommon) ToJSON(sc icmodule.StateContext, state *State) map[s
"totalPower": term.getTotalPower(),
"period": term.period,
"rewardFund": term.rewardFund.ToJSON(),
- "bondRequirement": term.bondRequirement.Percent(),
"revision": term.revision,
"isDecentralized": term.isDecentralized,
"mainPRepCount": term.mainPRepCount,
"iissVersion": term.GetIISSVersion(),
"preps": term.prepsToJSON(sc, state),
}
+ if term.revision < icmodule.RevisionSetBondRequirementRate {
+ jso["bondRequirement"] = term.bondRequirement.Percent()
+ } else {
+ jso["bondRequirementRate"] = term.bondRequirement.NumInt64()
+ }
+ return jso
}
func (term *termDataCommon) prepsToJSON(sc icmodule.StateContext, state *State) []interface{} {
@@ -411,7 +416,7 @@ func (term *termData) equal(other *termData) bool {
func (term *termData) clone() termData {
td := termData{
- termDataCommon: term.termDataCommon.clone(),
+ termDataCommon: term.termDataCommon.clone(),
}
switch term.Version() {
case termVersion1:
@@ -630,6 +635,8 @@ func (term *TermState) SetRrep(rrep *big.Int) {
// It assumes that state and totalSupply are not nil.
func NewNextTerm(sc icmodule.StateContext, state *State, totalSupply *big.Int, preps PRepSet) *TermState {
rev := sc.RevisionValue()
+ br := sc.GetBondRequirement()
+
// Previous term
tss := state.GetTermSnapshot()
if tss == nil {
@@ -660,7 +667,7 @@ func NewNextTerm(sc icmodule.StateContext, state *State, totalSupply *big.Int, p
totalSupply: totalSupply,
totalDelegated: state.GetTotalDelegation(),
rewardFund: state.GetRewardFund(rev),
- bondRequirement: state.GetBondRequirement(),
+ bondRequirement: br,
revision: rev,
isDecentralized: isDecentralized,
},
@@ -676,7 +683,7 @@ func NewNextTerm(sc icmodule.StateContext, state *State, totalSupply *big.Int, p
// Update PRepSnapshots
if isDecentralized {
termState.mainPRepCount = preps.GetPRepSize(GradeMain)
- termState.prepSnapshots = preps.ToPRepSnapshots(sc.GetBondRequirement())
+ termState.prepSnapshots = preps.ToPRepSnapshots(br)
}
return termState
@@ -693,7 +700,7 @@ func GenesisTerm(state *State, startHeight int64, revision int) *TermState {
totalSupply: icmodule.BigIntZero,
totalDelegated: icmodule.BigIntZero,
rewardFund: state.GetRewardFundV1().Clone(),
- bondRequirement: state.GetBondRequirement(),
+ bondRequirement: state.GetBondRequirement(revision),
revision: revision,
isDecentralized: false,
},
diff --git a/icon/iiss/icstate/term_test.go b/icon/iiss/icstate/term_test.go
index fb413125e..6dbe9661b 100644
--- a/icon/iiss/icstate/term_test.go
+++ b/icon/iiss/icstate/term_test.go
@@ -606,7 +606,7 @@ func TestGenesisTerm(t *testing.T) {
state := newDummyState(false)
assert.NoError(t, state.SetTermPeriod(tp))
- assert.NoError(t, state.SetBondRequirement(br))
+ assert.NoError(t, state.SetBondRequirement(revision, br))
assert.NoError(t, state.SetIRep(irep))
assert.NoError(t, state.SetRRep(rrep))
assert.NoError(t, state.SetRewardFund(rf))
@@ -653,7 +653,7 @@ func TestNewNextTerm(t *testing.T) {
// Initialize State
state := newDummyState(false)
assert.NoError(t, state.SetTermPeriod(tp))
- assert.NoError(t, state.SetBondRequirement(br))
+ assert.NoError(t, state.SetBondRequirement(icmodule.RevisionIISS, br))
assert.NoError(t, state.SetIRep(irep))
assert.NoError(t, state.SetRRep(rrep))
assert.NoError(t, state.SetRewardFund(rf))
diff --git a/icon/iiss/statecontext.go b/icon/iiss/statecontext.go
index 71c5c9e8e..d57a1e655 100644
--- a/icon/iiss/statecontext.go
+++ b/icon/iiss/statecontext.go
@@ -62,7 +62,7 @@ func (sc *stateContext) TermIISSVersion() int {
func (sc *stateContext) GetBondRequirement() icmodule.Rate {
if !sc.br.IsValid() {
- sc.br = sc.State.GetBondRequirement()
+ sc.br = sc.State.GetBondRequirement(sc.RevisionValue())
}
return sc.br
}
diff --git a/icon/revision.go b/icon/revision.go
index 9875e574b..14297a097 100644
--- a/icon/revision.go
+++ b/icon/revision.go
@@ -47,6 +47,7 @@ var revHandlerTable = []revHandlerItem{
{icmodule.RevisionIISS4R1, onRevIISS4R1},
{icmodule.RevisionFixIssueRegulator, onRevFixIssueRegulator},
{icmodule.RevisionRecoverUnderIssuance, onRevRecoverUnderIssuance},
+ {icmodule.RevisionSetBondRequirementRate, onRevSetBondRequirementRate},
}
// DO NOT update revHandlerMap manually
@@ -80,9 +81,7 @@ func (s *chainScore) handleRevisionChange(r1, r2 int) error {
return nil
}
-func onRevIISS(s *chainScore, _, toRev int) error {
- // goloop engine
-
+func onRevIISS(s *chainScore, rev, toRev int) error {
as := s.cc.GetAccountState(state.SystemID)
// enable Fee sharing 2.0
@@ -123,7 +122,7 @@ func onRevIISS(s *chainScore, _, toRev int) error {
if err := es.State.SetSubPRepCount(iconConfig.SubPRepCount.Int64()); err != nil {
return err
}
- if err := es.State.SetBondRequirement(icmodule.ToRate(iconConfig.BondRequirement.Int64())); err != nil {
+ if err := es.State.SetBondRequirement(rev, icmodule.ToRate(iconConfig.BondRequirement.Int64())); err != nil {
return err
}
if err := es.State.SetLockVariables(iconConfig.LockMinMultiplier.Value(), iconConfig.LockMaxMultiplier.Value()); err != nil {
@@ -176,8 +175,8 @@ func onRevIISS(s *chainScore, _, toRev int) error {
return err
}
}
- if es.State.GetBondRequirement() == icmodule.ToRate(icmodule.DefaultBondRequirement) {
- if err := es.State.SetBondRequirement(icmodule.ToRate(icmodule.IISS2BondRequirement)); err != nil {
+ if es.State.GetBondRequirement(rev) == icmodule.ToRate(icmodule.DefaultBondRequirement) {
+ if err := es.State.SetBondRequirement(rev, icmodule.ToRate(icmodule.IISS2BondRequirement)); err != nil {
return err
}
}
@@ -243,7 +242,7 @@ func onRevICON2R0(s *chainScore, _, _ int) error {
return nil
}
-func onRevICON2R1(s *chainScore, _, _ int) error {
+func onRevICON2R1(s *chainScore, rev, _ int) error {
if s.cc.ChainID() == CIDForMainNet {
// The time when predefined accounts will be blocked is changed from rev10 to rev14
s.blockAccounts()
@@ -265,8 +264,8 @@ func onRevICON2R1(s *chainScore, _, _ int) error {
return err
}
- if es.State.GetBondRequirement() == icmodule.ToRate(icmodule.IISS2BondRequirement) {
- if err := es.State.SetBondRequirement(icmodule.ToRate(icmodule.DefaultBondRequirement)); err != nil {
+ if es.State.GetBondRequirement(rev) == icmodule.ToRate(icmodule.IISS2BondRequirement) {
+ if err := es.State.SetBondRequirement(rev, icmodule.ToRate(icmodule.DefaultBondRequirement)); err != nil {
return err
}
}
@@ -486,3 +485,8 @@ func onRevRecoverUnderIssuance(s *chainScore, _, _ int) error {
return nil
}
+
+func onRevSetBondRequirementRate(s *chainScore, rev, _ int) error {
+ es := s.cc.GetExtensionState().(*iiss.ExtensionStateImpl)
+ return es.State.MigrateBondRequirement(rev)
+}