mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-24 15:25:18 +00:00
Committee migration (#665)
* draft: kava-3 to kava-4 committee migrations * migration: add busd, xrpb, btcb bep3 and cdp params to stability committee * add new pricefeed markets to safety committee * add harvest to committee
This commit is contained in:
parent
00f2068d1b
commit
8f69dcf960
@ -18,6 +18,8 @@ import (
|
||||
v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
||||
v0_11cdp "github.com/kava-labs/kava/x/cdp"
|
||||
v0_9cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_9"
|
||||
v0_11committee "github.com/kava-labs/kava/x/committee"
|
||||
v0_9committee "github.com/kava-labs/kava/x/committee/legacy/v0_9"
|
||||
v0_11harvest "github.com/kava-labs/kava/x/harvest"
|
||||
v0_11incentive "github.com/kava-labs/kava/x/incentive"
|
||||
v0_9incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_9"
|
||||
@ -164,6 +166,200 @@ func MigrateBep3(oldGenState v0_9bep3.GenesisState) v0_11bep3.GenesisState {
|
||||
}
|
||||
}
|
||||
|
||||
// MigrateCommittee migrates from a v0.9 (or v0.10) committee genesis state to a v0.11 committee genesis state
|
||||
func MigrateCommittee(oldGenState v0_9committee.GenesisState) v0_11committee.GenesisState {
|
||||
var newCommittees []v0_11committee.Committee
|
||||
var newStabilityCommittee v0_11committee.Committee
|
||||
var newSafetyCommittee v0_11committee.Committee
|
||||
var newProposals []v0_11committee.Proposal
|
||||
var newVotes []v0_11committee.Vote
|
||||
|
||||
for _, committee := range oldGenState.Committees {
|
||||
if committee.ID == 1 {
|
||||
newStabilityCommittee.Description = committee.Description
|
||||
newStabilityCommittee.ID = committee.ID
|
||||
newStabilityCommittee.Members = committee.Members
|
||||
newStabilityCommittee.VoteThreshold = committee.VoteThreshold
|
||||
newStabilityCommittee.ProposalDuration = committee.ProposalDuration
|
||||
var newStabilityPermissions []v0_11committee.Permission
|
||||
var newStabilitySubParamPermissions v0_11committee.SubParamChangePermission
|
||||
for _, permission := range committee.Permissions {
|
||||
subPermission, ok := permission.(v0_9committee.SubParamChangePermission)
|
||||
if ok {
|
||||
oldCollateralParam := subPermission.AllowedCollateralParams[0]
|
||||
newCollateralParam := v0_11committee.AllowedCollateralParam{
|
||||
Type: "bnb-a",
|
||||
Denom: false,
|
||||
AuctionSize: oldCollateralParam.AuctionSize,
|
||||
ConversionFactor: oldCollateralParam.ConversionFactor,
|
||||
DebtLimit: oldCollateralParam.DebtLimit,
|
||||
LiquidationMarketID: oldCollateralParam.LiquidationMarketID,
|
||||
SpotMarketID: oldCollateralParam.SpotMarketID,
|
||||
LiquidationPenalty: oldCollateralParam.LiquidationPenalty,
|
||||
LiquidationRatio: oldCollateralParam.LiquidationRatio,
|
||||
Prefix: oldCollateralParam.Prefix,
|
||||
StabilityFee: oldCollateralParam.StabilityFee,
|
||||
}
|
||||
oldDebtParam := subPermission.AllowedDebtParam
|
||||
newDebtParam := v0_11committee.AllowedDebtParam{
|
||||
ConversionFactor: oldDebtParam.ConversionFactor,
|
||||
DebtFloor: oldDebtParam.DebtFloor,
|
||||
Denom: oldDebtParam.Denom,
|
||||
ReferenceAsset: oldDebtParam.ReferenceAsset,
|
||||
SavingsRate: oldDebtParam.SavingsRate,
|
||||
}
|
||||
oldAssetParam := subPermission.AllowedAssetParams[0]
|
||||
newAssetParam := v0_11committee.AllowedAssetParam{
|
||||
Active: oldAssetParam.Active,
|
||||
CoinID: oldAssetParam.CoinID,
|
||||
Denom: oldAssetParam.Denom,
|
||||
Limit: oldAssetParam.Limit,
|
||||
MaxSwapAmount: true,
|
||||
MinBlockLock: true,
|
||||
}
|
||||
oldMarketParams := subPermission.AllowedMarkets
|
||||
var newMarketParams v0_11committee.AllowedMarkets
|
||||
for _, oldMarketParam := range oldMarketParams {
|
||||
newMarketParam := v0_11committee.AllowedMarket(oldMarketParam)
|
||||
newMarketParams = append(newMarketParams, newMarketParam)
|
||||
}
|
||||
// add btc, xrp, busd markets to committee
|
||||
btcMarketParam := v0_11committee.AllowedMarket{
|
||||
MarketID: "btc:usd",
|
||||
BaseAsset: false,
|
||||
QuoteAsset: false,
|
||||
Oracles: false,
|
||||
Active: true,
|
||||
}
|
||||
btc30MarketParam := v0_11committee.AllowedMarket{
|
||||
MarketID: "btc:usd:30",
|
||||
BaseAsset: false,
|
||||
QuoteAsset: false,
|
||||
Oracles: false,
|
||||
Active: true,
|
||||
}
|
||||
xrpMarketParam := v0_11committee.AllowedMarket{
|
||||
MarketID: "xrp:usd",
|
||||
BaseAsset: false,
|
||||
QuoteAsset: false,
|
||||
Oracles: false,
|
||||
Active: true,
|
||||
}
|
||||
xrp30MarketParam := v0_11committee.AllowedMarket{
|
||||
MarketID: "xrp:usd:30",
|
||||
BaseAsset: false,
|
||||
QuoteAsset: false,
|
||||
Oracles: false,
|
||||
Active: true,
|
||||
}
|
||||
busdMarketParam := v0_11committee.AllowedMarket{
|
||||
MarketID: "busd:usd",
|
||||
BaseAsset: false,
|
||||
QuoteAsset: false,
|
||||
Oracles: false,
|
||||
Active: true,
|
||||
}
|
||||
busd30MarketParam := v0_11committee.AllowedMarket{
|
||||
MarketID: "busd:usd:30",
|
||||
BaseAsset: false,
|
||||
QuoteAsset: false,
|
||||
Oracles: false,
|
||||
Active: true,
|
||||
}
|
||||
newMarketParams = append(newMarketParams, btcMarketParam, btc30MarketParam, xrpMarketParam, xrp30MarketParam, busdMarketParam, busd30MarketParam)
|
||||
oldAllowedParams := subPermission.AllowedParams
|
||||
var newAllowedParams v0_11committee.AllowedParams
|
||||
for _, oldAllowedParam := range oldAllowedParams {
|
||||
newAllowedParam := v0_11committee.AllowedParam(oldAllowedParam)
|
||||
if oldAllowedParam.Subspace == "bep3" && oldAllowedParam.Key == "SupportedAssets" {
|
||||
newAllowedParam.Key = "AssetParams"
|
||||
}
|
||||
harvestParam := v0_11committee.AllowedParam{Subspace: "harvest", Key: "Active"}
|
||||
|
||||
newAllowedParams = append(newAllowedParams, newAllowedParam, harvestParam)
|
||||
}
|
||||
|
||||
// --------------- ADD BUSD, XRP-B, BTC-B BEP3 parameters to Stability Committee Permissions
|
||||
busdAllowedAssetParam := v0_11committee.AllowedAssetParam{
|
||||
Active: true,
|
||||
CoinID: true, // allow busd coinID to be updated in case it gets its own slip-44
|
||||
Denom: "busd",
|
||||
Limit: true,
|
||||
MaxSwapAmount: true,
|
||||
MinBlockLock: true,
|
||||
}
|
||||
xrpbAllowedAssetParam := v0_11committee.AllowedAssetParam{
|
||||
Active: true,
|
||||
CoinID: false,
|
||||
Denom: "xrpb",
|
||||
Limit: true,
|
||||
MaxSwapAmount: true,
|
||||
MinBlockLock: true,
|
||||
}
|
||||
btcbAllowedAssetParam := v0_11committee.AllowedAssetParam{
|
||||
Active: true,
|
||||
CoinID: false,
|
||||
Denom: "btcb",
|
||||
Limit: true,
|
||||
MaxSwapAmount: true,
|
||||
MinBlockLock: true,
|
||||
}
|
||||
// --------- ADD BTC-B, XRP-B, BUSD(a), BUSD(b) cdp collateral params to stability committee
|
||||
busdaAllowedCollateralParam := v0_11committee.NewAllowedCollateralParam(
|
||||
"busd-a", false, false, true, true, true, false, false, false, false, false,
|
||||
)
|
||||
busdbAllowedCollateralParam := v0_11committee.NewAllowedCollateralParam(
|
||||
"busd-b", false, false, true, true, true, false, false, false, false, false,
|
||||
)
|
||||
btcbAllowedCollateralParam := v0_11committee.NewAllowedCollateralParam(
|
||||
"btcb-a", false, false, true, true, true, false, false, false, false, false,
|
||||
)
|
||||
xrpbAllowedCollateralParam := v0_11committee.NewAllowedCollateralParam(
|
||||
"xrpb-a", false, false, true, true, true, false, false, false, false, false,
|
||||
)
|
||||
|
||||
newStabilitySubParamPermissions.AllowedAssetParams = v0_11committee.AllowedAssetParams{
|
||||
newAssetParam, busdAllowedAssetParam, btcbAllowedAssetParam, xrpbAllowedAssetParam}
|
||||
newStabilitySubParamPermissions.AllowedCollateralParams = v0_11committee.AllowedCollateralParams{
|
||||
newCollateralParam, busdaAllowedCollateralParam, busdbAllowedCollateralParam, btcbAllowedCollateralParam, xrpbAllowedCollateralParam}
|
||||
newStabilitySubParamPermissions.AllowedDebtParam = newDebtParam
|
||||
newStabilitySubParamPermissions.AllowedMarkets = newMarketParams
|
||||
newStabilitySubParamPermissions.AllowedParams = newAllowedParams
|
||||
newStabilityPermissions = append(newStabilityPermissions, newStabilitySubParamPermissions)
|
||||
}
|
||||
}
|
||||
newStabilityPermissions = append(newStabilityPermissions, v0_11committee.TextPermission{})
|
||||
newStabilityCommittee.Permissions = newStabilityPermissions
|
||||
newCommittees = append(newCommittees, newStabilityCommittee)
|
||||
} else {
|
||||
newSafetyCommittee.ID = committee.ID
|
||||
newSafetyCommittee.Description = committee.Description
|
||||
newSafetyCommittee.Members = committee.Members
|
||||
newSafetyCommittee.Permissions = []v0_11committee.Permission{v0_11committee.SoftwareUpgradePermission{}}
|
||||
newSafetyCommittee.VoteThreshold = committee.VoteThreshold
|
||||
newSafetyCommittee.ProposalDuration = committee.ProposalDuration
|
||||
newCommittees = append(newCommittees, newSafetyCommittee)
|
||||
}
|
||||
}
|
||||
for _, oldProp := range oldGenState.Proposals {
|
||||
newPubProposal := v0_11committee.PubProposal(oldProp.PubProposal)
|
||||
newProp := v0_11committee.NewProposal(newPubProposal, oldProp.ID, oldProp.CommitteeID, oldProp.Deadline)
|
||||
newProposals = append(newProposals, newProp)
|
||||
}
|
||||
|
||||
for _, oldVote := range oldGenState.Votes {
|
||||
newVote := v0_11committee.NewVote(oldVote.ProposalID, oldVote.Voter)
|
||||
newVotes = append(newVotes, newVote)
|
||||
}
|
||||
|
||||
return v0_11committee.GenesisState{
|
||||
NextProposalID: oldGenState.NextProposalID,
|
||||
Committees: newCommittees,
|
||||
Proposals: newProposals,
|
||||
Votes: newVotes,
|
||||
}
|
||||
}
|
||||
|
||||
// MigrateAuth migrates from a v0.38.5 auth genesis state to a v0.39.1 auth genesis state
|
||||
func MigrateAuth(oldGenState v38_5auth.GenesisState) v39_1auth.GenesisState {
|
||||
var newAccounts v39_1authexported.GenesisAccounts
|
||||
|
@ -6,9 +6,9 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
v39_1auth "github.com/cosmos/cosmos-sdk/x/auth"
|
||||
@ -20,6 +20,7 @@ import (
|
||||
v38_5supply "github.com/kava-labs/kava/migrate/v0_11/legacy/cosmos-sdk/v0.38.5/supply"
|
||||
v0_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
||||
v0_9cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_9"
|
||||
v0_9committee "github.com/kava-labs/kava/x/committee/legacy/v0_9"
|
||||
v0_9incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_9"
|
||||
v0_9pricefeed "github.com/kava-labs/kava/x/pricefeed/legacy/v0_9"
|
||||
v0_11validator_vesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
@ -48,6 +49,22 @@ func TestMigrateBep3(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestMigrateCommittee(t *testing.T) {
|
||||
bz, err := ioutil.ReadFile(filepath.Join("testdata", "committee-v09.json"))
|
||||
require.NoError(t, err)
|
||||
var oldGenState v0_9committee.GenesisState
|
||||
cdc := codec.New()
|
||||
sdk.RegisterCodec(cdc)
|
||||
v0_9committee.RegisterCodec(cdc)
|
||||
|
||||
require.NotPanics(t, func() {
|
||||
cdc.MustUnmarshalJSON(bz, &oldGenState)
|
||||
})
|
||||
|
||||
newGenState := MigrateCommittee(oldGenState)
|
||||
err = newGenState.Validate()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
func TestMigrateAuth(t *testing.T) {
|
||||
bz, err := ioutil.ReadFile(filepath.Join("testdata", "auth-v09.json"))
|
||||
require.NoError(t, err)
|
||||
|
157
migrate/v0_11/testdata/committee-v09.json
vendored
Normal file
157
migrate/v0_11/testdata/committee-v09.json
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
{
|
||||
"committees": [
|
||||
{
|
||||
"description": "Kava Stability Committee",
|
||||
"id": "1",
|
||||
"members": [
|
||||
"kava1gru35up50ql2wxhegr880qy6ynl63ujlv8gum2",
|
||||
"kava1sc3mh3pkas5e7xd269am4xm5mp6zweyzmhjagj",
|
||||
"kava1c9ye54e3pzwm3e0zpdlel6pnavrj9qqv6e8r4h",
|
||||
"kava1m7p6sjqrz6mylz776ct48wj6lpnpcd0z82209d",
|
||||
"kava1a9pmkzk570egv3sflu3uwdf3gejl7qfy9hghzl"
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"type": "kava/SubParamChangePermission",
|
||||
"value": {
|
||||
"allowed_asset_params": [
|
||||
{
|
||||
"active": true,
|
||||
"coin_id": false,
|
||||
"denom": "bnb",
|
||||
"limit": true
|
||||
}
|
||||
],
|
||||
"allowed_collateral_params": [
|
||||
{
|
||||
"auction_size": true,
|
||||
"conversion_factor": false,
|
||||
"debt_limit": true,
|
||||
"denom": "bnb",
|
||||
"liquidation_market_id": false,
|
||||
"liquidation_penalty": false,
|
||||
"liquidation_ratio": false,
|
||||
"prefix": false,
|
||||
"spot_market_id": false,
|
||||
"stability_fee": true
|
||||
}
|
||||
],
|
||||
"allowed_debt_param": {
|
||||
"conversion_factor": false,
|
||||
"debt_floor": true,
|
||||
"denom": false,
|
||||
"reference_asset": false,
|
||||
"savings_rate": true
|
||||
},
|
||||
"allowed_markets": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": false,
|
||||
"market_id": "bnb:usd",
|
||||
"oracles": false,
|
||||
"quote_asset": false
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": false,
|
||||
"market_id": "bnb:usd:30",
|
||||
"oracles": false,
|
||||
"quote_asset": false
|
||||
}
|
||||
],
|
||||
"allowed_params": [
|
||||
{
|
||||
"key": "BidDuration",
|
||||
"subspace": "auction"
|
||||
},
|
||||
{
|
||||
"key": "IncrementSurplus",
|
||||
"subspace": "auction"
|
||||
},
|
||||
{
|
||||
"key": "IncrementDebt",
|
||||
"subspace": "auction"
|
||||
},
|
||||
{
|
||||
"key": "IncrementCollateral",
|
||||
"subspace": "auction"
|
||||
},
|
||||
{
|
||||
"key": "SupportedAssets",
|
||||
"subspace": "bep3"
|
||||
},
|
||||
{
|
||||
"key": "GlobalDebtLimit",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "SurplusThreshold",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "SurplusLot",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "DebtThreshold",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "DebtLot",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "DistributionFrequency",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "CollateralParams",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "DebtParam",
|
||||
"subspace": "cdp"
|
||||
},
|
||||
{
|
||||
"key": "Active",
|
||||
"subspace": "incentive"
|
||||
},
|
||||
{
|
||||
"key": "Active",
|
||||
"subspace": "kavadist"
|
||||
},
|
||||
{
|
||||
"key": "Markets",
|
||||
"subspace": "pricefeed"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "kava/TextPermission",
|
||||
"value": {}
|
||||
}
|
||||
],
|
||||
"proposal_duration": "604800000000000",
|
||||
"vote_threshold": "0.500000000000000000"
|
||||
},
|
||||
{
|
||||
"description": "Kava Safety Committee",
|
||||
"id": "2",
|
||||
"members": [
|
||||
"kava1e0agyg6eug9r62fly9sls77ycjgw8ax6xk73es"
|
||||
],
|
||||
"permissions": [
|
||||
{
|
||||
"type": "kava/SoftwareUpgradePermission",
|
||||
"value": {}
|
||||
}
|
||||
],
|
||||
"proposal_duration": "604800000000000",
|
||||
"vote_threshold": "0.500000000000000000"
|
||||
}
|
||||
],
|
||||
"next_proposal_id": "27",
|
||||
"proposals": [],
|
||||
"votes": []
|
||||
}
|
@ -29,8 +29,10 @@ const (
|
||||
QuerierRoute = types.QuerierRoute
|
||||
QueryCommittee = types.QueryCommittee
|
||||
QueryCommittees = types.QueryCommittees
|
||||
QueryNextProposalID = types.QueryNextProposalID
|
||||
QueryProposal = types.QueryProposal
|
||||
QueryProposals = types.QueryProposals
|
||||
QueryRawParams = types.QueryRawParams
|
||||
QueryTally = types.QueryTally
|
||||
QueryVote = types.QueryVote
|
||||
QueryVotes = types.QueryVotes
|
||||
@ -51,6 +53,7 @@ var (
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
GetKeyFromID = types.GetKeyFromID
|
||||
GetVoteKey = types.GetVoteKey
|
||||
NewAllowedCollateralParam = types.NewAllowedCollateralParam
|
||||
NewCommittee = types.NewCommittee
|
||||
NewCommitteeChangeProposal = types.NewCommitteeChangeProposal
|
||||
NewCommitteeDeleteProposal = types.NewCommitteeDeleteProposal
|
||||
@ -60,6 +63,7 @@ var (
|
||||
NewProposal = types.NewProposal
|
||||
NewQueryCommitteeParams = types.NewQueryCommitteeParams
|
||||
NewQueryProposalParams = types.NewQueryProposalParams
|
||||
NewQueryRawParamsParams = types.NewQueryRawParamsParams
|
||||
NewQueryVoteParams = types.NewQueryVoteParams
|
||||
NewVote = types.NewVote
|
||||
RegisterCodec = types.RegisterCodec
|
||||
@ -77,6 +81,7 @@ var (
|
||||
ErrProposalExpired = types.ErrProposalExpired
|
||||
ErrUnknownCommittee = types.ErrUnknownCommittee
|
||||
ErrUnknownProposal = types.ErrUnknownProposal
|
||||
ErrUnknownSubspace = types.ErrUnknownSubspace
|
||||
ErrUnknownVote = types.ErrUnknownVote
|
||||
ModuleCdc = types.ModuleCdc
|
||||
NextProposalIDKey = types.NextProposalIDKey
|
||||
@ -108,6 +113,7 @@ type (
|
||||
PubProposal = types.PubProposal
|
||||
QueryCommitteeParams = types.QueryCommitteeParams
|
||||
QueryProposalParams = types.QueryProposalParams
|
||||
QueryRawParamsParams = types.QueryRawParamsParams
|
||||
QueryVoteParams = types.QueryVoteParams
|
||||
SimpleParamChangePermission = types.SimpleParamChangePermission
|
||||
SoftwareUpgradePermission = types.SoftwareUpgradePermission
|
||||
|
798
x/committee/legacy/v0_11/types.go
Normal file
798
x/committee/legacy/v0_11/types.go
Normal file
@ -0,0 +1,798 @@
|
||||
package v0_11
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||
|
||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxCommitteeDescriptionLength int = 512
|
||||
)
|
||||
|
||||
// Permission is anything with a method that validates whether a proposal is allowed by it or not.
|
||||
type Permission interface {
|
||||
Allows(sdk.Context, *codec.Codec, ParamKeeper, PubProposal) bool
|
||||
}
|
||||
|
||||
type ParamKeeper interface {
|
||||
GetSubspace(string) (params.Subspace, bool)
|
||||
}
|
||||
|
||||
// A Committee is a collection of addresses that are allowed to vote and enact any governance proposal that passes their permissions.
|
||||
type Committee struct {
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Members []sdk.AccAddress `json:"members" yaml:"members"`
|
||||
Permissions []Permission `json:"permissions" yaml:"permissions"`
|
||||
VoteThreshold sdk.Dec `json:"vote_threshold" yaml:"vote_threshold"` // Smallest percentage of members that must vote for a proposal to pass.
|
||||
ProposalDuration time.Duration `json:"proposal_duration" yaml:"proposal_duration"` // The length of time a proposal remains active for. Proposals will close earlier if they get enough votes.
|
||||
}
|
||||
|
||||
func NewCommittee(id uint64, description string, members []sdk.AccAddress, permissions []Permission, threshold sdk.Dec, duration time.Duration) Committee {
|
||||
return Committee{
|
||||
ID: id,
|
||||
Description: description,
|
||||
Members: members,
|
||||
Permissions: permissions,
|
||||
VoteThreshold: threshold,
|
||||
ProposalDuration: duration,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Committee) HasMember(addr sdk.AccAddress) bool {
|
||||
for _, m := range c.Members {
|
||||
if m.Equals(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasPermissionsFor returns whether the committee is authorized to enact a proposal.
|
||||
// As long as one permission allows the proposal then it goes through. Its the OR of all permissions.
|
||||
func (c Committee) HasPermissionsFor(ctx sdk.Context, appCdc *codec.Codec, pk ParamKeeper, proposal PubProposal) bool {
|
||||
for _, p := range c.Permissions {
|
||||
if p.Allows(ctx, appCdc, pk, proposal) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Committee) Validate() error {
|
||||
|
||||
addressMap := make(map[string]bool, len(c.Members))
|
||||
for _, m := range c.Members {
|
||||
// check there are no duplicate members
|
||||
if _, ok := addressMap[m.String()]; ok {
|
||||
return fmt.Errorf("committe cannot have duplicate members, %s", m)
|
||||
}
|
||||
// check for valid addresses
|
||||
if m.Empty() {
|
||||
return fmt.Errorf("committee cannot have empty member address")
|
||||
}
|
||||
addressMap[m.String()] = true
|
||||
}
|
||||
|
||||
if len(c.Members) == 0 {
|
||||
return fmt.Errorf("committee cannot have zero members")
|
||||
}
|
||||
|
||||
if len(c.Description) > MaxCommitteeDescriptionLength {
|
||||
return fmt.Errorf("description length %d longer than max allowed %d", len(c.Description), MaxCommitteeDescriptionLength)
|
||||
}
|
||||
|
||||
for _, p := range c.Permissions {
|
||||
if p == nil {
|
||||
return fmt.Errorf("committee cannot have a nil permission")
|
||||
}
|
||||
}
|
||||
|
||||
// threshold must be in the range (0,1]
|
||||
if c.VoteThreshold.IsNil() || c.VoteThreshold.LTE(sdk.ZeroDec()) || c.VoteThreshold.GT(sdk.NewDec(1)) {
|
||||
return fmt.Errorf("invalid threshold: %s", c.VoteThreshold)
|
||||
}
|
||||
|
||||
if c.ProposalDuration < 0 {
|
||||
return fmt.Errorf("invalid proposal duration: %s", c.ProposalDuration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Proposals
|
||||
// ------------------------------------------
|
||||
|
||||
// PubProposal is the interface that all proposals must fulfill to be submitted to a committee.
|
||||
// Proposal types can be created external to this module. For example a ParamChangeProposal, or CommunityPoolSpendProposal.
|
||||
// It is pinned to the equivalent type in the gov module to create compatibility between proposal types.
|
||||
type PubProposal govtypes.Content
|
||||
|
||||
// Proposal is an internal record of a governance proposal submitted to a committee.
|
||||
type Proposal struct {
|
||||
PubProposal `json:"pub_proposal" yaml:"pub_proposal"`
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
CommitteeID uint64 `json:"committee_id" yaml:"committee_id"`
|
||||
Deadline time.Time `json:"deadline" yaml:"deadline"`
|
||||
}
|
||||
|
||||
func NewProposal(pubProposal PubProposal, id uint64, committeeID uint64, deadline time.Time) Proposal {
|
||||
return Proposal{
|
||||
PubProposal: pubProposal,
|
||||
ID: id,
|
||||
CommitteeID: committeeID,
|
||||
Deadline: deadline,
|
||||
}
|
||||
}
|
||||
|
||||
// HasExpiredBy calculates if the proposal will have expired by a certain time.
|
||||
// All votes must be cast before deadline, those cast at time == deadline are not valid
|
||||
func (p Proposal) HasExpiredBy(time time.Time) bool {
|
||||
return !time.Before(p.Deadline)
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface, and importantly overrides the String methods inherited from the embedded PubProposal type.
|
||||
func (p Proposal) String() string {
|
||||
bz, _ := yaml.Marshal(p)
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Votes
|
||||
// ------------------------------------------
|
||||
|
||||
type Vote struct {
|
||||
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
|
||||
Voter sdk.AccAddress `json:"voter" yaml:"voter"`
|
||||
}
|
||||
|
||||
func NewVote(proposalID uint64, voter sdk.AccAddress) Vote {
|
||||
return Vote{
|
||||
ProposalID: proposalID,
|
||||
Voter: voter,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Vote) Validate() error {
|
||||
if v.Voter.Empty() {
|
||||
return fmt.Errorf("voter address cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// GodPermission
|
||||
// ------------------------------------------
|
||||
|
||||
// GodPermission allows any governance proposal. It is used mainly for testing.
|
||||
type GodPermission struct{}
|
||||
|
||||
var _ Permission = GodPermission{}
|
||||
|
||||
func (GodPermission) Allows(sdk.Context, *codec.Codec, ParamKeeper, PubProposal) bool { return true }
|
||||
|
||||
func (GodPermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
}{
|
||||
Type: "god_permission",
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// SimpleParamChangePermission
|
||||
// ------------------------------------------
|
||||
|
||||
// SimpleParamChangePermission only allows changes to certain params
|
||||
type SimpleParamChangePermission struct {
|
||||
AllowedParams AllowedParams `json:"allowed_params" yaml:"allowed_params"`
|
||||
}
|
||||
|
||||
var _ Permission = SimpleParamChangePermission{}
|
||||
|
||||
func (perm SimpleParamChangePermission) Allows(_ sdk.Context, _ *codec.Codec, _ ParamKeeper, p PubProposal) bool {
|
||||
proposal, ok := p.(paramstypes.ParameterChangeProposal)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, change := range proposal.Changes {
|
||||
if !perm.AllowedParams.Contains(change) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (perm SimpleParamChangePermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
AllowedParams AllowedParams `yaml:"allowed_params"`
|
||||
}{
|
||||
Type: "param_change_permission",
|
||||
AllowedParams: perm.AllowedParams,
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
type AllowedParam struct {
|
||||
Subspace string `json:"subspace" yaml:"subspace"`
|
||||
Key string `json:"key" yaml:"key"`
|
||||
}
|
||||
type AllowedParams []AllowedParam
|
||||
|
||||
func (allowed AllowedParams) Contains(paramChange paramstypes.ParamChange) bool {
|
||||
for _, p := range allowed {
|
||||
if paramChange.Subspace == p.Subspace && paramChange.Key == p.Key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// TextPermission
|
||||
// ------------------------------------------
|
||||
|
||||
// TextPermission allows any text governance proposal.
|
||||
type TextPermission struct{}
|
||||
|
||||
var _ Permission = TextPermission{}
|
||||
|
||||
func (TextPermission) Allows(_ sdk.Context, _ *codec.Codec, _ ParamKeeper, p PubProposal) bool {
|
||||
_, ok := p.(govtypes.TextProposal)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (TextPermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
}{
|
||||
Type: "text_permission",
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// SoftwareUpgradePermission
|
||||
// ------------------------------------------
|
||||
|
||||
type SoftwareUpgradePermission struct{}
|
||||
|
||||
var _ Permission = SoftwareUpgradePermission{}
|
||||
|
||||
func (SoftwareUpgradePermission) Allows(_ sdk.Context, _ *codec.Codec, _ ParamKeeper, p PubProposal) bool {
|
||||
_, ok := p.(upgrade.SoftwareUpgradeProposal)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (SoftwareUpgradePermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
}{
|
||||
Type: "software_upgrade_permission",
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// SubParamChangePermission
|
||||
// ------------------------------------------
|
||||
|
||||
// ParamChangeProposal only allows changes to certain params
|
||||
type SubParamChangePermission struct {
|
||||
AllowedParams AllowedParams `json:"allowed_params" yaml:"allowed_params"`
|
||||
AllowedCollateralParams AllowedCollateralParams `json:"allowed_collateral_params" yaml:"allowed_collateral_params"`
|
||||
AllowedDebtParam AllowedDebtParam `json:"allowed_debt_param" yaml:"allowed_debt_param"`
|
||||
AllowedAssetParams AllowedAssetParams `json:"allowed_asset_params" yaml:"allowed_asset_params"`
|
||||
AllowedMarkets AllowedMarkets `json:"allowed_markets" yaml:"allowed_markets"`
|
||||
}
|
||||
|
||||
var _ Permission = SubParamChangePermission{}
|
||||
|
||||
func (perm SubParamChangePermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
AllowedParams AllowedParams `yaml:"allowed_params"`
|
||||
AllowedCollateralParams AllowedCollateralParams `yaml:"allowed_collateral_params"`
|
||||
AllowedDebtParam AllowedDebtParam `yaml:"allowed_debt_param"`
|
||||
AllowedAssetParams AllowedAssetParams `yaml:"allowed_asset_params"`
|
||||
AllowedMarkets AllowedMarkets `yaml:"allowed_markets"`
|
||||
}{
|
||||
Type: "param_change_permission",
|
||||
AllowedParams: perm.AllowedParams,
|
||||
AllowedCollateralParams: perm.AllowedCollateralParams,
|
||||
AllowedDebtParam: perm.AllowedDebtParam,
|
||||
AllowedAssetParams: perm.AllowedAssetParams,
|
||||
AllowedMarkets: perm.AllowedMarkets,
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
func (perm SubParamChangePermission) Allows(ctx sdk.Context, appCdc *codec.Codec, pk ParamKeeper, p PubProposal) bool {
|
||||
// Check pubproposal has correct type
|
||||
proposal, ok := p.(paramstypes.ParameterChangeProposal)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Check the param changes match the allowed keys
|
||||
for _, change := range proposal.Changes {
|
||||
if !perm.AllowedParams.Contains(change) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Check any CollateralParam changes are allowed
|
||||
|
||||
// Get the incoming CollaterParams value
|
||||
var foundIncomingCP bool
|
||||
var incomingCP cdptypes.CollateralParams
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == cdptypes.ModuleName && change.Key == string(cdptypes.KeyCollateralParams)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingCP = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingCP); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingCP {
|
||||
// Get the current value of the CollateralParams
|
||||
cdpSubspace, found := pk.GetSubspace(cdptypes.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentCP cdptypes.CollateralParams
|
||||
cdpSubspace.Get(ctx, cdptypes.KeyCollateralParams, ¤tCP) // panics if something goes wrong
|
||||
|
||||
// Check all the incoming changes in the CollateralParams are allowed
|
||||
collateralParamChangesAllowed := perm.AllowedCollateralParams.Allows(currentCP, incomingCP)
|
||||
if !collateralParamChangesAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check any DebtParam changes are allowed
|
||||
|
||||
// Get the incoming DebtParam value
|
||||
var foundIncomingDP bool
|
||||
var incomingDP cdptypes.DebtParam
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == cdptypes.ModuleName && change.Key == string(cdptypes.KeyDebtParam)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingDP = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingDP); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingDP {
|
||||
// Get the current value of the DebtParams
|
||||
cdpSubspace, found := pk.GetSubspace(cdptypes.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentDP cdptypes.DebtParam
|
||||
cdpSubspace.Get(ctx, cdptypes.KeyDebtParam, ¤tDP) // panics if something goes wrong
|
||||
|
||||
// Check the incoming changes in the DebtParam are allowed
|
||||
debtParamChangeAllowed := perm.AllowedDebtParam.Allows(currentDP, incomingDP)
|
||||
if !debtParamChangeAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check any AssetParams changes are allowed
|
||||
|
||||
// Get the incoming AssetParams value
|
||||
var foundIncomingAPs bool
|
||||
var incomingAPs bep3types.AssetParams
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == bep3types.ModuleName && change.Key == string(bep3types.KeyAssetParams)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingAPs = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingAPs); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingAPs {
|
||||
// Get the current value of the SupportedAssets
|
||||
subspace, found := pk.GetSubspace(bep3types.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentAPs bep3types.AssetParams
|
||||
subspace.Get(ctx, bep3types.KeyAssetParams, ¤tAPs) // panics if something goes wrong
|
||||
|
||||
// Check all the incoming changes in the CollateralParams are allowed
|
||||
assetParamsChangesAllowed := perm.AllowedAssetParams.Allows(currentAPs, incomingAPs)
|
||||
if !assetParamsChangesAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check any Markets changes are allowed
|
||||
|
||||
// Get the incoming Markets value
|
||||
var foundIncomingMs bool
|
||||
var incomingMs pricefeedtypes.Markets
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == pricefeedtypes.ModuleName && change.Key == string(pricefeedtypes.KeyMarkets)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingMs = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingMs); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingMs {
|
||||
// Get the current value of the Markets
|
||||
subspace, found := pk.GetSubspace(pricefeedtypes.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentMs pricefeedtypes.Markets
|
||||
subspace.Get(ctx, pricefeedtypes.KeyMarkets, ¤tMs) // panics if something goes wrong
|
||||
|
||||
// Check all the incoming changes in the Markets are allowed
|
||||
marketsChangesAllowed := perm.AllowedMarkets.Allows(currentMs, incomingMs)
|
||||
if !marketsChangesAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// AllowedCollateralParam cdp.CollateralParam fields that can be subject to committee governance
|
||||
type AllowedCollateralParam struct {
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Denom bool `json:"denom" yaml:"denom"`
|
||||
LiquidationRatio bool `json:"liquidation_ratio" yaml:"liquidation_ratio"`
|
||||
DebtLimit bool `json:"debt_limit" yaml:"debt_limit"`
|
||||
StabilityFee bool `json:"stability_fee" yaml:"stability_fee"`
|
||||
AuctionSize bool `json:"auction_size" yaml:"auction_size"`
|
||||
LiquidationPenalty bool `json:"liquidation_penalty" yaml:"liquidation_penalty"`
|
||||
Prefix bool `json:"prefix" yaml:"prefix"`
|
||||
SpotMarketID bool `json:"spot_market_id" yaml:"spot_market_id"`
|
||||
LiquidationMarketID bool `json:"liquidation_market_id" yaml:"liquidation_market_id"`
|
||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
}
|
||||
|
||||
type AllowedCollateralParams []AllowedCollateralParam
|
||||
|
||||
func (acps AllowedCollateralParams) Allows(current, incoming cdptypes.CollateralParams) bool {
|
||||
allAllowed := true
|
||||
|
||||
// do not allow CollateralParams to be added or removed
|
||||
// this checks both lists are the same size, then below checks each incoming matches a current
|
||||
if len(incoming) != len(current) {
|
||||
return false
|
||||
}
|
||||
|
||||
// for each param struct, check it is allowed, and if it is not, check the value has not changed
|
||||
for _, incomingCP := range incoming {
|
||||
// 1) check incoming cp is in list of allowed cps
|
||||
var foundAllowedCP bool
|
||||
var allowedCP AllowedCollateralParam
|
||||
for _, p := range acps {
|
||||
if p.Type != incomingCP.Type {
|
||||
continue
|
||||
}
|
||||
foundAllowedCP = true
|
||||
allowedCP = p
|
||||
}
|
||||
if !foundAllowedCP {
|
||||
// incoming had a CollateralParam that wasn't in the list of allowed ones
|
||||
return false
|
||||
}
|
||||
|
||||
// 2) Check incoming changes are individually allowed
|
||||
// find existing CollateralParam
|
||||
var foundCurrentCP bool
|
||||
var currentCP cdptypes.CollateralParam
|
||||
for _, p := range current {
|
||||
if p.Denom != incomingCP.Denom {
|
||||
continue
|
||||
}
|
||||
foundCurrentCP = true
|
||||
currentCP = p
|
||||
}
|
||||
if !foundCurrentCP {
|
||||
return false // not allowed to add param to list
|
||||
}
|
||||
// check changed values are all allowed
|
||||
allowed := allowedCP.Allows(currentCP, incomingCP)
|
||||
|
||||
allAllowed = allAllowed && allowed
|
||||
}
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
func (acp AllowedCollateralParam) Allows(current, incoming cdptypes.CollateralParam) bool {
|
||||
allowed := ((acp.Type == current.Type) && (acp.Type == incoming.Type)) && // require collatreral types to be all equal
|
||||
(current.Denom == incoming.Denom || acp.Denom) &&
|
||||
(current.LiquidationRatio.Equal(incoming.LiquidationRatio) || acp.LiquidationRatio) &&
|
||||
(current.DebtLimit.IsEqual(incoming.DebtLimit) || acp.DebtLimit) &&
|
||||
(current.StabilityFee.Equal(incoming.StabilityFee) || acp.StabilityFee) &&
|
||||
(current.AuctionSize.Equal(incoming.AuctionSize) || acp.AuctionSize) &&
|
||||
(current.LiquidationPenalty.Equal(incoming.LiquidationPenalty) || acp.LiquidationPenalty) &&
|
||||
((current.Prefix == incoming.Prefix) || acp.Prefix) &&
|
||||
((current.SpotMarketID == incoming.SpotMarketID) || acp.SpotMarketID) &&
|
||||
((current.LiquidationMarketID == incoming.LiquidationMarketID) || acp.LiquidationMarketID) &&
|
||||
(current.ConversionFactor.Equal(incoming.ConversionFactor) || acp.ConversionFactor)
|
||||
return allowed
|
||||
}
|
||||
|
||||
type AllowedDebtParam struct {
|
||||
Denom bool `json:"denom" yaml:"denom"`
|
||||
ReferenceAsset bool `json:"reference_asset" yaml:"reference_asset"`
|
||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
DebtFloor bool `json:"debt_floor" yaml:"debt_floor"`
|
||||
SavingsRate bool `json:"savings_rate" yaml:"savings_rate"`
|
||||
}
|
||||
|
||||
func (adp AllowedDebtParam) Allows(current, incoming cdptypes.DebtParam) bool {
|
||||
allowed := ((current.Denom == incoming.Denom) || adp.Denom) &&
|
||||
((current.ReferenceAsset == incoming.ReferenceAsset) || adp.ReferenceAsset) &&
|
||||
(current.ConversionFactor.Equal(incoming.ConversionFactor) || adp.ConversionFactor) &&
|
||||
(current.DebtFloor.Equal(incoming.DebtFloor) || adp.DebtFloor) &&
|
||||
(current.SavingsRate.Equal(incoming.SavingsRate) || adp.SavingsRate)
|
||||
return allowed
|
||||
}
|
||||
|
||||
type AllowedAssetParams []AllowedAssetParam
|
||||
|
||||
func (aaps AllowedAssetParams) Allows(current, incoming bep3types.AssetParams) bool {
|
||||
allAllowed := true
|
||||
|
||||
// do not allow AssetParams to be added or removed
|
||||
// this checks both lists are the same size, then below checks each incoming matches a current
|
||||
if len(incoming) != len(current) {
|
||||
return false
|
||||
}
|
||||
|
||||
// for each asset struct, check it is allowed, and if it is not, check the value has not changed
|
||||
for _, incomingAP := range incoming {
|
||||
// 1) check incoming ap is in list of allowed aps
|
||||
var foundAllowedAP bool
|
||||
var allowedAP AllowedAssetParam
|
||||
for _, p := range aaps {
|
||||
if p.Denom != incomingAP.Denom {
|
||||
continue
|
||||
}
|
||||
foundAllowedAP = true
|
||||
allowedAP = p
|
||||
}
|
||||
if !foundAllowedAP {
|
||||
// incoming had a AssetParam that wasn't in the list of allowed ones
|
||||
return false
|
||||
}
|
||||
|
||||
// 2) Check incoming changes are individually allowed
|
||||
// find existing SupportedAsset
|
||||
var foundCurrentAP bool
|
||||
var currentAP bep3types.AssetParam
|
||||
for _, p := range current {
|
||||
if p.Denom != incomingAP.Denom {
|
||||
continue
|
||||
}
|
||||
foundCurrentAP = true
|
||||
currentAP = p
|
||||
}
|
||||
if !foundCurrentAP {
|
||||
return false // not allowed to add asset to list
|
||||
}
|
||||
// check changed values are all allowed
|
||||
allowed := allowedAP.Allows(currentAP, incomingAP)
|
||||
|
||||
allAllowed = allAllowed && allowed
|
||||
}
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
type AllowedAssetParam struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
CoinID bool `json:"coin_id" yaml:"coin_id"`
|
||||
Limit bool `json:"limit" yaml:"limit"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
}
|
||||
|
||||
func (aap AllowedAssetParam) Allows(current, incoming bep3types.AssetParam) bool {
|
||||
|
||||
allowed := ((aap.Denom == current.Denom) && (aap.Denom == incoming.Denom)) && // require denoms to be all equal
|
||||
((current.CoinID == incoming.CoinID) || aap.CoinID) &&
|
||||
(current.SupplyLimit.Equals(incoming.SupplyLimit) || aap.Limit) &&
|
||||
((current.Active == incoming.Active) || aap.Active)
|
||||
return allowed
|
||||
}
|
||||
|
||||
type AllowedMarkets []AllowedMarket
|
||||
|
||||
func (ams AllowedMarkets) Allows(current, incoming pricefeedtypes.Markets) bool {
|
||||
allAllowed := true
|
||||
|
||||
// do not allow Markets to be added or removed
|
||||
// this checks both lists are the same size, then below checks each incoming matches a current
|
||||
if len(incoming) != len(current) {
|
||||
return false
|
||||
}
|
||||
|
||||
// for each market struct, check it is allowed, and if it is not, check the value has not changed
|
||||
for _, incomingM := range incoming {
|
||||
// 1) check incoming market is in list of allowed markets
|
||||
var foundAllowedM bool
|
||||
var allowedM AllowedMarket
|
||||
for _, p := range ams {
|
||||
if p.MarketID != incomingM.MarketID {
|
||||
continue
|
||||
}
|
||||
foundAllowedM = true
|
||||
allowedM = p
|
||||
}
|
||||
if !foundAllowedM {
|
||||
// incoming had a Market that wasn't in the list of allowed ones
|
||||
return false
|
||||
}
|
||||
|
||||
// 2) Check incoming changes are individually allowed
|
||||
// find existing SupportedAsset
|
||||
var foundCurrentM bool
|
||||
var currentM pricefeed.Market
|
||||
for _, p := range current {
|
||||
if p.MarketID != incomingM.MarketID {
|
||||
continue
|
||||
}
|
||||
foundCurrentM = true
|
||||
currentM = p
|
||||
}
|
||||
if !foundCurrentM {
|
||||
return false // not allowed to add market to list
|
||||
}
|
||||
// check changed values are all allowed
|
||||
allowed := allowedM.Allows(currentM, incomingM)
|
||||
|
||||
allAllowed = allAllowed && allowed
|
||||
}
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
type AllowedMarket struct {
|
||||
MarketID string `json:"market_id" yaml:"market_id"`
|
||||
BaseAsset bool `json:"base_asset" yaml:"base_asset"`
|
||||
QuoteAsset bool `json:"quote_asset" yaml:"quote_asset"`
|
||||
Oracles bool `json:"oracles" yaml:"oracles"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
}
|
||||
|
||||
func (am AllowedMarket) Allows(current, incoming pricefeedtypes.Market) bool {
|
||||
allowed := ((am.MarketID == current.MarketID) && (am.MarketID == incoming.MarketID)) && // require denoms to be all equal
|
||||
((current.BaseAsset == incoming.BaseAsset) || am.BaseAsset) &&
|
||||
((current.QuoteAsset == incoming.QuoteAsset) || am.QuoteAsset) &&
|
||||
(addressesEqual(current.Oracles, incoming.Oracles) || am.Oracles) &&
|
||||
((current.Active == incoming.Active) || am.Active)
|
||||
return allowed
|
||||
}
|
||||
|
||||
// addressesEqual check if slices of addresses are equal, the order matters
|
||||
func addressesEqual(addrs1, addrs2 []sdk.AccAddress) bool {
|
||||
if len(addrs1) != len(addrs2) {
|
||||
return false
|
||||
}
|
||||
areEqual := true
|
||||
for i := range addrs1 {
|
||||
areEqual = areEqual && addrs1[i].Equals(addrs2[i])
|
||||
}
|
||||
return areEqual
|
||||
}
|
||||
|
||||
// DefaultNextProposalID is the starting poiint for proposal IDs.
|
||||
const DefaultNextProposalID uint64 = 1
|
||||
|
||||
// GenesisState is state that must be provided at chain genesis.
|
||||
type GenesisState struct {
|
||||
NextProposalID uint64 `json:"next_proposal_id" yaml:"next_proposal_id"`
|
||||
Committees []Committee `json:"committees" yaml:"committees"`
|
||||
Proposals []Proposal `json:"proposals" yaml:"proposals"`
|
||||
Votes []Vote `json:"votes" yaml:"votes"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state object for the module.
|
||||
func NewGenesisState(nextProposalID uint64, committees []Committee, proposals []Proposal, votes []Vote) GenesisState {
|
||||
return GenesisState{
|
||||
NextProposalID: nextProposalID,
|
||||
Committees: committees,
|
||||
Proposals: proposals,
|
||||
Votes: votes,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultGenesisState returns the default genesis state for the module.
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return NewGenesisState(
|
||||
DefaultNextProposalID,
|
||||
[]Committee{},
|
||||
[]Proposal{},
|
||||
[]Vote{},
|
||||
)
|
||||
}
|
||||
|
||||
// Validate performs basic validation of genesis data.
|
||||
func (gs GenesisState) Validate() error {
|
||||
// validate committees
|
||||
committeeMap := make(map[uint64]bool, len(gs.Committees))
|
||||
for _, com := range gs.Committees {
|
||||
// check there are no duplicate IDs
|
||||
if _, ok := committeeMap[com.ID]; ok {
|
||||
return fmt.Errorf("duplicate committee ID found in genesis state; id: %d", com.ID)
|
||||
}
|
||||
committeeMap[com.ID] = true
|
||||
|
||||
// validate committee
|
||||
if err := com.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// validate proposals
|
||||
proposalMap := make(map[uint64]bool, len(gs.Proposals))
|
||||
for _, p := range gs.Proposals {
|
||||
// check there are no duplicate IDs
|
||||
if _, ok := proposalMap[p.ID]; ok {
|
||||
return fmt.Errorf("duplicate proposal ID found in genesis state; id: %d", p.ID)
|
||||
}
|
||||
proposalMap[p.ID] = true
|
||||
|
||||
// validate next proposal ID
|
||||
if p.ID >= gs.NextProposalID {
|
||||
return fmt.Errorf("NextProposalID is not greater than all proposal IDs; id: %d", p.ID)
|
||||
}
|
||||
|
||||
// check committee exists
|
||||
if !committeeMap[p.CommitteeID] {
|
||||
return fmt.Errorf("proposal refers to non existent committee; proposal: %+v", p)
|
||||
}
|
||||
|
||||
// validate pubProposal
|
||||
if err := p.PubProposal.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("proposal %d invalid: %w", p.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// validate votes
|
||||
for _, v := range gs.Votes {
|
||||
// validate committee
|
||||
if err := v.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check proposal exists
|
||||
if !proposalMap[v.ProposalID] {
|
||||
return fmt.Errorf("vote refers to non existent proposal; vote: %+v", v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
810
x/committee/legacy/v0_9/types.go
Normal file
810
x/committee/legacy/v0_9/types.go
Normal file
@ -0,0 +1,810 @@
|
||||
package v0_9
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types"
|
||||
upgrade "github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||
|
||||
bep3types "github.com/kava-labs/kava/x/bep3/types"
|
||||
cdptypes "github.com/kava-labs/kava/x/cdp/types"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types"
|
||||
)
|
||||
|
||||
const (
|
||||
MaxCommitteeDescriptionLength int = 512
|
||||
)
|
||||
|
||||
// Permission is anything with a method that validates whether a proposal is allowed by it or not.
|
||||
type Permission interface {
|
||||
Allows(sdk.Context, *codec.Codec, ParamKeeper, PubProposal) bool
|
||||
}
|
||||
|
||||
type ParamKeeper interface {
|
||||
GetSubspace(string) (params.Subspace, bool)
|
||||
}
|
||||
|
||||
// A Committee is a collection of addresses that are allowed to vote and enact any governance proposal that passes their permissions.
|
||||
type Committee struct {
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Members []sdk.AccAddress `json:"members" yaml:"members"`
|
||||
Permissions []Permission `json:"permissions" yaml:"permissions"`
|
||||
VoteThreshold sdk.Dec `json:"vote_threshold" yaml:"vote_threshold"` // Smallest percentage of members that must vote for a proposal to pass.
|
||||
ProposalDuration time.Duration `json:"proposal_duration" yaml:"proposal_duration"` // The length of time a proposal remains active for. Proposals will close earlier if they get enough votes.
|
||||
}
|
||||
|
||||
func NewCommittee(id uint64, description string, members []sdk.AccAddress, permissions []Permission, threshold sdk.Dec, duration time.Duration) Committee {
|
||||
return Committee{
|
||||
ID: id,
|
||||
Description: description,
|
||||
Members: members,
|
||||
Permissions: permissions,
|
||||
VoteThreshold: threshold,
|
||||
ProposalDuration: duration,
|
||||
}
|
||||
}
|
||||
|
||||
func (c Committee) HasMember(addr sdk.AccAddress) bool {
|
||||
for _, m := range c.Members {
|
||||
if m.Equals(addr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// HasPermissionsFor returns whether the committee is authorized to enact a proposal.
|
||||
// As long as one permission allows the proposal then it goes through. Its the OR of all permissions.
|
||||
func (c Committee) HasPermissionsFor(ctx sdk.Context, appCdc *codec.Codec, pk ParamKeeper, proposal PubProposal) bool {
|
||||
for _, p := range c.Permissions {
|
||||
if p.Allows(ctx, appCdc, pk, proposal) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c Committee) Validate() error {
|
||||
|
||||
addressMap := make(map[string]bool, len(c.Members))
|
||||
for _, m := range c.Members {
|
||||
// check there are no duplicate members
|
||||
if _, ok := addressMap[m.String()]; ok {
|
||||
return fmt.Errorf("committe cannot have duplicate members, %s", m)
|
||||
}
|
||||
// check for valid addresses
|
||||
if m.Empty() {
|
||||
return fmt.Errorf("committee cannot have empty member address")
|
||||
}
|
||||
addressMap[m.String()] = true
|
||||
}
|
||||
|
||||
if len(c.Members) == 0 {
|
||||
return fmt.Errorf("committee cannot have zero members")
|
||||
}
|
||||
|
||||
if len(c.Description) > MaxCommitteeDescriptionLength {
|
||||
return fmt.Errorf("description length %d longer than max allowed %d", len(c.Description), MaxCommitteeDescriptionLength)
|
||||
}
|
||||
|
||||
for _, p := range c.Permissions {
|
||||
if p == nil {
|
||||
return fmt.Errorf("committee cannot have a nil permission")
|
||||
}
|
||||
}
|
||||
|
||||
// threshold must be in the range (0,1]
|
||||
if c.VoteThreshold.IsNil() || c.VoteThreshold.LTE(sdk.ZeroDec()) || c.VoteThreshold.GT(sdk.NewDec(1)) {
|
||||
return fmt.Errorf("invalid threshold: %s", c.VoteThreshold)
|
||||
}
|
||||
|
||||
if c.ProposalDuration < 0 {
|
||||
return fmt.Errorf("invalid proposal duration: %s", c.ProposalDuration)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Proposals
|
||||
// ------------------------------------------
|
||||
|
||||
// PubProposal is the interface that all proposals must fulfill to be submitted to a committee.
|
||||
// Proposal types can be created external to this module. For example a ParamChangeProposal, or CommunityPoolSpendProposal.
|
||||
// It is pinned to the equivalent type in the gov module to create compatibility between proposal types.
|
||||
type PubProposal govtypes.Content
|
||||
|
||||
// Proposal is an internal record of a governance proposal submitted to a committee.
|
||||
type Proposal struct {
|
||||
PubProposal `json:"pub_proposal" yaml:"pub_proposal"`
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
CommitteeID uint64 `json:"committee_id" yaml:"committee_id"`
|
||||
Deadline time.Time `json:"deadline" yaml:"deadline"`
|
||||
}
|
||||
|
||||
func NewProposal(pubProposal PubProposal, id uint64, committeeID uint64, deadline time.Time) Proposal {
|
||||
return Proposal{
|
||||
PubProposal: pubProposal,
|
||||
ID: id,
|
||||
CommitteeID: committeeID,
|
||||
Deadline: deadline,
|
||||
}
|
||||
}
|
||||
|
||||
// HasExpiredBy calculates if the proposal will have expired by a certain time.
|
||||
// All votes must be cast before deadline, those cast at time == deadline are not valid
|
||||
func (p Proposal) HasExpiredBy(time time.Time) bool {
|
||||
return !time.Before(p.Deadline)
|
||||
}
|
||||
|
||||
// String implements the fmt.Stringer interface, and importantly overrides the String methods inherited from the embedded PubProposal type.
|
||||
func (p Proposal) String() string {
|
||||
bz, _ := yaml.Marshal(p)
|
||||
return string(bz)
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// Votes
|
||||
// ------------------------------------------
|
||||
|
||||
type Vote struct {
|
||||
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
|
||||
Voter sdk.AccAddress `json:"voter" yaml:"voter"`
|
||||
}
|
||||
|
||||
func NewVote(proposalID uint64, voter sdk.AccAddress) Vote {
|
||||
return Vote{
|
||||
ProposalID: proposalID,
|
||||
Voter: voter,
|
||||
}
|
||||
}
|
||||
|
||||
func (v Vote) Validate() error {
|
||||
if v.Voter.Empty() {
|
||||
return fmt.Errorf("voter address cannot be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// GodPermission
|
||||
// ------------------------------------------
|
||||
|
||||
// GodPermission allows any governance proposal. It is used mainly for testing.
|
||||
type GodPermission struct{}
|
||||
|
||||
var _ Permission = GodPermission{}
|
||||
|
||||
func (GodPermission) Allows(sdk.Context, *codec.Codec, ParamKeeper, PubProposal) bool { return true }
|
||||
|
||||
func (GodPermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
}{
|
||||
Type: "god_permission",
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// SimpleParamChangePermission
|
||||
// ------------------------------------------
|
||||
|
||||
// SimpleParamChangePermission only allows changes to certain params
|
||||
type SimpleParamChangePermission struct {
|
||||
AllowedParams AllowedParams `json:"allowed_params" yaml:"allowed_params"`
|
||||
}
|
||||
|
||||
var _ Permission = SimpleParamChangePermission{}
|
||||
|
||||
func (perm SimpleParamChangePermission) Allows(_ sdk.Context, _ *codec.Codec, _ ParamKeeper, p PubProposal) bool {
|
||||
proposal, ok := p.(paramstypes.ParameterChangeProposal)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
for _, change := range proposal.Changes {
|
||||
if !perm.AllowedParams.Contains(change) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (perm SimpleParamChangePermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
AllowedParams AllowedParams `yaml:"allowed_params"`
|
||||
}{
|
||||
Type: "param_change_permission",
|
||||
AllowedParams: perm.AllowedParams,
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
type AllowedParam struct {
|
||||
Subspace string `json:"subspace" yaml:"subspace"`
|
||||
Key string `json:"key" yaml:"key"`
|
||||
}
|
||||
type AllowedParams []AllowedParam
|
||||
|
||||
func (allowed AllowedParams) Contains(paramChange paramstypes.ParamChange) bool {
|
||||
for _, p := range allowed {
|
||||
if paramChange.Subspace == p.Subspace && paramChange.Key == p.Key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// TextPermission
|
||||
// ------------------------------------------
|
||||
|
||||
// TextPermission allows any text governance proposal.
|
||||
type TextPermission struct{}
|
||||
|
||||
var _ Permission = TextPermission{}
|
||||
|
||||
func (TextPermission) Allows(_ sdk.Context, _ *codec.Codec, _ ParamKeeper, p PubProposal) bool {
|
||||
_, ok := p.(govtypes.TextProposal)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (TextPermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
}{
|
||||
Type: "text_permission",
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// SoftwareUpgradePermission
|
||||
// ------------------------------------------
|
||||
|
||||
type SoftwareUpgradePermission struct{}
|
||||
|
||||
var _ Permission = SoftwareUpgradePermission{}
|
||||
|
||||
func (SoftwareUpgradePermission) Allows(_ sdk.Context, _ *codec.Codec, _ ParamKeeper, p PubProposal) bool {
|
||||
_, ok := p.(upgrade.SoftwareUpgradeProposal)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (SoftwareUpgradePermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
}{
|
||||
Type: "software_upgrade_permission",
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
// ------------------------------------------
|
||||
// SubParamChangePermission
|
||||
// ------------------------------------------
|
||||
|
||||
// ParamChangeProposal only allows changes to certain params
|
||||
type SubParamChangePermission struct {
|
||||
AllowedParams AllowedParams `json:"allowed_params" yaml:"allowed_params"`
|
||||
AllowedCollateralParams AllowedCollateralParams `json:"allowed_collateral_params" yaml:"allowed_collateral_params"`
|
||||
AllowedDebtParam AllowedDebtParam `json:"allowed_debt_param" yaml:"allowed_debt_param"`
|
||||
AllowedAssetParams AllowedAssetParams `json:"allowed_asset_params" yaml:"allowed_asset_params"`
|
||||
AllowedMarkets AllowedMarkets `json:"allowed_markets" yaml:"allowed_markets"`
|
||||
}
|
||||
|
||||
var _ Permission = SubParamChangePermission{}
|
||||
|
||||
func (perm SubParamChangePermission) MarshalYAML() (interface{}, error) {
|
||||
valueToMarshal := struct {
|
||||
Type string `yaml:"type"`
|
||||
AllowedParams AllowedParams `yaml:"allowed_params"`
|
||||
AllowedCollateralParams AllowedCollateralParams `yaml:"allowed_collateral_params"`
|
||||
AllowedDebtParam AllowedDebtParam `yaml:"allowed_debt_param"`
|
||||
AllowedAssetParams AllowedAssetParams `yaml:"allowed_asset_params"`
|
||||
AllowedMarkets AllowedMarkets `yaml:"allowed_markets"`
|
||||
}{
|
||||
Type: "param_change_permission",
|
||||
AllowedParams: perm.AllowedParams,
|
||||
AllowedCollateralParams: perm.AllowedCollateralParams,
|
||||
AllowedDebtParam: perm.AllowedDebtParam,
|
||||
AllowedAssetParams: perm.AllowedAssetParams,
|
||||
AllowedMarkets: perm.AllowedMarkets,
|
||||
}
|
||||
return valueToMarshal, nil
|
||||
}
|
||||
|
||||
func (perm SubParamChangePermission) Allows(ctx sdk.Context, appCdc *codec.Codec, pk ParamKeeper, p PubProposal) bool {
|
||||
// Check pubproposal has correct type
|
||||
proposal, ok := p.(paramstypes.ParameterChangeProposal)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
// Check the param changes match the allowed keys
|
||||
for _, change := range proposal.Changes {
|
||||
if !perm.AllowedParams.Contains(change) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Check any CollateralParam changes are allowed
|
||||
|
||||
// Get the incoming CollaterParams value
|
||||
var foundIncomingCP bool
|
||||
var incomingCP cdptypes.CollateralParams
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == cdptypes.ModuleName && change.Key == string(cdptypes.KeyCollateralParams)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingCP = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingCP); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingCP {
|
||||
// Get the current value of the CollateralParams
|
||||
cdpSubspace, found := pk.GetSubspace(cdptypes.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentCP cdptypes.CollateralParams
|
||||
cdpSubspace.Get(ctx, cdptypes.KeyCollateralParams, ¤tCP) // panics if something goes wrong
|
||||
|
||||
// Check all the incoming changes in the CollateralParams are allowed
|
||||
collateralParamChangesAllowed := perm.AllowedCollateralParams.Allows(currentCP, incomingCP)
|
||||
if !collateralParamChangesAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check any DebtParam changes are allowed
|
||||
|
||||
// Get the incoming DebtParam value
|
||||
var foundIncomingDP bool
|
||||
var incomingDP cdptypes.DebtParam
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == cdptypes.ModuleName && change.Key == string(cdptypes.KeyDebtParam)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingDP = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingDP); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingDP {
|
||||
// Get the current value of the DebtParams
|
||||
cdpSubspace, found := pk.GetSubspace(cdptypes.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentDP cdptypes.DebtParam
|
||||
cdpSubspace.Get(ctx, cdptypes.KeyDebtParam, ¤tDP) // panics if something goes wrong
|
||||
|
||||
// Check the incoming changes in the DebtParam are allowed
|
||||
debtParamChangeAllowed := perm.AllowedDebtParam.Allows(currentDP, incomingDP)
|
||||
if !debtParamChangeAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check any AssetParams changes are allowed
|
||||
|
||||
// Get the incoming AssetParams value
|
||||
var foundIncomingAPs bool
|
||||
var incomingAPs bep3types.AssetParams
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == bep3types.ModuleName && change.Key == string(bep3types.KeyAssetParams)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingAPs = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingAPs); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingAPs {
|
||||
// Get the current value of the SupportedAssets
|
||||
subspace, found := pk.GetSubspace(bep3types.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentAPs bep3types.AssetParams
|
||||
subspace.Get(ctx, bep3types.KeyAssetParams, ¤tAPs) // panics if something goes wrong
|
||||
|
||||
// Check all the incoming changes in the CollateralParams are allowed
|
||||
assetParamsChangesAllowed := perm.AllowedAssetParams.Allows(currentAPs, incomingAPs)
|
||||
if !assetParamsChangesAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check any Markets changes are allowed
|
||||
|
||||
// Get the incoming Markets value
|
||||
var foundIncomingMs bool
|
||||
var incomingMs pricefeedtypes.Markets
|
||||
for _, change := range proposal.Changes {
|
||||
if !(change.Subspace == pricefeedtypes.ModuleName && change.Key == string(pricefeedtypes.KeyMarkets)) {
|
||||
continue
|
||||
}
|
||||
// note: in case of duplicates take the last value
|
||||
foundIncomingMs = true
|
||||
if err := appCdc.UnmarshalJSON([]byte(change.Value), &incomingMs); err != nil {
|
||||
return false // invalid json value, so just disallow
|
||||
}
|
||||
}
|
||||
// only check if there was a proposed change
|
||||
if foundIncomingMs {
|
||||
// Get the current value of the Markets
|
||||
subspace, found := pk.GetSubspace(pricefeedtypes.ModuleName)
|
||||
if !found {
|
||||
return false // not using a panic to help avoid begin blocker panics
|
||||
}
|
||||
var currentMs pricefeedtypes.Markets
|
||||
subspace.Get(ctx, pricefeedtypes.KeyMarkets, ¤tMs) // panics if something goes wrong
|
||||
|
||||
// Check all the incoming changes in the Markets are allowed
|
||||
marketsChangesAllowed := perm.AllowedMarkets.Allows(currentMs, incomingMs)
|
||||
if !marketsChangesAllowed {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type AllowedCollateralParams []AllowedCollateralParam
|
||||
|
||||
func (acps AllowedCollateralParams) Allows(current, incoming cdptypes.CollateralParams) bool {
|
||||
allAllowed := true
|
||||
|
||||
// do not allow CollateralParams to be added or removed
|
||||
// this checks both lists are the same size, then below checks each incoming matches a current
|
||||
if len(incoming) != len(current) {
|
||||
return false
|
||||
}
|
||||
|
||||
// for each param struct, check it is allowed, and if it is not, check the value has not changed
|
||||
for _, incomingCP := range incoming {
|
||||
// 1) check incoming cp is in list of allowed cps
|
||||
var foundAllowedCP bool
|
||||
var allowedCP AllowedCollateralParam
|
||||
for _, p := range acps {
|
||||
if p.Denom != incomingCP.Denom {
|
||||
continue
|
||||
}
|
||||
foundAllowedCP = true
|
||||
allowedCP = p
|
||||
}
|
||||
if !foundAllowedCP {
|
||||
// incoming had a CollateralParam that wasn't in the list of allowed ones
|
||||
return false
|
||||
}
|
||||
|
||||
// 2) Check incoming changes are individually allowed
|
||||
// find existing CollateralParam
|
||||
var foundCurrentCP bool
|
||||
var currentCP cdptypes.CollateralParam
|
||||
for _, p := range current {
|
||||
if p.Denom != incomingCP.Denom {
|
||||
continue
|
||||
}
|
||||
foundCurrentCP = true
|
||||
currentCP = p
|
||||
}
|
||||
if !foundCurrentCP {
|
||||
return false // not allowed to add param to list
|
||||
}
|
||||
// check changed values are all allowed
|
||||
allowed := allowedCP.Allows(currentCP, incomingCP)
|
||||
|
||||
allAllowed = allAllowed && allowed
|
||||
}
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
type AllowedCollateralParam struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
LiquidationRatio bool `json:"liquidation_ratio" yaml:"liquidation_ratio"`
|
||||
DebtLimit bool `json:"debt_limit" yaml:"debt_limit"`
|
||||
StabilityFee bool `json:"stability_fee" yaml:"stability_fee"`
|
||||
AuctionSize bool `json:"auction_size" yaml:"auction_size"`
|
||||
LiquidationPenalty bool `json:"liquidation_penalty" yaml:"liquidation_penalty"`
|
||||
Prefix bool `json:"prefix" yaml:"prefix"`
|
||||
SpotMarketID bool `json:"spot_market_id" yaml:"spot_market_id"`
|
||||
LiquidationMarketID bool `json:"liquidation_market_id" yaml:"liquidation_market_id"`
|
||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
}
|
||||
|
||||
func (acp AllowedCollateralParam) Allows(current, incoming cdptypes.CollateralParam) bool {
|
||||
allowed := ((acp.Denom == current.Denom) && (acp.Denom == incoming.Denom)) && // require denoms to be all equal
|
||||
(current.LiquidationRatio.Equal(incoming.LiquidationRatio) || acp.LiquidationRatio) &&
|
||||
(current.DebtLimit.IsEqual(incoming.DebtLimit) || acp.DebtLimit) &&
|
||||
(current.StabilityFee.Equal(incoming.StabilityFee) || acp.StabilityFee) &&
|
||||
(current.AuctionSize.Equal(incoming.AuctionSize) || acp.AuctionSize) &&
|
||||
(current.LiquidationPenalty.Equal(incoming.LiquidationPenalty) || acp.LiquidationPenalty) &&
|
||||
((current.Prefix == incoming.Prefix) || acp.Prefix) &&
|
||||
((current.SpotMarketID == incoming.SpotMarketID) || acp.SpotMarketID) &&
|
||||
((current.LiquidationMarketID == incoming.LiquidationMarketID) || acp.LiquidationMarketID) &&
|
||||
(current.ConversionFactor.Equal(incoming.ConversionFactor) || acp.ConversionFactor)
|
||||
return allowed
|
||||
}
|
||||
|
||||
type AllowedDebtParam struct {
|
||||
Denom bool `json:"denom" yaml:"denom"`
|
||||
ReferenceAsset bool `json:"reference_asset" yaml:"reference_asset"`
|
||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
DebtFloor bool `json:"debt_floor" yaml:"debt_floor"`
|
||||
SavingsRate bool `json:"savings_rate" yaml:"savings_rate"`
|
||||
}
|
||||
|
||||
func (adp AllowedDebtParam) Allows(current, incoming cdptypes.DebtParam) bool {
|
||||
allowed := ((current.Denom == incoming.Denom) || adp.Denom) &&
|
||||
((current.ReferenceAsset == incoming.ReferenceAsset) || adp.ReferenceAsset) &&
|
||||
(current.ConversionFactor.Equal(incoming.ConversionFactor) || adp.ConversionFactor) &&
|
||||
(current.DebtFloor.Equal(incoming.DebtFloor) || adp.DebtFloor) &&
|
||||
(current.SavingsRate.Equal(incoming.SavingsRate) || adp.SavingsRate)
|
||||
return allowed
|
||||
}
|
||||
|
||||
type AllowedAssetParams []AllowedAssetParam
|
||||
|
||||
func (aaps AllowedAssetParams) Allows(current, incoming bep3types.AssetParams) bool {
|
||||
allAllowed := true
|
||||
|
||||
// do not allow AssetParams to be added or removed
|
||||
// this checks both lists are the same size, then below checks each incoming matches a current
|
||||
if len(incoming) != len(current) {
|
||||
return false
|
||||
}
|
||||
|
||||
// for each asset struct, check it is allowed, and if it is not, check the value has not changed
|
||||
for _, incomingAP := range incoming {
|
||||
// 1) check incoming ap is in list of allowed aps
|
||||
var foundAllowedAP bool
|
||||
var allowedAP AllowedAssetParam
|
||||
for _, p := range aaps {
|
||||
if p.Denom != incomingAP.Denom {
|
||||
continue
|
||||
}
|
||||
foundAllowedAP = true
|
||||
allowedAP = p
|
||||
}
|
||||
if !foundAllowedAP {
|
||||
// incoming had a AssetParam that wasn't in the list of allowed ones
|
||||
return false
|
||||
}
|
||||
|
||||
// 2) Check incoming changes are individually allowed
|
||||
// find existing SupportedAsset
|
||||
var foundCurrentAP bool
|
||||
var currentAP bep3types.AssetParam
|
||||
for _, p := range current {
|
||||
if p.Denom != incomingAP.Denom {
|
||||
continue
|
||||
}
|
||||
foundCurrentAP = true
|
||||
currentAP = p
|
||||
}
|
||||
if !foundCurrentAP {
|
||||
return false // not allowed to add asset to list
|
||||
}
|
||||
// check changed values are all allowed
|
||||
allowed := allowedAP.Allows(currentAP, incomingAP)
|
||||
|
||||
allAllowed = allAllowed && allowed
|
||||
}
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
type AllowedAssetParam struct {
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
CoinID bool `json:"coin_id" yaml:"coin_id"`
|
||||
Limit bool `json:"limit" yaml:"limit"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
}
|
||||
|
||||
func (aap AllowedAssetParam) Allows(current, incoming bep3types.AssetParam) bool {
|
||||
|
||||
allowed := ((aap.Denom == current.Denom) && (aap.Denom == incoming.Denom)) && // require denoms to be all equal
|
||||
((current.CoinID == incoming.CoinID) || aap.CoinID) &&
|
||||
(current.SupplyLimit.Equals(incoming.SupplyLimit) || aap.Limit) &&
|
||||
((current.Active == incoming.Active) || aap.Active)
|
||||
return allowed
|
||||
}
|
||||
|
||||
type AllowedMarkets []AllowedMarket
|
||||
|
||||
func (ams AllowedMarkets) Allows(current, incoming pricefeedtypes.Markets) bool {
|
||||
allAllowed := true
|
||||
|
||||
// do not allow Markets to be added or removed
|
||||
// this checks both lists are the same size, then below checks each incoming matches a current
|
||||
if len(incoming) != len(current) {
|
||||
return false
|
||||
}
|
||||
|
||||
// for each market struct, check it is allowed, and if it is not, check the value has not changed
|
||||
for _, incomingM := range incoming {
|
||||
// 1) check incoming market is in list of allowed markets
|
||||
var foundAllowedM bool
|
||||
var allowedM AllowedMarket
|
||||
for _, p := range ams {
|
||||
if p.MarketID != incomingM.MarketID {
|
||||
continue
|
||||
}
|
||||
foundAllowedM = true
|
||||
allowedM = p
|
||||
}
|
||||
if !foundAllowedM {
|
||||
// incoming had a Market that wasn't in the list of allowed ones
|
||||
return false
|
||||
}
|
||||
|
||||
// 2) Check incoming changes are individually allowed
|
||||
// find existing SupportedAsset
|
||||
var foundCurrentM bool
|
||||
var currentM pricefeed.Market
|
||||
for _, p := range current {
|
||||
if p.MarketID != incomingM.MarketID {
|
||||
continue
|
||||
}
|
||||
foundCurrentM = true
|
||||
currentM = p
|
||||
}
|
||||
if !foundCurrentM {
|
||||
return false // not allowed to add market to list
|
||||
}
|
||||
// check changed values are all allowed
|
||||
allowed := allowedM.Allows(currentM, incomingM)
|
||||
|
||||
allAllowed = allAllowed && allowed
|
||||
}
|
||||
return allAllowed
|
||||
}
|
||||
|
||||
type AllowedMarket struct {
|
||||
MarketID string `json:"market_id" yaml:"market_id"`
|
||||
BaseAsset bool `json:"base_asset" yaml:"base_asset"`
|
||||
QuoteAsset bool `json:"quote_asset" yaml:"quote_asset"`
|
||||
Oracles bool `json:"oracles" yaml:"oracles"`
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
}
|
||||
|
||||
func (am AllowedMarket) Allows(current, incoming pricefeedtypes.Market) bool {
|
||||
allowed := ((am.MarketID == current.MarketID) && (am.MarketID == incoming.MarketID)) && // require denoms to be all equal
|
||||
((current.BaseAsset == incoming.BaseAsset) || am.BaseAsset) &&
|
||||
((current.QuoteAsset == incoming.QuoteAsset) || am.QuoteAsset) &&
|
||||
(addressesEqual(current.Oracles, incoming.Oracles) || am.Oracles) &&
|
||||
((current.Active == incoming.Active) || am.Active)
|
||||
return allowed
|
||||
}
|
||||
|
||||
// addressesEqual check if slices of addresses are equal, the order matters
|
||||
func addressesEqual(addrs1, addrs2 []sdk.AccAddress) bool {
|
||||
if len(addrs1) != len(addrs2) {
|
||||
return false
|
||||
}
|
||||
areEqual := true
|
||||
for i := range addrs1 {
|
||||
areEqual = areEqual && addrs1[i].Equals(addrs2[i])
|
||||
}
|
||||
return areEqual
|
||||
}
|
||||
|
||||
// DefaultNextProposalID is the starting poiint for proposal IDs.
|
||||
const DefaultNextProposalID uint64 = 1
|
||||
|
||||
// GenesisState is state that must be provided at chain genesis.
|
||||
type GenesisState struct {
|
||||
NextProposalID uint64 `json:"next_proposal_id" yaml:"next_proposal_id"`
|
||||
Committees []Committee `json:"committees" yaml:"committees"`
|
||||
Proposals []Proposal `json:"proposals" yaml:"proposals"`
|
||||
Votes []Vote `json:"votes" yaml:"votes"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state object for the module.
|
||||
func NewGenesisState(nextProposalID uint64, committees []Committee, proposals []Proposal, votes []Vote) GenesisState {
|
||||
return GenesisState{
|
||||
NextProposalID: nextProposalID,
|
||||
Committees: committees,
|
||||
Proposals: proposals,
|
||||
Votes: votes,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultGenesisState returns the default genesis state for the module.
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return NewGenesisState(
|
||||
DefaultNextProposalID,
|
||||
[]Committee{},
|
||||
[]Proposal{},
|
||||
[]Vote{},
|
||||
)
|
||||
}
|
||||
|
||||
// Validate performs basic validation of genesis data.
|
||||
func (gs GenesisState) Validate() error {
|
||||
// validate committees
|
||||
committeeMap := make(map[uint64]bool, len(gs.Committees))
|
||||
for _, com := range gs.Committees {
|
||||
// check there are no duplicate IDs
|
||||
if _, ok := committeeMap[com.ID]; ok {
|
||||
return fmt.Errorf("duplicate committee ID found in genesis state; id: %d", com.ID)
|
||||
}
|
||||
committeeMap[com.ID] = true
|
||||
|
||||
// validate committee
|
||||
if err := com.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// validate proposals
|
||||
proposalMap := make(map[uint64]bool, len(gs.Proposals))
|
||||
for _, p := range gs.Proposals {
|
||||
// check there are no duplicate IDs
|
||||
if _, ok := proposalMap[p.ID]; ok {
|
||||
return fmt.Errorf("duplicate proposal ID found in genesis state; id: %d", p.ID)
|
||||
}
|
||||
proposalMap[p.ID] = true
|
||||
|
||||
// validate next proposal ID
|
||||
if p.ID >= gs.NextProposalID {
|
||||
return fmt.Errorf("NextProposalID is not greater than all proposal IDs; id: %d", p.ID)
|
||||
}
|
||||
|
||||
// check committee exists
|
||||
if !committeeMap[p.CommitteeID] {
|
||||
return fmt.Errorf("proposal refers to non existent committee; proposal: %+v", p)
|
||||
}
|
||||
|
||||
// validate pubProposal
|
||||
if err := p.PubProposal.ValidateBasic(); err != nil {
|
||||
return fmt.Errorf("proposal %d invalid: %w", p.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// validate votes
|
||||
for _, v := range gs.Votes {
|
||||
// validate committee
|
||||
if err := v.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check proposal exists
|
||||
if !proposalMap[v.ProposalID] {
|
||||
return fmt.Errorf("vote refers to non existent proposal; vote: %+v", v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterCodec registers the necessary types for the module
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
|
||||
// Proposals
|
||||
cdc.RegisterInterface((*PubProposal)(nil), nil)
|
||||
|
||||
// Permissions
|
||||
cdc.RegisterInterface((*Permission)(nil), nil)
|
||||
cdc.RegisterConcrete(GodPermission{}, "kava/GodPermission", nil)
|
||||
cdc.RegisterConcrete(SimpleParamChangePermission{}, "kava/SimpleParamChangePermission", nil)
|
||||
cdc.RegisterConcrete(TextPermission{}, "kava/TextPermission", nil)
|
||||
cdc.RegisterConcrete(SoftwareUpgradePermission{}, "kava/SoftwareUpgradePermission", nil)
|
||||
cdc.RegisterConcrete(SubParamChangePermission{}, "kava/SubParamChangePermission", nil)
|
||||
}
|
@ -385,6 +385,26 @@ type AllowedCollateralParam struct {
|
||||
ConversionFactor bool `json:"conversion_factor" yaml:"conversion_factor"`
|
||||
}
|
||||
|
||||
// NewAllowedCollateralParam return a new AllowedCollateralParam
|
||||
func NewAllowedCollateralParam(
|
||||
ctype string, denom, liqRatio, debtLimit,
|
||||
stabilityFee, auctionSize, liquidationPenalty,
|
||||
prefix, spotMarket, liquidationMarket, conversionFactor bool) AllowedCollateralParam {
|
||||
return AllowedCollateralParam{
|
||||
Type: ctype,
|
||||
Denom: denom,
|
||||
LiquidationRatio: liqRatio,
|
||||
DebtLimit: debtLimit,
|
||||
StabilityFee: stabilityFee,
|
||||
AuctionSize: auctionSize,
|
||||
LiquidationPenalty: liquidationPenalty,
|
||||
Prefix: prefix,
|
||||
SpotMarketID: spotMarket,
|
||||
LiquidationMarketID: liquidationMarket,
|
||||
ConversionFactor: conversionFactor,
|
||||
}
|
||||
}
|
||||
|
||||
func (acp AllowedCollateralParam) Allows(current, incoming cdptypes.CollateralParam) bool {
|
||||
allowed := ((acp.Type == current.Type) && (acp.Type == incoming.Type)) && // require collateral types to be all equal
|
||||
(current.Denom == incoming.Denom || acp.Denom) &&
|
||||
|
Loading…
Reference in New Issue
Block a user