mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-23 23:05:18 +00:00
Merge branch 'master' into ro-auction-audit-revisions
This commit is contained in:
commit
a5d70d1424
33
app/app.go
33
app/app.go
@ -30,6 +30,8 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||
"github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||
upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/bep3"
|
||||
@ -61,10 +63,14 @@ var (
|
||||
staking.AppModuleBasic{},
|
||||
mint.AppModuleBasic{},
|
||||
distr.AppModuleBasic{},
|
||||
gov.NewAppModuleBasic(paramsclient.ProposalHandler, distr.ProposalHandler, committee.ProposalHandler),
|
||||
gov.NewAppModuleBasic(
|
||||
paramsclient.ProposalHandler, distr.ProposalHandler, committee.ProposalHandler,
|
||||
upgradeclient.ProposalHandler,
|
||||
),
|
||||
params.AppModuleBasic{},
|
||||
crisis.AppModuleBasic{},
|
||||
slashing.AppModuleBasic{},
|
||||
upgrade.AppModuleBasic{},
|
||||
supply.AppModuleBasic{},
|
||||
evidence.AppModuleBasic{},
|
||||
auction.AppModuleBasic{},
|
||||
@ -89,7 +95,7 @@ var (
|
||||
cdp.ModuleName: {supply.Minter, supply.Burner},
|
||||
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
|
||||
cdp.SavingsRateMacc: {supply.Minter},
|
||||
bep3.ModuleName: {supply.Minter, supply.Burner},
|
||||
bep3.ModuleName: nil,
|
||||
kavadist.ModuleName: {supply.Minter},
|
||||
}
|
||||
)
|
||||
@ -118,6 +124,7 @@ type App struct {
|
||||
distrKeeper distr.Keeper
|
||||
govKeeper gov.Keeper
|
||||
crisisKeeper crisis.Keeper
|
||||
upgradeKeeper upgrade.Keeper
|
||||
paramsKeeper params.Keeper
|
||||
evidenceKeeper evidence.Keeper
|
||||
vvKeeper validatorvesting.Keeper
|
||||
@ -138,7 +145,7 @@ type App struct {
|
||||
|
||||
// NewApp returns a reference to an initialized App.
|
||||
func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
invCheckPeriod uint,
|
||||
skipUpgradeHeights map[int64]bool, invCheckPeriod uint,
|
||||
baseAppOptions ...func(*bam.BaseApp)) *App {
|
||||
|
||||
cdc := MakeCodec()
|
||||
@ -150,9 +157,9 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
keys := sdk.NewKVStoreKeys(
|
||||
bam.MainStoreKey, auth.StoreKey, staking.StoreKey,
|
||||
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
|
||||
gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey,
|
||||
auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey,
|
||||
kavadist.StoreKey, incentive.StoreKey, committee.StoreKey,
|
||||
gov.StoreKey, params.StoreKey, upgrade.StoreKey, evidence.StoreKey,
|
||||
validatorvesting.StoreKey, auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey,
|
||||
bep3.StoreKey, kavadist.StoreKey, incentive.StoreKey, committee.StoreKey,
|
||||
)
|
||||
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
|
||||
|
||||
@ -236,6 +243,11 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
app.supplyKeeper,
|
||||
auth.FeeCollectorName,
|
||||
)
|
||||
app.upgradeKeeper = upgrade.NewKeeper(
|
||||
skipUpgradeHeights,
|
||||
keys[upgrade.StoreKey],
|
||||
app.cdc,
|
||||
)
|
||||
|
||||
// create evidence keeper with router
|
||||
evidenceKeeper := evidence.NewKeeper(
|
||||
@ -269,6 +281,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
AddRoute(gov.RouterKey, gov.ProposalHandler).
|
||||
AddRoute(params.RouterKey, params.NewParamChangeProposalHandler(app.paramsKeeper)).
|
||||
AddRoute(distr.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.distrKeeper)).
|
||||
AddRoute(upgrade.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.upgradeKeeper)).
|
||||
AddRoute(committee.RouterKey, committee.NewProposalHandler(app.committeeKeeper))
|
||||
app.govKeeper = gov.NewKeeper(
|
||||
app.cdc,
|
||||
@ -311,6 +324,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
app.cdc,
|
||||
keys[bep3.StoreKey],
|
||||
app.supplyKeeper,
|
||||
app.accountKeeper,
|
||||
bep3Subspace,
|
||||
)
|
||||
app.kavadistKeeper = kavadist.NewKeeper(
|
||||
@ -346,6 +360,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper),
|
||||
distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper),
|
||||
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
|
||||
upgrade.NewAppModule(app.upgradeKeeper),
|
||||
evidence.NewAppModule(app.evidenceKeeper),
|
||||
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
|
||||
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
|
||||
@ -362,7 +377,11 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
// CanWithdrawInvariant invariant.
|
||||
// Auction.BeginBlocker will close out expired auctions and pay debt back to cdp.
|
||||
// So it should be run before cdp.BeginBlocker which cancels out debt with stable and starts more auctions.
|
||||
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, auction.ModuleName, cdp.ModuleName, bep3.ModuleName, incentive.ModuleName, committee.ModuleName)
|
||||
app.mm.SetOrderBeginBlockers(
|
||||
upgrade.ModuleName, mint.ModuleName, distr.ModuleName, slashing.ModuleName,
|
||||
validatorvesting.ModuleName, kavadist.ModuleName, auction.ModuleName, cdp.ModuleName,
|
||||
bep3.ModuleName, incentive.ModuleName, committee.ModuleName,
|
||||
)
|
||||
|
||||
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
|
||||
|
||||
|
@ -16,11 +16,11 @@ import (
|
||||
|
||||
func TestExport(t *testing.T) {
|
||||
db := db.NewMemDB()
|
||||
app := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
|
||||
app := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
|
||||
setGenesis(app)
|
||||
|
||||
// Making a new app object with the db, so that initchain hasn't been called
|
||||
newApp := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
|
||||
newApp := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
|
||||
_, _, err := newApp.ExportAppStateAndValidators(false, []string{})
|
||||
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
|
||||
}
|
||||
@ -28,7 +28,7 @@ func TestExport(t *testing.T) {
|
||||
// ensure that black listed addresses are properly set in bank keeper
|
||||
func TestBlackListedAddrs(t *testing.T) {
|
||||
db := db.NewMemDB()
|
||||
app := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
|
||||
app := NewApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
|
||||
|
||||
for acc := range mAccPerms {
|
||||
require.True(t, app.bankKeeper.BlacklistedAddr(app.supplyKeeper.GetModuleAddress(acc)))
|
||||
|
@ -8,9 +8,10 @@ const (
|
||||
|
||||
// Default simulation operation weights for messages and gov proposals
|
||||
const (
|
||||
DefaultWeightMsgPlaceBid int = 75
|
||||
DefaultWeightMsgCreateAtomicSwap int = 50
|
||||
DefaultWeightMsgUpdatePrices int = 50
|
||||
DefaultWeightMsgCdp int = 100
|
||||
DefaultWeightMsgClaimReward int = 50
|
||||
DefaultWeightMsgPlaceBid int = 75
|
||||
DefaultWeightMsgCreateAtomicSwap int = 50
|
||||
DefaultWeightMsgUpdatePrices int = 50
|
||||
DefaultWeightMsgCdp int = 100
|
||||
DefaultWeightMsgClaimReward int = 50
|
||||
OpWeightSubmitCommitteeChangeProposal int = 50
|
||||
)
|
||||
|
@ -8,6 +8,10 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
||||
@ -23,13 +27,10 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
dbm "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/bep3"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/kava-labs/kava/x/committee"
|
||||
"github.com/kava-labs/kava/x/incentive"
|
||||
"github.com/kava-labs/kava/x/kavadist"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
@ -79,7 +80,7 @@ func TestFullAppSimulation(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(dir))
|
||||
}()
|
||||
|
||||
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
app := NewApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
require.Equal(t, appName, app.Name())
|
||||
|
||||
// run randomized simulation
|
||||
@ -111,7 +112,7 @@ func TestAppImportExport(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(dir))
|
||||
}()
|
||||
|
||||
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
app := NewApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
require.Equal(t, appName, app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
@ -145,7 +146,7 @@ func TestAppImportExport(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(newDir))
|
||||
}()
|
||||
|
||||
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
require.Equal(t, appName, newApp.Name())
|
||||
|
||||
var genesisState GenesisState
|
||||
@ -178,6 +179,7 @@ func TestAppImportExport(t *testing.T) {
|
||||
{app.keys[kavadist.StoreKey], newApp.keys[kavadist.StoreKey], [][]byte{}},
|
||||
{app.keys[pricefeed.StoreKey], newApp.keys[pricefeed.StoreKey], [][]byte{}},
|
||||
{app.keys[validatorvesting.StoreKey], newApp.keys[validatorvesting.StoreKey], [][]byte{}},
|
||||
{app.keys[committee.StoreKey], newApp.keys[committee.StoreKey], [][]byte{}},
|
||||
}
|
||||
|
||||
for _, skp := range storeKeysPrefixes {
|
||||
@ -205,7 +207,7 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(dir))
|
||||
}()
|
||||
|
||||
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
app := NewApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
require.Equal(t, appName, app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
@ -244,7 +246,7 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(newDir))
|
||||
}()
|
||||
|
||||
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
|
||||
require.Equal(t, appName, newApp.Name())
|
||||
|
||||
newApp.InitChain(abci.RequestInitChain{
|
||||
@ -277,7 +279,7 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
for j := 0; j < numTimesToRunPerSeed; j++ {
|
||||
logger := log.NewNopLogger()
|
||||
db := dbm.NewMemDB()
|
||||
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt())
|
||||
app := NewApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, interBlockCacheOpt())
|
||||
|
||||
fmt.Printf(
|
||||
"running non-determinism simulation; seed %d: attempt: %d/%d\n",
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||
"github.com/cosmos/cosmos-sdk/x/upgrade"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/bep3"
|
||||
@ -58,10 +59,11 @@ func NewTestApp() TestApp {
|
||||
SetBip44CoinType(config)
|
||||
|
||||
db := tmdb.NewMemDB()
|
||||
app := NewApp(log.NewNopLogger(), db, nil, true, 0)
|
||||
app := NewApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, 0)
|
||||
return TestApp{App: *app}
|
||||
}
|
||||
|
||||
// nolint
|
||||
func (tApp TestApp) GetAccountKeeper() auth.AccountKeeper { return tApp.accountKeeper }
|
||||
func (tApp TestApp) GetBankKeeper() bank.Keeper { return tApp.bankKeeper }
|
||||
func (tApp TestApp) GetSupplyKeeper() supply.Keeper { return tApp.supplyKeeper }
|
||||
@ -71,6 +73,7 @@ func (tApp TestApp) GetMintKeeper() mint.Keeper { return tApp.mintKeep
|
||||
func (tApp TestApp) GetDistrKeeper() distribution.Keeper { return tApp.distrKeeper }
|
||||
func (tApp TestApp) GetGovKeeper() gov.Keeper { return tApp.govKeeper }
|
||||
func (tApp TestApp) GetCrisisKeeper() crisis.Keeper { return tApp.crisisKeeper }
|
||||
func (tApp TestApp) GetUpgradeKeeper() upgrade.Keeper { return tApp.upgradeKeeper }
|
||||
func (tApp TestApp) GetParamsKeeper() params.Keeper { return tApp.paramsKeeper }
|
||||
func (tApp TestApp) GetVVKeeper() validatorvesting.Keeper { return tApp.vvKeeper }
|
||||
func (tApp TestApp) GetAuctionKeeper() auction.Keeper { return tApp.auctionKeeper }
|
||||
|
@ -92,6 +92,18 @@ func TestKavaCLIKeysAddRecoverHDPath(t *testing.T) {
|
||||
f.KeysAddRecoverHDPath("test-recoverH5", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", 2, 17, "--legacy-hd-path")
|
||||
require.Equal(t, "kava1v9plmhvyhgxk3th9ydacm7j4z357s3nhhmy0tv", f.KeyAddress("test-recoverH5").String())
|
||||
|
||||
exitSuccess, _, _ := f.KeysAddRecover("test-recover-fail", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", "--legacy-hd-path --hd-path 44'/459'/0'/0/0")
|
||||
require.False(t, exitSuccess)
|
||||
|
||||
// test -hd-path flag
|
||||
exitSuccess, _, _ = f.KeysAddRecover("test-recoverH6", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", "--hd-path 44'/459'/0'/0/0")
|
||||
require.True(t, exitSuccess)
|
||||
require.Equal(t, "kava1rsjxn2e4dfl3a2qzuzzjvvgjmmate383g9q4cz", f.KeyAddress("test-recoverH6").String())
|
||||
|
||||
exitSuccess, _, _ = f.KeysAddRecover("test-recoverH7", "dentist task convince chimney quality leave banana trade firm crawl eternal easily", "--hd-path 44'/459'/2'/0/17")
|
||||
require.True(t, exitSuccess)
|
||||
require.Equal(t, "kava1xvsfnksmhr887skcfrm4pe3va54tkmrtw7wyer", f.KeyAddress("test-recoverH7").String())
|
||||
|
||||
// Cleanup testing directories
|
||||
f.Cleanup()
|
||||
}
|
||||
|
@ -17,18 +17,28 @@ import (
|
||||
NOTE TO FUTURE IMPLEMENTERS
|
||||
This monkey patches the sdk `keys` command, therefore needs to be reviewed on any sdk updates.
|
||||
|
||||
Where a bip44 coin type is used (cosmos-sdk 18de630d):
|
||||
- adding local keys
|
||||
- global variable `sdk.Config.CoinType` is used to derive the key from a mnemonic (supplied by user or generated), but only the private key is stored
|
||||
- adding ledger keys
|
||||
- global variable `sdk.Config.CoinType` is used to reference a key on a ledger device, bip44 path (not private key) is stored locally
|
||||
- signing txs with local keys
|
||||
The patch adds support for using kava's legacy bip44 coin type to the cli.
|
||||
Coin types are used to create a bip44 derivation path, which is used as a mapping from mnemonic to private key.
|
||||
|
||||
In cosmos-sdk v0.38.3, all private keys are stored without reference to the mnemonic or bip44 derivation path, except ledger keys.
|
||||
Ledger keys are just references to a private key on a ledger device. They contain the bip44 derivation path.
|
||||
To patch the cli, we only need to modify:
|
||||
- when new ledger references are created
|
||||
- anything to do with converting a mnemonic to a private key.
|
||||
|
||||
These only happen in `kvcli keys add` cmd.
|
||||
For private key generation, use a --legacy-hd-path flag to enable old coin type.
|
||||
The current cosmos ledger app (v1.5.3) only supports the legacy coin type. So we only need to ensure ledger reference creation doesn't use the new coin type.
|
||||
|
||||
Signing txs:
|
||||
- with local keys
|
||||
- the stored the priv key is used to sign, mnemonics or bip44 paths not involved
|
||||
- signing txs with ledger
|
||||
- with ledger
|
||||
- the stored bip44 path is used to instruct the ledger which key to sign with
|
||||
*/
|
||||
|
||||
const flagLegacyHDPath = "legacy-hd-path"
|
||||
const flagHDPath = "hd-path" // this is copied from keys add cmd because it's not exported
|
||||
|
||||
// getModifiedKeysCmd returns the standard cosmos-sdk/client/keys cmd but modified to support new and old bip44 coin types supported by kava.
|
||||
func getModifiedKeysCmd() *cobra.Command {
|
||||
@ -70,18 +80,20 @@ func monkeyPatchCmdKeysAdd(keysAddCmd *cobra.Command) {
|
||||
// replace the run function with a wrapped version that sets the old coin type in the global config
|
||||
oldRun := keysAddCmd.RunE
|
||||
keysAddCmd.RunE = func(cmd *cobra.Command, args []string) error {
|
||||
preExistingCoinType := sdk.GetConfig().GetCoinType()
|
||||
|
||||
if !viper.GetBool(flagLegacyHDPath) && viper.GetBool(flags.FlagUseLedger) {
|
||||
return fmt.Errorf("cosmos ledger app only supports legacy bip44 coin type, must use --%s flag when adding ledger key", flagLegacyHDPath)
|
||||
}
|
||||
if viper.GetBool(flagLegacyHDPath) && viper.IsSet(flagHDPath) {
|
||||
return fmt.Errorf("cannot use a custom hd path (--%s) and legacy bip44 coin type (--%s) at the same time", flagHDPath, flagLegacyHDPath)
|
||||
}
|
||||
if viper.GetBool(flagLegacyHDPath) {
|
||||
preExistingCoinType := sdk.GetConfig().GetCoinType()
|
||||
sdk.GetConfig().SetCoinType(sdk.CoinType) // set old coin type
|
||||
err := oldRun(cmd, args)
|
||||
sdk.GetConfig().SetCoinType(preExistingCoinType) // revert to preexisting coin type
|
||||
sdk.GetConfig().SetCoinType(preExistingCoinType) // revert to pre-existing coin type
|
||||
return err
|
||||
} else {
|
||||
if viper.GetBool(flags.FlagUseLedger) {
|
||||
return fmt.Errorf("cosmos ledger app only supports legacy bip44 coin type, must use --%s flag when adding ledger key", flagLegacyHDPath)
|
||||
}
|
||||
return oldRun(cmd, args)
|
||||
}
|
||||
return oldRun(cmd, args)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@ -12,8 +13,8 @@ import (
|
||||
"github.com/tendermint/tendermint/libs/cli"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
"github.com/cosmos/cosmos-sdk/server"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
@ -21,6 +22,7 @@ import (
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
|
||||
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
)
|
||||
|
||||
@ -46,21 +48,27 @@ func AddGenesisAccountCmd(
|
||||
the account address or key name and a list of initial coins. If a key name is given,
|
||||
the address will be looked up in the local Keybase. The list of initial tokens must
|
||||
contain valid denominations. Accounts may optionally be supplied with vesting parameters.
|
||||
If the account is a periodic or validator vesting account, vesting periods must be suppleid
|
||||
If the account is a periodic or validator vesting account, vesting periods must be supplied
|
||||
via a JSON file using the 'vesting-periods-file' flag or 'validator-vesting-file' flag,
|
||||
respectively.
|
||||
Example:
|
||||
%s add-genesis-account <account-name> <amount> --vesting-amount <amount> --vesting-end-time <unix-timestamp> --vesting-start-time <unix-timestamp> --vesting-periods <path/to/vesting.json>`, version.ClientName),
|
||||
),
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(_ *cobra.Command, args []string) error {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
config := ctx.Config
|
||||
config.SetRoot(viper.GetString(cli.HomeFlag))
|
||||
|
||||
addr, err := sdk.AccAddressFromBech32(args[0])
|
||||
inBuf := bufio.NewReader(cmd.InOrStdin())
|
||||
if err != nil {
|
||||
// attempt to lookup address from Keybase if no address was provided
|
||||
kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome))
|
||||
kb, err := keys.NewKeyring(
|
||||
sdk.KeyringServiceName(),
|
||||
viper.GetString(flags.FlagKeyringBackend),
|
||||
viper.GetString(flagClientHome),
|
||||
inBuf,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -47,20 +47,22 @@ func main() {
|
||||
PersistentPreRunE: server.PersistentPreRunEFn(ctx),
|
||||
}
|
||||
|
||||
rootCmd.AddCommand(genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome))
|
||||
rootCmd.AddCommand(genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, app.DefaultNodeHome))
|
||||
rootCmd.AddCommand(genutilcli.MigrateGenesisCmd(ctx, cdc))
|
||||
rootCmd.AddCommand(genutilcli.GenTxCmd(
|
||||
ctx,
|
||||
cdc,
|
||||
app.ModuleBasics,
|
||||
staking.AppModuleBasic{},
|
||||
auth.GenesisAccountIterator{},
|
||||
app.DefaultNodeHome,
|
||||
app.DefaultCLIHome))
|
||||
rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics))
|
||||
rootCmd.AddCommand(AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome))
|
||||
rootCmd.AddCommand(flags.NewCompletionCmd(rootCmd, true))
|
||||
rootCmd.AddCommand(
|
||||
genutilcli.InitCmd(ctx, cdc, app.ModuleBasics, app.DefaultNodeHome),
|
||||
genutilcli.CollectGenTxsCmd(ctx, cdc, auth.GenesisAccountIterator{}, app.DefaultNodeHome),
|
||||
genutilcli.MigrateGenesisCmd(ctx, cdc),
|
||||
genutilcli.GenTxCmd(
|
||||
ctx,
|
||||
cdc,
|
||||
app.ModuleBasics,
|
||||
staking.AppModuleBasic{},
|
||||
auth.GenesisAccountIterator{},
|
||||
app.DefaultNodeHome,
|
||||
app.DefaultCLIHome),
|
||||
genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics),
|
||||
AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome),
|
||||
flags.NewCompletionCmd(rootCmd, true),
|
||||
)
|
||||
|
||||
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)
|
||||
|
||||
@ -81,8 +83,13 @@ func newApp(logger log.Logger, db dbm.DB, traceStore io.Writer) abci.Application
|
||||
cache = store.NewCommitKVStoreCacheManager()
|
||||
}
|
||||
|
||||
skipUpgradeHeights := make(map[int64]bool)
|
||||
for _, h := range viper.GetIntSlice(server.FlagUnsafeSkipUpgrades) {
|
||||
skipUpgradeHeights[int64(h)] = true
|
||||
}
|
||||
|
||||
return app.NewApp(
|
||||
logger, db, traceStore, true, invCheckPeriod,
|
||||
logger, db, traceStore, true, skipUpgradeHeights, invCheckPeriod,
|
||||
baseapp.SetPruning(store.NewPruningOptionsFromString(viper.GetString("pruning"))),
|
||||
baseapp.SetMinGasPrices(viper.GetString(server.FlagMinGasPrices)),
|
||||
baseapp.SetHaltHeight(viper.GetUint64(server.FlagHaltHeight)),
|
||||
@ -96,13 +103,13 @@ func exportAppStateAndTMValidators(
|
||||
) (json.RawMessage, []tmtypes.GenesisValidator, error) {
|
||||
|
||||
if height != -1 {
|
||||
tempApp := app.NewApp(logger, db, traceStore, false, uint(1))
|
||||
tempApp := app.NewApp(logger, db, traceStore, false, map[int64]bool{}, uint(1))
|
||||
err := tempApp.LoadHeight(height)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return tempApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
|
||||
}
|
||||
tempApp := app.NewApp(logger, db, traceStore, true, uint(1))
|
||||
tempApp := app.NewApp(logger, db, traceStore, true, map[int64]bool{}, uint(1))
|
||||
return tempApp.ExportAppStateAndValidators(forZeroHeight, jailWhiteList)
|
||||
}
|
||||
|
@ -71,23 +71,20 @@
|
||||
{
|
||||
"auction_size": "50000000000",
|
||||
"conversion_factor": "8",
|
||||
"debt_limit": [
|
||||
{
|
||||
"debt_limit": {
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
},
|
||||
"denom": "bnb",
|
||||
"liquidation_penalty": "0.05",
|
||||
"liquidation_ratio": "2.0",
|
||||
"liquidation_ratio": "1.5",
|
||||
"market_id": "bnb:usd",
|
||||
"prefix": 1,
|
||||
"stability_fee": "1.0000000007829977"
|
||||
}
|
||||
],
|
||||
"debt_auction_threshold": "10000000000",
|
||||
"debt_params": [
|
||||
{
|
||||
"debt_param": {
|
||||
"conversion_factor": "6",
|
||||
"debt_floor": "10000000",
|
||||
"debt_limit": [
|
||||
@ -99,14 +96,11 @@
|
||||
"denom": "usdx",
|
||||
"reference_asset": "usd",
|
||||
"savings_rate": "0.95"
|
||||
}
|
||||
],
|
||||
"global_debt_limit": [
|
||||
{
|
||||
},
|
||||
"global_debt_limit": {
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
},
|
||||
"savings_distribution_frequency": "120000000000",
|
||||
"surplus_auction_threshold": "1000000000"
|
||||
},
|
||||
@ -114,6 +108,12 @@
|
||||
"previous_distribution_time": "1970-01-01T00:00:00Z",
|
||||
"starting_cdp_id": "1"
|
||||
},
|
||||
"committee": {
|
||||
"next_proposal_id": "1",
|
||||
"committees": [],
|
||||
"proposals": [],
|
||||
"votes": []
|
||||
},
|
||||
"crisis": {
|
||||
"constant_fee": {
|
||||
"amount": "1333000000",
|
||||
@ -171,6 +171,26 @@
|
||||
"voting_period": "600000000000"
|
||||
}
|
||||
},
|
||||
"incentive": {
|
||||
"params": {
|
||||
"active": true,
|
||||
"rewards": [
|
||||
{
|
||||
"active": true,
|
||||
"denom": "bnb",
|
||||
"available_rewards":
|
||||
{
|
||||
"amount": "50000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
"duration": "36288000000000000",
|
||||
"time_lock": "1892160000000000000",
|
||||
"claim_duration": "36288000000000000"
|
||||
}
|
||||
]
|
||||
},
|
||||
"previous_block_time": "1970-01-01T00:00:00Z"
|
||||
},
|
||||
"kavadist": {
|
||||
"params": {
|
||||
"active": true,
|
||||
|
@ -1,11 +1,10 @@
|
||||
#! /bin/bash
|
||||
|
||||
# kill kava if it is already running
|
||||
pgrep kvd | xargs kill
|
||||
pgrep kvcli | xargs kill
|
||||
pkill kvd && pkill kvcli
|
||||
|
||||
# TODO import from development environment in envkey
|
||||
password="password"
|
||||
password=""
|
||||
validatorMnemonic="equip town gesture square tomorrow volume nephew minute witness beef rich gadget actress egg sing secret pole winter alarm law today check violin uncover"
|
||||
# address: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c
|
||||
# address: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0
|
||||
@ -24,9 +23,10 @@ rm -rf $kvcliHome
|
||||
mkdir /tmp/kvdHome
|
||||
mkdir /tmp/kvcliHome
|
||||
# create validator key
|
||||
printf "$password\n$validatorMnemonic\n" | kvcli keys add vlad --recover --home $kvcliHome
|
||||
printf "$validatorMnemonic\n" | kvcli keys add vlad --recover --home $kvcliHome --keyring-backend test
|
||||
# create faucet key
|
||||
printf "$password\n$faucet\n" | kvcli --home $kvcliHome keys add faucet --recover --home $kvcliHome
|
||||
printf "$faucet\n" | kvcli --home $kvcliHome keys add faucet --recover --home $kvcliHome --keyring-backend test
|
||||
|
||||
# function used to show that it is still loading
|
||||
showLoading() {
|
||||
mypid=$!
|
||||
@ -51,11 +51,11 @@ kvd --home $kvdHome init --chain-id=testing vlad # doesn't need to be the same a
|
||||
} > /dev/null 2>&1
|
||||
kvcli --home $kvcliHome config chain-id testing # or set trust-node true
|
||||
# add validator account to genesis
|
||||
kvd --home $kvdHome add-genesis-account $(kvcli --home $kvcliHome keys show vlad -a) 10000000000000stake,1000000000000xrp,100000000000btc
|
||||
kvd --home $kvdHome add-genesis-account $(kvcli keys show vlad -a --keyring-backend test --home $kvcliHome) 10000000000000stake,1000000000000xrp,100000000000btc
|
||||
# add faucet account to genesis
|
||||
kvd --home $kvdHome add-genesis-account $(kvcli --home $kvcliHome keys show faucet -a) 10000000000000stake,1000000000000xrp,100000000000btc
|
||||
kvd --home $kvdHome add-genesis-account $(kvcli keys show faucet -a --keyring-backend test --home $kvcliHome) 10000000000000stake,1000000000000xrp,100000000000btc
|
||||
# Create a delegation tx for the validator and add to genesis
|
||||
printf "$password\n" | kvd --home $kvdHome gentx --name vlad --home-client $kvcliHome
|
||||
kvd --home $kvdHome gentx --name vlad --home-client $kvcliHome --keyring-backend test
|
||||
{
|
||||
kvd --home $kvdHome collect-gentxs
|
||||
} > /dev/null 2>&1
|
||||
@ -65,7 +65,7 @@ jq '.app_state.mint.params.blocks_per_year = "31540000"' $genesis > $genesis.tmp
|
||||
jq '.app_state.pricefeed.params.markets += [{"active": true, "base_asset": "xrp", "market_id": "xrp:usd", "oracles": ["kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c"], "quote_asset": "usd"}, {"active": true, "base_asset": "btc", "market_id": "btc:usd", "oracles": ["kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c"], "quote_asset": "usd"}]' $genesis > $genesis.tmp && mv $genesis.tmp $genesis
|
||||
jq '.app_state.pricefeed.posted_prices += [{"expiry": "2050-01-01T00:00:00Z", "market_id": "btc:usd", "oracle_address": "kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c", "price": "8700.0"}, {"expiry": "2050-01-01T00:00:00Z", "market_id": "xrp:usd", "oracle_address": "kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c", "price": "0.25"}]' $genesis > $genesis.tmp && mv $genesis.tmp $genesis
|
||||
# now update cdp params
|
||||
jq '.app_state.cdp.params = { "circuit_breaker": false, "collateral_params": [ { "auction_size": "10000000000", "conversion_factor": "8", "debt_limit": [ { "amount": "1000000000", "denom": "usdx" } ], "denom": "btc", "liquidation_penalty": "0.05", "liquidation_ratio": "1.5", "market_id": "btc:usd", "prefix": 0, "stability_fee": "1.0000000007829977" }, { "auction_size": "100000000", "conversion_factor": "6", "debt_limit": [ { "amount": "10000000", "denom": "usdx" } ], "denom": "xrp", "liquidation_penalty": "0.1", "liquidation_ratio": "2.0", "market_id": "xrp:usd", "prefix": 1, "stability_fee": "1.0000000007829977"} ], "debt_auction_threshold": "9000000", "debt_params": [ { "conversion_factor": "6", "debt_floor": "10000000", "debt_limit": [ { "amount": "2000000000000", "denom": "usdx" } ], "denom": "usdx", "reference_asset": "usd", "savings_rate": "0.95" } ], "global_debt_limit": [ { "amount": "2000000000000", "denom": "usdx" } ], "surplus_auction_threshold": "9000000", "savings_distribution_frequency": "120000000000" }' $genesis > $genesis.tmp && mv $genesis.tmp $genesis
|
||||
jq '.app_state.cdp.params = { "circuit_breaker": false, "collateral_params": [ { "auction_size": "10000000000", "conversion_factor": "8", "debt_limit": { "amount": "1000000000000", "denom": "usdx" }, "denom": "btc", "liquidation_penalty": "0.05", "liquidation_ratio": "1.5", "market_id": "btc:usd", "prefix": 0, "stability_fee": "1.0000000007829977" }, { "auction_size": "100000000", "conversion_factor": "6", "debt_limit": { "amount": "1000000000000", "denom": "usdx" }, "denom": "xrp", "liquidation_penalty": "0.1", "liquidation_ratio": "2.0", "market_id": "xrp:usd", "prefix": 1, "stability_fee": "1.0000000007829977"} ], "debt_auction_threshold": "10000000000", "debt_param": { "conversion_factor": "6", "debt_floor": "10000000", "denom": "usdx", "reference_asset": "usd", "savings_rate": "0.95" }, "global_debt_limit": { "amount": "2000000000000", "denom": "usdx" }, "surplus_auction_threshold": "10000000000", "savings_distribution_frequency": "43200000000000" }' $genesis > $genesis.tmp && mv $genesis.tmp $genesis
|
||||
# start the blockchain in the background, wait until it starts making blocks
|
||||
{
|
||||
kvd start --home $kvdHome & kvdPid="$!"
|
||||
@ -83,7 +83,7 @@ printf "\n"
|
||||
rm -f rest_test/setuptest
|
||||
go build rest_test/setup/setuptest.go & showLoading "Building go test file, please wait"
|
||||
# run the go code to send transactions to the chain and set it up correctly
|
||||
./setuptest $kvcliHome & showLoading "Sending messages to blockchain"
|
||||
./setuptest
|
||||
printf "\n"
|
||||
printf "Blockchain setup completed"
|
||||
printf "\n\n"
|
||||
|
@ -1,19 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
crkeys "github.com/cosmos/cosmos-sdk/crypto/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkrest "github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/version"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
|
||||
authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
@ -31,19 +30,19 @@ import (
|
||||
)
|
||||
|
||||
func init() {
|
||||
version.Name = "kava"
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
app.SetBip44CoinType(config)
|
||||
config.Seal()
|
||||
keybase = getKeybase()
|
||||
}
|
||||
|
||||
var (
|
||||
keybase crkeys.Keybase
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Printf("Please include the kvcli home directory as a command line argument\n")
|
||||
fmt.Printf("For example: ./setuptest /tmp/kvcliHome\n")
|
||||
fmt.Printf("Exiting...goodbye!\n")
|
||||
return
|
||||
}
|
||||
|
||||
// setup messages send to blockchain so it is in the correct state for testing
|
||||
sendProposal()
|
||||
@ -53,14 +52,6 @@ func main() {
|
||||
sendUndelegation()
|
||||
sendCoins()
|
||||
|
||||
sendProposal()
|
||||
sendDeposit()
|
||||
sendVote()
|
||||
sendDelegation()
|
||||
sendUndelegation()
|
||||
|
||||
sendCoins()
|
||||
|
||||
// create an XRP cdp and send to blockchain
|
||||
sendXrpCdp()
|
||||
|
||||
@ -234,7 +225,7 @@ func sendDeposit() {
|
||||
|
||||
// create a deposit transaction to send to the proposal
|
||||
amount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10000000))
|
||||
deposit := gov.NewMsgDeposit(addr, 2, amount) // TODO IMPORTANT '2' must match 'x-example' in swagger.yaml
|
||||
deposit := gov.NewMsgDeposit(addr, 1, amount) // Note: '1' must match 'x-example' in swagger.yaml
|
||||
depositToSend := []sdk.Msg{deposit}
|
||||
|
||||
sendMsgToBlockchain(cdc, address, keyname, password, depositToSend, keybase)
|
||||
@ -261,7 +252,7 @@ func sendVote() {
|
||||
// NOW SEND THE VOTE
|
||||
|
||||
// create a vote on a proposal to send to the blockchain
|
||||
vote := gov.NewMsgVote(addr, uint64(2), types.OptionYes) // TODO IMPORTANT '2' must match 'x-example' in swagger.yaml
|
||||
vote := gov.NewMsgVote(addr, uint64(1), types.OptionYes) // Note: '1' must match 'x-example' in swagger.yaml
|
||||
|
||||
// send a vote to the blockchain
|
||||
voteToSend := []sdk.Msg{vote}
|
||||
@ -281,7 +272,7 @@ func sendCoins() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
addrTo, err := sdk.AccAddressFromBech32("kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz") // TODO IMPORTANT this is the faucet address
|
||||
addrTo, err := sdk.AccAddressFromBech32("kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz") // Note: must match the faucet address
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -295,7 +286,7 @@ func sendCoins() {
|
||||
// create coins
|
||||
amount := sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 2000000))
|
||||
|
||||
coins := bank.NewMsgSend(addrFrom, addrTo, amount) // TODO IMPORTANT '2' must match 'x-example' in swagger.yaml
|
||||
coins := bank.NewMsgSend(addrFrom, addrTo, amount) // Note: '1' must match 'x-example' in swagger.yaml
|
||||
coinsToSend := []sdk.Msg{coins}
|
||||
|
||||
// NOW SEND THE COINS
|
||||
@ -306,14 +297,14 @@ func sendCoins() {
|
||||
}
|
||||
|
||||
func getTestAddress() (address string) {
|
||||
// the test address - TODO IMPORTANT make sure this lines up with startchain.sh
|
||||
// the test address - Note: this must match with startchain.sh
|
||||
address = "kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c"
|
||||
return address
|
||||
}
|
||||
|
||||
func getKeynameAndPassword() (keyname string, password string) {
|
||||
keyname = "vlad" // TODO - IMPORTANT this must match the keys in the startchain.sh script
|
||||
password = "password" // TODO - IMPORTANT this must match the keys in the startchain.sh script
|
||||
keyname = "vlad" // Note: this must match the keys in the startchain.sh script
|
||||
password = "" // Note: this must match the keys in the startchain.sh script
|
||||
return keyname, password
|
||||
}
|
||||
|
||||
@ -385,13 +376,22 @@ func sendUndelegation() {
|
||||
}
|
||||
|
||||
func getKeybase() crkeys.Keybase {
|
||||
|
||||
if keybase != nil {
|
||||
return keybase
|
||||
}
|
||||
|
||||
// create a keybase
|
||||
// IMPORTANT - TAKE THIS FROM COMMAND LINE PARAMETER and does NOT work with tilde i.e. ~/ does NOT work
|
||||
keybase, err := keys.NewKeyBaseFromDir(os.Args[1])
|
||||
// myKeybase, err := keys.NewKeyBaseFromDir("/tmp/kvcliHome")
|
||||
|
||||
inBuf := strings.NewReader("")
|
||||
keybase, err := crkeys.NewKeyring(sdk.KeyringServiceName(),
|
||||
"test", "/tmp/kvcliHome", inBuf)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return keybase
|
||||
}
|
||||
|
||||
@ -401,16 +401,17 @@ func sendMsgToBlockchain(cdc *codec.Codec, address string, keyname string,
|
||||
|
||||
// get the account number and sequence number
|
||||
accountNumber, sequenceNumber := getAccountNumberAndSequenceNumber(cdc, address)
|
||||
inBuf := bufio.NewReader(os.Stdin)
|
||||
txBldr := auth.NewTxBuilderFromCLI(inBuf).
|
||||
WithTxEncoder(authclient.GetTxEncoder(cdc)).WithChainID("testing").
|
||||
|
||||
txBldr := auth.NewTxBuilder(
|
||||
authclient.GetTxEncoder(cdc), accountNumber, sequenceNumber, 500000, 0,
|
||||
true, "testing", "memo", sdk.NewCoins(), sdk.NewDecCoins(),
|
||||
).WithTxEncoder(authclient.GetTxEncoder(cdc)).WithChainID("testing").
|
||||
WithKeybase(keybase).WithAccountNumber(accountNumber).
|
||||
WithSequence(sequenceNumber).WithGas(500000)
|
||||
|
||||
// build and sign the transaction
|
||||
// this is the *Amino* encoded version of the transaction
|
||||
// fmt.Printf("%+v", txBldr.Keybase())
|
||||
txBytes, err := txBldr.BuildAndSign("vlad", "password", msg)
|
||||
txBytes, err := txBldr.BuildAndSign("vlad", "", msg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -433,7 +434,9 @@ func sendMsgToBlockchain(cdc *codec.Codec, address string, keyname string,
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// fmt.Println("post body: ", string(jsonBytes))
|
||||
fmt.Println()
|
||||
fmt.Println("post body: ", string(jsonBytes))
|
||||
fmt.Println()
|
||||
|
||||
resp, err := http.Post("http://localhost:1317/txs", "application/json", bytes.NewBuffer(jsonBytes))
|
||||
if err != nil {
|
||||
|
@ -237,7 +237,7 @@
|
||||
description: Tx hash
|
||||
required: true
|
||||
type: string
|
||||
x-example: 4B2C3449FF2647BD51B54C32761FE4EBE7AE024BBE2A9898972A69BEB82D97C2
|
||||
x-example: FFC8D0E5D52D330AD315E950E5C18A9D65FC640156B9A95B5AEF07DDAE32E61D
|
||||
responses:
|
||||
200:
|
||||
description: Tx with the provided hash
|
||||
@ -400,13 +400,9 @@
|
||||
owner:
|
||||
$ref: '#/definitions/Address'
|
||||
collateral:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
principal:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinPrincipal'
|
||||
$ref: '#/definitions/CoinPrincipal'
|
||||
responses:
|
||||
200:
|
||||
description: Tx was successfully generated
|
||||
@ -450,9 +446,7 @@
|
||||
depositor:
|
||||
$ref: '#/definitions/Address'
|
||||
collateral:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
responses:
|
||||
200:
|
||||
description: Tx was successfully generated
|
||||
@ -496,9 +490,7 @@
|
||||
depositor:
|
||||
$ref: '#/definitions/Address'
|
||||
collateral:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
responses:
|
||||
200:
|
||||
description: Tx was successfully generated
|
||||
@ -540,9 +532,7 @@
|
||||
owner:
|
||||
$ref: '#/definitions/Address'
|
||||
principal:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinPrincipal'
|
||||
$ref: '#/definitions/CoinPrincipal'
|
||||
responses:
|
||||
200:
|
||||
description: Tx was successfully generated
|
||||
@ -584,9 +574,7 @@
|
||||
owner:
|
||||
$ref: '#/definitions/Address'
|
||||
payment:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinPrincipal'
|
||||
$ref: '#/definitions/CoinPrincipal'
|
||||
responses:
|
||||
200:
|
||||
description: Tx was successfully generated
|
||||
@ -725,7 +713,7 @@
|
||||
description: Server internal error
|
||||
/cdp/cdps/cdp/deposits/{owner}/{denom}:
|
||||
get:
|
||||
summary: Get the deposits associated with the cdp owned by the input owner address and with colalteral type equal to the input collateral denom
|
||||
summary: Get the deposits associated with the cdp owned by the input owner address and with collateral type equal to the input collateral denom
|
||||
tags:
|
||||
- CDP
|
||||
produces:
|
||||
@ -1888,7 +1876,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
@ -1911,7 +1899,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
@ -1934,7 +1922,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
@ -1966,7 +1954,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
- description: ''
|
||||
name: post_deposit_body
|
||||
in: body
|
||||
@ -2007,7 +1995,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: "2"
|
||||
x-example: "1"
|
||||
- type: string
|
||||
description: Bech32 depositor address
|
||||
name: depositor
|
||||
@ -2039,7 +2027,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
@ -2071,7 +2059,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
- description: valid value of `"option"` field can be `"yes"`, `"no"`, `"no_with_veto"` and `"abstain"`
|
||||
name: post_vote_body
|
||||
in: body
|
||||
@ -2111,7 +2099,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
- type: string
|
||||
description: Bech32 voter address
|
||||
name: voter
|
||||
@ -2143,7 +2131,7 @@
|
||||
name: proposalId
|
||||
required: true
|
||||
in: path
|
||||
x-example: '2'
|
||||
x-example: '1'
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
@ -3098,9 +3086,7 @@
|
||||
type: string
|
||||
example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9
|
||||
amount:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
$ref: '#/definitions/CoinCollateral'
|
||||
PricefeedParameters:
|
||||
type: object
|
||||
properties:
|
||||
|
@ -135,7 +135,6 @@ type (
|
||||
QueryAssetSupply = types.QueryAssetSupply
|
||||
QueryAtomicSwapByID = types.QueryAtomicSwapByID
|
||||
QueryAtomicSwaps = types.QueryAtomicSwaps
|
||||
SupplyKeeper = types.SupplyKeeper
|
||||
SwapDirection = types.SwapDirection
|
||||
SwapStatus = types.SwapStatus
|
||||
)
|
||||
|
@ -4,10 +4,18 @@ import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/bep3/types"
|
||||
)
|
||||
|
||||
// InitGenesis initializes the store state from a genesis state.
|
||||
func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper SupplyKeeper, gs GenesisState) {
|
||||
func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper, gs GenesisState) {
|
||||
// Check if the module account exists
|
||||
moduleAcc := supplyKeeper.GetModuleAccount(ctx, ModuleName)
|
||||
if moduleAcc == nil {
|
||||
panic(fmt.Sprintf("%s module account has not been set", ModuleName))
|
||||
}
|
||||
|
||||
if err := gs.Validate(); err != nil {
|
||||
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
|
||||
}
|
||||
|
@ -278,19 +278,14 @@ func (suite *GenesisTestSuite) TestGenesisState() {
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.SetupTest()
|
||||
if tc.expectPass {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.NotPanics(func() {
|
||||
suite.app.InitializeFromGenesisStates(tc.genState())
|
||||
})
|
||||
})
|
||||
suite.NotPanics(func() {
|
||||
suite.app.InitializeFromGenesisStates(tc.genState())
|
||||
}, tc.name)
|
||||
} else {
|
||||
suite.Run(tc.name, func() {
|
||||
suite.Panics(func() {
|
||||
suite.app.InitializeFromGenesisStates(tc.genState())
|
||||
})
|
||||
})
|
||||
suite.Panics(func() {
|
||||
suite.app.InitializeFromGenesisStates(tc.genState())
|
||||
}, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ func loadSwapAndSupply(addr sdk.AccAddress, index int) (bep3.AtomicSwap, bep3.As
|
||||
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
|
||||
swap := bep3.NewAtomicSwap(cs(coin), randomNumberHash,
|
||||
expireOffset, timestamp, addr, addr, TestSenderOtherChain,
|
||||
TestRecipientOtherChain, 0, bep3.Open, true, bep3.Incoming)
|
||||
TestRecipientOtherChain, 1, bep3.Open, true, bep3.Incoming)
|
||||
|
||||
supply := bep3.NewAssetSupply(coin.Denom, coin, c(coin.Denom, 0),
|
||||
c(coin.Denom, 0), c(coin.Denom, StandardSupplyLimit.Int64()))
|
||||
|
@ -19,14 +19,14 @@ type Keeper struct {
|
||||
cdc *codec.Codec
|
||||
paramSubspace subspace.Subspace
|
||||
supplyKeeper types.SupplyKeeper
|
||||
accountKeeper types.AccountKeeper
|
||||
}
|
||||
|
||||
// NewKeeper creates a bep3 keeper
|
||||
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.SupplyKeeper, paramstore subspace.Subspace) Keeper {
|
||||
if addr := sk.GetModuleAddress(types.ModuleName); addr == nil {
|
||||
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
|
||||
}
|
||||
|
||||
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey,
|
||||
sk types.SupplyKeeper, ak types.AccountKeeper,
|
||||
paramstore subspace.Subspace,
|
||||
) Keeper {
|
||||
if !paramstore.HasKeyTable() {
|
||||
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
|
||||
}
|
||||
@ -36,6 +36,7 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.SupplyKeeper, params
|
||||
cdc: cdc,
|
||||
paramSubspace: paramstore,
|
||||
supplyKeeper: sk,
|
||||
accountKeeper: ak,
|
||||
}
|
||||
return keeper
|
||||
}
|
||||
|
@ -55,6 +55,13 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times
|
||||
|
||||
switch direction {
|
||||
case types.Incoming:
|
||||
// If recipient's account doesn't exist, register it in state so that the address can send
|
||||
// a claim swap tx without needing to be registered in state by receiving a coin transfer.
|
||||
recipientAcc := k.accountKeeper.GetAccount(ctx, recipient)
|
||||
if recipientAcc == nil {
|
||||
newAcc := k.accountKeeper.NewAccountWithAddress(ctx, recipient)
|
||||
k.accountKeeper.SetAccount(ctx, newAcc)
|
||||
}
|
||||
err = k.IncrementIncomingAssetSupply(ctx, amount[0])
|
||||
case types.Outgoing:
|
||||
err = k.IncrementOutgoingAssetSupply(ctx, amount[0])
|
||||
|
@ -11,7 +11,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
sim "github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
@ -19,6 +18,7 @@ import (
|
||||
"github.com/kava-labs/kava/x/bep3/client/cli"
|
||||
"github.com/kava-labs/kava/x/bep3/client/rest"
|
||||
"github.com/kava-labs/kava/x/bep3/simulation"
|
||||
"github.com/kava-labs/kava/x/bep3/types"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -78,12 +78,12 @@ type AppModule struct {
|
||||
AppModuleBasic
|
||||
|
||||
keeper Keeper
|
||||
accountKeeper auth.AccountKeeper
|
||||
supplyKeeper SupplyKeeper
|
||||
accountKeeper types.AccountKeeper
|
||||
supplyKeeper types.SupplyKeeper
|
||||
}
|
||||
|
||||
// NewAppModule creates a new AppModule object
|
||||
func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper SupplyKeeper) AppModule {
|
||||
func NewAppModule(keeper Keeper, accountKeeper types.AccountKeeper, supplyKeeper types.SupplyKeeper) AppModule {
|
||||
return AppModule{
|
||||
AppModuleBasic: AppModuleBasic{},
|
||||
keeper: keeper,
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
appparams "github.com/kava-labs/kava/app/params"
|
||||
@ -28,7 +27,7 @@ const (
|
||||
|
||||
// WeightedOperations returns all the operations from the module with their respective weights
|
||||
func WeightedOperations(
|
||||
appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, k keeper.Keeper,
|
||||
appParams simulation.AppParams, cdc *codec.Codec, ak types.AccountKeeper, k keeper.Keeper,
|
||||
) simulation.WeightedOperations {
|
||||
var weightCreateAtomicSwap int
|
||||
|
||||
@ -47,19 +46,54 @@ func WeightedOperations(
|
||||
}
|
||||
|
||||
// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values
|
||||
func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation {
|
||||
func SimulateMsgCreateAtomicSwap(ak types.AccountKeeper, k keeper.Keeper) simulation.Operation {
|
||||
return func(
|
||||
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
|
||||
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||
|
||||
senderAddr := k.GetBnbDeputyAddress(ctx)
|
||||
|
||||
sender, found := simulation.FindAccount(accs, senderAddr)
|
||||
if !found {
|
||||
// Set up deputy address as it's required for all atomic swaps
|
||||
deputyAddr := k.GetBnbDeputyAddress(ctx)
|
||||
deputyAcc, foundDeputy := simulation.FindAccount(accs, deputyAddr)
|
||||
if !foundDeputy {
|
||||
return noOpMsg, nil, nil
|
||||
}
|
||||
|
||||
recipient, _ := simulation.RandomAcc(r, accs)
|
||||
// Get asset supplies and shuffle them
|
||||
supplies := k.GetAllAssetSupplies(ctx)
|
||||
r.Shuffle(len(supplies), func(i, j int) {
|
||||
supplies[i], supplies[j] = supplies[j], supplies[i]
|
||||
})
|
||||
|
||||
// Search for an account that holds coins received by an atomic swap
|
||||
senderOut, asset, found := findValidAccountAssetSupplyPair(accs, supplies, func(acc simulation.Account, asset types.AssetSupply) bool {
|
||||
if asset.CurrentSupply.Amount.IsPositive() {
|
||||
authAcc := ak.GetAccount(ctx, acc.Address)
|
||||
if authAcc.SpendableCoins(ctx.BlockTime()).AmountOf(asset.Denom).IsPositive() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Set sender, recipient, and denom depending on swap direction
|
||||
var sender simulation.Account
|
||||
var recipient simulation.Account
|
||||
var denom string
|
||||
// If an outgoing swap can be created, it's chosen 50% of the time.
|
||||
if found && r.Intn(100) < 50 {
|
||||
sender = senderOut
|
||||
recipient = deputyAcc
|
||||
denom = asset.Denom
|
||||
} else {
|
||||
sender = deputyAcc
|
||||
recipient, _ = simulation.RandomAcc(r, accs)
|
||||
// Randomly select an asset from supported assets
|
||||
assets, foundAsset := k.GetAssets(ctx)
|
||||
if !foundAsset {
|
||||
return noOpMsg, nil, fmt.Errorf("no supported assets found")
|
||||
}
|
||||
denom = assets[r.Intn(len(assets))].Denom
|
||||
}
|
||||
|
||||
recipientOtherChain := simulation.RandStringOfLength(r, 43)
|
||||
senderOtherChain := simulation.RandStringOfLength(r, 43)
|
||||
@ -73,36 +107,41 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat
|
||||
timestamp := ctx.BlockTime().Unix()
|
||||
randomNumberHash := types.CalculateRandomHash(randomNumber.BigInt().Bytes(), timestamp)
|
||||
|
||||
// Randomly select an asset from supported assets
|
||||
assets, found := k.GetAssets(ctx)
|
||||
if !found {
|
||||
return noOpMsg, nil, fmt.Errorf("no supported assets found")
|
||||
}
|
||||
asset := assets[r.Intn(len(assets))]
|
||||
|
||||
// Check that the sender has coins of this type
|
||||
senderAcc := ak.GetAccount(ctx, senderAddr)
|
||||
// Check that the sender has coins for fee
|
||||
senderAcc := ak.GetAccount(ctx, sender.Address)
|
||||
fees, err := simulation.RandomFees(r, ctx, senderAcc.SpendableCoins(ctx.BlockTime()))
|
||||
if err != nil {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, err
|
||||
}
|
||||
availableAmount := senderAcc.SpendableCoins(ctx.BlockTime()).Sub(fees).AmountOf(asset.Denom)
|
||||
// Get an amount of coins between 0.1 and 2% of total coins
|
||||
amount := availableAmount.Quo(sdk.NewInt(int64(simulation.RandIntBetween(r, 50, 1000))))
|
||||
if amount.IsZero() {
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, fmt.Sprintf("no-operation (all funds exhausted for asset %s)", asset.Denom), "", false, nil), nil, nil
|
||||
|
||||
// Get maximum valid amount
|
||||
maximumAmount := senderAcc.SpendableCoins(ctx.BlockTime()).Sub(fees).AmountOf(denom)
|
||||
// The maximum amount for outgoing swaps is limited by the asset's current supply
|
||||
if recipient.Equals(deputyAcc) {
|
||||
assetSupply, foundAssetSupply := k.GetAssetSupply(ctx, []byte(denom))
|
||||
if !foundAssetSupply {
|
||||
return noOpMsg, nil, fmt.Errorf("no asset supply found")
|
||||
}
|
||||
if maximumAmount.GT(assetSupply.CurrentSupply.Amount) {
|
||||
maximumAmount = assetSupply.CurrentSupply.Amount
|
||||
}
|
||||
}
|
||||
coin := sdk.NewCoin(asset.Denom, amount)
|
||||
coins := sdk.NewCoins(coin)
|
||||
expectedIncome := coin.String()
|
||||
|
||||
// Get an amount of coins between 0.1 and 2% of total coins
|
||||
amount := maximumAmount.Quo(sdk.NewInt(int64(simulation.RandIntBetween(r, 50, 1000))))
|
||||
if amount.IsZero() {
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, fmt.Sprintf("no-operation (all funds exhausted for asset %s)", denom), "", false, nil), nil, nil
|
||||
}
|
||||
coins := sdk.NewCoins(sdk.NewCoin(denom, amount))
|
||||
expectedIncome := coins.String()
|
||||
|
||||
// We're assuming that sims are run with -NumBlocks=100
|
||||
heightSpan := int64(55)
|
||||
crossChain := true
|
||||
|
||||
msg := types.NewMsgCreateAtomicSwap(
|
||||
senderAddr, recipient.Address, recipientOtherChain, senderOtherChain, randomNumberHash,
|
||||
timestamp, coins, expectedIncome, heightSpan, crossChain,
|
||||
sender.Address, recipient.Address, recipientOtherChain, senderOtherChain,
|
||||
randomNumberHash, timestamp, coins, expectedIncome, heightSpan, crossChain,
|
||||
)
|
||||
|
||||
tx := helpers.GenTx(
|
||||
@ -120,7 +159,7 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, err
|
||||
}
|
||||
|
||||
// If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation
|
||||
// Construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation
|
||||
var futureOp simulation.FutureOperation
|
||||
swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain)
|
||||
if r.Intn(100) < 50 {
|
||||
@ -143,7 +182,7 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat
|
||||
}
|
||||
}
|
||||
|
||||
func operationClaimAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper, swapID []byte, randomNumber []byte) simulation.Operation {
|
||||
func operationClaimAtomicSwap(ak types.AccountKeeper, k keeper.Keeper, swapID []byte, randomNumber []byte) simulation.Operation {
|
||||
return func(
|
||||
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
|
||||
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||
@ -176,7 +215,7 @@ func operationClaimAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper, swapID []b
|
||||
}
|
||||
}
|
||||
|
||||
func operationRefundAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper, swapID []byte) simulation.Operation {
|
||||
func operationRefundAtomicSwap(ak types.AccountKeeper, k keeper.Keeper, swapID []byte) simulation.Operation {
|
||||
return func(
|
||||
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string,
|
||||
) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||
@ -208,3 +247,16 @@ func operationRefundAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper, swapID []
|
||||
return simulation.NewOperationMsg(msg, true, result.Log), nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
// findValidAccountAssetSupplyPair finds an account for which the callback func returns true
|
||||
func findValidAccountAssetSupplyPair(accounts []simulation.Account, supplies types.AssetSupplies,
|
||||
cb func(simulation.Account, types.AssetSupply) bool) (simulation.Account, types.AssetSupply, bool) {
|
||||
for _, supply := range supplies {
|
||||
for _, acc := range accounts {
|
||||
if isValid := cb(acc, supply); isValid {
|
||||
return acc, supply, true
|
||||
}
|
||||
}
|
||||
}
|
||||
return simulation.Account{}, types.AssetSupply{}, false
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// AssetSupply contains information about an asset's supply
|
||||
@ -26,6 +27,23 @@ func NewAssetSupply(denom string, incomingSupply, outgoingSupply, currentSupply,
|
||||
}
|
||||
}
|
||||
|
||||
// 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.Limit.IsValid() {
|
||||
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "limit %s", a.Limit)
|
||||
}
|
||||
return sdk.ValidateDenom(a.Denom)
|
||||
}
|
||||
|
||||
// String implements stringer
|
||||
func (a AssetSupply) String() string {
|
||||
return fmt.Sprintf("Asset Supply"+
|
||||
|
70
x/bep3/types/asset_test.go
Normal file
70
x/bep3/types/asset_test.go
Normal file
@ -0,0 +1,70 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAssetSupplyValidate(t *testing.T) {
|
||||
coin := sdk.NewCoin("kava", sdk.OneInt())
|
||||
invalidCoin := sdk.Coin{Denom: "Invalid Denom", Amount: sdk.NewInt(-1)}
|
||||
testCases := []struct {
|
||||
msg string
|
||||
asset AssetSupply
|
||||
expPass bool
|
||||
}{
|
||||
{
|
||||
msg: "valid asset",
|
||||
asset: NewAssetSupply("kava", coin, coin, coin, coin),
|
||||
expPass: true,
|
||||
},
|
||||
{
|
||||
"invalid incoming supply",
|
||||
AssetSupply{IncomingSupply: invalidCoin},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid outgoing supply",
|
||||
AssetSupply{
|
||||
IncomingSupply: coin,
|
||||
OutgoingSupply: invalidCoin,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid current supply",
|
||||
AssetSupply{
|
||||
IncomingSupply: coin,
|
||||
OutgoingSupply: coin,
|
||||
CurrentSupply: invalidCoin,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid limit",
|
||||
AssetSupply{
|
||||
IncomingSupply: coin,
|
||||
OutgoingSupply: coin,
|
||||
CurrentSupply: coin,
|
||||
Limit: invalidCoin,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
msg: "invalid denom",
|
||||
asset: NewAssetSupply("Invalid Denom", coin, coin, coin, coin),
|
||||
expPass: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
err := tc.asset.Validate()
|
||||
if tc.expPass {
|
||||
require.NoError(t, err, tc.msg)
|
||||
} else {
|
||||
require.Error(t, err, tc.msg)
|
||||
}
|
||||
}
|
||||
}
|
@ -31,7 +31,7 @@ func atomicSwap(index int) types.AtomicSwap {
|
||||
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
|
||||
|
||||
swap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash, expireOffset, timestamp, kavaAddrs[0],
|
||||
kavaAddrs[1], binanceAddrs[0].String(), binanceAddrs[1].String(), 0, types.Open, true, types.Incoming)
|
||||
kavaAddrs[1], binanceAddrs[0].String(), binanceAddrs[1].String(), 1, types.Open, true, types.Incoming)
|
||||
|
||||
return swap
|
||||
}
|
||||
|
@ -2,18 +2,22 @@ package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
)
|
||||
|
||||
// SupplyKeeper defines the expected supply Keeper
|
||||
// SupplyKeeper defines the expected supply keeper (noalias)
|
||||
type SupplyKeeper interface {
|
||||
GetModuleAddress(name string) sdk.AccAddress
|
||||
GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI
|
||||
|
||||
SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) error
|
||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
|
||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
|
||||
|
||||
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error
|
||||
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error
|
||||
}
|
||||
|
||||
// AccountKeeper defines the expected account keeper (noalias)
|
||||
type AccountKeeper interface {
|
||||
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
||||
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authexported.Account
|
||||
SetAccount(ctx sdk.Context, acc authexported.Account)
|
||||
}
|
||||
|
@ -48,19 +48,31 @@ func (gs GenesisState) Validate() error {
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
denoms := map[string]bool{}
|
||||
for _, a := range gs.AssetSupplies {
|
||||
if denoms[a.Denom] {
|
||||
return fmt.Errorf("found duplicate asset denom %s", a.Denom)
|
||||
for _, asset := range gs.AssetSupplies {
|
||||
if denoms[asset.Denom] {
|
||||
return fmt.Errorf("found duplicate asset denom %s", asset.Denom)
|
||||
}
|
||||
denoms[a.Denom] = true
|
||||
|
||||
if err := asset.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
denoms[asset.Denom] = true
|
||||
}
|
||||
|
||||
ids := map[string]bool{}
|
||||
for _, a := range gs.AtomicSwaps {
|
||||
if ids[hex.EncodeToString(a.GetSwapID())] {
|
||||
return fmt.Errorf("found duplicate atomic swap ID %s", hex.EncodeToString(a.GetSwapID()))
|
||||
for _, swap := range gs.AtomicSwaps {
|
||||
if ids[hex.EncodeToString(swap.GetSwapID())] {
|
||||
return fmt.Errorf("found duplicate atomic swap ID %s", hex.EncodeToString(swap.GetSwapID()))
|
||||
}
|
||||
ids[hex.EncodeToString(a.GetSwapID())] = true
|
||||
|
||||
if err := swap.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ids[hex.EncodeToString(swap.GetSwapID())] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -21,12 +21,10 @@ func (suite *GenesisTestSuite) SetupTest() {
|
||||
config := sdk.GetConfig()
|
||||
app.SetBech32AddressPrefixes(config)
|
||||
|
||||
count := 10
|
||||
suite.swaps = atomicSwaps(count)
|
||||
coin := sdk.NewCoin("kava", sdk.OneInt())
|
||||
suite.swaps = atomicSwaps(10)
|
||||
|
||||
incomingSupply := int64(count * 50000)
|
||||
supply := types.NewAssetSupply("bnb", c("bnb", incomingSupply),
|
||||
c("bnb", 0), c("bnb", 0), c("bnb", 100000000000))
|
||||
supply := types.NewAssetSupply("kava", coin, coin, coin, coin)
|
||||
suite.supplies = types.AssetSupplies{supply}
|
||||
}
|
||||
|
||||
@ -72,6 +70,22 @@ func (suite *GenesisTestSuite) TestValidate() {
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid swap",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{types.AtomicSwap{Amount: sdk.Coins{sdk.Coin{Denom: "Invalid Denom", Amount: sdk.NewInt(-1)}}}},
|
||||
supplies: types.AssetSupplies{},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid supply",
|
||||
args{
|
||||
swaps: types.AtomicSwaps{},
|
||||
supplies: types.AssetSupplies{types.AssetSupply{Denom: "Invalid Denom"}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"duplicate supplies",
|
||||
args{
|
||||
@ -82,22 +96,19 @@ func (suite *GenesisTestSuite) TestValidate() {
|
||||
}}
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.SetupTest()
|
||||
suite.Run(tc.name, func() {
|
||||
var gs types.GenesisState
|
||||
if tc.name == "default" {
|
||||
gs = types.DefaultGenesisState()
|
||||
} else {
|
||||
gs = types.NewGenesisState(types.DefaultParams(), tc.args.swaps, tc.args.supplies)
|
||||
}
|
||||
var gs types.GenesisState
|
||||
if tc.name == "default" {
|
||||
gs = types.DefaultGenesisState()
|
||||
} else {
|
||||
gs = types.NewGenesisState(types.DefaultParams(), tc.args.swaps, tc.args.supplies)
|
||||
}
|
||||
|
||||
err := gs.Validate()
|
||||
if tc.expectPass {
|
||||
suite.Nil(err)
|
||||
} else {
|
||||
suite.Error(err)
|
||||
}
|
||||
})
|
||||
err := gs.Validate()
|
||||
if tc.expectPass {
|
||||
suite.Require().NoError(err, tc.name)
|
||||
} else {
|
||||
suite.Require().Error(err, tc.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,11 +3,14 @@ package types
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
tmbytes "github.com/tendermint/tendermint/libs/bytes"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// AtomicSwap contains the information for an atomic swap
|
||||
@ -56,19 +59,50 @@ func (a AtomicSwap) GetCoins() sdk.Coins {
|
||||
return sdk.NewCoins(a.Amount...)
|
||||
}
|
||||
|
||||
// Validate verifies that recipient is not empty
|
||||
// Validate performs a basic validation of an atomic swap fields.
|
||||
func (a AtomicSwap) Validate() error {
|
||||
if len(a.Sender) != AddrByteCount {
|
||||
return fmt.Errorf(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Sender)))
|
||||
}
|
||||
if len(a.Recipient) != AddrByteCount {
|
||||
return fmt.Errorf(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Recipient)))
|
||||
}
|
||||
if len(a.RandomNumberHash) != RandomNumberHashLength {
|
||||
return fmt.Errorf(fmt.Sprintf("the length of random number hash should be %d", RandomNumberHashLength))
|
||||
if !a.Amount.IsValid() {
|
||||
return fmt.Errorf("invalid amount: %s", a.Amount)
|
||||
}
|
||||
if !a.Amount.IsAllPositive() {
|
||||
return fmt.Errorf(fmt.Sprintf("the swapped out coin must be positive"))
|
||||
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.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
|
||||
}
|
||||
@ -111,6 +145,7 @@ func (swaps AtomicSwaps) String() string {
|
||||
// SwapStatus is the status of an AtomicSwap
|
||||
type SwapStatus byte
|
||||
|
||||
// swap statuses
|
||||
const (
|
||||
NULL SwapStatus = 0x00
|
||||
Open SwapStatus = 0x01
|
||||
|
@ -44,95 +44,205 @@ func (suite *AtomicSwapTestSuite) SetupTest() {
|
||||
}
|
||||
|
||||
func (suite *AtomicSwapTestSuite) TestNewAtomicSwap() {
|
||||
type args struct {
|
||||
amount sdk.Coins
|
||||
randomNumberHash tmbytes.HexBytes
|
||||
expireHeight int64
|
||||
timestamp int64
|
||||
sender sdk.AccAddress
|
||||
recipient sdk.AccAddress
|
||||
recipientOtherChain string
|
||||
senderOtherChain string
|
||||
closedBlock int64
|
||||
status types.SwapStatus
|
||||
crossChain bool
|
||||
direction types.SwapDirection
|
||||
}
|
||||
testCases := []struct {
|
||||
description string
|
||||
args args
|
||||
expectPass bool
|
||||
msg string
|
||||
swap types.AtomicSwap
|
||||
expectPass bool
|
||||
}{
|
||||
{
|
||||
"normal",
|
||||
args{
|
||||
amount: cs(c("bnb", 50000)),
|
||||
randomNumberHash: suite.randomNumberHashes[0],
|
||||
expireHeight: int64(360),
|
||||
timestamp: suite.timestamps[0],
|
||||
sender: suite.addrs[0],
|
||||
recipient: suite.addrs[5],
|
||||
recipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
senderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
closedBlock: 0,
|
||||
status: types.Open,
|
||||
crossChain: true,
|
||||
direction: types.Incoming,
|
||||
"valid Swap",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 360,
|
||||
Timestamp: suite.timestamps[0],
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5],
|
||||
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
ClosedBlock: 1,
|
||||
Status: types.Open,
|
||||
CrossChain: true,
|
||||
Direction: types.Incoming,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid random number hash length",
|
||||
args{
|
||||
amount: cs(c("bnb", 50000)),
|
||||
randomNumberHash: suite.randomNumberHashes[1][0:20],
|
||||
expireHeight: int64(360),
|
||||
timestamp: suite.timestamps[1],
|
||||
sender: suite.addrs[1],
|
||||
recipient: suite.addrs[5],
|
||||
recipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
senderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
closedBlock: 0,
|
||||
status: types.Open,
|
||||
crossChain: true,
|
||||
direction: types.Incoming,
|
||||
"invalid amount",
|
||||
types.AtomicSwap{
|
||||
Amount: sdk.Coins{sdk.Coin{Denom: "BNB", Amount: sdk.NewInt(10)}},
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid amount",
|
||||
args{
|
||||
amount: cs(c("bnb", 0)),
|
||||
randomNumberHash: suite.randomNumberHashes[2],
|
||||
expireHeight: int64(360),
|
||||
timestamp: suite.timestamps[2],
|
||||
sender: suite.addrs[2],
|
||||
recipient: suite.addrs[5],
|
||||
recipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
senderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
closedBlock: 0,
|
||||
status: types.Open,
|
||||
crossChain: true,
|
||||
direction: types.Incoming,
|
||||
"amount not positive",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 0)),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid random number hash length",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[1][0:20],
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"exp height 0",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"timestamp 0",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty sender",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: nil,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"empty recipient",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: nil,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid sender length",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0][:10],
|
||||
Recipient: suite.addrs[5],
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid recipient length",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5][:10],
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid sender other chain",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5],
|
||||
SenderOtherChain: "",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid recipient other chain",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5],
|
||||
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
RecipientOtherChain: "",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"closed block 0",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5],
|
||||
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
ClosedBlock: 0,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid status 0",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5],
|
||||
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
ClosedBlock: 1,
|
||||
Status: types.NULL,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid direction ",
|
||||
types.AtomicSwap{
|
||||
Amount: cs(c("bnb", 50000)),
|
||||
RandomNumberHash: suite.randomNumberHashes[0],
|
||||
ExpireHeight: 10,
|
||||
Timestamp: 10,
|
||||
Sender: suite.addrs[0],
|
||||
Recipient: suite.addrs[5],
|
||||
SenderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
|
||||
RecipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
|
||||
ClosedBlock: 1,
|
||||
Status: types.Open,
|
||||
Direction: types.INVALID,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
// Create atomic swap
|
||||
swap := types.NewAtomicSwap(tc.args.amount, tc.args.randomNumberHash, tc.args.expireHeight,
|
||||
tc.args.timestamp, tc.args.sender, tc.args.recipient, tc.args.senderOtherChain,
|
||||
tc.args.recipientOtherChain, tc.args.closedBlock, tc.args.status, tc.args.crossChain,
|
||||
tc.args.direction)
|
||||
|
||||
err := tc.swap.Validate()
|
||||
if tc.expectPass {
|
||||
suite.Nil(swap.Validate())
|
||||
suite.Equal(tc.args.amount, swap.GetCoins())
|
||||
expectedSwapID := types.CalculateSwapID(tc.args.randomNumberHash, tc.args.sender, tc.args.senderOtherChain)
|
||||
suite.Equal(tmbytes.HexBytes(expectedSwapID), swap.GetSwapID())
|
||||
suite.Require().NoError(err, tc.msg)
|
||||
suite.Require().Equal(tc.swap.Amount, tc.swap.GetCoins())
|
||||
|
||||
expectedSwapID := types.CalculateSwapID(tc.swap.RandomNumberHash, tc.swap.Sender, tc.swap.SenderOtherChain)
|
||||
suite.Require().Equal(tmbytes.HexBytes(expectedSwapID), tc.swap.GetSwapID())
|
||||
} else {
|
||||
suite.Error(swap.Validate())
|
||||
suite.Require().Error(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ const (
|
||||
EventTypeCdpLiquidation = types.EventTypeCdpLiquidation
|
||||
EventTypeBeginBlockerFatal = types.EventTypeBeginBlockerFatal
|
||||
AttributeKeyCdpID = types.AttributeKeyCdpID
|
||||
AttributeKeyDepositor = types.AttributeKeyDepositor
|
||||
AttributeKeyDeposit = types.AttributeKeyDeposit
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeKeyError = types.AttributeKeyError
|
||||
ModuleName = types.ModuleName
|
||||
|
@ -40,7 +40,7 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error {
|
||||
types.EventTypeCdpLiquidation,
|
||||
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||
sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyDepositor, fmt.Sprintf("%s", dep.Depositor)),
|
||||
sdk.NewAttribute(types.AttributeKeyDeposit, dep.String()),
|
||||
),
|
||||
)
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, types.LiquidatorMacc, sdk.NewCoins(dep.Amount))
|
||||
|
@ -18,10 +18,12 @@ The cdp module emits the following events:
|
||||
|
||||
### MsgWithdraw
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------|---------------|------------------|
|
||||
| message | module | cdp |
|
||||
| message | sender | {sender address} |
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------|--------------- |-----------------------|
|
||||
| message | cdp_withdrawal | {collateral amount} |
|
||||
| message | cdp_id | {cdp_id} |
|
||||
| message | module | cdp |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
### MsgDeposit
|
||||
|
||||
@ -43,10 +45,13 @@ The cdp module emits the following events:
|
||||
|
||||
### MsgRepayDebt
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------|---------------|------------------|
|
||||
| message | module | cdp |
|
||||
| message | sender | {sender address} |
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------------|---------------|--------------------|
|
||||
| cdp_repayment | amount | {repayment amount} |
|
||||
| cdp_repayment | cdp_id | {cdp id} |
|
||||
| cdp_close | cdp_id | {cdp id} |
|
||||
| message | module | cdp |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
## BeginBlock
|
||||
|
||||
@ -54,6 +59,6 @@ The cdp module emits the following events:
|
||||
|-------------------------|---------------|---------------------|
|
||||
| cdp_liquidation | module | cdp |
|
||||
| cdp_liquidation | cdp_id | {cdp id} |
|
||||
| cdp_liquidation | depositor | {depositor address} |
|
||||
| cdp_liquidation | deposit | {deposit} |
|
||||
| cdp_begin_blocker_error | module | cdp |
|
||||
| cdp_begin_blocker_error | error_message | {error} |
|
||||
|
@ -12,7 +12,7 @@ const (
|
||||
EventTypeBeginBlockerFatal = "cdp_begin_block_error"
|
||||
|
||||
AttributeKeyCdpID = "cdp_id"
|
||||
AttributeKeyDepositor = "depositor"
|
||||
AttributeKeyDeposit = "deposit"
|
||||
AttributeValueCategory = "cdp"
|
||||
AttributeKeyError = "error_message"
|
||||
)
|
||||
|
@ -12,6 +12,7 @@ const (
|
||||
AttributeKeyCommitteeID = types.AttributeKeyCommitteeID
|
||||
AttributeKeyProposalCloseStatus = types.AttributeKeyProposalCloseStatus
|
||||
AttributeKeyProposalID = types.AttributeKeyProposalID
|
||||
AttributeKeyVoter = types.AttributeKeyVoter
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeValueProposalFailed = types.AttributeValueProposalFailed
|
||||
AttributeValueProposalPassed = types.AttributeValueProposalPassed
|
||||
@ -43,6 +44,10 @@ var (
|
||||
// function aliases
|
||||
NewKeeper = keeper.NewKeeper
|
||||
NewQuerier = keeper.NewQuerier
|
||||
RegisterInvariants = keeper.RegisterInvariants
|
||||
ValidCommitteesInvariant = keeper.ValidCommitteesInvariant
|
||||
ValidProposalsInvariant = keeper.ValidProposalsInvariant
|
||||
ValidVotesInvariant = keeper.ValidVotesInvariant
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
GetKeyFromID = types.GetKeyFromID
|
||||
GetVoteKey = types.GetVoteKey
|
||||
|
145
x/committee/keeper/invariants.go
Normal file
145
x/committee/keeper/invariants.go
Normal file
@ -0,0 +1,145 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
// RegisterInvariants registers all committee invariants
|
||||
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
|
||||
|
||||
ir.RegisterRoute(types.ModuleName, "valid-committees",
|
||||
ValidCommitteesInvariant(k))
|
||||
ir.RegisterRoute(types.ModuleName, "valid-proposals",
|
||||
ValidProposalsInvariant(k))
|
||||
ir.RegisterRoute(types.ModuleName, "valid-votes",
|
||||
ValidVotesInvariant(k))
|
||||
}
|
||||
|
||||
// ValidCommitteesInvariant verifies that all committees in the store are independently valid
|
||||
func ValidCommitteesInvariant(k Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
|
||||
var validationErr error
|
||||
var invalidCommittee types.Committee
|
||||
k.IterateCommittees(ctx, func(com types.Committee) bool {
|
||||
|
||||
if err := com.Validate(); err != nil {
|
||||
validationErr = err
|
||||
invalidCommittee = com
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
broken := validationErr != nil
|
||||
invariantMessage := sdk.FormatInvariant(
|
||||
types.ModuleName,
|
||||
"valid committees",
|
||||
fmt.Sprintf(
|
||||
"\tfound invalid committee, reason: %s\n"+
|
||||
"\tcommittee:\n\t%+v\n",
|
||||
validationErr, invalidCommittee),
|
||||
)
|
||||
return invariantMessage, broken
|
||||
}
|
||||
}
|
||||
|
||||
// ValidProposalsInvariant verifies that all proposals in the store are valid
|
||||
func ValidProposalsInvariant(k Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
|
||||
var validationErr error
|
||||
var invalidProposal types.Proposal
|
||||
k.IterateProposals(ctx, func(proposal types.Proposal) bool {
|
||||
invalidProposal = proposal
|
||||
|
||||
if err := proposal.PubProposal.ValidateBasic(); err != nil {
|
||||
validationErr = err
|
||||
return true
|
||||
}
|
||||
|
||||
currentTime := ctx.BlockTime()
|
||||
if !currentTime.Equal(time.Time{}) { // this avoids a simulator bug where app.InitGenesis is called with blockTime=0 instead of the correct time
|
||||
if proposal.Deadline.Before(currentTime) {
|
||||
validationErr = fmt.Errorf("deadline after current block time %s", currentTime)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
com, found := k.GetCommittee(ctx, proposal.CommitteeID)
|
||||
if !found {
|
||||
validationErr = fmt.Errorf("proposal has no committee %d", proposal.CommitteeID)
|
||||
return true
|
||||
}
|
||||
|
||||
if !com.HasPermissionsFor(proposal.PubProposal) {
|
||||
validationErr = fmt.Errorf("proposal not permitted for committee %+v", com)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
broken := validationErr != nil
|
||||
invariantMessage := sdk.FormatInvariant(
|
||||
types.ModuleName,
|
||||
"valid proposals",
|
||||
fmt.Sprintf(
|
||||
"\tfound invalid proposal, reason: %s\n"+
|
||||
"\tproposal:\n\t%s\n",
|
||||
validationErr, invalidProposal),
|
||||
)
|
||||
return invariantMessage, broken
|
||||
}
|
||||
}
|
||||
|
||||
// ValidVotesInvariant verifies that all votes in the store are valid
|
||||
func ValidVotesInvariant(k Keeper) sdk.Invariant {
|
||||
return func(ctx sdk.Context) (string, bool) {
|
||||
|
||||
var validationErr error
|
||||
var invalidVote types.Vote
|
||||
k.IterateVotes(ctx, func(vote types.Vote) bool {
|
||||
invalidVote = vote
|
||||
|
||||
if vote.Voter.Empty() {
|
||||
validationErr = fmt.Errorf("empty voter address")
|
||||
return true
|
||||
}
|
||||
|
||||
proposal, found := k.GetProposal(ctx, vote.ProposalID)
|
||||
if !found {
|
||||
validationErr = fmt.Errorf("vote has no proposal %d", vote.ProposalID)
|
||||
return true
|
||||
}
|
||||
|
||||
com, found := k.GetCommittee(ctx, proposal.CommitteeID)
|
||||
if !found {
|
||||
validationErr = fmt.Errorf("vote's proposal has no committee %d", proposal.CommitteeID)
|
||||
return true
|
||||
}
|
||||
if !com.HasMember(vote.Voter) {
|
||||
validationErr = fmt.Errorf("voter is not a member of committee %+v", com)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
broken := validationErr != nil
|
||||
invariantMessage := sdk.FormatInvariant(
|
||||
types.ModuleName,
|
||||
"valid votes",
|
||||
fmt.Sprintf(
|
||||
"\tfound invalid vote, reason: %s\n"+
|
||||
"\tvote:\n\t%+v\n",
|
||||
validationErr, invalidVote),
|
||||
)
|
||||
return invariantMessage, broken
|
||||
}
|
||||
}
|
@ -74,6 +74,7 @@ func (k Keeper) AddVote(ctx sdk.Context, proposalID uint64, voter sdk.AccAddress
|
||||
types.EventTypeProposalVote,
|
||||
sdk.NewAttribute(types.AttributeKeyCommitteeID, fmt.Sprintf("%d", com.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", pr.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyVoter, voter.String()),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
|
@ -95,7 +95,9 @@ func (AppModule) Name() string {
|
||||
}
|
||||
|
||||
// RegisterInvariants register module invariants
|
||||
func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
|
||||
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
|
||||
RegisterInvariants(ir, am.keeper)
|
||||
}
|
||||
|
||||
// Route module message route name
|
||||
func (AppModule) Route() string {
|
||||
@ -149,12 +151,12 @@ func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) {
|
||||
simulation.RandomizedGenState(simState)
|
||||
}
|
||||
|
||||
// TODO
|
||||
func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent {
|
||||
return nil
|
||||
// ProposalContents returns functions that generate gov proposals for the module
|
||||
func (am AppModule) ProposalContents(simState module.SimulationState) []sim.WeightedProposalContent {
|
||||
return simulation.ProposalContents(am.keeper, simState.ParamChanges)
|
||||
}
|
||||
|
||||
// RandomizedParams returns functions that generate params for the module.
|
||||
// RandomizedParams returns functions that generate params for the module
|
||||
func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange {
|
||||
return nil
|
||||
}
|
||||
@ -164,7 +166,7 @@ func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
|
||||
sdr[StoreKey] = simulation.DecodeStore
|
||||
}
|
||||
|
||||
// WeightedOperations returns the all the auction module operations with their respective weights.
|
||||
// WeightedOperations returns the module operations for use in simulations
|
||||
func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
|
||||
return nil // TODO simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper)
|
||||
return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper, simState.Contents)
|
||||
}
|
||||
|
71
x/committee/simulation/common.go
Normal file
71
x/committee/simulation/common.go
Normal file
@ -0,0 +1,71 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
)
|
||||
|
||||
func RandomAddresses(r *rand.Rand, accs []simulation.Account) []sdk.AccAddress {
|
||||
r.Shuffle(len(accs), func(i, j int) {
|
||||
accs[i], accs[j] = accs[j], accs[i]
|
||||
})
|
||||
|
||||
var addresses []sdk.AccAddress
|
||||
numAddresses := r.Intn(len(accs) + 1)
|
||||
for i := 0; i < numAddresses; i++ {
|
||||
addresses = append(addresses, accs[i].Address)
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
func RandomPositiveDuration(r *rand.Rand, inclusiveMin, exclusiveMax time.Duration) (time.Duration, error) {
|
||||
min := int64(inclusiveMin)
|
||||
max := int64(exclusiveMax)
|
||||
if min < 0 || max < 0 {
|
||||
return 0, fmt.Errorf("min and max must be positive")
|
||||
}
|
||||
if min >= max {
|
||||
return 0, fmt.Errorf("max must be < min")
|
||||
}
|
||||
randPositiveInt64 := r.Int63n(max-min) + min
|
||||
return time.Duration(randPositiveInt64), nil
|
||||
}
|
||||
|
||||
func RandomTime(r *rand.Rand, inclusiveMin, exclusiveMax time.Time) (time.Time, error) {
|
||||
if exclusiveMax.Before(inclusiveMin) {
|
||||
return time.Time{}, fmt.Errorf("max must be > min")
|
||||
}
|
||||
period := exclusiveMax.Sub(inclusiveMin)
|
||||
subPeriod, err := RandomPositiveDuration(r, 0, period)
|
||||
if err != nil {
|
||||
return time.Time{}, err
|
||||
}
|
||||
return inclusiveMin.Add(subPeriod), nil
|
||||
}
|
||||
|
||||
// RandInt randomly generates an sdk.Int in the range [inclusiveMin, inclusiveMax]. It works for negative and positive integers.
|
||||
func RandIntInclusive(r *rand.Rand, inclusiveMin, inclusiveMax sdk.Int) (sdk.Int, error) {
|
||||
if inclusiveMin.GT(inclusiveMax) {
|
||||
return sdk.Int{}, fmt.Errorf("min larger than max")
|
||||
}
|
||||
return RandInt(r, inclusiveMin, inclusiveMax.Add(sdk.OneInt()))
|
||||
}
|
||||
|
||||
// RandInt randomly generates an sdk.Int in the range [inclusiveMin, exclusiveMax). It works for negative and positive integers.
|
||||
func RandInt(r *rand.Rand, inclusiveMin, exclusiveMax sdk.Int) (sdk.Int, error) {
|
||||
// validate input
|
||||
if inclusiveMin.GTE(exclusiveMax) {
|
||||
return sdk.Int{}, fmt.Errorf("min larger or equal to max")
|
||||
}
|
||||
// shift the range to start at 0
|
||||
shiftedRange := exclusiveMax.Sub(inclusiveMin) // should always be positive given the check above
|
||||
// randomly pick from the shifted range
|
||||
shiftedRandInt := sdk.NewIntFromBigInt(new(big.Int).Rand(r, shiftedRange.BigInt()))
|
||||
// shift back to the original range
|
||||
return shiftedRandInt.Add(inclusiveMin), nil
|
||||
}
|
@ -1,13 +1,43 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
// DecodeStore unmarshals the KVPair's Value to the corresponding module type
|
||||
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
|
||||
// TODO implement this
|
||||
return ""
|
||||
switch {
|
||||
case bytes.Equal(kvA.Key[:1], types.CommitteeKeyPrefix):
|
||||
var committeeA, committeeB types.Committee
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &committeeA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &committeeB)
|
||||
return fmt.Sprintf("%v\n%v", committeeA, committeeB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.ProposalKeyPrefix):
|
||||
var proposalA, proposalB types.Proposal
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &proposalA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &proposalB)
|
||||
return fmt.Sprintf("%v\n%v", proposalA, proposalB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.VoteKeyPrefix):
|
||||
var voteA, voteB types.Vote
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &voteA)
|
||||
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &voteB)
|
||||
return fmt.Sprintf("%v\n%v", voteA, voteB)
|
||||
|
||||
case bytes.Equal(kvA.Key[:1], types.NextProposalIDKey):
|
||||
proposalIDA := types.Uint64FromBytes(kvA.Value)
|
||||
proposalIDB := types.Uint64FromBytes(kvB.Value)
|
||||
return fmt.Sprintf("%d\n%d", proposalIDA, proposalIDB)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
|
||||
}
|
||||
}
|
||||
|
77
x/committee/simulation/decoder_test.go
Normal file
77
x/committee/simulation/decoder_test.go
Normal file
@ -0,0 +1,77 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tendermint/tendermint/libs/kv"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
func makeTestCodec() (cdc *codec.Codec) {
|
||||
cdc = codec.New()
|
||||
sdk.RegisterCodec(cdc)
|
||||
govtypes.RegisterCodec(cdc)
|
||||
types.RegisterCodec(cdc)
|
||||
return cdc
|
||||
}
|
||||
|
||||
func TestDecodeStore(t *testing.T) {
|
||||
cdc := makeTestCodec()
|
||||
|
||||
committee := types.NewCommittee(
|
||||
12,
|
||||
"This committee is for testing.",
|
||||
nil,
|
||||
[]types.Permission{types.TextPermission{}},
|
||||
sdk.MustNewDecFromStr("0.667"),
|
||||
time.Hour*24*7,
|
||||
)
|
||||
proposal := types.Proposal{
|
||||
ID: 34,
|
||||
CommitteeID: 12,
|
||||
Deadline: time.Date(1998, time.January, 1, 1, 0, 0, 0, time.UTC),
|
||||
PubProposal: govtypes.NewTextProposal("A Title", "A description of this proposal."),
|
||||
}
|
||||
vote := types.Vote{
|
||||
ProposalID: 9,
|
||||
Voter: nil,
|
||||
}
|
||||
|
||||
kvPairs := kv.Pairs{
|
||||
kv.Pair{Key: types.CommitteeKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&committee)},
|
||||
kv.Pair{Key: types.ProposalKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&proposal)},
|
||||
kv.Pair{Key: types.VoteKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&vote)},
|
||||
kv.Pair{Key: types.NextProposalIDKey, Value: sdk.Uint64ToBigEndian(10)},
|
||||
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expectedLog string
|
||||
}{
|
||||
{"Committee", fmt.Sprintf("%v\n%v", committee, committee)},
|
||||
{"Proposal", fmt.Sprintf("%v\n%v", proposal, proposal)},
|
||||
{"Vote", fmt.Sprintf("%v\n%v", vote, vote)},
|
||||
{"NextProposalID", "10\n10"},
|
||||
{"other", ""},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
i, tt := i, tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
switch i {
|
||||
case len(tests) - 1:
|
||||
require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name)
|
||||
default:
|
||||
require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -2,21 +2,122 @@ package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// Block time params are un-exported constants in cosmos-sdk/x/simulation.
|
||||
// Copy them here in lieu of importing them.
|
||||
minTimePerBlock time.Duration = (10000 / 2) * time.Second
|
||||
maxTimePerBlock time.Duration = 10000 * time.Second
|
||||
// Calculate the average block time
|
||||
AverageBlockTime time.Duration = (maxTimePerBlock - minTimePerBlock) / 2
|
||||
|
||||
FallbackCommitteeID uint64 = 0
|
||||
)
|
||||
|
||||
// RandomizedGenState generates a random GenesisState for the module
|
||||
func RandomizedGenState(simState *module.SimulationState) {
|
||||
r := simState.Rand
|
||||
|
||||
// TODO implement this fully
|
||||
// - randomly generating the genesis params
|
||||
// - overwriting with genesis provided to simulation
|
||||
genesisState := types.DefaultGenesisState()
|
||||
// Create an always present committee with god permissions to ensure any randomly generated proposal can always be submitted.
|
||||
// Without this, proposals can often not be submitted as there aren't any committees with the right set of permissions available.
|
||||
// It provides more control over how often different proposal types happen during simulation.
|
||||
// It also makes the code simpler--proposals can just be randomly generated and submitted without having to comply to permissions that happen to be available at the time.
|
||||
fallbackCommittee := types.NewCommittee(
|
||||
FallbackCommitteeID,
|
||||
"A committee with god permissions that will always be in state and not deleted. It ensures any generated proposal can always be submitted and passed.",
|
||||
RandomAddresses(r, simState.Accounts),
|
||||
[]types.Permission{types.GodPermission{}},
|
||||
sdk.MustNewDecFromStr("0.5"),
|
||||
AverageBlockTime*10,
|
||||
)
|
||||
|
||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState))
|
||||
// Create other committees
|
||||
numCommittees := r.Intn(100)
|
||||
committees := []types.Committee{fallbackCommittee}
|
||||
for i := 0; i < numCommittees; i++ {
|
||||
com, err := RandomCommittee(r, firstNAccounts(25, simState.Accounts), paramChangeToAllowedParams(simState.ParamChanges))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
committees = append(committees, com)
|
||||
}
|
||||
|
||||
// Add genesis state to simState
|
||||
genesisState := types.NewGenesisState(
|
||||
types.DefaultNextProposalID,
|
||||
committees,
|
||||
[]types.Proposal{},
|
||||
[]types.Vote{},
|
||||
)
|
||||
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, []byte{})
|
||||
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState)
|
||||
}
|
||||
|
||||
func RandomCommittee(r *rand.Rand, availableAccs []simulation.Account, allowedParams []types.AllowedParam) (types.Committee, error) {
|
||||
// pick committee members
|
||||
if len(availableAccs) < 1 {
|
||||
return types.Committee{}, fmt.Errorf("must be ≥ 1 addresses")
|
||||
}
|
||||
var members []sdk.AccAddress
|
||||
for len(members) < 1 {
|
||||
members = RandomAddresses(r, availableAccs)
|
||||
}
|
||||
|
||||
// pick proposal duration
|
||||
dur, err := RandomPositiveDuration(r, 0, AverageBlockTime*10)
|
||||
if err != nil {
|
||||
return types.Committee{}, err
|
||||
}
|
||||
|
||||
// pick committee vote threshold, must be in interval (0,1]
|
||||
threshold := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("1").Sub(sdk.SmallestDec())).Add(sdk.SmallestDec())
|
||||
|
||||
return types.NewCommittee(
|
||||
r.Uint64(), // could collide with other committees, but unlikely
|
||||
simulation.RandStringOfLength(r, r.Intn(types.MaxCommitteeDescriptionLength+1)),
|
||||
members,
|
||||
RandomPermissions(r, allowedParams),
|
||||
threshold,
|
||||
dur,
|
||||
), nil
|
||||
}
|
||||
|
||||
func RandomPermissions(r *rand.Rand, allowedParams []types.AllowedParam) []types.Permission {
|
||||
var permissions []types.Permission
|
||||
if r.Intn(100) < 50 {
|
||||
permissions = append(permissions, types.TextPermission{})
|
||||
}
|
||||
if r.Intn(100) < 50 {
|
||||
r.Shuffle(len(allowedParams), func(i, j int) {
|
||||
allowedParams[i], allowedParams[j] = allowedParams[j], allowedParams[i]
|
||||
})
|
||||
permissions = append(permissions,
|
||||
types.ParamChangePermission{
|
||||
AllowedParams: allowedParams[:r.Intn(len(allowedParams)+1)],
|
||||
})
|
||||
}
|
||||
return permissions
|
||||
}
|
||||
|
||||
func paramChangeToAllowedParams(paramChanges []simulation.ParamChange) []types.AllowedParam {
|
||||
var allowedParams []types.AllowedParam
|
||||
for _, pc := range paramChanges {
|
||||
allowedParams = append(
|
||||
allowedParams,
|
||||
types.AllowedParam{
|
||||
Subspace: pc.Subspace,
|
||||
Key: pc.Key,
|
||||
},
|
||||
)
|
||||
}
|
||||
return allowedParams
|
||||
}
|
||||
|
199
x/committee/simulation/operations.go
Normal file
199
x/committee/simulation/operations.go
Normal file
@ -0,0 +1,199 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
"github.com/cosmos/cosmos-sdk/simapp/helpers"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
"github.com/kava-labs/kava/x/committee/keeper"
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
var (
|
||||
proposalPassPercentage = 0.9
|
||||
)
|
||||
|
||||
type AccountKeeper interface {
|
||||
GetAccount(sdk.Context, sdk.AccAddress) authexported.Account
|
||||
}
|
||||
|
||||
// WeightedOperations creates an operation (with weight) for each type of proposal generator.
|
||||
// Custom proposal generators can be added for more control over types of proposal submitted, eg to increase likelyhood of particular cdp param changes.
|
||||
func WeightedOperations(appParams simulation.AppParams, cdc *codec.Codec, ak AccountKeeper,
|
||||
k keeper.Keeper, wContents []simulation.WeightedProposalContent) simulation.WeightedOperations {
|
||||
|
||||
var wops simulation.WeightedOperations
|
||||
|
||||
for _, wContent := range wContents {
|
||||
wContent := wContent // pin variable
|
||||
if wContent.AppParamsKey == OpWeightSubmitCommitteeChangeProposal {
|
||||
// don't include committee change/delete proposals as they're not enabled for submission to committees
|
||||
continue
|
||||
}
|
||||
var weight int
|
||||
// TODO this doesn't allow weights to be different from what they are in the gov module
|
||||
appParams.GetOrGenerate(cdc, wContent.AppParamsKey, &weight, nil,
|
||||
func(_ *rand.Rand) { weight = wContent.DefaultWeight })
|
||||
|
||||
wops = append(
|
||||
wops,
|
||||
simulation.NewWeightedOperation(
|
||||
weight,
|
||||
SimulateMsgSubmitProposal(ak, k, wContent.ContentSimulatorFn),
|
||||
),
|
||||
)
|
||||
}
|
||||
return wops
|
||||
}
|
||||
|
||||
// SimulateMsgSubmitProposal creates a proposal using the passed contentSimulatorFn and tries to find a committee that has permissions for it. If it can't then it uses the fallback committee.
|
||||
// If the fallback committee isn't there (eg when using an non-generated genesis) and no committee can be found this emits a no-op msg and doesn't do anything.
|
||||
// For each submit proposal msg, future ops for the vote messages are generated. Sometimes it doesn't run enough votes to allow the proposal to timeout - the likelihood of this happening is controlled by a parameter.
|
||||
func SimulateMsgSubmitProposal(ak AccountKeeper, k keeper.Keeper, contentSim simulation.ContentSimulatorFn) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string) (simulation.OperationMsg, []simulation.FutureOperation, error) {
|
||||
|
||||
// 1) Send a submit proposal msg
|
||||
|
||||
committees := k.GetCommittees(ctx)
|
||||
// shuffle committees to ensure proposals are distributed across them evenly
|
||||
r.Shuffle(len(committees), func(i, j int) {
|
||||
committees[i], committees[j] = committees[j], committees[i]
|
||||
})
|
||||
// move fallback committee to the end of slice
|
||||
for i, c := range committees {
|
||||
if c.ID == FallbackCommitteeID {
|
||||
// switch places with last element
|
||||
committees[i], committees[len(committees)-1] = committees[len(committees)-1], committees[i]
|
||||
}
|
||||
}
|
||||
// pick a committee that has permissions for proposal
|
||||
pp := types.PubProposal(contentSim(r, ctx, accs))
|
||||
var selectedCommittee types.Committee
|
||||
var found bool
|
||||
for _, c := range committees {
|
||||
if c.HasPermissionsFor(pp) {
|
||||
selectedCommittee = c
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
// fallback committee was not present, this should only happen if not using the generated genesis state
|
||||
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation (no committee has permissions for proposal)", "", false, nil), nil, nil
|
||||
}
|
||||
|
||||
// create the msg and tx
|
||||
proposer := selectedCommittee.Members[r.Intn(len(selectedCommittee.Members))] // won't panic as committees must have ≥ 1 members
|
||||
msg := types.NewMsgSubmitProposal(
|
||||
pp,
|
||||
proposer,
|
||||
selectedCommittee.ID,
|
||||
)
|
||||
account := ak.GetAccount(ctx, proposer)
|
||||
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
|
||||
if err != nil {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, err
|
||||
}
|
||||
proposerAcc, found := simulation.FindAccount(accs, proposer)
|
||||
if !found {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("address not in account list")
|
||||
}
|
||||
tx := helpers.GenTx(
|
||||
[]sdk.Msg{msg},
|
||||
fees,
|
||||
helpers.DefaultGenTxGas,
|
||||
chainID,
|
||||
[]uint64{account.GetAccountNumber()},
|
||||
[]uint64{account.GetSequence()},
|
||||
proposerAcc.PrivKey,
|
||||
)
|
||||
// submit tx
|
||||
_, result, err := app.Deliver(tx)
|
||||
if err != nil {
|
||||
// to aid debugging, add the stack trace to the comment field of the returned opMsg
|
||||
return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
|
||||
}
|
||||
// to aid debugging, add the result log to the comment field
|
||||
submitOpMsg := simulation.NewOperationMsg(msg, true, result.Log)
|
||||
|
||||
// 2) Schedule vote operations
|
||||
|
||||
// get submitted proposal
|
||||
proposalID := types.Uint64FromBytes(result.Data)
|
||||
proposal, found := k.GetProposal(ctx, proposalID)
|
||||
if !found {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("can't find proposal with ID '%d'", proposalID)
|
||||
}
|
||||
|
||||
// pick the voters
|
||||
// num voters determined by whether the proposal should pass or not
|
||||
numMembers := int64(len(selectedCommittee.Members))
|
||||
majority := selectedCommittee.VoteThreshold.Mul(sdk.NewInt(numMembers).ToDec()).Ceil().TruncateInt64()
|
||||
|
||||
numVoters := r.Int63n(majority) // in interval [0, majority)
|
||||
shouldPass := r.Float64() < proposalPassPercentage
|
||||
if shouldPass {
|
||||
numVoters = majority + r.Int63n(numMembers-majority+1) // in interval [majority, numMembers]
|
||||
}
|
||||
voters := selectedCommittee.Members[:numVoters]
|
||||
|
||||
// schedule vote operations
|
||||
var futureOps []simulation.FutureOperation
|
||||
for _, v := range voters {
|
||||
voteTime, err := RandomTime(r, ctx.BlockTime(), proposal.Deadline)
|
||||
if err != nil {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("random time generation failed: %w", err)
|
||||
}
|
||||
fop := simulation.FutureOperation{
|
||||
BlockTime: voteTime,
|
||||
Op: SimulateMsgVote(k, ak, v, proposal.ID),
|
||||
}
|
||||
futureOps = append(futureOps, fop)
|
||||
}
|
||||
|
||||
return submitOpMsg, futureOps, nil
|
||||
}
|
||||
}
|
||||
|
||||
func SimulateMsgVote(k keeper.Keeper, ak AccountKeeper, voter sdk.AccAddress, proposalID uint64) simulation.Operation {
|
||||
return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string) (
|
||||
opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) {
|
||||
|
||||
msg := types.NewMsgVote(voter, proposalID)
|
||||
|
||||
account := ak.GetAccount(ctx, voter)
|
||||
fees, err := simulation.RandomFees(r, ctx, account.SpendableCoins(ctx.BlockTime()))
|
||||
if err != nil {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, err
|
||||
}
|
||||
|
||||
voterAcc, found := simulation.FindAccount(accs, voter)
|
||||
if !found {
|
||||
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("address not in account list")
|
||||
}
|
||||
|
||||
tx := helpers.GenTx(
|
||||
[]sdk.Msg{msg},
|
||||
fees,
|
||||
helpers.DefaultGenTxGas,
|
||||
chainID,
|
||||
[]uint64{account.GetAccountNumber()},
|
||||
[]uint64{account.GetSequence()},
|
||||
voterAcc.PrivKey,
|
||||
)
|
||||
|
||||
_, result, err := app.Deliver(tx)
|
||||
if err != nil {
|
||||
// to aid debugging, add the stack trace to the comment field of the returned opMsg
|
||||
return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err
|
||||
}
|
||||
// to aid debugging, add the result log to the comment field
|
||||
return simulation.NewOperationMsg(msg, true, result.Log), nil, nil
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
)
|
||||
|
||||
// ParamChanges defines the parameters that can be modified by param change proposals
|
||||
// on the simulation
|
||||
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
|
||||
// TODO implement this
|
||||
return []simulation.ParamChange{}
|
||||
}
|
173
x/committee/simulation/proposals.go
Normal file
173
x/committee/simulation/proposals.go
Normal file
@ -0,0 +1,173 @@
|
||||
package simulation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
|
||||
appparams "github.com/kava-labs/kava/app/params"
|
||||
"github.com/kava-labs/kava/x/committee/keeper"
|
||||
"github.com/kava-labs/kava/x/committee/types"
|
||||
)
|
||||
|
||||
const OpWeightSubmitCommitteeChangeProposal = "op_weight_submit_committee_change_proposal"
|
||||
|
||||
// ProposalContents defines the module weighted proposals' contents
|
||||
func ProposalContents(k keeper.Keeper, paramChanges []simulation.ParamChange) []simulation.WeightedProposalContent {
|
||||
return []simulation.WeightedProposalContent{
|
||||
{
|
||||
AppParamsKey: OpWeightSubmitCommitteeChangeProposal,
|
||||
DefaultWeight: appparams.OpWeightSubmitCommitteeChangeProposal,
|
||||
ContentSimulatorFn: SimulateCommitteeChangeProposalContent(k, paramChanges),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SimulateCommitteeChangeProposalContent generates gov proposal contents that either:
|
||||
// - create new committees
|
||||
// - change existing committees
|
||||
// - delete committees
|
||||
// It does not alter the fallback committee.
|
||||
func SimulateCommitteeChangeProposalContent(k keeper.Keeper, paramChanges []simulation.ParamChange) simulation.ContentSimulatorFn {
|
||||
return func(r *rand.Rand, ctx sdk.Context, accs []simulation.Account) govtypes.Content {
|
||||
allowedParams := paramChangeToAllowedParams(paramChanges)
|
||||
|
||||
// get current committees, ignoring the fallback committee
|
||||
var committees []types.Committee
|
||||
k.IterateCommittees(ctx, func(com types.Committee) bool {
|
||||
if com.ID != FallbackCommitteeID {
|
||||
committees = append(committees, com)
|
||||
}
|
||||
return false
|
||||
})
|
||||
if len(committees) < 1 { // create a committee if none exist
|
||||
com, err := RandomCommittee(r, firstNAccounts(25, accs), allowedParams) // limit num members to avoid overflowing hardcoded gov ops gas limit
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return types.NewCommitteeChangeProposal(
|
||||
simulation.RandStringOfLength(r, 10),
|
||||
simulation.RandStringOfLength(r, 100),
|
||||
com,
|
||||
)
|
||||
}
|
||||
|
||||
// create a proposal content
|
||||
|
||||
var content govtypes.Content
|
||||
switch choice := r.Intn(100); {
|
||||
|
||||
// create committee
|
||||
case choice < 20:
|
||||
com, err := RandomCommittee(r, firstNAccounts(25, accs), allowedParams) // limit num members to avoid overflowing hardcoded gov ops gas limit
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
content = types.NewCommitteeChangeProposal(
|
||||
simulation.RandStringOfLength(r, 10),
|
||||
simulation.RandStringOfLength(r, 100),
|
||||
com,
|
||||
)
|
||||
|
||||
// update committee
|
||||
case choice < 80:
|
||||
com := committees[r.Intn(len(committees))]
|
||||
|
||||
// update members
|
||||
if r.Intn(100) < 50 {
|
||||
if len(accs) == 0 {
|
||||
panic("must have at least one account availabel to use as committee member")
|
||||
}
|
||||
var members []sdk.AccAddress
|
||||
for len(members) < 1 {
|
||||
members = RandomAddresses(r, firstNAccounts(25, accs)) // limit num members to avoid overflowing hardcoded gov ops gas limit
|
||||
}
|
||||
com.Members = members
|
||||
}
|
||||
// update permissions
|
||||
if r.Intn(100) < 50 {
|
||||
com.Permissions = RandomPermissions(r, allowedParams)
|
||||
}
|
||||
// update proposal duration
|
||||
if r.Intn(100) < 50 {
|
||||
dur, err := RandomPositiveDuration(r, 0, AverageBlockTime*100)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
com.ProposalDuration = dur
|
||||
}
|
||||
// update vote threshold
|
||||
if r.Intn(100) < 50 {
|
||||
// VoteThreshold must be in interval (0,1]
|
||||
com.VoteThreshold = simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("1").Sub(sdk.SmallestDec())).Add(sdk.SmallestDec())
|
||||
}
|
||||
|
||||
content = types.NewCommitteeChangeProposal(
|
||||
simulation.RandStringOfLength(r, 10),
|
||||
simulation.RandStringOfLength(r, 100),
|
||||
com,
|
||||
)
|
||||
|
||||
// delete committee
|
||||
default:
|
||||
com := committees[r.Intn(len(committees))]
|
||||
content = types.NewCommitteeDeleteProposal(
|
||||
simulation.RandStringOfLength(r, 10),
|
||||
simulation.RandStringOfLength(r, 100),
|
||||
com.ID,
|
||||
)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// Example custom ParamChangeProposal generator to only generate change to interesting cdp params.
|
||||
// This allows more control over what params are changed within a simulation.
|
||||
func SimulateCDPParamChangeProposalContent(cdpKeeper cdpkeeper.Keeper, paramChangePool []simulation.ParamChange) simulation.ContentSimulatorFn {
|
||||
return func(r *rand.Rand, ctx sdk.Context, _ []simulation.Account) govtypes.Content {
|
||||
|
||||
var paramChanges []paramstypes.ParamChange
|
||||
|
||||
// alter sub params
|
||||
cp := cdpKeeper.GetParams(ctx).CollateralParams
|
||||
if len(cp) == 0 {
|
||||
return nil
|
||||
}
|
||||
cp[0].StabilityFee = sdk.MustNewDecFromStr("0.000001") // TODO generate
|
||||
paramChanges = append(
|
||||
paramChanges,
|
||||
paramstypes.NewParamChange(cdptypes.ModuleName, "?", string(cdptypes.ModuleCdc.MustMarshalJSON(cp))),
|
||||
)
|
||||
|
||||
// alter normal param
|
||||
for _, pc := range paramChangePool {
|
||||
if pc.Subspace == cdptypes.ModuleName && pc.Key == string(cdptypes.KeyGlobalDebtLimit) {
|
||||
paramChanges = append(
|
||||
paramChanges,
|
||||
paramstypes.NewParamChange(pc.Subspace, pc.Key, pc.SimValue(r)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return paramstypes.NewParameterChangeProposal(
|
||||
simulation.RandStringOfLength(r, 140), // title
|
||||
simulation.RandStringOfLength(r, 5000), // description
|
||||
paramChanges, // set of changes
|
||||
)
|
||||
}
|
||||
}
|
||||
*/
|
||||
func firstNAccounts(n int, accs []simulation.Account) []simulation.Account {
|
||||
if n < 0 {
|
||||
panic(fmt.Sprintf("n must be ≥ 0"))
|
||||
}
|
||||
if n > len(accs) {
|
||||
return accs
|
||||
}
|
||||
return accs[:n]
|
||||
}
|
5
x/committee/spec/01_concepts.md
Normal file
5
x/committee/spec/01_concepts.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Concepts
|
||||
|
||||
For a general introduction to governance using the Comsos-SDK, see [x/gov](https://github.com/cosmos/cosmos-sdk/blob/v0.38.3/x/gov/spec/01_concepts.md).
|
||||
|
||||
This module provides companion governance functionality to `x/gov` by allowing the creation of committees, or groups of addresses that can vote on proposals for which they have permission and which bypass the usual on-chain governance structures. Permissions scope the types of proposals that committees can submit and vote on. This allows for committees with unlimited breadth (ie, a committee can have permission to perform any governance action), or narrowly scoped abilities (ie, a committee can only change a single parameter of a single module within a specified range). Further, vote tallying is "first-past-the-post", so proposals can be enacted more rapidly and with greater flexibility than permitted by `x/gov`.
|
19
x/committee/spec/02_state.md
Normal file
19
x/committee/spec/02_state.md
Normal file
@ -0,0 +1,19 @@
|
||||
# State
|
||||
|
||||
## Genesis state
|
||||
|
||||
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the committee module to resume.
|
||||
|
||||
```go
|
||||
// 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"`
|
||||
}
|
||||
```
|
||||
|
||||
## Store
|
||||
|
||||
For complete implementation details for how items are stored, see [keys.go](../types/keys.go). The committee module store state consists of committees, proposals, and votes. When a proposal expires or passes, the proposal and associated votes are deleted from state.
|
34
x/committee/spec/03_messages.md
Normal file
34
x/committee/spec/03_messages.md
Normal file
@ -0,0 +1,34 @@
|
||||
# Messages
|
||||
|
||||
Committee members submit proposals using a `MsgSubmitProposal`
|
||||
|
||||
```go
|
||||
// MsgSubmitProposal is used by committee members to create a new proposal that they can vote on.
|
||||
type MsgSubmitProposal struct {
|
||||
PubProposal PubProposal `json:"pub_proposal" yaml:"pub_proposal"`
|
||||
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"`
|
||||
CommitteeID uint64 `json:"committee_id" yaml:"committee_id"`
|
||||
}
|
||||
```
|
||||
|
||||
## State Modifications
|
||||
|
||||
* Generate new `ProposalID`
|
||||
* Create new `Proposal` with deadline equal to the time that the proposal will expire.
|
||||
|
||||
Committee members vote 'yes' on a proposal using a `MsgVote`
|
||||
|
||||
```go
|
||||
// MsgVote is submitted by committee members to vote on proposals.
|
||||
type MsgVote struct {
|
||||
ProposalID uint64 `json:"proposal_id" yaml:"proposal_id"`
|
||||
Voter sdk.AccAddress `json:"voter" yaml:"voter"`
|
||||
}
|
||||
```
|
||||
|
||||
## State Modifications
|
||||
|
||||
* Create a new `Vote`
|
||||
* If the proposal is over the threshold:
|
||||
* Enact the proposal (proposals may cause state modifications)
|
||||
* Delete the proposal and associated votes
|
33
x/committee/spec/04_events.md
Normal file
33
x/committee/spec/04_events.md
Normal file
@ -0,0 +1,33 @@
|
||||
# Events
|
||||
|
||||
The `x/committee` module emits the following events:
|
||||
|
||||
## MsgSubmitProposal
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|--------------------|
|
||||
| proposal_submit | committee_id | {committee ID} |
|
||||
| proposal_submit | proposal_id | {proposal ID} |
|
||||
| message | module | committee |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
## MsgVote
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|--------------------|
|
||||
| proposal_vote | committee_id | {committee ID} |
|
||||
| proposal_vote | proposal_id | {proposal ID} |
|
||||
| proposal_vote | voter | {voter address} |
|
||||
| proposal_close | committee_id | {committee ID} |
|
||||
| proposal_close | proposal_id | {proposal ID} |
|
||||
| proposal_close | status | {outcome} |
|
||||
| message | module | committee |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
## BeginBlock
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|--------------------|
|
||||
| proposal_close | committee_id | {committee ID} |
|
||||
| proposal_close | proposal_id | {proposal ID} |
|
||||
| proposal_close | status | proposal_timeout |
|
3
x/committee/spec/05_params.md
Normal file
3
x/committee/spec/05_params.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Parameters
|
||||
|
||||
The committee module has no parameters. Committees are created using the `x/gov` module and and inherit the parameters controlling governance proposals from `x/gov`.
|
10
x/committee/spec/06_begin_block.md
Normal file
10
x/committee/spec/06_begin_block.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Begin Block
|
||||
|
||||
At the start of each block, expired proposals are deleted. The logic is as follows:
|
||||
|
||||
```go
|
||||
// BeginBlocker runs at the start of every block.
|
||||
func BeginBlocker(ctx sdk.Context, _ abci.RequestBeginBlock, k Keeper) {
|
||||
k.CloseExpiredProposals(ctx)
|
||||
}
|
||||
```
|
@ -3,17 +3,25 @@
|
||||
|
||||
## Table of Contents
|
||||
|
||||
<!-- TOC -->
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[Messages](03_messages.md)**
|
||||
4. **[Events](04_events.md)**
|
||||
5. **[Params](05_params.md)**
|
||||
6. **[BeginBlock](06_begin_block.md)**
|
||||
|
||||
## Overview
|
||||
|
||||
The `x/committee` module is an additional governance module to `cosmos-sdk/x/gov`.
|
||||
The `x/committee` module is Cosmos-SDK module that acts as an additional governance module to `cosmos-sdk/x/gov`.
|
||||
|
||||
It allows groups of accounts to vote on and enact proposals without a full chain governance vote. Certain proposal types can then be decided on quickly in emergency situations, or low risk parameter updates can be delegated to a smaller group of individuals.
|
||||
|
||||
Committees work with "proposals", using the same type from the `gov` module so they are compatible with all existing proposal types such as param changes, or community pool spend, or text proposals.
|
||||
|
||||
Committees have members and permissions.
|
||||
Committees have members and permissions. Committees are 'elected' via traditional `gov` proposals - ie. all coin-holders vote on the creation, deletion, and updating of committees.
|
||||
|
||||
Members vote on proposals, with just simple one vote per member, no deposits or slashing. More sophisticated voting could be added.
|
||||
Members of committees vote on proposals, with one vote per member and no deposits or slashing. Only a member of a committee can submit a proposal for that committee. More sophisticated voting could be added, as well as the ability for committees to edit themselves or other committees. A proposal passes when the number of votes is over the threshold for that committee. Vote thresholds are set per committee. Committee members vote yes by casting a vote and vote no by abstaining from voting and letting the proposal expire.
|
||||
|
||||
Permissions scope the allowed set of proposals a committee can enact. For example:
|
||||
|
||||
@ -21,6 +29,4 @@ Permissions scope the allowed set of proposals a committee can enact. For exampl
|
||||
- allow the committee to change auction bid increments, but only within the range [0, 0.1]
|
||||
- allow the committee to only disable cdp msg types, but not staking or gov
|
||||
|
||||
A permission acts as a filter for incoming gov proposals, rejecting them if they do not pass. A permission can be any type with a method `Allows(p Proposal) bool`. They reject all proposals that they don't explicitly allow.
|
||||
|
||||
This allows permissions to be parameterized to allow fine grained control specified at runtime. For example a generic parameter permission type can allow a committee to only change a particular param, or only change params within a certain percentage.
|
||||
A permission acts as a filter for incoming gov proposals, rejecting them at the handler if they do not have the required permissions. A permission can be any type with a method `Allows(p Proposal) bool`. The handler will reject all proposals that are not explicitly allowed. This allows permissions to be parameterized to allow fine grained control specified at runtime. For example a generic parameter permission type can allow a committee to only change a particular param, or only change params within a certain range.
|
||||
|
@ -63,31 +63,30 @@ func (c Committee) Validate() error {
|
||||
for _, m := range c.Members {
|
||||
// check there are no duplicate members
|
||||
if _, ok := addressMap[m.String()]; ok {
|
||||
return fmt.Errorf("duplicate member found in committee, %s", m)
|
||||
return fmt.Errorf("committe cannot have duplicate members, %s", m)
|
||||
}
|
||||
// check for valid addresses
|
||||
if m.Empty() {
|
||||
return fmt.Errorf("committee %d invalid: found empty member address", c.ID)
|
||||
return fmt.Errorf("committee cannot have empty member address")
|
||||
}
|
||||
addressMap[m.String()] = true
|
||||
|
||||
}
|
||||
|
||||
if len(c.Members) == 0 {
|
||||
return fmt.Errorf("committee %d invalid: cannot have zero members", c.ID)
|
||||
return fmt.Errorf("committee cannot have zero members")
|
||||
}
|
||||
|
||||
if len(c.Description) > MaxCommitteeDescriptionLength {
|
||||
return fmt.Errorf("invalid description")
|
||||
return fmt.Errorf("description length %d longer than max allowed %d", len(c.Description), MaxCommitteeDescriptionLength)
|
||||
}
|
||||
|
||||
// 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")
|
||||
return fmt.Errorf("invalid threshold: %s", c.VoteThreshold)
|
||||
}
|
||||
|
||||
if c.ProposalDuration < 0 {
|
||||
return fmt.Errorf("invalid proposal duration")
|
||||
return fmt.Errorf("invalid proposal duration: %s", c.ProposalDuration)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -10,6 +10,7 @@ const (
|
||||
AttributeKeyCommitteeID = "committee_id"
|
||||
AttributeKeyProposalID = "proposal_id"
|
||||
AttributeKeyProposalCloseStatus = "status"
|
||||
AttributeKeyVoter = "voter"
|
||||
AttributeValueProposalPassed = "proposal_passed"
|
||||
AttributeValueProposalTimeout = "proposal_timeout"
|
||||
AttributeValueProposalFailed = "proposal_failed"
|
||||
|
@ -65,7 +65,7 @@ func (gs GenesisState) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// validate proposals - pp.Val, no duplicate IDs, no ids >= nextID, committee needs to exist
|
||||
// validate proposals
|
||||
proposalMap := make(map[uint64]bool, len(gs.Proposals))
|
||||
for _, p := range gs.Proposals {
|
||||
// check there are no duplicate IDs
|
||||
|
@ -1,9 +1,10 @@
|
||||
package incentive
|
||||
|
||||
// nolint
|
||||
// autogenerated code using github.com/rigelrozanski/multitool
|
||||
// aliases generated for the following subdirectories:
|
||||
// ALIASGEN: github.com/kava-labs/kava/x/incentive/keeper
|
||||
// ALIASGEN: github.com/kava-labs/kava/x/incentive/types
|
||||
package incentive
|
||||
|
||||
import (
|
||||
"github.com/kava-labs/kava/x/incentive/keeper"
|
||||
@ -11,18 +12,24 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
EventTypeClaim = types.EventTypeClaim
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeKeySender = types.AttributeKeySender
|
||||
ModuleName = types.ModuleName
|
||||
StoreKey = types.StoreKey
|
||||
RouterKey = types.RouterKey
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
QuerierRoute = types.QuerierRoute
|
||||
QueryGetClaims = types.QueryGetClaims
|
||||
RestClaimOwner = types.RestClaimOwner
|
||||
RestClaimDenom = types.RestClaimDenom
|
||||
QueryGetParams = types.QueryGetParams
|
||||
EventTypeClaim = types.EventTypeClaim
|
||||
EventTypeRewardPeriod = types.EventTypeRewardPeriod
|
||||
EventTypeClaimPeriod = types.EventTypeClaimPeriod
|
||||
EventTypeClaimPeriodExpiry = types.EventTypeClaimPeriodExpiry
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeKeyClaimedBy = types.AttributeKeyClaimedBy
|
||||
AttributeKeyClaimAmount = types.AttributeKeyClaimAmount
|
||||
AttributeKeyRewardPeriod = types.AttributeKeyRewardPeriod
|
||||
AttributeKeyClaimPeriod = types.AttributeKeyClaimPeriod
|
||||
ModuleName = types.ModuleName
|
||||
StoreKey = types.StoreKey
|
||||
RouterKey = types.RouterKey
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
QuerierRoute = types.QuerierRoute
|
||||
QueryGetClaims = types.QueryGetClaims
|
||||
RestClaimOwner = types.RestClaimOwner
|
||||
RestClaimDenom = types.RestClaimDenom
|
||||
QueryGetParams = types.QueryGetParams
|
||||
)
|
||||
|
||||
var (
|
||||
@ -46,6 +53,7 @@ var (
|
||||
NewRewardPeriod = types.NewRewardPeriod
|
||||
NewClaimPeriod = types.NewClaimPeriod
|
||||
NewClaim = types.NewClaim
|
||||
NewRewardPeriodFromReward = types.NewRewardPeriodFromReward
|
||||
|
||||
// variable aliases
|
||||
ModuleCdc = types.ModuleCdc
|
||||
|
@ -1,8 +1,6 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
@ -29,10 +27,13 @@ func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, denom string,
|
||||
return err
|
||||
}
|
||||
|
||||
k.DeleteClaim(ctx, addr, denom, id)
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeClaim,
|
||||
sdk.NewAttribute(types.AttributeKeySender, fmt.Sprintf("%s", addr)),
|
||||
sdk.NewAttribute(types.AttributeKeyClaimedBy, addr.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyClaimAmount, claim.Reward.String()),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
@ -103,6 +104,13 @@ func (k Keeper) DeleteExpiredClaimsAndClaimPeriods(ctx sdk.Context) {
|
||||
return false
|
||||
})
|
||||
k.DeleteClaimPeriod(ctx, cp.ID, cp.Denom)
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeClaimPeriodExpiry,
|
||||
sdk.NewAttribute(types.AttributeKeyClaimPeriod, cp.String()),
|
||||
),
|
||||
)
|
||||
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
@ -20,18 +20,15 @@ func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod)
|
||||
|
||||
// CreateNewRewardPeriod creates a new reward period from the input reward
|
||||
func (k Keeper) CreateNewRewardPeriod(ctx sdk.Context, reward types.Reward) {
|
||||
// reward periods store the amount of rewards paid PER SECOND
|
||||
rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt()
|
||||
rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond)
|
||||
rp := types.RewardPeriod{
|
||||
Denom: reward.Denom,
|
||||
Start: ctx.BlockTime(),
|
||||
End: ctx.BlockTime().Add(reward.Duration),
|
||||
Reward: rewardCoinPerSecond,
|
||||
ClaimEnd: ctx.BlockTime().Add(reward.Duration).Add(reward.ClaimDuration),
|
||||
ClaimTimeLock: reward.TimeLock,
|
||||
}
|
||||
rp := types.NewRewardPeriodFromReward(reward, ctx.BlockTime())
|
||||
k.SetRewardPeriod(ctx, rp)
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeRewardPeriod,
|
||||
sdk.NewAttribute(types.AttributeKeyRewardPeriod, rp.String()),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// CreateAndDeleteRewardPeriods creates reward periods for active rewards that don't already have a reward period and deletes reward periods for inactive rewards that currently have a reward period
|
||||
@ -95,6 +92,12 @@ func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) {
|
||||
func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, denom string, end time.Time, timeLock time.Duration) {
|
||||
id := k.GetNextClaimPeriodID(ctx, denom)
|
||||
claimPeriod := types.NewClaimPeriod(denom, id, end, timeLock)
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeClaimPeriod,
|
||||
sdk.NewAttribute(types.AttributeKeyClaimPeriod, claimPeriod.String()),
|
||||
),
|
||||
)
|
||||
k.SetClaimPeriod(ctx, claimPeriod)
|
||||
k.SetNextClaimPeriodID(ctx, denom, id+1)
|
||||
}
|
||||
|
5
x/incentive/spec/01_concepts.md
Normal file
5
x/incentive/spec/01_concepts.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Concepts
|
||||
|
||||
This module presents an implementation of user incentives that are controlled by governance. When users take a certain action, in this case opening a CDP, they become eligible for rewards. Rewards are __opt in__ meaning that users must submit a message before the claim deadline to claim their rewards. The goals and background of this module were subject of a previous Kava governance proposal, which can be found [here](https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf).
|
||||
|
||||
When governance adds a collateral type to be eligible for rewards, they set the rate (coins/time) at which rewards are given to users, the length of each reward period, the length of each claim period, and the amount of time reward coins must vest before users who claim them can transfer them. For the duration of a reward period, any user that has minted USDX using an eligible collateral type will ratably accumulate rewards in a `Claim` object. For example, if a user has minted 10% of all USDX for the duration of the reward period, they will earn 10% of all rewards for that period. When the reward period ends, the claim period begins immediately, at which point users can submit a message to claim their rewards. Rewards are time-locked, meaning that when a user claims rewards they will receive them as a vesting balance on their account. Vesting balances can be used to stake coins, but cannot be transferred until the vesting period ends.
|
57
x/incentive/spec/02_state.md
Normal file
57
x/incentive/spec/02_state.md
Normal file
@ -0,0 +1,57 @@
|
||||
# State
|
||||
|
||||
## Parameters and Genesis State
|
||||
|
||||
`Parameters` define the collateral types which are eligible for rewards, the rate at which rewards are given to users, and the amount of time rewards must vest before users can transfer them.
|
||||
|
||||
```go
|
||||
// Params governance parameters for the incentive module
|
||||
type Params struct {
|
||||
Active bool `json:"active" yaml:"active"` // top level governance switch to disable all rewards
|
||||
Rewards Rewards `json:"rewards" yaml:"rewards"`
|
||||
}
|
||||
|
||||
// Reward stores the specified state for a single reward period.
|
||||
type Reward struct {
|
||||
Active bool `json:"active" yaml:"active"` // governance switch to disable a period
|
||||
Denom string `json:"denom" yaml:"denom"` // the collateral denom rewards apply to, must be found in the cdp collaterals
|
||||
AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period
|
||||
Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period
|
||||
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` // how long rewards for this period are timelocked
|
||||
ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards
|
||||
}
|
||||
```
|
||||
|
||||
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the incentive module to resume.
|
||||
|
||||
```go
|
||||
// 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"`
|
||||
RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"`
|
||||
ClaimPeriods ClaimPeriods `json:"claim_periods" yaml:"claim_periods"`
|
||||
Claims Claims `json:"claims" yaml:"claims"`
|
||||
NextClaimPeriodIDs GenesisClaimPeriodIDs `json:"next_claim_period_ids" yaml:"next_claim_period_ids"`
|
||||
}
|
||||
```
|
||||
|
||||
## Store
|
||||
|
||||
For complete details for how items are stored, see [keys.go](../types/keys.go).
|
||||
|
||||
### Reward Period Creation
|
||||
|
||||
At genesis, or when a collateral is added to rewards, a `RewardPeriod` is created in the store by adding to the existing array of `[]RewardPeriod`. If the previous period for that collateral expired, it is deleted. This implies that, for each collateral, there will only ever be one reward period.
|
||||
|
||||
### Reward Period Deletion
|
||||
|
||||
When a `RewardPeriod` expires, a new `ClaimPeriod` is created in the store with the next sequential ID for that collateral (ie, if the previous claim period was ID 1, the next one will be ID 2) and the current `RewardPeriod` is deleted from the array of `[]RewardPeriod`.
|
||||
|
||||
### Reward Claim Creation
|
||||
|
||||
Every block, CDPs are iterated over and the collateral denom is checked for rewards eligibility. For eligible CDPs, a `Claim` is created in the store for all CDP owners, if one doesn't already exist. The claim object is associated with a `ClaimPeriod` via the ID. This implies that a `Claim` is created before `ClaimPeriod` are created. Therefore, a user who submits a `MsgClaimReward` will only be paid out IF 1) they have one or more active `Claim` objects, and 2) the `ClaimPeriod` with the associated ID for that object exists AND the current block time is between the start time and end time for that `ClaimPeriod`.
|
||||
|
||||
### Reward Claim Deletion
|
||||
|
||||
For claimed rewards, the `Claim` is deleted from the store by deleting the key associated with that denom, ID, and owner. Unclaimed rewards are handled as follows: Each block, the `ClaimPeriod` objects for each denom are iterated over and checked for expiry. If expired, all `Claim` objects for that ID are deleted, as well as the `ClaimPeriod` object. Since claim periods are monotonically increasing, once a non-expired claim period is reached, the iteration can be stopped.
|
16
x/incentive/spec/03_messages.md
Normal file
16
x/incentive/spec/03_messages.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Messages
|
||||
|
||||
Users claim rewards using a `MsgClaimReward`.
|
||||
|
||||
```go
|
||||
// MsgClaimReward message type used to claim rewards
|
||||
type MsgClaimReward struct {
|
||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
}
|
||||
```
|
||||
|
||||
## State Modifications
|
||||
|
||||
* Accumulated rewards for active claims are transferred from the `kavadist` module account to the users account as vesting coins
|
||||
* The corresponding claim object(s) are deleted from the store
|
20
x/incentive/spec/04_events.md
Normal file
20
x/incentive/spec/04_events.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Events
|
||||
|
||||
The `x/incentive` module emits the following events:
|
||||
|
||||
## MsgClaimReward
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|--------------------|
|
||||
| claim_reward | claimed_by | {claiming address} |
|
||||
| claim_reward | claim_amount | {amount claimed} |
|
||||
| message | module | incentive |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
## BeginBlock
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|--------------------|
|
||||
| new_claim_period | claim_period | {claim period} |
|
||||
| new_reward_period | reward_period | {reward period} |
|
||||
| claim_period_expiry | claim_period | {claim period} |
|
19
x/incentive/spec/05_params.md
Normal file
19
x/incentive/spec/05_params.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Parameters
|
||||
|
||||
The incentive module contains the following parameters:
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
|------------|----------------|---------------|--------------------------------------------------|
|
||||
| Active | bool | "true" | boolean for if this module is active |
|
||||
| Rewards | array (Reward) | [{see below}] | array of params for each inflationary period |
|
||||
|
||||
Each `Reward` has the following parameters
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
|------------------|--------------------|----------------------------------|----------------------------------------------------------------|
|
||||
| Active | bool | "true | boolean for if rewards for this collateral are active |
|
||||
| Denom | string | "bnb" | the collateral for which rewards are eligible |
|
||||
| AvailableRewards | object (coin) | {"denom":"kava","amount":"1000"} | the rewards available per reward period |
|
||||
| Duration | string (time ns) | "172800000000000" | the duration of each reward period |
|
||||
| TimeLock | string (time ns) | "172800000000000" | the duration for which claimed rewards will be vesting |
|
||||
| ClaimDuration | string (time ns) | "172800000000000" | how long users have to claim rewards before they expire |
|
11
x/incentive/spec/06_begin_block.md
Normal file
11
x/incentive/spec/06_begin_block.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Begin Block
|
||||
|
||||
At the start of each block, expired claims and claim periods are deleted, rewards are applied to CDPs for any ongoing reward periods, expired reward periods are deleted and replaced with a new reward period (if active), and claim periods are created for expiring reward periods. The logic is as follows:
|
||||
|
||||
```go
|
||||
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
||||
k.DeleteExpiredClaimsAndClaimPeriods(ctx)
|
||||
k.ApplyRewardsToCdps(ctx)
|
||||
k.CreateAndDeleteRewardPeriods(ctx)
|
||||
}
|
||||
```
|
17
x/incentive/spec/README.md
Normal file
17
x/incentive/spec/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# `incentive`
|
||||
|
||||
<!-- TOC -->
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[Messages](03_messages.md)**
|
||||
4. **[Events](04_events.md)**
|
||||
5. **[Params](05_params.md)**
|
||||
6. **[BeginBlock](06_begin_block.md)**
|
||||
|
||||
## Abstract
|
||||
|
||||
`x/incentive` is an implementation of a Cosmos SDK Module that allows for governance controlled user incentives for users who create stable assets by opening a collateralized debt position (CDP). Governance proposes an array of collateral rewards, with each item representing a collateral type that will be eligible for rewards. Each collateral reward specifies the number of coins awarded per period, the length of rewards periods, the length of claim periods. Governance can alter the collateral rewards using parameter change proposals as well as adding or removing collateral types. All changes to parameters would take place in the _next_ period.
|
||||
|
||||
### Dependencies
|
||||
|
||||
This module depends on `x/cdp` for users to be able to create CDPs and on `x/kavadist`, which controls the module account from where rewards are spent. In the event that the module account is not funded, user's attempt to claim rewards will fail.
|
@ -1,8 +1,15 @@
|
||||
package types
|
||||
|
||||
// Events emitted by the incentive module
|
||||
const (
|
||||
EventTypeClaim = "claim_reward"
|
||||
EventTypeClaim = "claim_reward"
|
||||
EventTypeRewardPeriod = "new_reward_period"
|
||||
EventTypeClaimPeriod = "new_claim_period"
|
||||
EventTypeClaimPeriodExpiry = "claim_period_expiry"
|
||||
|
||||
AttributeValueCategory = ModuleName
|
||||
AttributeKeySender = "sender"
|
||||
AttributeValueCategory = ModuleName
|
||||
AttributeKeyClaimedBy = "claimed_by"
|
||||
AttributeKeyClaimAmount = "claim_amount"
|
||||
AttributeKeyRewardPeriod = "reward_period"
|
||||
AttributeKeyClaimPeriod = "claim_period"
|
||||
)
|
||||
|
@ -25,8 +25,8 @@ func (rp RewardPeriod) String() string {
|
||||
End: %s,
|
||||
Reward: %s,
|
||||
Claim End: %s,
|
||||
Claim Time Lock: %s`,
|
||||
rp.Denom, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock)
|
||||
Claim Time Lock: %s
|
||||
`, rp.Denom, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock)
|
||||
}
|
||||
|
||||
// NewRewardPeriod returns a new RewardPeriod
|
||||
@ -52,6 +52,16 @@ type ClaimPeriod struct {
|
||||
TimeLock time.Duration `json:"time_lock" yaml:"time_lock"`
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (cp ClaimPeriod) String() string {
|
||||
return fmt.Sprintf(`Claim Period:
|
||||
Denom: %s,
|
||||
ID: %d,
|
||||
End: %s,
|
||||
Claim Time Lock: %s
|
||||
`, cp.Denom, cp.ID, cp.End, cp.TimeLock)
|
||||
}
|
||||
|
||||
// NewClaimPeriod returns a new ClaimPeriod
|
||||
func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod {
|
||||
return ClaimPeriod{
|
||||
@ -89,9 +99,24 @@ func (c Claim) String() string {
|
||||
Owner: %s,
|
||||
Denom: %s,
|
||||
Reward: %s,
|
||||
Claim Period ID: %d,`,
|
||||
c.Owner, c.Denom, c.Reward, c.ClaimPeriodID)
|
||||
Claim Period ID: %d,
|
||||
`, c.Owner, c.Denom, c.Reward, c.ClaimPeriodID)
|
||||
}
|
||||
|
||||
// Claims array of Claim
|
||||
type Claims []Claim
|
||||
|
||||
// NewRewardPeriodFromReward returns a new reward period from the input reward and block time
|
||||
func NewRewardPeriodFromReward(reward Reward, blockTime time.Time) RewardPeriod {
|
||||
// note: reward periods store the amount of rewards paid PER SECOND
|
||||
rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt()
|
||||
rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond)
|
||||
return RewardPeriod{
|
||||
Denom: reward.Denom,
|
||||
Start: blockTime,
|
||||
End: blockTime.Add(reward.Duration),
|
||||
Reward: rewardCoinPerSecond,
|
||||
ClaimEnd: blockTime.Add(reward.Duration).Add(reward.ClaimDuration),
|
||||
ClaimTimeLock: reward.TimeLock,
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, ti
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeKavaDist,
|
||||
sdk.NewAttribute(types.AttributeKeyInflation, amountToMint.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyInflation, sdk.NewCoin(denom, amountToMint).String()),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
|
3
x/kavadist/spec/01_concepts.md
Normal file
3
x/kavadist/spec/01_concepts.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Concepts
|
||||
|
||||
The minting mechanism in this module is designed to allow governance to determine a set of inflationary periods and the APR rate of inflation for each period. This module mints coins each block according to the schedule such that after 1 year the APR inflation worth of coins will have been minted. Governance can alter the APR inflation using a parameter change proposal. Parameter change proposals that change the APR will take effect in the block after they pass.
|
30
x/kavadist/spec/02_state.md
Normal file
30
x/kavadist/spec/02_state.md
Normal file
@ -0,0 +1,30 @@
|
||||
# State
|
||||
|
||||
## Parameters and Genesis State
|
||||
|
||||
`Parameters` define the rate at which inflationary coins are minted and for how long inflationary periods last.
|
||||
|
||||
```go
|
||||
// Params governance parameters for kavadist module
|
||||
type Params struct {
|
||||
Active bool `json:"active" yaml:"active"`
|
||||
Periods Periods `json:"periods" yaml:"periods"`
|
||||
}
|
||||
|
||||
// Period stores the specified start and end dates, and the inflation, expressed as a decimal representing the yearly APR of 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
|
||||
}
|
||||
```
|
||||
|
||||
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the kavadist module to resume.
|
||||
|
||||
```go
|
||||
// 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"`
|
||||
}
|
||||
```
|
3
x/kavadist/spec/03_messages.md
Normal file
3
x/kavadist/spec/03_messages.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Messages
|
||||
|
||||
There are no messages in the kavadist module. All state transitions are controlled by parameters, which can be updated via parameter change proposals.
|
10
x/kavadist/spec/04_events.md
Normal file
10
x/kavadist/spec/04_events.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Events
|
||||
|
||||
The `x/kavadist` module emits the following events:
|
||||
|
||||
## BeginBlock
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------------|-----------------|
|
||||
| kavadist | kava_dist_inflation | {amount} |
|
||||
| kavadist | kava_dist_status | "inactive" |
|
15
x/kavadist/spec/05_params.md
Normal file
15
x/kavadist/spec/05_params.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Parameters
|
||||
|
||||
The kavadist module has the following parameters:
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
|------------|----------------|---------------|--------------------------------------------------|
|
||||
| Periods | array (Period) | [{see below}] | array of params for each inflationary period |
|
||||
|
||||
Each `Period` has the following parameters
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
|------------|--------------------|--------------------------|----------------------------------------------------------------|
|
||||
| Start | time.Time | "2020-03-01T15:20:00Z" | the time when the period will start |
|
||||
| End | time.Time | "2020-06-01T15:20:00Z" | the time when the period will end |
|
||||
| Inflation | sdk.Dec | "1.000000003022265980" | the per-second inflation for the period |
|
12
x/kavadist/spec/06_begin_block.md
Normal file
12
x/kavadist/spec/06_begin_block.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Begin Block
|
||||
|
||||
At the start of each block, the inflationary coins for the ongoing period, if any, are minted. The logic is as follows:
|
||||
|
||||
```go
|
||||
func BeginBlocker(ctx sdk.Context, k Keeper) {
|
||||
err := k.MintPeriodInflation(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
13
x/kavadist/spec/README.md
Normal file
13
x/kavadist/spec/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# `kavadist`
|
||||
|
||||
<!-- TOC -->
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[Messages](03_messages.md)**
|
||||
4. **[Events](04_events.md)**
|
||||
5. **[Params](05_params.md)**
|
||||
6. **[BeginBlock](06_begin_block.md)**
|
||||
|
||||
## Abstract
|
||||
|
||||
`x/kavadist` is an implementation of a Cosmos SDK Module that allows for governance controlled minting of coins into a module account. Coins are minted during inflationary periods, which each period have a governance specified APR and duration. This module does not provide functionality for spending or distributing the minted coins.
|
@ -9,15 +9,15 @@ import (
|
||||
// EndBlocker updates the current pricefeed
|
||||
func EndBlocker(ctx sdk.Context, k Keeper) {
|
||||
// Update the current price of each asset.
|
||||
for _, a := range k.GetMarkets(ctx) {
|
||||
if a.Active {
|
||||
err := k.SetCurrentPrices(ctx, a.MarketID)
|
||||
for _, market := range k.GetMarkets(ctx) {
|
||||
if market.Active {
|
||||
err := k.SetCurrentPrices(ctx, market.MarketID)
|
||||
if err != nil {
|
||||
// In the event of failure, emit an event.
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
EventTypeNoValidPrices,
|
||||
sdk.NewAttribute(AttributeKeyPriceUpdateFailed, fmt.Sprintf("%s", a.MarketID)),
|
||||
sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
|
||||
),
|
||||
)
|
||||
continue
|
||||
|
@ -12,26 +12,25 @@ import (
|
||||
|
||||
// nolint
|
||||
const (
|
||||
EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated
|
||||
EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice
|
||||
EventTypeNoValidPrices = types.EventTypeNoValidPrices
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeMarketID = types.AttributeMarketID
|
||||
AttributeMarketPrice = types.AttributeMarketPrice
|
||||
AttributeOracle = types.AttributeOracle
|
||||
AttributeExpiry = types.AttributeExpiry
|
||||
AttributeKeyPriceUpdateFailed = types.AttributeKeyPriceUpdateFailed
|
||||
ModuleName = types.ModuleName
|
||||
StoreKey = types.StoreKey
|
||||
RouterKey = types.RouterKey
|
||||
QuerierRoute = types.QuerierRoute
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
TypeMsgPostPrice = types.TypeMsgPostPrice
|
||||
QueryGetParams = types.QueryGetParams
|
||||
QueryMarkets = types.QueryMarkets
|
||||
QueryOracles = types.QueryOracles
|
||||
QueryRawPrices = types.QueryRawPrices
|
||||
QueryPrice = types.QueryPrice
|
||||
EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated
|
||||
EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice
|
||||
EventTypeNoValidPrices = types.EventTypeNoValidPrices
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeMarketID = types.AttributeMarketID
|
||||
AttributeMarketPrice = types.AttributeMarketPrice
|
||||
AttributeOracle = types.AttributeOracle
|
||||
AttributeExpiry = types.AttributeExpiry
|
||||
ModuleName = types.ModuleName
|
||||
StoreKey = types.StoreKey
|
||||
RouterKey = types.RouterKey
|
||||
QuerierRoute = types.QuerierRoute
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
TypeMsgPostPrice = types.TypeMsgPostPrice
|
||||
QueryGetParams = types.QueryGetParams
|
||||
QueryMarkets = types.QueryMarkets
|
||||
QueryOracles = types.QueryOracles
|
||||
QueryRawPrices = types.QueryRawPrices
|
||||
QueryPrice = types.QueryPrice
|
||||
)
|
||||
|
||||
// nolint
|
||||
|
3
x/pricefeed/spec/01_concepts.md
Normal file
3
x/pricefeed/spec/01_concepts.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Concepts
|
||||
|
||||
Prices can be posted by any account which is added as an oracle. Oracles are specific to each market and can be updated via param change proposals. When an oracle posts a price, they submit a message to the blockchain that contains the current price for that market and a time when that price should be considered expired. If an oracle posts a new price, that price becomes the current price for that oracle, regardless of the previous price's expiry. A group of prices posted by a set of oracles for a particular market are referred to as 'raw prices' and the current median price of all valid oracle prices is referred to as the 'current price'. Each block, the current price for each market is determined by calculating the median of the raw prices.
|
44
x/pricefeed/spec/02_state.md
Normal file
44
x/pricefeed/spec/02_state.md
Normal file
@ -0,0 +1,44 @@
|
||||
# State
|
||||
|
||||
## Parameters and genesis state
|
||||
|
||||
`Paramaters` determine which markets are tracked by the pricefeed and which oracles are authorized to post prices for a given market. There is only one active parameter set at any given time. Updates to parameters can be made via on-chain parameter update proposals.
|
||||
|
||||
```go
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type Markets []Market
|
||||
```
|
||||
|
||||
`GenesisState` defines the state that must be persisted when the blockchain stops/stars in order for the normal function of the pricefeed to resume.
|
||||
|
||||
```go
|
||||
// GenesisState - pricefeed state that must be provided at genesis
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
PostedPrices []PostedPrice `json:"posted_prices" yaml:"posted_prices"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type PostedPrices []PostedPrice
|
||||
```
|
||||
|
20
x/pricefeed/spec/03_messages.md
Normal file
20
x/pricefeed/spec/03_messages.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Messages
|
||||
|
||||
## Posting Prices
|
||||
|
||||
An authorized oraclef for a particular market can post the current price for that market using the `MsgPostPrice` type.
|
||||
|
||||
```go
|
||||
// MsgPostPrice struct representing a posted price message.
|
||||
// Used by oracles to input prices to the pricefeed
|
||||
type MsgPostPrice struct {
|
||||
From sdk.AccAddress `json:"from" yaml:"from"` // client that sent in this address
|
||||
MarketID string `json:"market_id" yaml:"market_id"` // asset code used by exchanges/api
|
||||
Price sdk.Dec `json:"price" yaml:"price"` // price in decimal (max precision 18)
|
||||
Expiry time.Time `json:"expiry" yaml:"expiry"` // expiry time
|
||||
}
|
||||
```
|
||||
|
||||
### State Modifications
|
||||
|
||||
* Update the raw price for the oracle for this market. This replaces any previous price for that oracle.
|
22
x/pricefeed/spec/04_events.md
Normal file
22
x/pricefeed/spec/04_events.md
Normal file
@ -0,0 +1,22 @@
|
||||
# Events
|
||||
|
||||
The `x/pricefeed` module emits the following events:
|
||||
|
||||
## MsgPostPrice
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|---------------|------------------|
|
||||
| oracle_updated_price | market_id | {market ID} |
|
||||
| oracle_updated_price | oracle | {oracle} |
|
||||
| oracle_updated_price | market_price | {price} |
|
||||
| oracle_updated_price | expiry | {expiry} |
|
||||
| message | module | pricefeed |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
## BeginBlock
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|----------------------|-----------------|-----------------|
|
||||
| market_price_updated | market_id | {market ID} |
|
||||
| market_price_updated | market_price | {price} |
|
||||
| no_valid_prices | market_id | {market ID} |
|
17
x/pricefeed/spec/05_params.md
Normal file
17
x/pricefeed/spec/05_params.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Parameters
|
||||
|
||||
The pricefeed module has the following parameters:
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
|------------|----------------|---------------|--------------------------------------------------|
|
||||
| Markets | array (Market) | [{see below}] | array of params for each market in the pricefeed |
|
||||
|
||||
Each `Market` has the following parameters
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
|------------|--------------------|--------------------------|----------------------------------------------------------------|
|
||||
| MarketID | string | "bnb:usd" | identifier for the market -- **must** be unique across markets |
|
||||
| BaseAsset | string | "bnb" | the base asset for the market pair |
|
||||
| QuoteAsset | string | "usd" | the quote asset for the market pair |
|
||||
| Oracles | array (AccAddress) | ["kava1...", "kava1..."] | addresses which can post prices for the market |
|
||||
| Active | bool | true | flag to disable oracle interactions with the module |
|
26
x/pricefeed/spec/06_end_block.md
Normal file
26
x/pricefeed/spec/06_end_block.md
Normal file
@ -0,0 +1,26 @@
|
||||
# End Block
|
||||
|
||||
At the end of each block, the current price is calculated as the median of all raw prices for each market. The logic is as follows:
|
||||
|
||||
```go
|
||||
// EndBlocker updates the current pricefeed
|
||||
func EndBlocker(ctx sdk.Context, k Keeper) {
|
||||
// Update the current price of each asset.
|
||||
for _, market := range k.GetMarkets(ctx) {
|
||||
if market.Active {
|
||||
err := k.SetCurrentPrices(ctx, market.MarketID)
|
||||
if err != nil {
|
||||
// In the event of failure, emit an event.
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
EventTypeNoValidPrices,
|
||||
sdk.NewAttribute(AttributeMarketID, fmt.Sprintf("%s", market.MarketID)),
|
||||
),
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
```
|
13
x/pricefeed/spec/README.md
Normal file
13
x/pricefeed/spec/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# `pricefeed`
|
||||
|
||||
<!-- TOC -->
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[Messages](03_messages.md)**
|
||||
4. **[Events](04_events.md)**
|
||||
5. **[Params](05_params.md)**
|
||||
6. **[EndBlock](06_end_block.md)**
|
||||
|
||||
## Abstract
|
||||
|
||||
`x/pricefeed` is an implementation of a Cosmos SDK Module that handles the posting of prices for various markets by a group of whitelisted oracles. At the end of each block, the median price of all oracle posted prices is determined for each market and stored.
|
@ -6,10 +6,9 @@ const (
|
||||
EventTypeOracleUpdatedPrice = "oracle_updated_price"
|
||||
EventTypeNoValidPrices = "no_valid_prices"
|
||||
|
||||
AttributeValueCategory = ModuleName
|
||||
AttributeMarketID = "market_id"
|
||||
AttributeMarketPrice = "market_price"
|
||||
AttributeOracle = "oracle"
|
||||
AttributeExpiry = "expiry"
|
||||
AttributeKeyPriceUpdateFailed = "price_update_failed"
|
||||
AttributeValueCategory = ModuleName
|
||||
AttributeMarketID = "market_id"
|
||||
AttributeMarketPrice = "market_price"
|
||||
AttributeOracle = "oracle"
|
||||
AttributeExpiry = "expiry"
|
||||
)
|
||||
|
@ -1,8 +1,7 @@
|
||||
# Concepts
|
||||
|
||||
The validator-vesting module is responsible for managing Validator Vesting Accounts, a vesting account for which the release of coins is tied to the validation of the blockchain. Validator Vesting Accounts implemnt the cosmos-sdk vesting account spec, which can be found [here](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth/spec).
|
||||
The validator-vesting module is responsible for managing Validator Vesting Accounts, a vesting account for which the release of coins is tied to the validation of the blockchain. Validator Vesting Accounts implement the cosmos-sdk vesting account spec, which can be found [here](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth/spec).
|
||||
|
||||
The main concept the Validator Vesting Account introduces is that of _conditional vesting_, or vesting accounts in which it is possible for some or all of the vesting coins to fail to vest. For Validator Vesting Accounts, vesting is broken down into user-specified __vesting periods__. Each vesting period specifies an amount of coins that could vest in that period, and how long the vesting period lasts.
|
||||
|
||||
For each vesting period, a __signing threshold__ is specified, which is the percentage of blocks that must be signed for the coins to successfully vest. After a period ends, coins that are successfully vested become freely spendable. Coins that do not successfuly vest are burned, or sent to an optional return address.
|
||||
|
||||
For each vesting period, a __signing threshold__ is specified, which is the percentage of blocks that must be signed for the coins to successfully vest. After a period ends, coins that are successfully vested become freely spendable. Coins that do not successfully vest are burned, or sent to an optional return address.
|
||||
|
@ -2,24 +2,26 @@
|
||||
|
||||
## Validator Vesting Account type
|
||||
|
||||
Validator Vesting Accounts implement the `cosmos-sdk` vesting account spec and extend the `PeriodicVestingAccountType`:
|
||||
Validator Vesting Accounts implement the `cosmos-sdk` vesting account interfaces and extends the `PeriodicVestingAccountType`:
|
||||
|
||||
```go
|
||||
type ValidatorVestingAccount struct {
|
||||
*vesting.PeriodicVestingAccount
|
||||
|
||||
ValidatorAddress sdk.ConsAddress // The validator address which will be used to check if blocks were signed
|
||||
ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"` // The account where coins will be returned in the event of a failed vesting period
|
||||
SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"` // The percentage of blocks, as an integer between 0 and 100, that must be signed each period for coins to successfully vest.
|
||||
MissingSignCount []int64 `json:"missing_sign_count" yaml:"missing_sign_count"` // An array of two integers which track the number of blocks that were not signed during the current period and the total number of blocks which have passed during the current period, respectively.
|
||||
VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"` //An 2d array with length equal to the number of vesting periods. After each period, the value at the first index of that period is updated with 1 to represent that the period is over. The value at the second index is updated to 0 for unsucessful vesting and 1 for successful vesting.
|
||||
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` // The debt currently owed by the account. Debt accumulates in the event of unsuccessful vesting periods.
|
||||
}
|
||||
VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"` //An 2d array with length equal to the number of vesting periods. After each period, the value at the first index of that period is updated with 1 to represent that the period is over. The value at the second index is updated to 0 for unsuccessful vesting and 1 for successful vesting.
|
||||
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` // The debt currently owed by the account. Debt accumulates in the event of unsuccessful vesting periods.
|
||||
}
|
||||
```
|
||||
|
||||
## Stores
|
||||
|
||||
There is one `KVStore` in `validator-vesting` which stores
|
||||
|
||||
* A mapping from each ValidatorVestingAccount `address` to `[]Byte{0}`
|
||||
* A mapping from `previous_block_time_prefix` to `time.Time`
|
||||
|
||||
The use of `[]Byte{0}` value for each `address` key reflects that this module only accesses the store to get or iterate over keys, and does not require storing an value.
|
||||
The use of `[]Byte{0}` value for each `address` key reflects that this module only accesses the store to get or iterate over keys, and does not require storing an value. The storage of the actual account state is done by the cosmos-sdk `x/auth` `AccountKeeper`.
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Begin Block
|
||||
|
||||
At each `BeginBlock`, all validator vesting accounts are iterated over to update the status of the current vesting period. Note that the address of each account is retreived by iterating over the keys in the `validator-vesting` store, while the account objects are stored and accessed using the `auth` module's `AccountKeeper`. For each account, the block count is incremented, the missed sign count is incremented if the validator did not sign the block or was not found in the validator set. By comparing the blocktime of the current `BeginBlock`, with the value of `previousBlockTime` stored in the `validator-vesting` store, it is determined if the end of the current period has been reached. If the current period has ended, the `VestingPeriodProgress` field is updated to reflect if the coins for the ending period successfully vested or not. After updates are made regarding the status of the current vesting period, any outstanding debt on the account is attempted to be collected. If there is enough `SpendableBalance` on the account to cover the debt, coins are sent to the `ReturnAdress` or burned. If there is not enough `SpendableBalance` to cover the debt, all delegations of the account are `Unbonded`. Once those unbonding events reach maturity, the coins freed from the undonding will be used to cover the debt. Finally, the time of the previous block is stored in the validator vesting account keeper, which is used to determine when a period has ended.
|
||||
At each `BeginBlock`, all validator vesting accounts are iterated over to update the status of the current vesting period. Note that the address of each account is retrieved by iterating over the keys in the `validator-vesting` store, while the account objects are stored and accessed using the `auth` module's `AccountKeeper`. For each account, the block count is incremented, the missed sign count is incremented if the validator did not sign the block or was not found in the validator set. By comparing the blocktime of the current `BeginBlock`, with the value of `previousBlockTime` stored in the `validator-vesting` store, it is determined if the end of the current period has been reached. If the current period has ended, the `VestingPeriodProgress` field is updated to reflect if the coins for the ending period successfully vested or not. After updates are made regarding the status of the current vesting period, any outstanding debt on the account is attempted to be collected. If there is enough `SpendableBalance` on the account to cover the debt, coins are sent to the `ReturnAdress` or burned. If there is not enough `SpendableBalance` to cover the debt, all delegations of the account are `Unbonded`. Once those unbonding events reach maturity, the coins freed from the unbonding will be used to cover the debt. Finally, the time of the previous block is stored in the validator vesting account keeper, which is used to determine when a period has ended.
|
||||
|
||||
```go
|
||||
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
|
||||
@ -42,4 +42,3 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
|
||||
k.SetPreviousBlockTime(ctx, currentBlockTime)
|
||||
}
|
||||
```
|
||||
|
||||
|
14
x/validator-vesting/spec/README.md
Normal file
14
x/validator-vesting/spec/README.md
Normal file
@ -0,0 +1,14 @@
|
||||
# `validator-vesting`
|
||||
|
||||
<!-- TOC -->
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[BeginBlock](03_begin_block.md)**
|
||||
|
||||
## Abstract
|
||||
|
||||
`x/validator-vesting` is an implementation of a Cosmos SDK sub-module that defines a new type of vesting account, `ValidatorVestingAccount`. This account implements the Cosmos SDK `VestingAccount` interface and extends it to add conditions to the vesting balance. In this implementation, in order to receive the vesting balance, the validator vesting account specifies a validator that must sign a given `SigningThreshold` of blocks during each vesting period in order for coins to successfully vest.
|
||||
|
||||
## Dependencies
|
||||
|
||||
This module uses the Cosmos SDK `x/auth` module and `x/auth/vesting` sub-module definitions of `Account` and `VestingAccount`. The actual state of a `ValidatorVestingAccount` is stored in the `x/auth` keeper, while this module merely stores a list of addresses that correspond to validator vesting accounts for fast iteration.
|
Loading…
Reference in New Issue
Block a user