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

feat: Parameter update delay at specific block height #243

Merged
merged 17 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
17 changes: 17 additions & 0 deletions proto/ojo/oracle/v1/oracle.proto
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,20 @@ message PriceStamp {
message ValidatorRewardSet {
repeated string validator_set = 1 [ (gogoproto.moretags) = "yaml:\"validator_set\"" ];
}

// ParamUpdatePlan specifies updates to the Oracle module parameters
// and at which block height they should occur.
message ParamUpdatePlan {
option (gogoproto.equal) = true;
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;

// Store keys of the Oracle params getting updated.
repeated string keys = 1;

// The height at which the param update must be performed.
int64 height = 2;

// Changes to the oracle parameters.
Params changes = 3 [ (gogoproto.nullable) = false ];
}
39 changes: 34 additions & 5 deletions proto/ojo/oracle/v1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ service Msg {
// GovUpdateParams updates the oracle parameters.
rpc GovUpdateParams(MsgGovUpdateParams)
returns (MsgGovUpdateParamsResponse);

// GovCancelUpdateParams cancels a plan to update the oracle parameters.
rpc GovCancelUpdateParams(MsgGovCancelUpdateParams)
returns (MsgGovCancelUpdateParamsResponse);
}

// MsgAggregateExchangeRatePrevote represents a message to submit an aggregate
Expand Down Expand Up @@ -88,12 +92,37 @@ message MsgGovUpdateParams {
option (cosmos.msg.v1.signer) = "authority";

// authority is the address of the governance account.
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];
string title = 2;
string description = 3;
repeated string keys = 4;
Params changes = 5 [ (gogoproto.nullable) = false ];
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// title of the proposal
string title = 2;

// description of the proposal
string description = 3;

// plan is the param update plan
ParamUpdatePlan plan = 4 [(gogoproto.nullable) = false];
}

// MsgGovUpdateParamsResponse defines the Msg/GovUpdateParams response type.
message MsgGovUpdateParamsResponse {}

// MsgGovCancelUpdateParams defines the Msg/GovCancelUpdateParams request type.
message MsgGovCancelUpdateParams {
option (gogoproto.equal) = true;
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;
option (cosmos.msg.v1.signer) = "authority";

// authority is the address of the governance account.
string authority = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// title of the proposal
string title = 2;

// description of the proposal
string description = 3;
}

// MsgGovCancelUpdateParamsResponse defines the Msg/GovCancelUpdateParamsResponse response type.
message MsgGovCancelUpdateParamsResponse {}
7 changes: 7 additions & 0 deletions x/oracle/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ import (
func EndBlocker(ctx sdk.Context, k keeper.Keeper) error {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)

// Check for Oracle parameter update plans and execute it if one is
// found and the update plan height is set to the current block.
plan, found := k.GetParamUpdatePlan(ctx)
rbajollari marked this conversation as resolved.
Show resolved Hide resolved
if found && plan.ShouldExecute(ctx) {
k.ExecuteParamUpdatePlan(ctx, plan)
}

params := k.GetParams(ctx)

// Set all current active validators into the ValidatorRewardSet at
Expand Down
49 changes: 49 additions & 0 deletions x/oracle/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,55 @@ func (s *IntegrationTestSuite) TestEndblockerHistoracle() {
}
}

func (s *IntegrationTestSuite) TestUpdateOracleParams() {
app, ctx := s.app, s.ctx
blockHeight := ctx.BlockHeight()

// Schedule param update plan for current block height
err := app.OracleKeeper.ScheduleParamUpdatePlan(
ctx,
types.ParamUpdatePlan{
Keys: []string{"VoteThreshold"},
Height: blockHeight,
Changes: types.Params{
VoteThreshold: sdk.NewDecWithPrec(40, 2),
},
},
)
s.Require().NoError(err)
_, found := s.app.OracleKeeper.GetParamUpdatePlan(s.ctx)
s.Require().Equal(true, found)

// Check Vote Threshold was updated
oracle.EndBlocker(ctx, app.OracleKeeper)
s.Require().Equal(sdk.NewDecWithPrec(40, 2), app.OracleKeeper.VoteThreshold(ctx))

// Schedule param update plan for current block height and then cancel it
err = app.OracleKeeper.ScheduleParamUpdatePlan(
ctx,
types.ParamUpdatePlan{
Keys: []string{"VoteThreshold"},
Height: blockHeight,
Changes: types.Params{
VoteThreshold: sdk.NewDecWithPrec(50, 2),
},
},
)
s.Require().NoError(err)
_, found = s.app.OracleKeeper.GetParamUpdatePlan(s.ctx)
s.Require().Equal(true, found)

// Cancel update
err = app.OracleKeeper.ClearParamUpdatePlan(ctx)
s.Require().NoError(err)
_, found = s.app.OracleKeeper.GetParamUpdatePlan(s.ctx)
s.Require().Equal(false, found)

// Check Vote Threshold wasn't updated
oracle.EndBlocker(ctx, app.OracleKeeper)
s.Require().Equal(sdk.NewDecWithPrec(40, 2), app.OracleKeeper.VoteThreshold(ctx))
}

func TestOracleTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}
139 changes: 139 additions & 0 deletions x/oracle/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,142 @@ func (k Keeper) ValidateFeeder(ctx sdk.Context, feederAddr sdk.AccAddress, valAd

return nil
}

// ScheduleParamUpdatePlan schedules a param update plan.
rbajollari marked this conversation as resolved.
Show resolved Hide resolved
func (k Keeper) ScheduleParamUpdatePlan(ctx sdk.Context, plan types.ParamUpdatePlan) error {
if plan.Height < ctx.BlockHeight() {
return types.ErrInvalidRequest.Wrap("param update cannot be scheduled in the past")
}
if err := k.ValidateParamChanges(ctx, plan.Keys, plan.Changes); err != nil {
return err
}

store := ctx.KVStore(k.storeKey)

bz := k.cdc.MustMarshal(&plan)
store.Set(types.KeyParamUpdatePlan(), bz)

return nil
}

// ClearParamUpdatePlan will clear an upcoming param update plan if one exists and return
// an error if one isn't found.
func (k Keeper) ClearParamUpdatePlan(ctx sdk.Context) error {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyParamUpdatePlan())
if bz == nil {
return types.ErrInvalidRequest.Wrap("No param update plan found")
}

store.Delete(types.KeyParamUpdatePlan())
return nil
}

// GetParamUpdatePlan will return whether an upcoming param update plan exists and the plan
// if it does.
func (k Keeper) GetParamUpdatePlan(ctx sdk.Context) (plan types.ParamUpdatePlan, havePlan bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.KeyParamUpdatePlan())
if bz == nil {
return plan, false
}

k.cdc.MustUnmarshal(bz, &plan)
return plan, true
}

// ValidateParamChanges validates parameter changes against the existing oracle parameters.
func (k Keeper) ValidateParamChanges(ctx sdk.Context, keys []string, changes types.Params) error {
params := k.GetParams(ctx)

for _, key := range keys {
switch key {
case string(types.KeyVotePeriod):
params.VotePeriod = changes.VotePeriod

case string(types.KeyVoteThreshold):
params.VoteThreshold = changes.VoteThreshold

case string(types.KeyRewardBands):
params.RewardBands = changes.RewardBands

case string(types.KeyRewardDistributionWindow):
params.RewardDistributionWindow = changes.RewardDistributionWindow

case string(types.KeyAcceptList):
params.AcceptList = changes.AcceptList.Normalize()

case string(types.KeyMandatoryList):
params.MandatoryList = changes.MandatoryList.Normalize()

case string(types.KeySlashFraction):
params.SlashFraction = changes.SlashFraction

case string(types.KeySlashWindow):
params.SlashWindow = changes.SlashWindow

case string(types.KeyMinValidPerWindow):
params.MinValidPerWindow = changes.MinValidPerWindow

case string(types.KeyHistoricStampPeriod):
params.HistoricStampPeriod = changes.HistoricStampPeriod

case string(types.KeyMedianStampPeriod):
params.MedianStampPeriod = changes.MedianStampPeriod

case string(types.KeyMaximumPriceStamps):
params.MaximumPriceStamps = changes.MaximumPriceStamps

case string(types.KeyMaximumMedianStamps):
params.MaximumMedianStamps = changes.MaximumMedianStamps
}
}

return params.Validate()
}

// ExecuteParamUpdatePlan will execute a given param update plan.
func (k Keeper) ExecuteParamUpdatePlan(ctx sdk.Context, plan types.ParamUpdatePlan) {
for _, key := range plan.Keys {
switch key {
case string(types.KeyVotePeriod):
k.SetVotePeriod(ctx, plan.Changes.VotePeriod)

case string(types.KeyVoteThreshold):
k.SetVoteThreshold(ctx, plan.Changes.VoteThreshold)

case string(types.KeyRewardBands):
k.SetRewardBand(ctx, plan.Changes.RewardBands)

case string(types.KeyRewardDistributionWindow):
k.SetRewardDistributionWindow(ctx, plan.Changes.RewardDistributionWindow)

case string(types.KeyAcceptList):
k.SetAcceptList(ctx, plan.Changes.AcceptList.Normalize())

case string(types.KeyMandatoryList):
k.SetMandatoryList(ctx, plan.Changes.MandatoryList.Normalize())

case string(types.KeySlashFraction):
k.SetSlashFraction(ctx, plan.Changes.SlashFraction)

case string(types.KeySlashWindow):
k.SetSlashWindow(ctx, plan.Changes.SlashWindow)

case string(types.KeyMinValidPerWindow):
k.SetMinValidPerWindow(ctx, plan.Changes.MinValidPerWindow)

case string(types.KeyHistoricStampPeriod):
k.SetHistoricStampPeriod(ctx, plan.Changes.HistoricStampPeriod)

case string(types.KeyMedianStampPeriod):
k.SetMedianStampPeriod(ctx, plan.Changes.MedianStampPeriod)

case string(types.KeyMaximumPriceStamps):
k.SetMaximumPriceStamps(ctx, plan.Changes.MaximumPriceStamps)

case string(types.KeyMaximumMedianStamps):
k.SetMaximumMedianStamps(ctx, plan.Changes.MaximumMedianStamps)
}
}
}
Loading