add v0.13 legacy types for kava-5 (#784)

This commit is contained in:
Kevin Davis 2021-02-02 15:10:57 -07:00 committed by GitHub
parent 6118876074
commit dd3470260d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 2327 additions and 0 deletions

View File

@ -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
}

View File

@ -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)
}

View File

@ -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, &currentCP) // 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, &currentDP) // 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, &currentAPs) // 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, &currentMs) // 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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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"`
}