From 58f8ad51d5afb6a841fed232e3d41167069b551c Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 26 Jan 2021 01:13:12 +0700 Subject: [PATCH 1/2] Add a feature to the DPOS contract to toggle migration mode Migration mode will prevent delegators and validators from creating new delegations, consolidating delegations, redelegating, and unbonding, as well as registering and unregistering candidates. In this mode rewards will continue to accrue, so they need to be disabled separately by setting the max yearly rewards to zero. --- builtin/plugins/dposv3/dpos.go | 84 ++++++++++++++++++++++++++++++++++ features/features.go | 3 ++ 2 files changed, 87 insertions(+) diff --git a/builtin/plugins/dposv3/dpos.go b/builtin/plugins/dposv3/dpos.go index ccbb3e95cf..b681b09a8b 100644 --- a/builtin/plugins/dposv3/dpos.go +++ b/builtin/plugins/dposv3/dpos.go @@ -74,6 +74,7 @@ var ( errDistributionNotFound = errors.New("Distribution record not found.") errOnlyOracle = errors.New("Function can only be called with oracle address.") errDelegationLocked = errors.New("Delegation currently locked.") + errMigrationModeEnabled = errors.New("Migration mode enabled.") ) type ( @@ -129,6 +130,7 @@ type ( SetMaxDowntimePercentageRequest = dtypes.SetMaxDowntimePercentageRequest EnableValidatorJailingRequest = dtypes.EnableValidatorJailingRequest IgnoreUnbondLocktimeRequest = dtypes.IgnoreUnbondLocktimeRequest + ToggleMigrationModeRequest = dtypes.ToggleMigrationModeRequest Candidate = dtypes.Candidate CandidateStatistic = dtypes.CandidateStatistic Delegation = dtypes.Delegation @@ -258,6 +260,16 @@ func (c *DPOS) Delegate(ctx contract.Context, req *DelegateRequest) error { delegator := ctx.Message().Sender ctx.Logger().Info("DPOSv3 Delegate", "delegator", delegator, "request", req) + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + state, err := LoadState(ctx) + if err != nil { + return err + } + if state.Params.MigrationModeEnabled { + return errMigrationModeEnabled + } + } + if req.ValidatorAddress == nil { return logDposError(ctx, errors.New("Delegate called with req.ValidatorAddress == nil"), req.String()) } @@ -341,6 +353,16 @@ func (c *DPOS) Delegate(ctx contract.Context, req *DelegateRequest) error { } func (c *DPOS) Redelegate(ctx contract.Context, req *RedelegateRequest) error { + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + state, err := LoadState(ctx) + if err != nil { + return err + } + if state.Params.MigrationModeEnabled { + return errMigrationModeEnabled + } + } + delegator := ctx.Message().Sender if req.ValidatorAddress == nil { @@ -480,6 +502,16 @@ func (c *DPOS) ConsolidateDelegations(ctx contract.Context, req *ConsolidateDele delegator := ctx.Message().Sender ctx.Logger().Info("DPOSv3 ConsolidateDelegations", "delegator", delegator, "request", req) + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + state, err := LoadState(ctx) + if err != nil { + return err + } + if state.Params.MigrationModeEnabled { + return errMigrationModeEnabled + } + } + // Unless considation is for the limbo validator, check that the new // validator address corresponds to one of the registered candidates if loom.UnmarshalAddressPB(req.ValidatorAddress).Compare(LimboValidatorAddress(ctx)) != 0 { @@ -591,6 +623,16 @@ func (c *DPOS) CheckRewardsFromAllValidators(ctx contract.StaticContext, req *Ch /// a delegator has delegated to, and returns the total amount which will be transferred to the /// delegator's account after the next election. func (c *DPOS) ClaimRewardsFromAllValidators(ctx contract.Context, req *ClaimDelegatorRewardsRequest) (*ClaimDelegatorRewardsResponse, error) { + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + state, err := LoadState(ctx) + if err != nil { + return nil, err + } + if state.Params.MigrationModeEnabled { + return nil, errMigrationModeEnabled + } + } + if ctx.FeatureEnabled(features.DPOSVersion3_6, false) { return c.claimRewardsFromAllValidators2(ctx, req) } @@ -717,6 +759,16 @@ func (c *DPOS) Unbond(ctx contract.Context, req *UnbondRequest) error { delegator := ctx.Message().Sender ctx.Logger().Info("DPOSv3 Unbond", "delegator", delegator, "request", req) + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + state, err := LoadState(ctx) + if err != nil { + return err + } + if state.Params.MigrationModeEnabled { + return errMigrationModeEnabled + } + } + if req.ValidatorAddress == nil { return logDposError(ctx, errors.New("Unbond called with req.ValidatorAddress == nil"), req.String()) } else if req.Amount == nil { @@ -1027,6 +1079,10 @@ func (c *DPOS) RegisterCandidate(ctx contract.Context, req *RegisterCandidateReq return err } + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) && state.Params.MigrationModeEnabled { + return errMigrationModeEnabled + } + if (statistic == nil || common.IsZero(statistic.WhitelistAmount.Value)) && common.IsPositive(state.Params.RegistrationRequirement.Value) { // A currently unregistered candidate must make a loom token deposit // = 'registrationRequirement' in order to run for validator. @@ -1195,6 +1251,16 @@ func (c *DPOS) UpdateCandidateInfo(ctx contract.Context, req *UpdateCandidateInf // Leaving the validator set mid-election period results in a loss of rewards // but it should not result in slashing due to downtime. func (c *DPOS) UnregisterCandidate(ctx contract.Context, req *UnregisterCandidateRequest) error { + if ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + state, err := LoadState(ctx) + if err != nil { + return err + } + if state.Params.MigrationModeEnabled { + return errMigrationModeEnabled + } + } + candidateAddress := ctx.Message().Sender // Allow oracle to specify the candidate @@ -1621,6 +1687,24 @@ func (c *DPOS) IgnoreUnbondLocktime(ctx contract.Context, req *IgnoreUnbondLockt return saveState(ctx, state) } +func (c *DPOS) ToggleMigrationMode(ctx contract.Context, req *ToggleMigrationModeRequest) error { + if !ctx.FeatureEnabled(features.DPOSVersion3_11, false) { + return errors.New("DPOS v3.11 is not enabled") + } + + state, err := LoadState(ctx) + if err != nil { + return err + } + sender := ctx.Message().Sender + if state.Params.OracleAddress == nil || sender.Compare(loom.UnmarshalAddressPB(state.Params.OracleAddress)) != 0 { + return errOnlyOracle + } + + state.Params.MigrationModeEnabled = !state.Params.MigrationModeEnabled + return saveState(ctx, state) +} + // *************************** // REWARDS & SLASHING // *************************** diff --git a/features/features.go b/features/features.go index 050e012533..62f1ff7565 100644 --- a/features/features.go +++ b/features/features.go @@ -61,6 +61,9 @@ const ( DPOSVersion3_9 = "dpos:v3.9" // Makes it possible for the oracle to call Redelegate & UnregisterCandidate DPOSVersion3_10 = "dpos:v3.10" + // Make it possible to put the DPOS contract into migration mode, where + // delegation/redelegation/consolidation and validator registration is disabled + DPOSVersion3_11 = "dpos:v3.11" // Enables rewards to be distributed even when a delegator owns less than 0.01% of the validator's stake // Also makes whitelists give bonuses correctly if whitelist locktime tier is set to be 0-3 (else defaults to 5%) From 1d2d4d8e515aa2395b0957e1b66fd6de748dd310 Mon Sep 17 00:00:00 2001 From: Vadim Macagon Date: Tue, 26 Jan 2021 22:12:18 +0700 Subject: [PATCH 2/2] Add dpos3 toggle-migration-mode command --- cmd/loom/dposV3_commands.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/cmd/loom/dposV3_commands.go b/cmd/loom/dposV3_commands.go index c55f4f6ac5..e6954f570b 100644 --- a/cmd/loom/dposV3_commands.go +++ b/cmd/loom/dposV3_commands.go @@ -1636,6 +1636,28 @@ func IgnoreUnbondLocktimeCmd() *cobra.Command { return cmd } +const toggleMigrationModeCmdExample = ` +loom dpos3 toggle-migration-mode -k path/to/private_key +` + +func ToggleMigrationModeCmd() *cobra.Command { + var flags cli.ContractCallFlags + cmd := &cobra.Command{ + Use: "toggle-migration-mode", + Example: toggleMigrationModeCmdExample, + RunE: func(cmd *cobra.Command, args []string) error { + if err := cli.CallContractWithFlags( + &flags, DPOSV3ContractName, "ToggleMigrationMode", &dposv3.ToggleMigrationModeRequest{}, nil, + ); err != nil { + return err + } + return nil + }, + } + cli.AddContractCallFlags(cmd.Flags(), &flags) + return cmd +} + func NewDPOSV3Command() *cobra.Command { cmd := &cobra.Command{ Use: "dpos3 ", @@ -1684,6 +1706,7 @@ func NewDPOSV3Command() *cobra.Command { UnjailValidatorCmdV3(), EnableValidatorJailingCmd(), IgnoreUnbondLocktimeCmd(), + ToggleMigrationModeCmd(), ) return cmd }