mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-26 08:15:19 +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_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
||||||
v0_11cdp "github.com/kava-labs/kava/x/cdp"
|
v0_11cdp "github.com/kava-labs/kava/x/cdp"
|
||||||
v0_9cdp "github.com/kava-labs/kava/x/cdp/legacy/v0_9"
|
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_11harvest "github.com/kava-labs/kava/x/harvest"
|
||||||
v0_11incentive "github.com/kava-labs/kava/x/incentive"
|
v0_11incentive "github.com/kava-labs/kava/x/incentive"
|
||||||
v0_9incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_9"
|
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
|
// 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 {
|
func MigrateAuth(oldGenState v38_5auth.GenesisState) v39_1auth.GenesisState {
|
||||||
var newAccounts v39_1authexported.GenesisAccounts
|
var newAccounts v39_1authexported.GenesisAccounts
|
||||||
|
@ -6,9 +6,9 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
|
||||||
v39_1auth "github.com/cosmos/cosmos-sdk/x/auth"
|
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"
|
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_9bep3 "github.com/kava-labs/kava/x/bep3/legacy/v0_9"
|
||||||
v0_9cdp "github.com/kava-labs/kava/x/cdp/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_9incentive "github.com/kava-labs/kava/x/incentive/legacy/v0_9"
|
||||||
v0_9pricefeed "github.com/kava-labs/kava/x/pricefeed/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"
|
v0_11validator_vesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||||
@ -48,6 +49,22 @@ func TestMigrateBep3(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
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) {
|
func TestMigrateAuth(t *testing.T) {
|
||||||
bz, err := ioutil.ReadFile(filepath.Join("testdata", "auth-v09.json"))
|
bz, err := ioutil.ReadFile(filepath.Join("testdata", "auth-v09.json"))
|
||||||
require.NoError(t, err)
|
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
|
QuerierRoute = types.QuerierRoute
|
||||||
QueryCommittee = types.QueryCommittee
|
QueryCommittee = types.QueryCommittee
|
||||||
QueryCommittees = types.QueryCommittees
|
QueryCommittees = types.QueryCommittees
|
||||||
|
QueryNextProposalID = types.QueryNextProposalID
|
||||||
QueryProposal = types.QueryProposal
|
QueryProposal = types.QueryProposal
|
||||||
QueryProposals = types.QueryProposals
|
QueryProposals = types.QueryProposals
|
||||||
|
QueryRawParams = types.QueryRawParams
|
||||||
QueryTally = types.QueryTally
|
QueryTally = types.QueryTally
|
||||||
QueryVote = types.QueryVote
|
QueryVote = types.QueryVote
|
||||||
QueryVotes = types.QueryVotes
|
QueryVotes = types.QueryVotes
|
||||||
@ -51,6 +53,7 @@ var (
|
|||||||
DefaultGenesisState = types.DefaultGenesisState
|
DefaultGenesisState = types.DefaultGenesisState
|
||||||
GetKeyFromID = types.GetKeyFromID
|
GetKeyFromID = types.GetKeyFromID
|
||||||
GetVoteKey = types.GetVoteKey
|
GetVoteKey = types.GetVoteKey
|
||||||
|
NewAllowedCollateralParam = types.NewAllowedCollateralParam
|
||||||
NewCommittee = types.NewCommittee
|
NewCommittee = types.NewCommittee
|
||||||
NewCommitteeChangeProposal = types.NewCommitteeChangeProposal
|
NewCommitteeChangeProposal = types.NewCommitteeChangeProposal
|
||||||
NewCommitteeDeleteProposal = types.NewCommitteeDeleteProposal
|
NewCommitteeDeleteProposal = types.NewCommitteeDeleteProposal
|
||||||
@ -60,6 +63,7 @@ var (
|
|||||||
NewProposal = types.NewProposal
|
NewProposal = types.NewProposal
|
||||||
NewQueryCommitteeParams = types.NewQueryCommitteeParams
|
NewQueryCommitteeParams = types.NewQueryCommitteeParams
|
||||||
NewQueryProposalParams = types.NewQueryProposalParams
|
NewQueryProposalParams = types.NewQueryProposalParams
|
||||||
|
NewQueryRawParamsParams = types.NewQueryRawParamsParams
|
||||||
NewQueryVoteParams = types.NewQueryVoteParams
|
NewQueryVoteParams = types.NewQueryVoteParams
|
||||||
NewVote = types.NewVote
|
NewVote = types.NewVote
|
||||||
RegisterCodec = types.RegisterCodec
|
RegisterCodec = types.RegisterCodec
|
||||||
@ -77,6 +81,7 @@ var (
|
|||||||
ErrProposalExpired = types.ErrProposalExpired
|
ErrProposalExpired = types.ErrProposalExpired
|
||||||
ErrUnknownCommittee = types.ErrUnknownCommittee
|
ErrUnknownCommittee = types.ErrUnknownCommittee
|
||||||
ErrUnknownProposal = types.ErrUnknownProposal
|
ErrUnknownProposal = types.ErrUnknownProposal
|
||||||
|
ErrUnknownSubspace = types.ErrUnknownSubspace
|
||||||
ErrUnknownVote = types.ErrUnknownVote
|
ErrUnknownVote = types.ErrUnknownVote
|
||||||
ModuleCdc = types.ModuleCdc
|
ModuleCdc = types.ModuleCdc
|
||||||
NextProposalIDKey = types.NextProposalIDKey
|
NextProposalIDKey = types.NextProposalIDKey
|
||||||
@ -108,6 +113,7 @@ type (
|
|||||||
PubProposal = types.PubProposal
|
PubProposal = types.PubProposal
|
||||||
QueryCommitteeParams = types.QueryCommitteeParams
|
QueryCommitteeParams = types.QueryCommitteeParams
|
||||||
QueryProposalParams = types.QueryProposalParams
|
QueryProposalParams = types.QueryProposalParams
|
||||||
|
QueryRawParamsParams = types.QueryRawParamsParams
|
||||||
QueryVoteParams = types.QueryVoteParams
|
QueryVoteParams = types.QueryVoteParams
|
||||||
SimpleParamChangePermission = types.SimpleParamChangePermission
|
SimpleParamChangePermission = types.SimpleParamChangePermission
|
||||||
SoftwareUpgradePermission = types.SoftwareUpgradePermission
|
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"`
|
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 {
|
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
|
allowed := ((acp.Type == current.Type) && (acp.Type == incoming.Type)) && // require collateral types to be all equal
|
||||||
(current.Denom == incoming.Denom || acp.Denom) &&
|
(current.Denom == incoming.Denom || acp.Denom) &&
|
||||||
|
Loading…
Reference in New Issue
Block a user