diff --git a/x/auction/legacy/v0_13/types.go b/x/auction/legacy/v0_13/types.go new file mode 100644 index 00000000..bbff2701 --- /dev/null +++ b/x/auction/legacy/v0_13/types.go @@ -0,0 +1,555 @@ +package v0_13 + +import ( + "errors" + "fmt" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply" +) + +// Defaults for auction params +const ( + // DefaultMaxAuctionDuration max length of auction + DefaultMaxAuctionDuration time.Duration = 2 * 24 * time.Hour + // DefaultBidDuration how long an auction gets extended when someone bids + DefaultBidDuration time.Duration = 1 * time.Hour + CollateralAuctionType = "collateral" + SurplusAuctionType = "surplus" + DebtAuctionType = "debt" + ForwardAuctionPhase = "forward" + ReverseAuctionPhase = "reverse" + DefaultNextAuctionID uint64 = 1 +) + +// module variables +var ( + // DefaultIncrement is the smallest percent change a new bid must have from the old one + DefaultIncrement sdk.Dec = sdk.MustNewDecFromStr("0.05") + KeyBidDuration = []byte("BidDuration") + KeyMaxAuctionDuration = []byte("MaxAuctionDuration") + KeyIncrementSurplus = []byte("IncrementSurplus") + KeyIncrementDebt = []byte("IncrementDebt") + KeyIncrementCollateral = []byte("IncrementCollateral") + emptyDec = sdk.Dec{} +) + +// GenesisAuction interface for auctions at genesis +type GenesisAuction interface { + Auction + GetModuleAccountCoins() sdk.Coins + Validate() error +} + +// GenesisAuctions is a slice of genesis auctions. +type GenesisAuctions []GenesisAuction + +// GenesisState is auction state that must be provided at chain genesis. +type GenesisState struct { + NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` + Params Params `json:"params" yaml:"params"` + Auctions GenesisAuctions `json:"auctions" yaml:"auctions"` +} + +// NewGenesisState returns a new genesis state object for auctions module. +func NewGenesisState(nextID uint64, ap Params, ga GenesisAuctions) GenesisState { + return GenesisState{ + NextAuctionID: nextID, + Params: ap, + Auctions: ga, + } +} + +// DefaultGenesisState returns the default genesis state for auction module. +func DefaultGenesisState() GenesisState { + return NewGenesisState( + DefaultNextAuctionID, + DefaultParams(), + GenesisAuctions{}, + ) +} + +// Validate validates genesis inputs. It returns error if validation of any input fails. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + + ids := map[uint64]bool{} + for _, a := range gs.Auctions { + + if err := a.Validate(); err != nil { + return fmt.Errorf("found invalid auction: %w", err) + } + + if ids[a.GetID()] { + return fmt.Errorf("found duplicate auction ID (%d)", a.GetID()) + } + ids[a.GetID()] = true + + if a.GetID() >= gs.NextAuctionID { + return fmt.Errorf("found auction ID ≥ the nextAuctionID (%d ≥ %d)", a.GetID(), gs.NextAuctionID) + } + } + return nil +} + +// Params is the governance parameters for the auction module. +type Params struct { + MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction + BidDuration time.Duration `json:"bid_duration" yaml:"bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry. + IncrementSurplus sdk.Dec `json:"increment_surplus" yaml:"increment_surplus"` // percentage change (of auc.Bid) required for a new bid on a surplus auction + IncrementDebt sdk.Dec `json:"increment_debt" yaml:"increment_debt"` // percentage change (of auc.Lot) required for a new bid on a debt auction + IncrementCollateral sdk.Dec `json:"increment_collateral" yaml:"increment_collateral"` // percentage change (of auc.Bid or auc.Lot) required for a new bid on a collateral auction +} + +// NewParams returns a new Params object. +func NewParams(maxAuctionDuration, bidDuration time.Duration, incrementSurplus, incrementDebt, incrementCollateral sdk.Dec) Params { + return Params{ + MaxAuctionDuration: maxAuctionDuration, + BidDuration: bidDuration, + IncrementSurplus: incrementSurplus, + IncrementDebt: incrementDebt, + IncrementCollateral: incrementCollateral, + } +} + +// DefaultParams returns the default parameters for auctions. +func DefaultParams() Params { + return NewParams( + DefaultMaxAuctionDuration, + DefaultBidDuration, + DefaultIncrement, + DefaultIncrement, + DefaultIncrement, + ) +} + +// Validate checks that the parameters have valid values. +func (p Params) Validate() error { + if err := validateBidDurationParam(p.BidDuration); err != nil { + return err + } + + if err := validateMaxAuctionDurationParam(p.MaxAuctionDuration); err != nil { + return err + } + + if p.BidDuration > p.MaxAuctionDuration { + return errors.New("bid duration param cannot be larger than max auction duration") + } + + if err := validateIncrementSurplusParam(p.IncrementSurplus); err != nil { + return err + } + + if err := validateIncrementDebtParam(p.IncrementDebt); err != nil { + return err + } + + return validateIncrementCollateralParam(p.IncrementCollateral) +} + +func validateBidDurationParam(i interface{}) error { + bidDuration, ok := i.(time.Duration) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if bidDuration < 0 { + return fmt.Errorf("bid duration cannot be negative %d", bidDuration) + } + + return nil +} + +func validateMaxAuctionDurationParam(i interface{}) error { + maxAuctionDuration, ok := i.(time.Duration) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if maxAuctionDuration < 0 { + return fmt.Errorf("max auction duration cannot be negative %d", maxAuctionDuration) + } + + return nil +} + +func validateIncrementSurplusParam(i interface{}) error { + incrementSurplus, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if incrementSurplus == emptyDec || incrementSurplus.IsNil() { + return errors.New("surplus auction increment cannot be nil or empty") + } + + if incrementSurplus.IsNegative() { + return fmt.Errorf("surplus auction increment cannot be less than zero %s", incrementSurplus) + } + + return nil +} + +func validateIncrementDebtParam(i interface{}) error { + incrementDebt, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if incrementDebt == emptyDec || incrementDebt.IsNil() { + return errors.New("debt auction increment cannot be nil or empty") + } + + if incrementDebt.IsNegative() { + return fmt.Errorf("debt auction increment cannot be less than zero %s", incrementDebt) + } + + return nil +} + +func validateIncrementCollateralParam(i interface{}) error { + incrementCollateral, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if incrementCollateral == emptyDec || incrementCollateral.IsNil() { + return errors.New("collateral auction increment cannot be nil or empty") + } + + if incrementCollateral.IsNegative() { + return fmt.Errorf("collateral auction increment cannot be less than zero %s", incrementCollateral) + } + + return nil +} + +// Auction is an interface for handling common actions on auctions. +type Auction interface { + GetID() uint64 + WithID(uint64) Auction + + GetInitiator() string + GetLot() sdk.Coin + GetBidder() sdk.AccAddress + GetBid() sdk.Coin + GetEndTime() time.Time + + GetType() string + GetPhase() string +} + +// Auctions is a slice of auctions. +type Auctions []Auction + +// BaseAuction is a common type shared by all Auctions. +type BaseAuction struct { + ID uint64 `json:"id" yaml:"id"` + Initiator string `json:"initiator" yaml:"initiator"` // Module name that starts the auction. Pays out Lot. + Lot sdk.Coin `json:"lot" yaml:"lot"` // Coins that will paid out by Initiator to the winning bidder. + Bidder sdk.AccAddress `json:"bidder" yaml:"bidder"` // Latest bidder. Receiver of Lot. + Bid sdk.Coin `json:"bid" yaml:"bid"` // Coins paid into the auction the bidder. + HasReceivedBids bool `json:"has_received_bids" yaml:"has_received_bids"` // Whether the auction has received any bids or not. + EndTime time.Time `json:"end_time" yaml:"end_time"` // Current auction closing time. Triggers at the end of the block with time ≥ EndTime. + MaxEndTime time.Time `json:"max_end_time" yaml:"max_end_time"` // Maximum closing time. Auctions can close before this but never after. +} + +// GetID is a getter for auction ID. +func (a BaseAuction) GetID() uint64 { return a.ID } + +// GetInitiator is a getter for auction Initiator. +func (a BaseAuction) GetInitiator() string { return a.Initiator } + +// GetLot is a getter for auction Lot. +func (a BaseAuction) GetLot() sdk.Coin { return a.Lot } + +// GetBidder is a getter for auction Bidder. +func (a BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } + +// GetBid is a getter for auction Bid. +func (a BaseAuction) GetBid() sdk.Coin { return a.Bid } + +// GetEndTime is a getter for auction end time. +func (a BaseAuction) GetEndTime() time.Time { return a.EndTime } + +// GetType returns the auction type. Used to identify auctions in event attributes. +func (a BaseAuction) GetType() string { return "base" } + +// Validate verifies that the auction end time is before max end time +func (a BaseAuction) Validate() error { + // ID can be 0 for surplus, debt and collateral auctions + if strings.TrimSpace(a.Initiator) == "" { + return errors.New("auction initiator cannot be blank") + } + if !a.Lot.IsValid() { + return fmt.Errorf("invalid lot: %s", a.Lot) + } + // NOTE: bidder can be empty for Surplus and Collateral auctions + if !a.Bidder.Empty() && len(a.Bidder) != sdk.AddrLen { + return fmt.Errorf("the expected bidder address length is %d, actual length is %d", sdk.AddrLen, len(a.Bidder)) + } + if !a.Bid.IsValid() { + return fmt.Errorf("invalid bid: %s", a.Bid) + } + if a.EndTime.IsZero() || a.MaxEndTime.IsZero() { + return errors.New("end time cannot be zero") + } + if a.EndTime.After(a.MaxEndTime) { + return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime) + } + return nil +} + +func (a BaseAuction) String() string { + return fmt.Sprintf(`Auction %d: + Initiator: %s + Lot: %s + Bidder: %s + Bid: %s + End Time: %s + Max End Time: %s`, + a.GetID(), a.Initiator, a.Lot, + a.Bidder, a.Bid, a.GetEndTime().String(), + a.MaxEndTime.String(), + ) +} + +// SurplusAuction is a forward auction that burns what it receives from bids. +// It is normally used to sell off excess pegged asset acquired by the CDP system. +type SurplusAuction struct { + BaseAuction `json:"base_auction" yaml:"base_auction"` +} + +// WithID returns an auction with the ID set. +func (a SurplusAuction) WithID(id uint64) Auction { a.ID = id; return a } + +// GetType returns the auction type. Used to identify auctions in event attributes. +func (a SurplusAuction) GetType() string { return SurplusAuctionType } + +// GetModuleAccountCoins returns the total number of coins held in the module account for this auction. +// It is used in genesis initialize the module account correctly. +func (a SurplusAuction) GetModuleAccountCoins() sdk.Coins { + // a.Bid is paid out on bids, so is never stored in the module account + return sdk.NewCoins(a.Lot) +} + +// GetPhase returns the direction of a surplus auction, which never changes. +func (a SurplusAuction) GetPhase() string { return ForwardAuctionPhase } + +// NewSurplusAuction returns a new surplus auction. +func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction { + auction := SurplusAuction{BaseAuction{ + // no ID + Initiator: seller, + Lot: lot, + Bidder: nil, + Bid: sdk.NewInt64Coin(bidDenom, 0), + HasReceivedBids: false, // new auctions don't have any bids + EndTime: endTime, + MaxEndTime: endTime, + }} + return auction +} + +// DebtAuction is a reverse auction that mints what it pays out. +// It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral. +type DebtAuction struct { + BaseAuction `json:"base_auction" yaml:"base_auction"` + + CorrespondingDebt sdk.Coin `json:"corresponding_debt" yaml:"corresponding_debt"` +} + +// WithID returns an auction with the ID set. +func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a } + +// GetType returns the auction type. Used to identify auctions in event attributes. +func (a DebtAuction) GetType() string { return DebtAuctionType } + +// GetModuleAccountCoins returns the total number of coins held in the module account for this auction. +// It is used in genesis initialize the module account correctly. +func (a DebtAuction) GetModuleAccountCoins() sdk.Coins { + // a.Lot is minted at auction close, so is never stored in the module account + // a.Bid is paid out on bids, so is never stored in the module account + return sdk.NewCoins(a.CorrespondingDebt) +} + +// GetPhase returns the direction of a debt auction, which never changes. +func (a DebtAuction) GetPhase() string { return ReverseAuctionPhase } + +// Validate validates the DebtAuction fields values. +func (a DebtAuction) Validate() error { + if !a.CorrespondingDebt.IsValid() { + return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt) + } + return a.BaseAuction.Validate() +} + +// NewDebtAuction returns a new debt auction. +func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction { + // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) + // Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount. + // This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed. + auction := DebtAuction{ + BaseAuction: BaseAuction{ + // no ID + Initiator: buyerModAccName, + Lot: initialLot, + Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer. + Bid: bid, // amount that the buyer is buying - doesn't change over course of auction + HasReceivedBids: false, // new auctions don't have any bids + EndTime: endTime, + MaxEndTime: endTime, + }, + CorrespondingDebt: debt, + } + return auction +} + +// CollateralAuction is a two phase auction. +// Initially, in forward auction phase, bids can be placed up to a max bid. +// Then it switches to a reverse auction phase, where the initial amount up for auction is bid down. +// Unsold Lot is sent to LotReturns, being divided among the addresses by weight. +// Collateral auctions are normally used to sell off collateral seized from CDPs. +type CollateralAuction struct { + BaseAuction `json:"base_auction" yaml:"base_auction"` + + CorrespondingDebt sdk.Coin `json:"corresponding_debt" yaml:"corresponding_debt"` + MaxBid sdk.Coin `json:"max_bid" yaml:"max_bid"` + LotReturns WeightedAddresses `json:"lot_returns" yaml:"lot_returns"` +} + +// WithID returns an auction with the ID set. +func (a CollateralAuction) WithID(id uint64) Auction { a.ID = id; return a } + +// GetType returns the auction type. Used to identify auctions in event attributes. +func (a CollateralAuction) GetType() string { return CollateralAuctionType } + +// GetModuleAccountCoins returns the total number of coins held in the module account for this auction. +// It is used in genesis initialize the module account correctly. +func (a CollateralAuction) GetModuleAccountCoins() sdk.Coins { + // a.Bid is paid out on bids, so is never stored in the module account + return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt)...) +} + +// IsReversePhase returns whether the auction has switched over to reverse phase or not. +// CollateralAuctions initially start in forward phase. +func (a CollateralAuction) IsReversePhase() bool { + return a.Bid.IsEqual(a.MaxBid) +} + +// GetPhase returns the direction of a collateral auction. +func (a CollateralAuction) GetPhase() string { + if a.IsReversePhase() { + return ReverseAuctionPhase + } + return ForwardAuctionPhase +} + +// GetLotReturns returns a collateral auction's lot owners +func (a CollateralAuction) GetLotReturns() WeightedAddresses { + return a.LotReturns +} + +// Validate validates the CollateralAuction fields values. +func (a CollateralAuction) Validate() error { + if !a.CorrespondingDebt.IsValid() { + return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt) + } + if !a.MaxBid.IsValid() { + return fmt.Errorf("invalid max bid: %s", a.MaxBid) + } + if err := a.LotReturns.Validate(); err != nil { + return fmt.Errorf("invalid lot returns: %w", err) + } + return a.BaseAuction.Validate() +} + +func (a CollateralAuction) String() string { + return fmt.Sprintf(`Auction %d: + Initiator: %s + Lot: %s + Bidder: %s + Bid: %s + End Time: %s + Max End Time: %s + Max Bid %s + LotReturns %s + Corresponding Debt %s`, + a.GetID(), a.Initiator, a.Lot, + a.Bidder, a.Bid, a.GetEndTime().String(), + a.MaxEndTime.String(), a.MaxBid, a.LotReturns, a.CorrespondingDebt, + ) +} + +// NewCollateralAuction returns a new collateral auction. +func NewCollateralAuction(seller string, lot sdk.Coin, endTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses, debt sdk.Coin) CollateralAuction { + auction := CollateralAuction{ + BaseAuction: BaseAuction{ + // no ID + Initiator: seller, + Lot: lot, + Bidder: nil, + Bid: sdk.NewInt64Coin(maxBid.Denom, 0), + HasReceivedBids: false, // new auctions don't have any bids + EndTime: endTime, + MaxEndTime: endTime}, + CorrespondingDebt: debt, + MaxBid: maxBid, + LotReturns: lotReturns, + } + return auction +} + +// WeightedAddresses is a type for storing some addresses and associated weights. +type WeightedAddresses struct { + Addresses []sdk.AccAddress `json:"addresses" yaml:"addresses"` + Weights []sdk.Int `json:"weights" yaml:"weights"` +} + +// NewWeightedAddresses returns a new list addresses with weights. +func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, error) { + wa := WeightedAddresses{ + Addresses: addrs, + Weights: weights, + } + if err := wa.Validate(); err != nil { + return WeightedAddresses{}, err + } + return wa, nil +} + +// Validate checks for that the weights are not negative, not all zero, and the lengths match. +func (wa WeightedAddresses) Validate() error { + if len(wa.Weights) < 1 { + return fmt.Errorf("must be at least 1 weighted address") + } + + if len(wa.Addresses) != len(wa.Weights) { + return fmt.Errorf("number of addresses doesn't match number of weights, %d ≠ %d", len(wa.Addresses), len(wa.Weights)) + } + + totalWeight := sdk.ZeroInt() + for i := range wa.Addresses { + if wa.Addresses[i].Empty() { + return fmt.Errorf("address %d cannot be empty", i) + } + if len(wa.Addresses[i]) != sdk.AddrLen { + return fmt.Errorf("address %d has an invalid length: expected %d, got %d", i, sdk.AddrLen, len(wa.Addresses[i])) + } + if wa.Weights[i].IsNegative() { + return fmt.Errorf("weight %d contains a negative amount: %s", i, wa.Weights[i]) + } + totalWeight = totalWeight.Add(wa.Weights[i]) + } + + if !totalWeight.IsPositive() { + return fmt.Errorf("total weight must be positive") + } + + return nil +} diff --git a/x/bep3/legacy/v0_13/types.go b/x/bep3/legacy/v0_13/types.go new file mode 100644 index 00000000..83f05cbf --- /dev/null +++ b/x/bep3/legacy/v0_13/types.go @@ -0,0 +1,423 @@ +package v0_13 + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + + "github.com/tendermint/tendermint/crypto/tmhash" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// module constants +const ( + bech32MainPrefix = "kava" + NULL SwapStatus = 0x00 + Open SwapStatus = 0x01 + Completed SwapStatus = 0x02 + Expired SwapStatus = 0x03 + INVALID SwapDirection = 0x00 + Incoming SwapDirection = 0x01 + Outgoing SwapDirection = 0x02 + + CreateAtomicSwap = "createAtomicSwap" + ClaimAtomicSwap = "claimAtomicSwap" + RefundAtomicSwap = "refundAtomicSwap" + CalcSwapID = "calcSwapID" + Int64Size = 8 + RandomNumberHashLength = 32 + RandomNumberLength = 32 + AddrByteCount = 20 + MaxOtherChainAddrLength = 64 + SwapIDLength = 32 + MaxExpectedIncomeLength = 64 +) + +// Parameter keys +var ( + KeyAssetParams = []byte("AssetParams") + + DefaultBnbDeputyFixedFee sdk.Int = sdk.NewInt(1000) // 0.00001 BNB + DefaultMinAmount sdk.Int = sdk.ZeroInt() + DefaultMaxAmount sdk.Int = sdk.NewInt(1000000000000) // 10,000 BNB + DefaultMinBlockLock uint64 = 220 + DefaultMaxBlockLock uint64 = 270 + DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) +) + +// GenesisState - all bep3 state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + AtomicSwaps AtomicSwaps `json:"atomic_swaps" yaml:"atomic_swaps"` + Supplies AssetSupplies `json:"supplies" yaml:"supplies"` + PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` +} + +// NewGenesisState creates a new GenesisState object +func NewGenesisState(params Params, swaps AtomicSwaps, supplies AssetSupplies, previousBlockTime time.Time) GenesisState { + return GenesisState{ + Params: params, + AtomicSwaps: swaps, + Supplies: supplies, + PreviousBlockTime: previousBlockTime, + } +} + +// DefaultGenesisState - default GenesisState used by Cosmos Hub +func DefaultGenesisState() GenesisState { + return NewGenesisState( + DefaultParams(), + AtomicSwaps{}, + AssetSupplies{}, + DefaultPreviousBlockTime, + ) +} + +// Validate validates genesis inputs. It returns error if validation of any input fails. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + + ids := map[string]bool{} + for _, swap := range gs.AtomicSwaps { + if ids[hex.EncodeToString(swap.GetSwapID())] { + return fmt.Errorf("found duplicate atomic swap ID %s", hex.EncodeToString(swap.GetSwapID())) + } + + if err := swap.Validate(); err != nil { + return err + } + + ids[hex.EncodeToString(swap.GetSwapID())] = true + } + + supplyDenoms := map[string]bool{} + for _, supply := range gs.Supplies { + if err := supply.Validate(); err != nil { + return err + } + if supplyDenoms[supply.GetDenom()] { + return fmt.Errorf("found duplicate denom in asset supplies %s", supply.GetDenom()) + } + supplyDenoms[supply.GetDenom()] = true + } + return nil +} + +// Params governance parameters for bep3 module +type Params struct { + AssetParams AssetParams `json:"asset_params" yaml:"asset_params"` +} + +// NewParams returns a new params object +func NewParams(ap AssetParams, +) Params { + return Params{ + AssetParams: ap, + } +} + +// DefaultParams returns default params for bep3 module +func DefaultParams() Params { + return NewParams(AssetParams{}) +} + +// AssetParam parameters that must be specified for each bep3 asset +type AssetParam struct { + Denom string `json:"denom" yaml:"denom"` // name of the asset + CoinID int `json:"coin_id" yaml:"coin_id"` // SLIP-0044 registered coin type - see https://github.com/satoshilabs/slips/blob/master/slip-0044.md + SupplyLimit SupplyLimit `json:"supply_limit" yaml:"supply_limit"` // asset supply limit + Active bool `json:"active" yaml:"active"` // denotes if asset is available or paused + DeputyAddress sdk.AccAddress `json:"deputy_address" yaml:"deputy_address"` // the address of the relayer process + FixedFee sdk.Int `json:"fixed_fee" yaml:"fixed_fee"` // the fixed fee charged by the relayer process for outgoing swaps + MinSwapAmount sdk.Int `json:"min_swap_amount" yaml:"min_swap_amount"` // Minimum swap amount + MaxSwapAmount sdk.Int `json:"max_swap_amount" yaml:"max_swap_amount"` // Maximum swap amount + MinBlockLock uint64 `json:"min_block_lock" yaml:"min_block_lock"` // Minimum swap block lock + MaxBlockLock uint64 `json:"max_block_lock" yaml:"max_block_lock"` // Maximum swap block lock +} + +// NewAssetParam returns a new AssetParam +func NewAssetParam( + denom string, coinID int, limit SupplyLimit, active bool, + deputyAddr sdk.AccAddress, fixedFee sdk.Int, minSwapAmount sdk.Int, + maxSwapAmount sdk.Int, minBlockLock uint64, maxBlockLock uint64, +) AssetParam { + return AssetParam{ + Denom: denom, + CoinID: coinID, + SupplyLimit: limit, + Active: active, + DeputyAddress: deputyAddr, + FixedFee: fixedFee, + MinSwapAmount: minSwapAmount, + MaxSwapAmount: maxSwapAmount, + MinBlockLock: minBlockLock, + MaxBlockLock: maxBlockLock, + } +} + +// AssetParams array of AssetParam +type AssetParams []AssetParam + +// SupplyLimit parameters that control the absolute and time-based limits for an assets's supply +type SupplyLimit struct { + Limit sdk.Int `json:"limit" yaml:"limit"` // the absolute supply limit for an asset + TimeLimited bool `json:"time_limited" yaml:"time_limited"` // boolean for if the supply is also limited by time + TimePeriod time.Duration `json:"time_period" yaml:"time_period"` // the duration for which the supply time limit applies + TimeBasedLimit sdk.Int `json:"time_based_limit" yaml:"time_based_limit"` // the supply limit for an asset for each time period +} + +// Validate ensure that params have valid values +func (p Params) Validate() error { + return validateAssetParams(p.AssetParams) +} + +func validateAssetParams(i interface{}) error { + assetParams, ok := i.(AssetParams) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + coinDenoms := make(map[string]bool) + for _, asset := range assetParams { + if err := sdk.ValidateDenom(asset.Denom); err != nil { + return fmt.Errorf(fmt.Sprintf("asset denom invalid: %s", asset.Denom)) + } + + if asset.CoinID < 0 { + return fmt.Errorf(fmt.Sprintf("asset %s coin id must be a non negative integer", asset.Denom)) + } + + if asset.SupplyLimit.Limit.IsNegative() { + return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply limit: %s", asset.Denom, asset.SupplyLimit.Limit)) + } + + if asset.SupplyLimit.TimeBasedLimit.IsNegative() { + return fmt.Errorf(fmt.Sprintf("asset %s has invalid (negative) supply time limit: %s", asset.Denom, asset.SupplyLimit.TimeBasedLimit)) + } + + if asset.SupplyLimit.TimeBasedLimit.GT(asset.SupplyLimit.Limit) { + return fmt.Errorf(fmt.Sprintf("asset %s cannot have supply time limit > supply limit: %s>%s", asset.Denom, asset.SupplyLimit.TimeBasedLimit, asset.SupplyLimit.Limit)) + } + + _, found := coinDenoms[asset.Denom] + if found { + return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate denom", asset.Denom)) + } + + coinDenoms[asset.Denom] = true + + if asset.DeputyAddress.Empty() { + return fmt.Errorf("deputy address cannot be empty for %s", asset.Denom) + } + + if len(asset.DeputyAddress.Bytes()) != sdk.AddrLen { + return fmt.Errorf("%s deputy address invalid bytes length got %d, want %d", asset.Denom, len(asset.DeputyAddress.Bytes()), sdk.AddrLen) + } + + if asset.FixedFee.IsNegative() { + return fmt.Errorf("asset %s cannot have a negative fixed fee %s", asset.Denom, asset.FixedFee) + } + + if asset.MinBlockLock > asset.MaxBlockLock { + return fmt.Errorf("asset %s has minimum block lock > maximum block lock %d > %d", asset.Denom, asset.MinBlockLock, asset.MaxBlockLock) + } + + if !asset.MinSwapAmount.IsPositive() { + return fmt.Errorf(fmt.Sprintf("asset %s must have a positive minimum swap amount, got %s", asset.Denom, asset.MinSwapAmount)) + } + + if !asset.MaxSwapAmount.IsPositive() { + return fmt.Errorf(fmt.Sprintf("asset %s must have a positive maximum swap amount, got %s", asset.Denom, asset.MaxSwapAmount)) + } + + if asset.MinSwapAmount.GT(asset.MaxSwapAmount) { + return fmt.Errorf("asset %s has minimum swap amount > maximum swap amount %s > %s", asset.Denom, asset.MinSwapAmount, asset.MaxSwapAmount) + } + } + + return nil +} + +// AtomicSwap contains the information for an atomic swap +type AtomicSwap struct { + Amount sdk.Coins `json:"amount" yaml:"amount"` + RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"` + ExpireHeight uint64 `json:"expire_height" yaml:"expire_height"` + Timestamp int64 `json:"timestamp" yaml:"timestamp"` + Sender sdk.AccAddress `json:"sender" yaml:"sender"` + Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"` + SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"` + RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"` + ClosedBlock int64 `json:"closed_block" yaml:"closed_block"` + Status SwapStatus `json:"status" yaml:"status"` + CrossChain bool `json:"cross_chain" yaml:"cross_chain"` + Direction SwapDirection `json:"direction" yaml:"direction"` +} + +// NewAtomicSwap returns a new AtomicSwap +func NewAtomicSwap(amount sdk.Coins, randomNumberHash tmbytes.HexBytes, expireHeight uint64, timestamp int64, + sender, recipient sdk.AccAddress, senderOtherChain string, recipientOtherChain string, closedBlock int64, + status SwapStatus, crossChain bool, direction SwapDirection) AtomicSwap { + return AtomicSwap{ + Amount: amount, + RandomNumberHash: randomNumberHash, + ExpireHeight: expireHeight, + Timestamp: timestamp, + Sender: sender, + Recipient: recipient, + SenderOtherChain: senderOtherChain, + RecipientOtherChain: recipientOtherChain, + ClosedBlock: closedBlock, + Status: status, + CrossChain: crossChain, + Direction: direction, + } +} + +// GetSwapID calculates the ID of an atomic swap +func (a AtomicSwap) GetSwapID() tmbytes.HexBytes { + return CalculateSwapID(a.RandomNumberHash, a.Sender, a.SenderOtherChain) +} + +// GetCoins returns the swap's amount as sdk.Coins +func (a AtomicSwap) GetCoins() sdk.Coins { + return sdk.NewCoins(a.Amount...) +} + +// Validate performs a basic validation of an atomic swap fields. +func (a AtomicSwap) Validate() error { + if !a.Amount.IsValid() { + return fmt.Errorf("invalid amount: %s", a.Amount) + } + if !a.Amount.IsAllPositive() { + return fmt.Errorf("the swapped out coin must be positive: %s", a.Amount) + } + if len(a.RandomNumberHash) != RandomNumberHashLength { + return fmt.Errorf("the length of random number hash should be %d", RandomNumberHashLength) + } + if a.ExpireHeight == 0 { + return errors.New("expire height cannot be 0") + } + if a.Timestamp == 0 { + return errors.New("timestamp cannot be 0") + } + if a.Sender.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender cannot be empty") + } + if a.Recipient.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "recipient cannot be empty") + } + if len(a.Sender) != AddrByteCount { + return fmt.Errorf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Sender)) + } + if len(a.Recipient) != AddrByteCount { + return fmt.Errorf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Recipient)) + } + // NOTE: These adresses may not have a bech32 prefix. + if strings.TrimSpace(a.SenderOtherChain) == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender other chain cannot be blank") + } + if strings.TrimSpace(a.RecipientOtherChain) == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "recipient other chain cannot be blank") + } + if a.Status == Completed && a.ClosedBlock == 0 { + return errors.New("closed block cannot be 0") + } + if a.Status == NULL || a.Status > 3 { + return errors.New("invalid swap status") + } + if a.Direction == INVALID || a.Direction > 2 { + return errors.New("invalid swap direction") + } + return nil +} + +// AtomicSwaps is a slice of AtomicSwap +type AtomicSwaps []AtomicSwap + +type SwapStatus byte + +// NewSwapStatusFromString converts string to SwapStatus type +func NewSwapStatusFromString(str string) SwapStatus { + switch str { + case "Open", "open": + return Open + case "Completed", "completed": + return Completed + case "Expired", "expired": + return Expired + default: + return NULL + } +} + +// AssetSupply contains information about an asset's supply +type AssetSupply struct { + IncomingSupply sdk.Coin `json:"incoming_supply" yaml:"incoming_supply"` + OutgoingSupply sdk.Coin `json:"outgoing_supply" yaml:"outgoing_supply"` + CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"` + TimeLimitedCurrentSupply sdk.Coin `json:"time_limited_current_supply" yaml:"time_limited_current_supply"` + TimeElapsed time.Duration `json:"time_elapsed" yaml:"time_elapsed"` +} + +// NewAssetSupply initializes a new AssetSupply +func NewAssetSupply(incomingSupply, outgoingSupply, currentSupply, timeLimitedSupply sdk.Coin, timeElapsed time.Duration) AssetSupply { + return AssetSupply{ + IncomingSupply: incomingSupply, + OutgoingSupply: outgoingSupply, + CurrentSupply: currentSupply, + TimeLimitedCurrentSupply: timeLimitedSupply, + TimeElapsed: timeElapsed, + } +} + +// Validate performs a basic validation of an asset supply fields. +func (a AssetSupply) Validate() error { + if !a.IncomingSupply.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "incoming supply %s", a.IncomingSupply) + } + if !a.OutgoingSupply.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "outgoing supply %s", a.OutgoingSupply) + } + if !a.CurrentSupply.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "current supply %s", a.CurrentSupply) + } + if !a.TimeLimitedCurrentSupply.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "time-limited current supply %s", a.CurrentSupply) + } + denom := a.CurrentSupply.Denom + if (a.IncomingSupply.Denom != denom) || + (a.OutgoingSupply.Denom != denom) || + (a.TimeLimitedCurrentSupply.Denom != denom) { + return fmt.Errorf("asset supply denoms do not match %s %s %s %s", a.CurrentSupply.Denom, a.IncomingSupply.Denom, a.OutgoingSupply.Denom, a.TimeLimitedCurrentSupply.Denom) + } + return nil +} + +// GetDenom getter method for the denom of the asset supply +func (a AssetSupply) GetDenom() string { + return a.CurrentSupply.Denom +} + +// AssetSupplies is a slice of AssetSupply +type AssetSupplies []AssetSupply + +// SwapDirection is the direction of an AtomicSwap +type SwapDirection byte + +// CalculateSwapID calculates the hash of a RandomNumberHash, sdk.AccAddress, and string +func CalculateSwapID(randomNumberHash []byte, sender sdk.AccAddress, senderOtherChain string) []byte { + senderOtherChain = strings.ToLower(senderOtherChain) + data := randomNumberHash + data = append(data, sender.Bytes()...) + data = append(data, []byte(senderOtherChain)...) + return tmhash.Sum(data) +} diff --git a/x/committee/legacy/v0_13/types.go b/x/committee/legacy/v0_13/types.go new file mode 100644 index 00000000..cbbdf196 --- /dev/null +++ b/x/committee/legacy/v0_13/types.go @@ -0,0 +1,798 @@ +package v0_13 + +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/legacy/v0_11" + "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 +} diff --git a/x/issuance/legacy/v0_13/types.go b/x/issuance/legacy/v0_13/types.go new file mode 100644 index 00000000..8445b455 --- /dev/null +++ b/x/issuance/legacy/v0_13/types.go @@ -0,0 +1,187 @@ +package v0_13 + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +const ( + // ModuleName The name that will be used throughout the module + ModuleName = "issuance" + + // StoreKey Top level store key where all module items will be stored + StoreKey = ModuleName + + // RouterKey Top level router key + RouterKey = ModuleName + + // DefaultParamspace default name for parameter store + DefaultParamspace = ModuleName + + // QuerierRoute route used for abci queries + QuerierRoute = ModuleName +) + +// Parameter keys and default values +var ( + KeyAssets = []byte("Assets") + DefaultAssets = Assets{} + ModuleAccountName = ModuleName + AssetSupplyPrefix = []byte{0x01} + PreviousBlockTimeKey = []byte{0x02} +) + +// Params governance parameters for the issuance module +type Params struct { + Assets Assets `json:"assets" yaml:"assets"` +} + +// NewParams returns a new params object +func NewParams(assets Assets) Params { + return Params{Assets: assets} +} + +// DefaultParams returns default params for issuance module +func DefaultParams() Params { + return NewParams(DefaultAssets) +} + +// GenesisState is the state that must be provided at genesis for the issuance module +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + Supplies AssetSupplies `json:"supplies" yaml:"supplies"` +} + +// NewGenesisState returns a new GenesisState +func NewGenesisState(params Params, supplies AssetSupplies) GenesisState { + return GenesisState{ + Params: params, + Supplies: supplies, + } +} + +// DefaultGenesisState returns the default GenesisState for the issuance module +func DefaultGenesisState() GenesisState { + return GenesisState{ + Params: DefaultParams(), + Supplies: AssetSupplies{}, + } +} + +// RateLimit parameters for rate-limiting the supply of an issued asset +type RateLimit struct { + Active bool `json:"active" yaml:"active"` + Limit sdk.Int `json:"limit" yaml:"limit"` + TimePeriod time.Duration `json:"time_period" yaml:"time_period"` +} + +// NewRateLimit initializes a new RateLimit +func NewRateLimit(active bool, limit sdk.Int, timePeriod time.Duration) RateLimit { + return RateLimit{ + Active: active, + Limit: limit, + TimePeriod: timePeriod, + } +} + +// Assets array of Asset +type Assets []Asset + +// Validate checks if all assets are valid and there are no duplicate entries +func (as Assets) Validate() error { + assetDenoms := make(map[string]bool) + for _, a := range as { + if assetDenoms[a.Denom] { + return fmt.Errorf("cannot have duplicate asset denoms: %s", a.Denom) + } + if err := a.Validate(); err != nil { + return err + } + assetDenoms[a.Denom] = true + } + return nil +} + +// Asset type for assets in the issuance module +type Asset struct { + Owner sdk.AccAddress `json:"owner" yaml:"owner"` + Denom string `json:"denom" yaml:"denom"` + BlockedAddresses []sdk.AccAddress `json:"blocked_addresses" yaml:"blocked_addresses"` + Paused bool `json:"paused" yaml:"paused"` + Blockable bool `json:"blockable" yaml:"blockable"` + RateLimit RateLimit `json:"rate_limit" yaml:"rate_limit"` +} + +// NewAsset returns a new Asset +func NewAsset(owner sdk.AccAddress, denom string, blockedAddresses []sdk.AccAddress, paused bool, blockable bool, limit RateLimit) Asset { + return Asset{ + Owner: owner, + Denom: denom, + BlockedAddresses: blockedAddresses, + Paused: paused, + Blockable: blockable, + RateLimit: limit, + } +} + +// Validate performs a basic check of asset fields +func (a Asset) Validate() error { + if a.Owner.Empty() { + return fmt.Errorf("owner must not be empty") + } + if !a.Blockable && len(a.BlockedAddresses) > 0 { + return fmt.Errorf("asset %s does not support blocking, blocked-list should be empty: %s", a.Denom, a.BlockedAddresses) + } + for _, address := range a.BlockedAddresses { + if address.Empty() { + return fmt.Errorf("blocked address must not be empty") + } + if a.Owner.Equals(address) { + return fmt.Errorf("asset owner cannot be blocked") + } + } + return sdk.ValidateDenom(a.Denom) +} + +// AssetSupply contains information about an asset's rate-limited supply (the total supply of the asset is tracked in the top-level supply module) +type AssetSupply struct { + CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"` + TimeElapsed time.Duration `json:"time_elapsed" yaml:"time_elapsed"` +} + +// NewAssetSupply initializes a new AssetSupply +func NewAssetSupply(currentSupply sdk.Coin, timeElapsed time.Duration) AssetSupply { + return AssetSupply{ + CurrentSupply: currentSupply, + TimeElapsed: timeElapsed, + } +} + +// Validate performs a basic validation of an asset supply fields. +func (a AssetSupply) Validate() error { + if !a.CurrentSupply.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "outgoing supply %s", a.CurrentSupply) + } + return nil +} + +// String implements stringer +func (a AssetSupply) String() string { + return fmt.Sprintf(` + asset supply: + Current supply: %s + Time elapsed: %s + `, + a.CurrentSupply, a.TimeElapsed) +} + +// GetDenom getter method for the denom of the asset supply +func (a AssetSupply) GetDenom() string { + return a.CurrentSupply.Denom +} + +// AssetSupplies is a slice of AssetSupply +type AssetSupplies []AssetSupply diff --git a/x/kavadist/legacy/v0_13/types.go b/x/kavadist/legacy/v0_13/types.go new file mode 100644 index 00000000..cd1bdf85 --- /dev/null +++ b/x/kavadist/legacy/v0_13/types.go @@ -0,0 +1,81 @@ +package v0_13 + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + cdptypes "github.com/kava-labs/kava/x/cdp/legacy/v0_13" + + tmtime "github.com/tendermint/tendermint/types/time" +) + +// Parameter keys and default values +var ( + KeyActive = []byte("Active") + KeyPeriods = []byte("Periods") + DefaultActive = false + DefaultPeriods = Periods{} + DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) + GovDenom = cdptypes.DefaultGovDenom +) + +// GenesisState is the state that must be provided at genesis. +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` +} + +// NewGenesisState returns a new genesis state +func NewGenesisState(params Params, previousBlockTime time.Time) GenesisState { + return GenesisState{ + Params: params, + PreviousBlockTime: previousBlockTime, + } +} + +// DefaultGenesisState returns a default genesis state +func DefaultGenesisState() GenesisState { + return GenesisState{ + Params: DefaultParams(), + PreviousBlockTime: DefaultPreviousBlockTime, + } +} + +// Params governance parameters for kavadist module +type Params struct { + Active bool `json:"active" yaml:"active"` + Periods Periods `json:"periods" yaml:"periods"` +} + +// NewParams returns a new params object +func NewParams(active bool, periods Periods) Params { + return Params{ + Active: active, + Periods: periods, + } +} + +// DefaultParams returns default params for kavadist module +func DefaultParams() Params { + return NewParams(DefaultActive, DefaultPeriods) +} + +// Period stores the specified start and end dates, and the inflation, expressed as a decimal representing the yearly APR of KAVA tokens that will be minted during that period +type Period struct { + Start time.Time `json:"start" yaml:"start"` // example "2020-03-01T15:20:00Z" + End time.Time `json:"end" yaml:"end"` // example "2020-06-01T15:20:00Z" + Inflation sdk.Dec `json:"inflation" yaml:"inflation"` // example "1.000000003022265980" - 10% inflation +} + +// NewPeriod returns a new instance of Period +func NewPeriod(start time.Time, end time.Time, inflation sdk.Dec) Period { + return Period{ + Start: start, + End: end, + Inflation: inflation, + } +} + +// Periods array of Period +type Periods []Period diff --git a/x/pricefeed/legacy/v0_13/types.go b/x/pricefeed/legacy/v0_13/types.go new file mode 100644 index 00000000..8da28ada --- /dev/null +++ b/x/pricefeed/legacy/v0_13/types.go @@ -0,0 +1,211 @@ +package v0_13 + +import ( + "errors" + "fmt" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Parameter keys +var ( + KeyMarkets = []byte("Markets") + DefaultMarkets = Markets{} +) + +// GenesisState - pricefeed state that must be provided at genesis +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + PostedPrices PostedPrices `json:"posted_prices" yaml:"posted_prices"` +} + +// NewGenesisState creates a new genesis state for the pricefeed module +func NewGenesisState(p Params, pp []PostedPrice) GenesisState { + return GenesisState{ + Params: p, + PostedPrices: pp, + } +} + +// DefaultGenesisState defines default GenesisState for pricefeed +func DefaultGenesisState() GenesisState { + return NewGenesisState( + DefaultParams(), + []PostedPrice{}, + ) +} + +// Validate performs basic validation of genesis data returning an +// error for any failed validation criteria. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + return gs.PostedPrices.Validate() +} + +// Params params for pricefeed. Can be altered via governance +type Params struct { + Markets Markets `json:"markets" yaml:"markets"` // Array containing the markets supported by the pricefeed +} + +// NewParams creates a new AssetParams object +func NewParams(markets Markets) Params { + return Params{ + Markets: markets, + } +} + +// DefaultParams default params for pricefeed +func DefaultParams() Params { + return NewParams(DefaultMarkets) +} + +// Validate ensure that params have valid values +func (p Params) Validate() error { + return validateMarketParams(p.Markets) +} + +func validateMarketParams(i interface{}) error { + markets, ok := i.(Markets) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return markets.Validate() +} + +// Market an asset in the pricefeed +type Market struct { + MarketID string `json:"market_id" yaml:"market_id"` + BaseAsset string `json:"base_asset" yaml:"base_asset"` + QuoteAsset string `json:"quote_asset" yaml:"quote_asset"` + Oracles []sdk.AccAddress `json:"oracles" yaml:"oracles"` + Active bool `json:"active" yaml:"active"` +} + +// NewMarket returns a new Market +func NewMarket(id, base, quote string, oracles []sdk.AccAddress, active bool) Market { + return Market{ + MarketID: id, + BaseAsset: base, + QuoteAsset: quote, + Oracles: oracles, + Active: active, + } +} + +// Validate performs a basic validation of the market params +func (m Market) Validate() error { + if strings.TrimSpace(m.MarketID) == "" { + return errors.New("market id cannot be blank") + } + if err := sdk.ValidateDenom(m.BaseAsset); err != nil { + return fmt.Errorf("invalid base asset: %w", err) + } + if err := sdk.ValidateDenom(m.QuoteAsset); err != nil { + return fmt.Errorf("invalid quote asset: %w", err) + } + seenOracles := make(map[string]bool) + for i, oracle := range m.Oracles { + if oracle.Empty() { + return fmt.Errorf("oracle %d is empty", i) + } + if seenOracles[oracle.String()] { + return fmt.Errorf("duplicated oracle %s", oracle) + } + seenOracles[oracle.String()] = true + } + return nil +} + +// Markets array type for oracle +type Markets []Market + +// Validate checks if all the markets are valid and there are no duplicated +// entries. +func (ms Markets) Validate() error { + seenMarkets := make(map[string]bool) + for _, m := range ms { + if seenMarkets[m.MarketID] { + return fmt.Errorf("duplicated market %s", m.MarketID) + } + if err := m.Validate(); err != nil { + return err + } + seenMarkets[m.MarketID] = true + } + return nil +} + +// CurrentPrice struct that contains the metadata of a current price for a particular market in the pricefeed module. +type CurrentPrice struct { + MarketID string `json:"market_id" yaml:"market_id"` + Price sdk.Dec `json:"price" yaml:"price"` +} + +// NewCurrentPrice returns an instance of CurrentPrice +func NewCurrentPrice(marketID string, price sdk.Dec) CurrentPrice { + return CurrentPrice{MarketID: marketID, Price: price} +} + +// CurrentPrices type for an array of CurrentPrice +type CurrentPrices []CurrentPrice + +// PostedPrice price for market posted by a specific oracle +type PostedPrice struct { + MarketID string `json:"market_id" yaml:"market_id"` + OracleAddress sdk.AccAddress `json:"oracle_address" yaml:"oracle_address"` + Price sdk.Dec `json:"price" yaml:"price"` + Expiry time.Time `json:"expiry" yaml:"expiry"` +} + +// NewPostedPrice returns a new PostedPrice +func NewPostedPrice(marketID string, oracle sdk.AccAddress, price sdk.Dec, expiry time.Time) PostedPrice { + return PostedPrice{ + MarketID: marketID, + OracleAddress: oracle, + Price: price, + Expiry: expiry, + } +} + +// Validate performs a basic check of a PostedPrice params. +func (pp PostedPrice) Validate() error { + if strings.TrimSpace(pp.MarketID) == "" { + return errors.New("market id cannot be blank") + } + if pp.OracleAddress.Empty() { + return errors.New("oracle address cannot be empty") + } + if pp.Price.IsNegative() { + return fmt.Errorf("posted price cannot be negative %s", pp.Price) + } + if pp.Expiry.IsZero() { + return errors.New("expiry time cannot be zero") + } + return nil +} + +// PostedPrices type for an array of PostedPrice +type PostedPrices []PostedPrice + +// Validate checks if all the posted prices are valid and there are no duplicated +// entries. +func (pps PostedPrices) Validate() error { + seenPrices := make(map[string]bool) + for _, pp := range pps { + if pp.OracleAddress != nil && seenPrices[pp.MarketID+pp.OracleAddress.String()] { + return fmt.Errorf("duplicated posted price for marked id %s and oracle address %s", pp.MarketID, pp.OracleAddress) + } + + if err := pp.Validate(); err != nil { + return err + } + seenPrices[pp.MarketID+pp.OracleAddress.String()] = true + } + + return nil +} diff --git a/x/validator-vesting/legacy/v0_13/types.go b/x/validator-vesting/legacy/v0_13/types.go new file mode 100644 index 00000000..0931d923 --- /dev/null +++ b/x/validator-vesting/legacy/v0_13/types.go @@ -0,0 +1,72 @@ +package v0_13 + +import ( + "errors" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + + tmtime "github.com/tendermint/tendermint/types/time" +) + +// Assert ValidatorVestingAccount implements the vestexported.VestingAccount interface +// Assert ValidatorVestingAccount implements the authexported.GenesisAccount interface +var _ vestexported.VestingAccount = (*ValidatorVestingAccount)(nil) +var _ authexported.GenesisAccount = (*ValidatorVestingAccount)(nil) + +// GenesisState - all auth state that must be provided at genesis +type GenesisState struct { + PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(prevBlockTime time.Time) GenesisState { + return GenesisState{ + PreviousBlockTime: prevBlockTime, + } +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(tmtime.Canonical(time.Unix(0, 0))) +} + +// ValidateGenesis returns nil because accounts are validated by auth +func ValidateGenesis(data GenesisState) error { + if data.PreviousBlockTime.IsZero() { + return errors.New("previous block time cannot be zero") + } + return nil +} + +// ValidatorVestingAccount implements the VestingAccount interface. It +// conditionally vests by unlocking coins during each specified period, provided +// that the validator address has validated at least **SigningThreshold** blocks during +// the previous vesting period. The signing threshold takes values 0 to 100 are represents the +// percentage of blocks that must be signed each period for the vesting to complete successfully. +// If the validator has not signed at least the threshold percentage of blocks during a period, +// the coins are returned to the return address, or burned if the return address is null. +type ValidatorVestingAccount struct { + *vestingtypes.PeriodicVestingAccount + ValidatorAddress sdk.ConsAddress `json:"validator_address" yaml:"validator_address"` + ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"` + SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"` + CurrentPeriodProgress CurrentPeriodProgress `json:"current_period_progress" yaml:"current_period_progress"` + VestingPeriodProgress []VestingProgress `json:"vesting_period_progress" yaml:"vesting_period_progress"` + DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` +} + +// CurrentPeriodProgress tracks the progress of the current vesting period +type CurrentPeriodProgress struct { + MissedBlocks int64 `json:"missed_blocks" yaml:"missed_blocks"` + TotalBlocks int64 `json:"total_blocks" yaml:"total_blocks"` +} + +// VestingProgress tracks the status of each vesting period +type VestingProgress struct { + PeriodComplete bool `json:"period_complete" yaml:"period_complete"` + VestingSuccessful bool `json:"vesting_successful" yaml:"vesting_successful"` +}