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(sponsorship): only allow sponsoring rollapps with bonded sequencers #1275

Merged
merged 3 commits into from
Oct 3, 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
1 change: 1 addition & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ func (a *AppKeepers) InitKeepers(
a.AccountKeeper,
a.StakingKeeper,
a.IncentivesKeeper,
a.SequencerKeeper,
authtypes.NewModuleAddress(govtypes.ModuleName).String(),
)

Expand Down
15 changes: 9 additions & 6 deletions x/sponsorship/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Keeper struct {

stakingKeeper types.StakingKeeper
incentivesKeeper types.IncentivesKeeper
sequencerKeeper types.SequencerKeeper
}

// NewKeeper returns a new instance of the x/sponsorship keeper.
Expand All @@ -32,6 +33,7 @@ func NewKeeper(
ak types.AccountKeeper,
sk types.StakingKeeper,
ik types.IncentivesKeeper,
sqk types.SequencerKeeper,
authority string,
) Keeper {
// ensure the module account is set
Expand All @@ -53,12 +55,6 @@ func NewKeeper(
"params",
collcompat.ProtoValue[types.Params](cdc),
),
distribution: collections.NewItem(
sb,
types.DistributionPrefix(),
"distribution",
collcompat.ProtoValue[types.Distribution](cdc),
),
delegatorValidatorPower: collections.NewMap(
sb,
types.DelegatorValidatorPrefix(),
Expand All @@ -69,6 +65,12 @@ func NewKeeper(
),
collcompat.IntValue,
),
distribution: collections.NewItem(
sb,
types.DistributionPrefix(),
"distribution",
collcompat.ProtoValue[types.Distribution](cdc),
),
votes: collections.NewMap(
sb,
types.VotePrefix(),
Expand All @@ -78,5 +80,6 @@ func NewKeeper(
),
stakingKeeper: sk,
incentivesKeeper: ik,
sequencerKeeper: sqk,
}
}
56 changes: 41 additions & 15 deletions x/sponsorship/keeper/votes.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,36 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

incentivestypes "github.com/dymensionxyz/dymension/v3/x/incentives/types"
sequencertypes "github.com/dymensionxyz/dymension/v3/x/sequencer/types"
"github.com/dymensionxyz/dymension/v3/x/sponsorship/types"
)

func (k Keeper) Vote(ctx sdk.Context, voter sdk.AccAddress, weights []types.GaugeWeight) (types.Vote, types.Distribution, error) {
// Get module params
params, err := k.GetParams(ctx)
if err != nil {
return types.Vote{}, types.Distribution{}, fmt.Errorf("cannot get module params: %w", err)
}

// Validate specified weights
err = k.validateWeights(ctx, weights, params.MinAllocationWeight)
if err != nil {
return types.Vote{}, types.Distribution{}, fmt.Errorf("error validating weights: %w", err)
}

// Check if the user's voted. If they have, revoke the previous vote to place a new one.
voted, err := k.Voted(ctx, voter)
if err != nil {
return types.Vote{}, types.Distribution{}, fmt.Errorf("cannot verify if the voter has already voted: %w", err)
}

if voted {
_, err := k.RevokeVote(ctx, voter)
if err != nil {
return types.Vote{}, types.Distribution{}, fmt.Errorf("failed to revoke previous vote: %w", err)
}
}

params, err := k.GetParams(ctx)
if err != nil {
return types.Vote{}, types.Distribution{}, fmt.Errorf("cannot get module params: %w", err)
}

err = k.validateWeights(ctx, weights, params.MinAllocationWeight)
if err != nil {
return types.Vote{}, types.Distribution{}, fmt.Errorf("error validating weights: %w", err)
}

// Get the user’s total voting power from the x/staking
vpBreakdown, err := k.GetValidatorBreakdown(ctx, voter)
if err != nil {
Expand Down Expand Up @@ -107,21 +111,43 @@ func (k Keeper) revokeVote(ctx sdk.Context, voter sdk.AccAddress, vote types.Vot
return d, nil
}

// validateWeights validates that no gauge got less than MinAllocationWeight and all of them are perpetual
// validateWeights validates that
// - No gauge get less than MinAllocationWeight
// - All gauges exist
// - All gauges are perpetual
// - Rollapp gauges have at least one bonded sequencer
func (k Keeper) validateWeights(ctx sdk.Context, weights []types.GaugeWeight, minAllocationWeight math.Int) error {
for _, weight := range weights {
// No gauge get less than MinAllocationWeight
if weight.Weight.LT(minAllocationWeight) {
return fmt.Errorf("gauge weight '%s' is less than min allocation weight '%s'", weight.Weight, minAllocationWeight)
return fmt.Errorf("gauge weight is less than min allocation weight: gauge weight %s, min allocation %s", weight.Weight, minAllocationWeight)
}

// All gauges exist
gauge, err := k.incentivesKeeper.GetGaugeByID(ctx, weight.GaugeId)
if err != nil {
return fmt.Errorf("failed to get gauge by id '%d': %w", weight.GaugeId, err)
return fmt.Errorf("failed to get gauge by id: %d: %w", weight.GaugeId, err)
}

// All gauges are perpetual
if !gauge.IsPerpetual {
return fmt.Errorf("gauge '%d' is not perpetual", weight.GaugeId)
return fmt.Errorf("gauge is not perpetual: %d", weight.GaugeId)
}

// Rollapp gauges have at least one bonded sequencer
switch distrTo := gauge.DistributeTo.(type) {
case *incentivestypes.Gauge_Asset:
// no additional restrictions for asset gauges
case *incentivestypes.Gauge_Rollapp:
// we allow sponsoring only rollapps with bonded sequencers
bondedSequencers := k.sequencerKeeper.GetSequencersByRollappByStatus(ctx, distrTo.Rollapp.RollappId, sequencertypes.Bonded)
if len(bondedSequencers) == 0 {
return fmt.Errorf("rollapp has no bonded sequencers: %s'", distrTo.Rollapp.RollappId)
}
default:
return fmt.Errorf("gauge has an unsupported distribution type: gauge id %d, type %T", gauge.Id, distrTo)
}

}
return nil
}
Expand Down
81 changes: 79 additions & 2 deletions x/sponsorship/keeper/votes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ func (s *KeeperTestSuite) TestMsgVote() {
},
},
expectErr: true,
errorContains: "failed to get gauge by id '2'",
errorContains: "failed to get gauge by id: 2",
},
{
name: "Weight is less than the min allocation",
Expand Down Expand Up @@ -271,7 +271,7 @@ func (s *KeeperTestSuite) TestMsgVote() {
},
},
expectErr: true,
errorContains: "gauge weight '20000000000000000000' is less than min allocation weight '30000000000000000000'",
errorContains: "gauge weight is less than min allocation weight: gauge weight 20000000000000000000, min allocation 30000000000000000000",
},
{
name: "Not enough voting power",
Expand Down Expand Up @@ -387,6 +387,83 @@ func (s *KeeperTestSuite) TestMsgVote() {
}
}

func (s *KeeperTestSuite) TestMsgVoteRollAppGaugeBondedSequencer() {
raName := "testrollapp_1-1"

// create a rollapp, subsequently the rollapp gauge must be created
s.CreateRollappByName(raName)
// create a bonded sequencer
proposer := s.CreateDefaultSequencer(s.Ctx, raName)

// verify the sequencer is bonded
seq, found := s.App.SequencerKeeper.GetSequencer(s.Ctx, proposer)
s.Require().True(found)
s.Require().True(seq.IsBonded())

// create a validator and a delegator
initial := sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(1_000_000))
val := s.CreateValidator()
del := s.CreateDelegator(val.GetOperator(), initial)

// case a vote to the rollapp gauge created above
voteResp, err := s.msgServer.Vote(s.Ctx, &types.MsgVote{
Voter: del.GetDelegatorAddr().String(),
Weights: []types.GaugeWeight{
{GaugeId: 1, Weight: types.DYM.MulRaw(20)},
},
})
s.Require().NoError(err)
s.Require().NotNil(voteResp)

// check the distribution is correct
expectedDistr := types.Distribution{
VotingPower: math.NewInt(1_000_000),
Gauges: []types.Gauge{
{GaugeId: 1, Power: math.NewInt(200_000)},
},
}
distr := s.GetDistribution()
err = distr.Validate()
s.Require().NoError(err)
s.Require().True(expectedDistr.Equal(distr), "expect: %v\nactual: %v", expectedDistr, distr)
}

func (s *KeeperTestSuite) TestMsgVoteRollAppGaugeNonBondedSequencer() {
raName := "testrollapp_1-1"

// create a rollapp, subsequently the rollapp gauge must be created
s.CreateRollappByName(raName)
// create a bonded sequencer
proposer := s.CreateDefaultSequencer(s.Ctx, raName)

// jail the sequencer
err := s.App.SequencerKeeper.JailSequencerOnFraud(s.Ctx, proposer)
s.Require().NoError(err)

// create a validator and a delegator
initial := sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(1_000_000))
val := s.CreateValidator()
del := s.CreateDelegator(val.GetOperator(), initial)

// case a vote to the rollapp gauge created above
voteResp, err := s.msgServer.Vote(s.Ctx, &types.MsgVote{
Voter: del.GetDelegatorAddr().String(),
Weights: []types.GaugeWeight{
{GaugeId: 1, Weight: types.DYM.MulRaw(20)},
},
})
s.Require().Error(err)
s.Require().ErrorContains(err, "rollapp has no bonded sequencers")
s.Require().Nil(voteResp)

// check the distribution is correct. the distr is empty.
expectedDistr := types.NewDistribution()
distr := s.GetDistribution()
err = distr.Validate()
s.Require().NoError(err)
s.Require().True(expectedDistr.Equal(distr), "expect: %v\nactual: %v", expectedDistr, distr)
}

func (s *KeeperTestSuite) TestMsgRevokeVote() {
addr := apptesting.CreateRandomAccounts(3)

Expand Down
5 changes: 5 additions & 0 deletions x/sponsorship/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

incentivestypes "github.com/dymensionxyz/dymension/v3/x/incentives/types"
sequencertypes "github.com/dymensionxyz/dymension/v3/x/sequencer/types"
)

// AccountKeeper defines the contract required for account APIs.
Expand All @@ -21,3 +22,7 @@ type StakingKeeper interface {
type IncentivesKeeper interface {
GetGaugeByID(ctx sdk.Context, gaugeID uint64) (*incentivestypes.Gauge, error)
}

type SequencerKeeper interface {
GetSequencersByRollappByStatus(ctx sdk.Context, rollappId string, status sequencertypes.OperatingStatus) []sequencertypes.Sequencer
}
Loading