Merge branch 'master' into ro-emergency-gov-module

This commit is contained in:
rhuairahrighairigh 2020-04-27 15:55:20 +01:00
commit 6c3d525f0f
281 changed files with 19955 additions and 2838 deletions

View File

@ -101,7 +101,7 @@ clean:
# This tool checks local markdown links as well.
# Set to exclude riot links as they trigger false positives
link-check:
@go run github.com/raviqqe/liche -r . --exclude "^http://127.*|^https://riot.im/app*"
@go run github.com/raviqqe/liche -r . --exclude "^http://127.*|^https://riot.im/app*|^http://kava-testnet*|^https://testnet-dex*"
########################################
### Testing
@ -112,19 +112,32 @@ link-check:
test-all: build
# basic app tests
@go test ./app -v
# cli tests
@go test ./cli_test -tags cli_test -v -p 4
# basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks)
@go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h
# basic simulation (seed "4" happens to not unbond all validators before reaching 100 blocks)
@go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h
# other sim tests
@go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h
@go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h
@go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h
@go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h
@# AppStateDeterminism does not use Seed flag
@go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -v -timeout 24h
@go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h
# run module tests and short simulations
test-basic: test
@go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m
# other sim tests
@go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m
@go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m
@# AppStateDeterminism does not use Seed flag
@go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m
test:
@go test ./...
test-rest:
rest_test/./run_all_tests_from_make.sh
test-cli:
@go test ./cli_test -tags cli_test -v -p 4
# Kick start lots of sims on an AWS cluster.
# This submits an AWS Batch job to run a lot of sims, each within a docker image. Results are uploaded to S3
start-remote-sims:
@ -140,4 +153,4 @@ start-remote-sims:
-—job-definition kava-sim-master \
-—container-override environment=[{SIM_NAME=master-$(VERSION)}]
.PHONY: all build-linux install clean build test test-all start-remote-sims
.PHONY: all build-linux install clean build test test-cli test-all test-rest test-basic start-remote-sims

View File

@ -19,11 +19,9 @@
### [Telegram](https://t.me/kavalabs) | [Medium](https://medium.com/kava-labs) | [Validator Chat](https://riot.im/app/#/room/#kava-validators:matrix.org)
### Participate in Kava testnets and [snag a founder badge](./docs/REWARDS.md)!
</div>
Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [comsos-sdk](https://github.com/cosmos/cosmos-sdk).
Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [cosmos-sdk](https://github.com/cosmos/cosmos-sdk).
## Mainnet

View File

@ -4,13 +4,15 @@ import (
"io"
"os"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
dbm "github.com/tendermint/tm-db"
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/version"
@ -19,6 +21,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
"github.com/cosmos/cosmos-sdk/x/evidence"
"github.com/cosmos/cosmos-sdk/x/genutil"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/mint"
@ -29,10 +32,14 @@ import (
"github.com/cosmos/cosmos-sdk/x/supply"
"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"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
const (
@ -60,10 +67,14 @@ var (
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
supply.AppModuleBasic{},
evidence.AppModuleBasic{},
auction.AppModuleBasic{},
cdp.AppModuleBasic{},
pricefeed.AppModuleBasic{},
committee.AppModuleBasic{},
bep3.AppModuleBasic{},
kavadist.AppModuleBasic{},
incentive.AppModuleBasic{},
)
// module account permissions
@ -78,10 +89,16 @@ var (
auction.ModuleName: nil,
cdp.ModuleName: {supply.Minter, supply.Burner},
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
cdp.SavingsRateMacc: {supply.Minter},
bep3.ModuleName: {supply.Minter, supply.Burner},
kavadist.ModuleName: {supply.Minter},
}
)
// Extended ABCI application
// Verify app interface at compile time
var _ simapp.App = (*App)(nil)
// App represents an extended ABCI application
type App struct {
*bam.BaseApp
cdc *codec.Codec
@ -103,11 +120,15 @@ type App struct {
govKeeper gov.Keeper
crisisKeeper crisis.Keeper
paramsKeeper params.Keeper
evidenceKeeper evidence.Keeper
vvKeeper validatorvesting.Keeper
auctionKeeper auction.Keeper
cdpKeeper cdp.Keeper
pricefeedKeeper pricefeed.Keeper
committeeKeeper committee.Keeper
bep3Keeper bep3.Keeper
kavadistKeeper kavadist.Keeper
incentiveKeeper incentive.Keeper
// the module manager
mm *module.Manager
@ -130,8 +151,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, validatorvesting.StoreKey,
auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, committee.StoreKey,
gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey,
auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey,
kavadist.StoreKey, incentive.StoreKey, committee.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
@ -144,7 +166,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
}
// init params keeper and subspaces
app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace)
app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey])
authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace)
bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace)
stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace)
@ -152,61 +174,83 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace)
slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace)
govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable())
evidenceSubspace := app.paramsKeeper.Subspace(evidence.DefaultParamspace)
crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace)
auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace)
cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace)
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace)
bep3Subspace := app.paramsKeeper.Subspace(bep3.DefaultParamspace)
kavadistSubspace := app.paramsKeeper.Subspace(kavadist.DefaultParamspace)
incentiveSubspace := app.paramsKeeper.Subspace(incentive.DefaultParamspace)
// add keepers
app.accountKeeper = auth.NewAccountKeeper(
app.cdc,
keys[auth.StoreKey],
authSubspace,
auth.ProtoBaseAccount)
auth.ProtoBaseAccount,
)
app.bankKeeper = bank.NewBaseKeeper(
app.accountKeeper,
bankSubspace,
bank.DefaultCodespace,
app.ModuleAccountAddrs())
app.ModuleAccountAddrs(),
)
app.supplyKeeper = supply.NewKeeper(
app.cdc,
keys[supply.StoreKey],
app.accountKeeper,
app.bankKeeper,
mAccPerms)
mAccPerms,
)
stakingKeeper := staking.NewKeeper(
app.cdc,
keys[staking.StoreKey],
app.supplyKeeper,
stakingSubspace,
staking.DefaultCodespace)
)
app.mintKeeper = mint.NewKeeper(
app.cdc,
keys[mint.StoreKey],
mintSubspace,
&stakingKeeper,
app.supplyKeeper,
auth.FeeCollectorName)
auth.FeeCollectorName,
)
app.distrKeeper = distr.NewKeeper(
app.cdc,
keys[distr.StoreKey],
distrSubspace,
&stakingKeeper,
app.supplyKeeper,
distr.DefaultCodespace,
auth.FeeCollectorName,
app.ModuleAccountAddrs())
app.ModuleAccountAddrs(),
)
app.slashingKeeper = slashing.NewKeeper(
app.cdc,
keys[slashing.StoreKey],
&stakingKeeper,
slashingSubspace,
slashing.DefaultCodespace)
)
app.crisisKeeper = crisis.NewKeeper(
crisisSubspace,
invCheckPeriod,
app.supplyKeeper,
auth.FeeCollectorName)
auth.FeeCollectorName,
)
// create evidence keeper with router
evidenceKeeper := evidence.NewKeeper(
app.cdc,
keys[evidence.StoreKey],
evidenceSubspace,
&app.stakingKeeper,
app.slashingKeeper,
)
evidenceRouter := evidence.NewRouter()
evidenceKeeper.SetRouter(evidenceRouter)
app.evidenceKeeper = *evidenceKeeper
// create committee keeper with router
committeeGovRouter := gov.NewRouter()
committeeGovRouter.
AddRoute(gov.RouterKey, gov.ProposalHandler).
@ -218,7 +262,10 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
app.cdc,
keys[committee.StoreKey],
committeeGovRouter,
committee.DefaultCodespace) // TODO blacklist module addresses?)
committee.DefaultCodespace, // TODO blacklist module addresses?)
)
// create gov keeper with router
govRouter := gov.NewRouter()
govRouter.
AddRoute(gov.RouterKey, gov.ProposalHandler).
@ -231,26 +278,28 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
govSubspace,
app.supplyKeeper,
&stakingKeeper,
gov.DefaultCodespace,
govRouter)
govRouter,
)
app.vvKeeper = validatorvesting.NewKeeper(
app.cdc,
keys[validatorvesting.StoreKey],
app.accountKeeper,
app.bankKeeper,
app.supplyKeeper,
&stakingKeeper)
&stakingKeeper,
)
app.pricefeedKeeper = pricefeed.NewKeeper(
app.cdc,
keys[pricefeed.StoreKey],
pricefeedSubspace,
pricefeed.DefaultCodespace)
// NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType)
)
app.auctionKeeper = auction.NewKeeper(
app.cdc,
keys[auction.StoreKey],
app.supplyKeeper,
auctionSubspace)
auctionSubspace,
)
app.cdpKeeper = cdp.NewKeeper(
app.cdc,
keys[cdp.StoreKey],
@ -258,7 +307,28 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
app.pricefeedKeeper,
app.auctionKeeper,
app.supplyKeeper,
cdp.DefaultCodespace)
app.accountKeeper,
)
app.bep3Keeper = bep3.NewKeeper(
app.cdc,
keys[bep3.StoreKey],
app.supplyKeeper,
bep3Subspace,
)
app.kavadistKeeper = kavadist.NewKeeper(
app.cdc,
keys[kavadist.StoreKey],
kavadistSubspace,
app.supplyKeeper,
)
app.incentiveKeeper = incentive.NewKeeper(
app.cdc,
keys[incentive.StoreKey],
incentiveSubspace,
app.supplyKeeper,
app.cdpKeeper,
app.accountKeeper,
)
// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
@ -273,36 +343,41 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
bank.NewAppModule(app.bankKeeper, app.accountKeeper),
crisis.NewAppModule(&app.crisisKeeper),
supply.NewAppModule(app.supplyKeeper, app.accountKeeper),
distr.NewAppModule(app.distrKeeper, app.supplyKeeper),
gov.NewAppModule(app.govKeeper, app.supplyKeeper),
gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper),
mint.NewAppModule(app.mintKeeper),
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
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),
evidence.NewAppModule(app.evidenceKeeper),
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
auction.NewAppModule(app.auctionKeeper, app.supplyKeeper),
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper),
pricefeed.NewAppModule(app.pricefeedKeeper),
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
cdp.NewAppModule(app.cdpKeeper, app.accountKeeper, app.pricefeedKeeper, app.supplyKeeper),
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper),
committee.NewAppModule(app.committeeKeeper),
)
// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
// 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, auction.ModuleName, cdp.ModuleName, committee.ModuleName)
// 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.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
// Note: genutils must occur after staking so that pools are properly
// initialized with tokens from genesis accounts.
//
// Note: Changing the order of the auth module and modules that use module accounts
// results in subtle changes to the way accounts are loaded from genesis.
app.mm.SetOrderInitGenesis(
auth.ModuleName, validatorvesting.ModuleName, distr.ModuleName,
auth.ModuleName, // loads all accounts - should run before any module with a module account
validatorvesting.ModuleName, distr.ModuleName,
staking.ModuleName, bank.ModuleName, slashing.ModuleName,
gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName,
pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, committee.ModuleName, // TODO is this order ok?
gov.ModuleName, mint.ModuleName, evidence.ModuleName,
pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName,
bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName, committee.ModuleName
supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis
crisis.ModuleName, // runs the invariants at genesis - should run after other modules
genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts.
)
app.mm.RegisterInvariants(&app.crisisKeeper)
@ -317,15 +392,17 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
bank.NewAppModule(app.bankKeeper, app.accountKeeper),
supply.NewAppModule(app.supplyKeeper, app.accountKeeper),
gov.NewAppModule(app.govKeeper, app.supplyKeeper),
gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper),
mint.NewAppModule(app.mintKeeper),
distr.NewAppModule(app.distrKeeper, app.supplyKeeper),
distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper),
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper), // TODO how is the order be decided here? Is this order correct?
pricefeed.NewAppModule(app.pricefeedKeeper),
auction.NewAppModule(app.auctionKeeper, app.supplyKeeper),
// TODO committee
slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper),
pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper),
cdp.NewAppModule(app.cdpKeeper, app.accountKeeper, app.pricefeedKeeper, app.supplyKeeper),
auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper),
bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper),
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper),
)
app.sm.RegisterStoreDecoders()
@ -344,7 +421,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
if loadLatest {
err := app.LoadLatestVersion(app.keys[bam.MainStoreKey])
if err != nil {
cmn.Exit(err.Error())
tmos.Exit(err.Error())
}
}
@ -413,6 +490,11 @@ func (app *App) Codec() *codec.Codec {
return app.cdc
}
// SimulationManager implements the SimulationApp interface
func (app *App) SimulationManager() *module.SimulationManager {
return app.sm
}
// GetMaccPerms returns a mapping of the application's module account permissions.
func GetMaccPerms() map[string][]string {
perms := make(map[string][]string)

View File

@ -13,10 +13,9 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking"
)
// export the state of the app for a genesis file
func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string) (
appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
// ExportAppStateAndValidators export the state of the app for a genesis file
func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string,
) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) {
// as if they could withdraw from the start of the next block
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
@ -25,7 +24,6 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []
}
genState := app.mm.ExportGenesis(ctx)
appState, err = codec.MarshalJSONIndent(app.cdc, genState)
if err != nil {
return nil, nil, err
@ -35,6 +33,8 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []
}
// prepare for fresh start at zero height
// NOTE zero height genesis is a temporary feature which will be deprecated
// in favour of export at a block height
func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) {
applyWhiteList := false
@ -60,14 +60,24 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string
// withdraw all validator commission
app.stakingKeeper.IterateValidators(ctx, func(_ int64, val staking.ValidatorI) (stop bool) {
_, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator())
accumCommission := app.distrKeeper.GetValidatorAccumulatedCommission(ctx, val.GetOperator())
if accumCommission.IsZero() {
return false
}
_, err := app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator())
if err != nil {
log.Fatal(err)
}
return false
})
// withdraw all delegator rewards
dels := app.stakingKeeper.GetAllDelegations(ctx)
for _, delegation := range dels {
_, _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress)
_, err := app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress)
if err != nil {
log.Fatal(err)
}
}
// clear validator slash events
@ -86,7 +96,7 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator())
feePool := app.distrKeeper.GetFeePool(ctx)
feePool.CommunityPool = feePool.CommunityPool.Add(scraps)
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
app.distrKeeper.SetFeePool(ctx, feePool)
app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator())
@ -128,7 +138,6 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string
iter := sdk.KVStoreReversePrefixIterator(store, staking.ValidatorsKey)
counter := int16(0)
var valConsAddrs []sdk.ConsAddress
for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(iter.Key()[1:])
validator, found := app.stakingKeeper.GetValidator(ctx, addr)
@ -137,7 +146,6 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string
}
validator.UnbondingHeight = 0
valConsAddrs = append(valConsAddrs, validator.ConsAddress())
if applyWhiteList && !whiteListMap[addr.String()] {
validator.Jailed = true
}

19
app/params/doc.go Normal file
View File

@ -0,0 +1,19 @@
/*
Package params defines the simulation parameters for the Kava app.
It contains the default weights used for each transaction used on the module's
simulation. These weights define the chance for a transaction to be simulated at
any gived operation.
You can repace the default values for the weights by providing a params.json
file with the weights defined for each of the transaction operations:
{
"op_weight_msg_send": 60,
"op_weight_msg_delegate": 100,
}
In the example above, the `MsgSend` has 60% chance to be simulated, while the
`MsgDelegate` will always be simulated.
*/
package params

16
app/params/params.go Normal file
View File

@ -0,0 +1,16 @@
package params
// Simulation parameter constants
const (
StakePerAccount = "stake_per_account"
InitiallyBondedValidators = "initially_bonded_validators"
)
// Default simulation operation weights for messages and gov proposals
const (
DefaultWeightMsgPlaceBid int = 100
DefaultWeightMsgCreateAtomicSwap int = 100
DefaultWeightMsgUpdatePrices int = 100
DefaultWeightMsgCdp int = 100
DefaultWeightMsgClaimReward int = 100
)

View File

@ -3,60 +3,35 @@ package app
import (
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"os"
"testing"
"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"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
authsimops "github.com/cosmos/cosmos-sdk/x/auth/simulation/operations"
banksimops "github.com/cosmos/cosmos-sdk/x/bank/simulation/operations"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
distrsimops "github.com/cosmos/cosmos-sdk/x/distribution/simulation/operations"
"github.com/cosmos/cosmos-sdk/x/gov"
govsimops "github.com/cosmos/cosmos-sdk/x/gov/simulation/operations"
"github.com/cosmos/cosmos-sdk/x/mint"
"github.com/cosmos/cosmos-sdk/x/params"
paramsimops "github.com/cosmos/cosmos-sdk/x/params/simulation/operations"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/slashing"
slashingsimops "github.com/cosmos/cosmos-sdk/x/slashing/simulation/operations"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations"
"github.com/cosmos/cosmos-sdk/x/supply"
)
// Simulation parameter constants
const (
StakePerAccount = "stake_per_account"
InitiallyBondedValidators = "initially_bonded_validators"
OpWeightDeductFee = "op_weight_deduct_fee"
OpWeightMsgSend = "op_weight_msg_send"
OpWeightSingleInputMsgMultiSend = "op_weight_single_input_msg_multisend"
OpWeightMsgSetWithdrawAddress = "op_weight_msg_set_withdraw_address"
OpWeightMsgWithdrawDelegationReward = "op_weight_msg_withdraw_delegation_reward"
OpWeightMsgWithdrawValidatorCommission = "op_weight_msg_withdraw_validator_commission"
OpWeightSubmitVotingSlashingTextProposal = "op_weight_submit_voting_slashing_text_proposal"
OpWeightSubmitVotingSlashingCommunitySpendProposal = "op_weight_submit_voting_slashing_community_spend_proposal"
OpWeightSubmitVotingSlashingParamChangeProposal = "op_weight_submit_voting_slashing_param_change_proposal"
OpWeightMsgDeposit = "op_weight_msg_deposit"
OpWeightMsgCreateValidator = "op_weight_msg_create_validator"
OpWeightMsgEditValidator = "op_weight_msg_edit_validator"
OpWeightMsgDelegate = "op_weight_msg_delegate"
OpWeightMsgUndelegate = "op_weight_msg_undelegate"
OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate"
OpWeightMsgUnjail = "op_weight_msg_unjail"
)
type StoreKeysPrefixes struct {
A sdk.StoreKey
B sdk.StoreKey
Prefixes [][]byte
}
// TestMain runs setup and teardown code before all tests.
func TestMain(m *testing.M) {
@ -66,207 +41,11 @@ func TestMain(m *testing.M) {
config.Seal()
// load the values from simulation specific flags
simapp.GetSimulatorFlags()
// run tests
exitCode := m.Run()
os.Exit(exitCode)
}
func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOperation {
ap := make(simulation.AppParams)
paramChanges := app.sm.GenerateParamChanges(config.Seed)
if config.ParamsFile != "" {
bz, err := ioutil.ReadFile(config.ParamsFile)
if err != nil {
panic(err)
}
app.cdc.MustUnmarshalJSON(bz, &ap)
}
// nolint: govet
return []simulation.WeightedOperation{
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightDeductFee, &v, nil,
func(_ *rand.Rand) {
v = 5
})
return v
}(nil),
authsimops.SimulateDeductFee(app.accountKeeper, app.supplyKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgSend, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
banksimops.SimulateMsgSend(app.accountKeeper, app.bankKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSingleInputMsgMultiSend, &v, nil,
func(_ *rand.Rand) {
v = 10
})
return v
}(nil),
banksimops.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgSetWithdrawAddress, &v, nil,
func(_ *rand.Rand) {
v = 50
})
return v
}(nil),
distrsimops.SimulateMsgSetWithdrawAddress(app.distrKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgWithdrawDelegationReward, &v, nil,
func(_ *rand.Rand) {
v = 50
})
return v
}(nil),
distrsimops.SimulateMsgWithdrawDelegatorReward(app.distrKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgWithdrawValidatorCommission, &v, nil,
func(_ *rand.Rand) {
v = 50
})
return v
}(nil),
distrsimops.SimulateMsgWithdrawValidatorCommission(app.distrKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingTextProposal, &v, nil,
func(_ *rand.Rand) {
v = 5
})
return v
}(nil),
govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, govsimops.SimulateTextProposalContent),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingCommunitySpendProposal, &v, nil,
func(_ *rand.Rand) {
v = 5
})
return v
}(nil),
govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, distrsimops.SimulateCommunityPoolSpendProposalContent(app.distrKeeper)),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingParamChangeProposal, &v, nil,
func(_ *rand.Rand) {
v = 5
})
return v
}(nil),
govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, paramsimops.SimulateParamChangeProposalContent(paramChanges)),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgDeposit, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
govsimops.SimulateMsgDeposit(app.govKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgCreateValidator, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
stakingsimops.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgEditValidator, &v, nil,
func(_ *rand.Rand) {
v = 5
})
return v
}(nil),
stakingsimops.SimulateMsgEditValidator(app.stakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgDelegate, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
stakingsimops.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgUndelegate, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
stakingsimops.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgBeginRedelegate, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
stakingsimops.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper),
},
{
func(_ *rand.Rand) int {
var v int
ap.GetOrGenerate(app.cdc, OpWeightMsgUnjail, &v, nil,
func(_ *rand.Rand) {
v = 100
})
return v
}(nil),
slashingsimops.SimulateMsgUnjail(app.slashingKeeper),
},
}
}
// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
// an IAVLStore for faster simulation speed.
func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
@ -279,197 +58,96 @@ func interBlockCacheOpt() func(*baseapp.BaseApp) {
return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager())
}
// Profile with:
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/GaiaApp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out
func BenchmarkFullAppSimulation(b *testing.B) {
logger := log.NewNopLogger()
config := simapp.NewConfigFromFlags()
var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
db, _ = sdk.NewLevelDB("Simulation", dir)
defer func() {
db.Close()
_ = os.RemoveAll(dir)
}()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt())
// Run randomized simulation
// TODO: parameterize numbers, save for a later PR
_, simParams, simErr := simulation.SimulateFromSeed(
b, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
if err := ExportStateToJSON(app, config.ExportStatePath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if config.ExportParamsPath != "" {
if err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if simErr != nil {
fmt.Println(simErr)
b.FailNow()
}
if config.Commit {
fmt.Println("\nGoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
}
}
// TestFullAppSimulation runs a standard simulation of the app, modified by cmd line flag values.
func TestFullAppSimulation(t *testing.T) {
if !simapp.FlagEnabledValue {
config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
if skip {
t.Skip("skipping application simulation")
}
var logger log.Logger
config := simapp.NewConfigFromFlags()
if simapp.FlagVerboseValue {
logger = log.TestingLogger()
} else {
logger = log.NewNopLogger()
}
var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
db, _ = sdk.NewLevelDB("Simulation", dir)
require.NoError(t, err, "simulation setup failed")
defer func() {
db.Close()
_ = os.RemoveAll(dir)
require.NoError(t, os.RemoveAll(dir))
}()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "kava", app.Name())
require.Equal(t, appName, app.Name())
// Run randomized simulation
// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
err := ExportStateToJSON(app, config.ExportStatePath)
require.NoError(t, err)
}
if config.ExportParamsPath != "" {
err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath)
require.NoError(t, err)
}
require.NoError(t, simErr)
if config.Commit {
// for memdb:
// fmt.Println("Database Size", db.Stats()["database.size"])
fmt.Println("\nGoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
}
}
// TestAppImportExport runs a simulation, exports the state, imports it, then checks the db state is same after import as it was before export.
func TestAppImportExport(t *testing.T) {
if !simapp.FlagEnabledValue {
t.Skip("skipping application import/export simulation")
}
var logger log.Logger
config := simapp.NewConfigFromFlags()
if simapp.FlagVerboseValue {
logger = log.TestingLogger()
} else {
logger = log.NewNopLogger()
}
var db dbm.DB
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
db, _ = sdk.NewLevelDB("Simulation", dir)
defer func() {
db.Close()
_ = os.RemoveAll(dir)
}()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "kava", app.Name())
// Run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)
// export state and simParams before the simulation error is checked
if config.ExportStatePath != "" {
err := ExportStateToJSON(app, config.ExportStatePath)
err = simapp.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
}
if config.ExportParamsPath != "" {
err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath)
require.NoError(t, err)
}
require.NoError(t, simErr)
if config.Commit {
// for memdb:
// fmt.Println("Database Size", db.Stats()["database.size"])
fmt.Println("\nGoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
simapp.PrintStats(db)
}
}
func TestAppImportExport(t *testing.T) {
config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
if skip {
t.Skip("skipping application import/export simulation")
}
require.NoError(t, err, "simulation setup failed")
defer func() {
db.Close()
require.NoError(t, os.RemoveAll(dir))
}()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, app.Name())
// Run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)
// export state and simParams before the simulation error is checked
err = simapp.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)
if config.Commit {
simapp.PrintStats(db)
}
fmt.Printf("exporting genesis...\n")
appState, _, err := app.ExportAppStateAndValidators(false, []string{})
require.NoError(t, err)
fmt.Printf("importing genesis...\n")
newDir, _ := ioutil.TempDir("", "goleveldb-app-sim-2")
newDB, _ := sdk.NewLevelDB("Simulation-2", dir)
_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
require.NoError(t, err, "simulation setup failed")
defer func() {
newDB.Close()
_ = os.RemoveAll(newDir)
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "kava", newApp.Name())
require.Equal(t, appName, newApp.Name())
var genesisState simapp.GenesisState
err = app.cdc.UnmarshalJSON(appState, &genesisState)
var genesisState GenesisState
err = app.Codec().UnmarshalJSON(appState, &genesisState)
require.NoError(t, err)
ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
ctxB := newApp.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
newApp.mm.InitGenesis(ctxB, genesisState)
fmt.Printf("comparing stores...\n")
ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
type StoreKeysPrefixes struct {
A sdk.StoreKey
B sdk.StoreKey
Prefixes [][]byte
}
storeKeysPrefixes := []StoreKeysPrefixes{
{app.keys[baseapp.MainStoreKey], newApp.keys[baseapp.MainStoreKey], [][]byte{}},
@ -486,118 +164,84 @@ func TestAppImportExport(t *testing.T) {
{app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}},
}
for _, storeKeysPrefix := range storeKeysPrefixes {
storeKeyA := storeKeysPrefix.A
storeKeyB := storeKeysPrefix.B
prefixes := storeKeysPrefix.Prefixes
for _, skp := range storeKeysPrefixes {
storeA := ctxA.KVStore(skp.A)
storeB := ctxB.KVStore(skp.B)
storeA := ctxA.KVStore(storeKeyA)
storeB := ctxB.KVStore(storeKeyB)
failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, prefixes)
failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes)
require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare")
fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), storeKeyA, storeKeyB)
require.Len(t, failedKVAs, 0, simapp.GetSimulationLog(storeKeyA.Name(), app.sm.StoreDecoders, app.cdc, failedKVAs, failedKVBs))
fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B)
require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, app.Codec(), failedKVAs, failedKVBs))
}
}
// TestAppSimulationAfterImport runs a simulation, exports it, imports it and runs another simulation.
func TestAppSimulationAfterImport(t *testing.T) {
if !simapp.FlagEnabledValue {
config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
if skip {
t.Skip("skipping application simulation after import")
}
var logger log.Logger
config := simapp.NewConfigFromFlags()
if simapp.FlagVerboseValue {
logger = log.TestingLogger()
} else {
logger = log.NewNopLogger()
}
dir, _ := ioutil.TempDir("", "goleveldb-app-sim")
db, _ := sdk.NewLevelDB("Simulation", dir)
require.NoError(t, err, "simulation setup failed")
defer func() {
db.Close()
_ = os.RemoveAll(dir)
require.NoError(t, os.RemoveAll(dir))
}()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "kava", app.Name())
require.Equal(t, appName, app.Name())
// Run randomized simulation
// Run randomized simulation
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
err := ExportStateToJSON(app, config.ExportStatePath)
// export state and simParams before the simulation error is checked
err = simapp.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
}
if config.ExportParamsPath != "" {
err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath)
require.NoError(t, err)
}
require.NoError(t, simErr)
if config.Commit {
// for memdb:
// fmt.Println("Database Size", db.Stats()["database.size"])
fmt.Println("\nGoLevelDB Stats")
fmt.Println(db.Stats()["leveldb.stats"])
fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"])
simapp.PrintStats(db)
}
if stopEarly {
// we can't export or import a zero-validator genesis
fmt.Printf("We can't export or import a zero-validator genesis, exiting test...\n")
fmt.Println("can't export or import a zero-validator genesis, exiting test...")
return
}
fmt.Printf("Exporting genesis...\n")
fmt.Printf("exporting genesis...\n")
appState, _, err := app.ExportAppStateAndValidators(true, []string{})
if err != nil {
panic(err)
}
require.NoError(t, err)
fmt.Printf("Importing genesis...\n")
fmt.Printf("importing genesis...\n")
newDir, _ := ioutil.TempDir("", "goleveldb-app-sim-2")
newDB, _ := sdk.NewLevelDB("Simulation-2", dir)
_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
require.NoError(t, err, "simulation setup failed")
defer func() {
newDB.Close()
_ = os.RemoveAll(newDir)
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt)
require.Equal(t, "kava", newApp.Name())
newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, appName, newApp.Name())
newApp.InitChain(abci.RequestInitChain{
AppStateBytes: appState,
})
// Run randomized simulation on imported app
_, _, err = simulation.SimulateFromSeed(
t, os.Stdout, newApp.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(newApp, config), newApp.ModuleAccountAddrs(), config,
t, os.Stdout, newApp.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(newApp, newApp.Codec(), config),
newApp.ModuleAccountAddrs(), config,
)
require.NoError(t, err)
}
// TODO: Make another test for the fuzzer itself, which just has noOp txs
// and doesn't depend on the application.
// TestAppStateDeterminism runs several sims with the same seed and checks the states are equal.
func TestAppStateDeterminism(t *testing.T) {
if !simapp.FlagEnabledValue {
t.Skip("skipping application simulation")
@ -608,27 +252,25 @@ func TestAppStateDeterminism(t *testing.T) {
config.ExportParamsPath = ""
config.OnOperation = false
config.AllInvariants = false
config.ChainID = helpers.SimAppChainID
numSeeds := 3
numTimesToRunPerSeed := 5
numTimesToRunPerSeed := 2
appHashList := make([]json.RawMessage, numTimesToRunPerSeed)
for i := 0; i < numSeeds; i++ {
config.Seed = rand.Int63()
for j := 0; j < numTimesToRunPerSeed; j++ {
logger := log.NewNopLogger()
db := dbm.NewMemDB()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt())
fmt.Printf(
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",
config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
"running non-determinism simulation; seed %d: attempt: %d/%d\n",
config.Seed, j+1, numTimesToRunPerSeed,
)
_, _, err := simulation.SimulateFromSeed(
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()),
simapp.SimulationOperations(app, app.Codec(), config),
app.ModuleAccountAddrs(), config,
)
require.NoError(t, err)
@ -638,69 +280,8 @@ func TestAppStateDeterminism(t *testing.T) {
if j != 0 {
require.Equal(
t, appHashList[0], appHashList[j],
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
"non-determinism in seed %d: attempt: %d/%d\n", config.Seed, j+1, numTimesToRunPerSeed,
)
}
}
}
}
func BenchmarkInvariants(b *testing.B) {
logger := log.NewNopLogger()
config := simapp.NewConfigFromFlags()
config.AllInvariants = false
dir, _ := ioutil.TempDir("", "goleveldb-app-invariant-bench")
db, _ := sdk.NewLevelDB("simulation", dir)
defer func() {
db.Close()
os.RemoveAll(dir)
}()
app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt())
// 2. Run parameterized simulation (w/o invariants)
_, simParams, simErr := simulation.SimulateFromSeed(
b, ioutil.Discard, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm),
testAndRunTxs(app, config), app.ModuleAccountAddrs(), config,
)
// export state and params before the simulation error is checked
if config.ExportStatePath != "" {
if err := ExportStateToJSON(app, config.ExportStatePath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if config.ExportParamsPath != "" {
if err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil {
fmt.Println(err)
b.Fail()
}
}
if simErr != nil {
fmt.Println(simErr)
b.FailNow()
}
ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1})
// 3. Benchmark each invariant separately
//
// NOTE: We use the crisis keeper as it has all the invariants registered with
// their respective metadata which makes it useful for testing/benchmarking.
for _, cr := range app.crisisKeeper.Routes() {
b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) {
for n := 0; n < b.N; n++ {
if res, stop := cr.Invar(ctx); stop {
fmt.Printf("broken invariant at block %d of %d\n%s", ctx.BlockHeight()-1, config.NumBlocks, res)
b.FailNow()
}
}
})
}
}

View File

@ -28,8 +28,11 @@ import (
"github.com/cosmos/cosmos-sdk/x/supply"
"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"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
@ -49,6 +52,10 @@ type TestApp struct {
}
func NewTestApp() TestApp {
config := sdk.GetConfig()
SetBech32AddressPrefixes(config)
SetBip44CoinType(config)
db := tmdb.NewMemDB()
app := NewApp(log.NewNopLogger(), db, nil, true, 0)
return TestApp{App: *app}
@ -68,6 +75,9 @@ func (tApp TestApp) GetVVKeeper() validatorvesting.Keeper { return tApp.vvKeeper
func (tApp TestApp) GetAuctionKeeper() auction.Keeper { return tApp.auctionKeeper }
func (tApp TestApp) GetCDPKeeper() cdp.Keeper { return tApp.cdpKeeper }
func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper }
func (tApp TestApp) GetBep3Keeper() bep3.Keeper { return tApp.bep3Keeper }
func (tApp TestApp) GetKavadistKeeper() kavadist.Keeper { return tApp.kavadistKeeper }
func (tApp TestApp) GetIncentiveKeeper() incentive.Keeper { return tApp.incentiveKeeper }
func (tApp TestApp) GetCommitteeKeeper() committee.Keeper { return tApp.committeeKeeper }
// This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states

View File

@ -14,7 +14,6 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/client"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys"
@ -208,7 +207,7 @@ func (f *Fixtures) UnsafeResetAll(flags ...string) {
// NOTE: GDInit sets the ChainID for the Fixtures instance
func (f *Fixtures) GDInit(moniker string, flags ...string) {
cmd := fmt.Sprintf("%s init -o --home=%s %s", f.GaiadBinary, f.GaiadHome, moniker)
_, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
_, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
var chainID string
var initRes map[string]json.RawMessage
@ -231,13 +230,13 @@ func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, fl
// GenTx is gaiad gentx
func (f *Fixtures) GenTx(name string, flags ...string) {
cmd := fmt.Sprintf("%s gentx --name=%s --home=%s --home-client=%s", f.GaiadBinary, name, f.GaiadHome, f.GaiacliHome)
executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// CollectGenTxs is gaiad collect-gentxs
func (f *Fixtures) CollectGenTxs(flags ...string) {
cmd := fmt.Sprintf("%s collect-gentxs --home=%s", f.GaiadBinary, f.GaiadHome)
executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// GDStart runs gaiad start with the appropriate flags and returns a process
@ -276,19 +275,19 @@ func (f *Fixtures) KeysDelete(name string, flags ...string) {
// KeysAdd is gaiacli keys add
func (f *Fixtures) KeysAdd(name string, flags ...string) {
cmd := fmt.Sprintf("%s keys add --home=%s %s", f.GaiacliBinary, f.GaiacliHome, name)
executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// KeysAddRecover prepares gaiacli keys add --recover
func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) (exitSuccess bool, stdout, stderr string) {
cmd := fmt.Sprintf("%s keys add --home=%s --recover %s", f.GaiacliBinary, f.GaiacliHome, name)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass, mnemonic)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass, mnemonic)
}
// KeysAddRecoverHDPath prepares gaiacli keys add --recover --account --index
func (f *Fixtures) KeysAddRecoverHDPath(name, mnemonic string, account uint32, index uint32, flags ...string) {
cmd := fmt.Sprintf("%s keys add --home=%s --recover %s --account %d --index %d", f.GaiacliBinary, f.GaiacliHome, name, account, index)
executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass, mnemonic)
executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass, mnemonic)
}
// KeysShow is gaiacli keys show
@ -324,25 +323,25 @@ func (f *Fixtures) CLIConfig(key, value string, flags ...string) {
// TxSend is gaiacli tx send
func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx send %s %s %s %v", f.GaiacliBinary, from, to, amount, f.Flags())
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxSign is gaiacli tx sign
func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx sign %v --from=%s %v", f.GaiacliBinary, f.Flags(), signer, fileName)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxBroadcast is gaiacli tx broadcast
func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx broadcast %v %v", f.GaiacliBinary, f.Flags(), fileName)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxEncode is gaiacli tx encode
func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx encode %v %v", f.GaiacliBinary, f.Flags(), fileName)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxMultisign is gaiacli tx multisign
@ -364,13 +363,13 @@ func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk.
cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05")
cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10")
cmd += fmt.Sprintf(" --min-self-delegation=%v", "1")
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxStakingUnbond is gaiacli tx staking unbond
func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool {
cmd := fmt.Sprintf("%s tx staking unbond %s %v --from=%s %v", f.GaiacliBinary, validator, shares, from, f.Flags())
return executeWrite(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWrite(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
//___________________________________________________________________________________
@ -380,19 +379,19 @@ func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress
func (f *Fixtures) TxGovSubmitProposal(from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx gov submit-proposal %v --from=%s --type=%s", f.GaiacliBinary, f.Flags(), from, typ)
cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxGovDeposit is gaiacli tx gov deposit
func (f *Fixtures) TxGovDeposit(proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx gov deposit %d %s --from=%s %v", f.GaiacliBinary, proposalID, amount, from, f.Flags())
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxGovVote is gaiacli tx gov vote
func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx gov vote %d %s --from=%s %v", f.GaiacliBinary, proposalID, option, from, f.Flags())
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxGovSubmitParamChangeProposal executes a CLI parameter change proposal
@ -406,7 +405,7 @@ func (f *Fixtures) TxGovSubmitParamChangeProposal(
f.GaiacliBinary, proposalPath, from, f.Flags(),
)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
// TxGovSubmitCommunityPoolSpendProposal executes a CLI community pool spend proposal
@ -420,7 +419,7 @@ func (f *Fixtures) TxGovSubmitCommunityPoolSpendProposal(
f.GaiacliBinary, proposalPath, from, f.Flags(),
)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass)
return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass)
}
//___________________________________________________________________________________

View File

@ -12,6 +12,7 @@ import (
"github.com/tendermint/tendermint/libs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/client/rpc"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -48,7 +49,7 @@ func main() {
}
// Add --chain-id to persistent flags and mark it required
rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node")
rootCmd.PersistentFlags().String(flags.FlagChainID, "", "Chain ID of tendermint node")
rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error {
return initConfig(rootCmd)
}
@ -59,13 +60,13 @@ func main() {
client.ConfigCmd(app.DefaultCLIHome),
queryCmd(cdc),
txCmd(cdc),
client.LineBreak,
flags.LineBreak,
lcd.ServeCommand(cdc, registerRoutes),
client.LineBreak,
flags.LineBreak,
getModifiedKeysCmd(),
client.LineBreak,
flags.LineBreak,
version.Cmd,
client.NewCompletionCmd(rootCmd, true),
flags.NewCompletionCmd(rootCmd, true),
)
// Add flags and prefix all env exposed with KA
@ -87,12 +88,12 @@ func queryCmd(cdc *amino.Codec) *cobra.Command {
queryCmd.AddCommand(
authcmd.GetAccountCmd(cdc),
client.LineBreak,
flags.LineBreak,
rpc.ValidatorCommand(cdc),
rpc.BlockCommand(),
authcmd.QueryTxsByEventsCmd(cdc),
authcmd.QueryTxCmd(cdc),
client.LineBreak,
flags.LineBreak,
)
// add modules' query commands
@ -109,14 +110,14 @@ func txCmd(cdc *amino.Codec) *cobra.Command {
txCmd.AddCommand(
bankcmd.SendTxCmd(cdc),
client.LineBreak,
flags.LineBreak,
authcmd.GetSignCommand(cdc),
authcmd.GetMultiSignCommand(cdc),
client.LineBreak,
flags.LineBreak,
authcmd.GetBroadcastCommand(cdc),
authcmd.GetEncodeCommand(cdc),
authcmd.GetDecodeCommand(cdc),
client.LineBreak,
flags.LineBreak,
)
// add modules' tx commands
@ -156,7 +157,7 @@ func initConfig(cmd *cobra.Command) error {
return err
}
}
if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil {
if err := viper.BindPFlag(flags.FlagChainID, cmd.PersistentFlags().Lookup(flags.FlagChainID)); err != nil {
return err
}
if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil {

View File

@ -14,7 +14,7 @@ import (
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -59,7 +59,7 @@ func main() {
app.DefaultCLIHome))
rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics))
rootCmd.AddCommand(AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome))
rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true))
rootCmd.AddCommand(flags.NewCompletionCmd(rootCmd, true))
server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators)

View File

@ -1,4 +1,19 @@
# Contrib
Resources and examples for running and interacting with the kava blockchain.
Resources and examples for running and interacting with the kava blockchain. `contrib` contains sample genesis files, example governance proposals, and rest-server request examples for different versions of kava.
## testnet-4000
kava-testnet-4000 introduces the cdp and auction modules, allowing users to create CDPs and mint usdx as well as participate in auctions.
Configuration and rest-server interaction files are available in the [testnet-4000](./testnet-4000/README.md) directory.
## testnet-5000
kava-testnet-5000 introduces the bep3 modules, allowing users to transfer BNB between the bnbchain testnet and kava.
Configuration and rest-server interaction files are available in the [testnet-5000](./testnet-5000/README.md) directory.
## dev
During development new features offer require modifications to the genesis file. The dev directory houses these genesis files.

View File

@ -0,0 +1,352 @@
{
"genesis_time": "2020-03-07T18:27:07.837213082Z",
"chain_id": "testing",
"consensus_params": {
"block": {
"max_bytes": "22020096",
"max_gas": "-1",
"time_iota_ms": "1000"
},
"evidence": {
"max_age": "100000"
},
"validator": {
"pub_key_types": [
"ed25519"
]
}
},
"app_hash": "",
"app_state": {
"cdp": {
"cdps": [],
"debt_denom": "debt",
"deposits": [],
"gov_denom": "ukava",
"previous_distribution_time": "1970-01-01T00:00:00Z",
"params": {
"circuit_breaker": false,
"collateral_params": [
{
"auction_size": "5000000000",
"conversion_factor": "6",
"debt_limit": [
{
"amount": "10000000",
"denom": "usdx"
}
],
"denom": "xrp",
"liquidation_penalty": "0.05",
"liquidation_ratio": "2.0",
"market_id": "xrp:usd",
"prefix": 0,
"stability_fee": "1.000000001547126"
},
{
"auction_size": "10000000",
"conversion_factor": "8",
"debt_limit": [
{
"amount": "10000000",
"denom": "usdx"
}
],
"denom": "btc",
"liquidation_penalty": "0.05",
"liquidation_ratio": "1.5",
"market_id": "btc:usd",
"prefix": 1,
"stability_fee": "1.0000000007829977"
}
],
"debt_auction_threshold": "1000000000",
"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"
}
],
"savings_distribution_frequency": "120000000000",
"surplus_auction_threshold": "1000000000"
},
"previous_block_time": "1970-01-01T00:00:00Z",
"starting_cdp_id": "1"
},
"bank": {
"send_enabled": true
},
"params": null,
"bep3": {
"params": {
"bnb_deputy_address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
"min_block_lock": "80",
"max_block_lock": "600",
"supported_assets": [
{
"denom": "ukava",
"coin_id": "459",
"limit": "1",
"active": false
}
]
},
"atomic_swaps": [],
"assets_supplies": []
},
"pricefeed": {
"params": {
"markets": [
{
"active": true,
"base_asset": "xrp",
"market_id": "xrp:usd",
"oracles": [
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"kava1xq4cspcgl9thzmn6lkvd6dlx28wsr63zw4mlmf"
],
"quote_asset": "usd"
},
{
"active": true,
"base_asset": "btc",
"market_id": "btc:usd",
"oracles": [
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
],
"quote_asset": "usd"
}
]
},
"posted_prices": [
{
"expiry": "2050-01-01T00:00:00Z",
"market_id": "btc:usd",
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"price": "8700.0"
},
{
"expiry": "2050-01-01T00:00:00Z",
"market_id": "xrp:usd",
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"price": "0.25"
}
]
},
"gov": {
"starting_proposal_id": "1",
"deposits": null,
"votes": null,
"proposals": null,
"deposit_params": {
"min_deposit": [
{
"denom": "ukava",
"amount": "10000000"
}
],
"max_deposit_period": "172800000000000"
},
"voting_params": {
"voting_period": "172800000000000"
},
"tally_params": {
"quorum": "0.334000000000000000",
"threshold": "0.500000000000000000",
"veto": "0.334000000000000000"
}
},
"staking": {
"params": {
"unbonding_time": "1814400000000000",
"max_validators": 100,
"max_entries": 7,
"bond_denom": "ukava"
},
"last_total_power": "0",
"last_validator_powers": null,
"validators": null,
"delegations": null,
"unbonding_delegations": null,
"redelegations": null,
"exported": false
},
"mint": {
"minter": {
"inflation": "0.130000000000000000",
"annual_provisions": "0.000000000000000000"
},
"params": {
"mint_denom": "ukava",
"inflation_rate_change": "0.130000000000000000",
"inflation_max": "0.200000000000000000",
"inflation_min": "0.070000000000000000",
"goal_bonded": "0.670000000000000000",
"blocks_per_year": "6311520"
}
},
"auth": {
"params": {
"max_memo_characters": "256",
"tx_sig_limit": "7",
"tx_size_cost_per_byte": "10",
"sig_verify_cost_ed25519": "590",
"sig_verify_cost_secp256k1": "1000"
},
"accounts": [
{
"type": "cosmos-sdk/Account",
"value": {
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"coins": [
{
"denom": "ukava",
"amount": "1000000000000"
},
{
"denom": "btc",
"amount": "10000000000"
},
{
"denom": "xrp",
"amount": "1000000000000"
}
],
"public_key": null,
"account_number": "0",
"sequence": "0"
}
},
{
"type": "cosmos-sdk/Account",
"value": {
"address": "kava19dp7luf32mlqnw6mhpgk0g37ule7wm2g8gck8a",
"coins": [
{
"denom": "ukava",
"amount": "1000000000000"
}
],
"public_key": null,
"account_number": "0",
"sequence": "0"
}
}
]
},
"auction": {
"next_auction_id": "1",
"params": {
"max_auction_duration": "172800000000000",
"bid_duration": "3600000000000"
},
"auctions": []
},
"validatorvesting": {
"previous_block_time": "1970-01-01T00:00:00Z"
},
"supply": {
"supply": []
},
"crisis": {
"constant_fee": {
"denom": "ukava",
"amount": "1000"
}
},
"distribution": {
"fee_pool": {
"community_pool": []
},
"community_tax": "0.020000000000000000",
"base_proposer_reward": "0.010000000000000000",
"bonus_proposer_reward": "0.040000000000000000",
"withdraw_addr_enabled": true,
"delegator_withdraw_infos": [],
"previous_proposer": "",
"outstanding_rewards": [],
"validator_accumulated_commissions": [],
"validator_historical_rewards": [],
"validator_current_rewards": [],
"delegator_starting_infos": [],
"validator_slash_events": []
},
"genutil": {
"gentxs": [
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgCreateValidator",
"value": {
"description": {
"moniker": "kava-tester",
"identity": "",
"website": "",
"security_contact": "",
"details": ""
},
"commission": {
"rate": "0.100000000000000000",
"max_rate": "0.200000000000000000",
"max_change_rate": "0.010000000000000000"
},
"min_self_delegation": "1",
"delegator_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"validator_address": "kavavaloper15qdefkmwswysgg4qxgqpqr35k3m49pkx8yhpte",
"pubkey": "kavavalconspub1zcjduepq3qyhcxfpa0u2efeu3htuxjz0248khl2cqfcm8jz2n0dr2e2a6tuqqafg2g",
"value": {
"denom": "ukava",
"amount": "10000000000"
}
}
}
],
"fee": {
"amount": [],
"gas": "200000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
},
"signature": "ZIzw2qsaJzsNuROW1JYYH1ZOA3jrc4ZCHvrCxirWNlEZqIvnyC42nBQLIPQ+d+PIcpldLVy0KAkb8NBXj9G0nQ=="
}
],
"memo": "6fff8e9b327f0811e7a25c1419781167f82ec7b3@172.31.40.66:26656"
}
}
]
},
"slashing": {
"params": {
"max_evidence_age": "120000000000",
"signed_blocks_window": "100",
"min_signed_per_window": "0.500000000000000000",
"downtime_jail_duration": "600000000000",
"slash_fraction_double_sign": "0.050000000000000000",
"slash_fraction_downtime": "0.010000000000000000"
},
"signing_infos": {},
"missed_blocks": {}
}
}
}

View File

@ -0,0 +1,352 @@
{
"genesis_time": "2020-03-07T18:27:07.837213082Z",
"chain_id": "testing",
"consensus_params": {
"block": {
"max_bytes": "22020096",
"max_gas": "-1",
"time_iota_ms": "1000"
},
"evidence": {
"max_age": "100000"
},
"validator": {
"pub_key_types": [
"ed25519"
]
}
},
"app_hash": "",
"app_state": {
"cdp": {
"cdps": [],
"debt_denom": "debt",
"deposits": [],
"gov_denom": "ukava",
"previous_distribution_time": "1970-01-01T00:00:00Z",
"params": {
"circuit_breaker": false,
"collateral_params": [
{
"auction_size": "5000000000",
"conversion_factor": "6",
"debt_limit": [
{
"amount": "10000000",
"denom": "usdx"
}
],
"denom": "xrp",
"liquidation_penalty": "0.05",
"liquidation_ratio": "2.0",
"market_id": "xrp:usd",
"prefix": 0,
"stability_fee": "1.000000001547126"
},
{
"auction_size": "10000000",
"conversion_factor": "8",
"debt_limit": [
{
"amount": "10000000",
"denom": "usdx"
}
],
"denom": "btc",
"liquidation_penalty": "0.05",
"liquidation_ratio": "1.5",
"market_id": "btc:usd",
"prefix": 1,
"stability_fee": "1.0000000007829977"
}
],
"debt_auction_threshold": "1000000000",
"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"
}
],
"savings_distribution_frequency": "120000000000",
"surplus_auction_threshold": "1000000000"
},
"previous_block_time": "1970-01-01T00:00:00Z",
"starting_cdp_id": "1"
},
"bank": {
"send_enabled": true
},
"params": null,
"bep3": {
"params": {
"bnb_deputy_address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
"min_block_lock": "80",
"max_block_lock": "600",
"supported_assets": [
{
"denom": "ukava",
"coin_id": "459",
"limit": "1",
"active": false
}
]
},
"atomic_swaps": [],
"assets_supplies": []
},
"pricefeed": {
"params": {
"markets": [
{
"active": true,
"base_asset": "xrp",
"market_id": "xrp:usd",
"oracles": [
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"kava1xq4cspcgl9thzmn6lkvd6dlx28wsr63zw4mlmf"
],
"quote_asset": "usd"
},
{
"active": true,
"base_asset": "btc",
"market_id": "btc:usd",
"oracles": [
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
],
"quote_asset": "usd"
}
]
},
"posted_prices": [
{
"expiry": "2050-01-01T00:00:00Z",
"market_id": "btc:usd",
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"price": "8700.0"
},
{
"expiry": "2050-01-01T00:00:00Z",
"market_id": "xrp:usd",
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"price": "0.25"
}
]
},
"gov": {
"starting_proposal_id": "1",
"deposits": null,
"votes": null,
"proposals": null,
"deposit_params": {
"min_deposit": [
{
"denom": "ukava",
"amount": "10000000"
}
],
"max_deposit_period": "172800000000000"
},
"voting_params": {
"voting_period": "172800000000000"
},
"tally_params": {
"quorum": "0.334000000000000000",
"threshold": "0.500000000000000000",
"veto": "0.334000000000000000"
}
},
"staking": {
"params": {
"unbonding_time": "1814400000000000",
"max_validators": 100,
"max_entries": 7,
"bond_denom": "ukava"
},
"last_total_power": "0",
"last_validator_powers": null,
"validators": null,
"delegations": null,
"unbonding_delegations": null,
"redelegations": null,
"exported": false
},
"mint": {
"minter": {
"inflation": "0.130000000000000000",
"annual_provisions": "0.000000000000000000"
},
"params": {
"mint_denom": "ukava",
"inflation_rate_change": "0.130000000000000000",
"inflation_max": "0.200000000000000000",
"inflation_min": "0.070000000000000000",
"goal_bonded": "0.670000000000000000",
"blocks_per_year": "6311520"
}
},
"auth": {
"params": {
"max_memo_characters": "256",
"tx_sig_limit": "7",
"tx_size_cost_per_byte": "10",
"sig_verify_cost_ed25519": "590",
"sig_verify_cost_secp256k1": "1000"
},
"accounts": [
{
"type": "cosmos-sdk/Account",
"value": {
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"coins": [
{
"denom": "ukava",
"amount": "1000000000000"
},
{
"denom": "btc",
"amount": "10000000000"
},
{
"denom": "xrp",
"amount": "1000000000000"
}
],
"public_key": null,
"account_number": "0",
"sequence": "0"
}
},
{
"type": "cosmos-sdk/Account",
"value": {
"address": "kava19dp7luf32mlqnw6mhpgk0g37ule7wm2g8gck8a",
"coins": [
{
"denom": "ukava",
"amount": "1000000000000"
}
],
"public_key": null,
"account_number": "0",
"sequence": "0"
}
}
]
},
"auction": {
"next_auction_id": "1",
"params": {
"max_auction_duration": "172800000000000",
"bid_duration": "3600000000000"
},
"auctions": []
},
"validatorvesting": {
"previous_block_time": "1970-01-01T00:00:00Z"
},
"supply": {
"supply": []
},
"crisis": {
"constant_fee": {
"denom": "ukava",
"amount": "1000"
}
},
"distribution": {
"fee_pool": {
"community_pool": []
},
"community_tax": "0.020000000000000000",
"base_proposer_reward": "0.010000000000000000",
"bonus_proposer_reward": "0.040000000000000000",
"withdraw_addr_enabled": true,
"delegator_withdraw_infos": [],
"previous_proposer": "",
"outstanding_rewards": [],
"validator_accumulated_commissions": [],
"validator_historical_rewards": [],
"validator_current_rewards": [],
"delegator_starting_infos": [],
"validator_slash_events": []
},
"genutil": {
"gentxs": [
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "cosmos-sdk/MsgCreateValidator",
"value": {
"description": {
"moniker": "kava-tester",
"identity": "",
"website": "",
"security_contact": "",
"details": ""
},
"commission": {
"rate": "0.100000000000000000",
"max_rate": "0.200000000000000000",
"max_change_rate": "0.010000000000000000"
},
"min_self_delegation": "1",
"delegator_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"validator_address": "kavavaloper15qdefkmwswysgg4qxgqpqr35k3m49pkx8yhpte",
"pubkey": "kavavalconspub1zcjduepq3qyhcxfpa0u2efeu3htuxjz0248khl2cqfcm8jz2n0dr2e2a6tuqqafg2g",
"value": {
"denom": "ukava",
"amount": "10000000000"
}
}
}
],
"fee": {
"amount": [],
"gas": "200000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
},
"signature": "ZIzw2qsaJzsNuROW1JYYH1ZOA3jrc4ZCHvrCxirWNlEZqIvnyC42nBQLIPQ+d+PIcpldLVy0KAkb8NBXj9G0nQ=="
}
],
"memo": "6fff8e9b327f0811e7a25c1419781167f82ec7b3@172.31.40.66:26656"
}
}
]
},
"slashing": {
"params": {
"max_evidence_age": "120000000000",
"signed_blocks_window": "100",
"min_signed_per_window": "0.500000000000000000",
"downtime_jail_duration": "600000000000",
"slash_fraction_double_sign": "0.050000000000000000",
"slash_fraction_downtime": "0.010000000000000000"
},
"signing_infos": {},
"missed_blocks": {}
}
}
}

View File

@ -6,7 +6,7 @@ Resources and examples for running and interacting with kava-testnet-4000
### Setup
Before making a request, query account information for the signing account. Note the 'accountnumber' and 'sequence' fields, we'll need them later in order to send our request:
Before making a request, query account information for the signing account. Note the 'accountnumber' and 'sequence' fields, we'll need them later in order to send our request:
```bash
kvcli q auth account $(kvcli keys show accB -a)
@ -22,37 +22,37 @@ Now we'll create an unsigned request, sign it, and broadcast it to the Kava bloc
### Create CDP example request
Format the base request in create-cdp.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the CDP creation request's params 'owner', 'collateral', and 'principal'. An example formatted request can be found in `example-create-cdp.json`.
Format the base request in create-cdp.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the CDP creation request's params 'owner', 'collateral', and 'principal'. An example formatted request can be found in `example-create-cdp.json`.
```bash
# Create an unsigned request
curl -H "Content-Type: application/json" -X PUT -d @./contrib/requests/create-cdp.json http://127.0.0.1:1317/cdp | jq > ./contrib/requests/create-cdp-unsigned.json
curl -H "Content-Type: application/json" -X PUT -d @./contrib/testnet-4000/rest_examples/cdp/create-cdp.json http://127.0.0.1:1317/cdp | jq > ./contrib/testnet-4000/rest_examples/cdp/create-cdp-unsigned.json
# Sign the request
kvcli tx sign ./contrib/requests/create-cdp-unsigned.json --from accB --offline --chain-id testing --sequence 1 --account-number 2 | jq > ./contrib/requests/broadcast-create-cdp.json
kvcli tx sign ./contrib/testnet-4000/rest_examples/cdp/create-cdp-unsigned.json --from accB --offline --chain-id testing --sequence 1 --account-number 2 | jq > ./contrib/testnet-4000/rest_examples/cdp/broadcast-create-cdp.json
# Broadcast the request
kvcli tx broadcast ./contrib/requests/broadcast-create-cdp.json
kvcli tx broadcast ./contrib/testnet-4000/rest_examples/cdp/broadcast-create-cdp.json
```
Congratulations, you've just created a CDP on Kava using the rest server!
### Post market price example request
Note that only market oracles can post prices, other senders will have their transactions rejected by Kava.
Note that only market oracles can post prices, other senders will have their transactions rejected by Kava.
Format the base request in post-price.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the post price request's params 'from', 'market_id', 'price', and 'expiry'. An example formatted request can be found in `example-post-price.json`.
Format the base request in post-price.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the post price request's params 'from', 'market_id', 'price', and 'expiry'. An example formatted request can be found in `example-post-price.json`.
```bash
# Create an unsigned request
curl -H "Content-Type: application/json" -X PUT -d @./contrib/requests/post-price.json http://127.0.0.1:1317/pricefeed/postprice | jq > ./contrib/requests/post-price-unsigned.json
curl -H "Content-Type: application/json" -X PUT -d @./contrib/testnet-4000/rest_examples/pricefeed/post-price.json http://127.0.0.1:1317/pricefeed/postprice | jq > ./contrib/testnet-4000/rest_examples/pricefeed/post-price-unsigned.json
# Sign the request
kvcli tx sign ./contrib/requests/post-price-unsigned.json --from validator --offline --chain-id testing --sequence 96 --account-number 0 | jq > ./contrib/requests/broadcast-post-price.json
kvcli tx sign ./contrib/testnet-4000/rest_examples/pricefeed/post-price-unsigned.json --from validator --offline --chain-id testing --sequence 96 --account-number 0 | jq > ./contrib/testnet-4000/rest_examples/pricefeed/broadcast-post-price.json
# Broadcast the request
kvcli tx broadcast ./contrib/requests/broadcast-post-price.json
kvcli tx broadcast ./contrib/testnet-4000/rest_examples/pricefeed/broadcast-post-price.json
```
Congratulations, you've just posted a current market price on Kava using the rest server!
@ -60,3 +60,11 @@ Congratulations, you've just posted a current market price on Kava using the res
## Governance proposals
Example governance proposals are located in `/proposal_examples`.
## Dredd automated REST API testing
To run the dredd tests to hit the endpoints and test the api run the following command from the `kava` folder:
```bash
make test_dredd
```

View File

@ -0,0 +1,125 @@
# Testnet-5000
Testnet-5000 introduces transfers between Kava and Bnbchain via BEP3.
This guide will walk you through interacting with the blockchains and transferring coins via the rest server. To send transactions, we'll create an unsigned request, sign it, and broadcast it to the Kava blockchain.
## Rest server requests
### Setup
We'll be using Kava's CLI to build, sign, and broadcast the transactions:
```bash
# Download kvcli
make install
```
Before making a request, query account information for the signing account. Note the 'accountnumber' and 'sequence' fields, we'll need them later in order to send our request:
```bash
kvcli q auth account $(kvcli keys show testuser -a)
```
### Create swap
Use the example file in `rest_examples/create-swap.json` to format the request. First, update the header parameters 'from', 'chain-id', 'account_number', 'sequence'.
Next, we'll update the swap's creation parameters. For that, we need a unique random number that will be used to claim the funds.
WARNING: Don't use `calc-rnh` for the generation of secrets in production. These values should be generated client-side for the safety of user funds.
```bash
# Generate a sample random number, timestamp, and random number hash
kvcli q bep3 calc-rnh now
# Expected output:
# Random number: 110802331073994018312675691928205725441742309715720953510374321628333109608728
# Timestamp: 1585203985
# Random number hash: 4644fc2d9a2389c60e621785b873ae187e320eaded1687edaa120961428eba9e
```
In the same json file, populate each of the following parameters
- from
- to
- recipient_other_chain
- sender_other_chain
- random_number_hash
- timestamp
- amount
- expected_income
- height_span
- cross_chain
Once each parameter is populated, it's time to create our swap:
```bash
# Create an unsigned request
curl -H "Content-Type: application/json" -X POST -d @./contrib/testnet-5000/rest_examples/create-swap.json http://127.0.0.1:1317/bep3/swap/create | jq > ./contrib/testnet-5000/rest_examples/create-swap-unsigned.json
# Sign the request
kvcli tx sign ./contrib/testnet-5000/rest_examples/create-swap-unsigned.json --from testnetdeputy --offline --chain-id testing --sequence 0 --account-number 5 | jq > ./contrib/testnet-5000/rest_examples/broadcast-create-swap.json
# Broadcast the request
kvcli tx broadcast ./contrib/testnet-5000/rest_examples/broadcast-create-swap.json
```
The tx broadcast will log information in the terminal, including the txhash. This tx hash can be used to get information about the transaction, including the swap creation event that includes the swap's ID:
```bash
# Get information about the transaction
curl -H "Content-Type: application/json" -X GET http://localhost:1317/txs/81A1955216F6D985ECB4770E29B9BCED8F73A42D0C0FD566372CF673CCB81587
```
Congratulations, you've just created a swap on Kava! The swap will be automatically relayed over to Bnbchain where it it can be claimed using the secret random number from above.
# Claim swap
Only unexpired swaps can be claimed. To claim a swap, we'll use the secret random number that matches this swap's timestamp and random number hash.
Generally, claimable swaps must be created on Bnbchain.
// TODO: add link to Bnbchain document with interaction steps
Use the example file in `rest_examples/claim-swap.json` to format the request. Again, update the header parameters 'from', 'account_number', 'sequence'. Check your account using the command from above to ensure that the parameters match the blockchain's state.
In the same json file, populate each listed parameter:
- swap_id
- random_number
Once the `swap_id` parameter is populated, it's time to claim our swap:
```bash
# Create an unsigned request
curl -H "Content-Type: application/json" -X POST -d @./contrib/testnet-5000/rest_examples/claim-swap.json http://127.0.0.1:1317/bep3/swap/claim | jq > ./contrib/testnet-5000/rest_examples/claim-swap-unsigned.json
# Sign the request
kvcli tx sign ./contrib/testnet-5000/rest_examples/claim-swap-unsigned.json --from user --offline --chain-id testing --sequence 0 --account-number 1 | jq > ./contrib/testnet-5000/rest_examples/broadcast-claim-swap.json
# Broadcast the request
kvcli tx broadcast ./contrib/testnet-5000/rest_examples/broadcast-claim-swap.json
```
# Refund swap
Only expired swaps may be refunded.
Use the example file in `rest_examples/refund-swap.json` to format the request. Again, update the header parameters 'from', 'account_number', 'sequence'. Check your account using the command from above to ensure that the parameters match the blockchain's state.
In the same json file, populate each parameter:
- swap_id
Once the `swap_id` parameter is populated, it's time to refund our swap:
```bash
# Create an unsigned request
curl -H "Content-Type: application/json" -X POST -d @./contrib/testnet-5000/rest_examples/refund-swap.json http://127.0.0.1:1317/bep3/swap/refund | jq > ./contrib/testnet-5000/rest_examples/refund-swap-unsigned.json
# Sign the request
kvcli tx sign ./contrib/testnet-5000/rest_examples/refund-swap-unsigned.json --from user --offline --chain-id testing --sequence 0 --account-number 1 | jq > ./contrib/testnet-5000/rest_examples/broadcast-refund-swap.json
# Broadcast the request
kvcli tx broadcast ./contrib/testnet-5000/rest_examples/broadcast-refund-swap.json
```

View File

@ -0,0 +1,244 @@
{
"genesis_time": "2020-03-27T21:00:00Z",
"chain_id": "testing",
"consensus_params": {
"block": {
"max_bytes": "200000",
"max_gas": "2000000",
"time_iota_ms": "1000"
},
"evidence": {
"max_age": "1000000"
},
"validator": {
"pub_key_types": ["ed25519"]
}
},
"app_hash": "",
"app_state": {
"auth": {
"accounts": [],
"params": {
"max_memo_characters": "256",
"sig_verify_cost_ed25519": "590",
"sig_verify_cost_secp256k1": "1000",
"tx_sig_limit": "7",
"tx_size_cost_per_byte": "10"
}
},
"kavadist": {
"params": {
"active": true,
"periods": [
{
"start": "2020-03-28T15:20:00Z",
"end": "2021-03-28T15:20:00Z",
"inflation": "1.000000003022265980"
}
]
},
"previous_block_time": "1970-01-01T00:00:00Z"
},
"slashing": {
"missed_blocks": {},
"params": {
"downtime_jail_duration": "600000000000",
"max_evidence_age": "3600000000000",
"min_signed_per_window": "0.010000000000000000",
"signed_blocks_window": "1000",
"slash_fraction_double_sign": "0.050000000000000000",
"slash_fraction_downtime": "0.000100000000000000"
},
"signing_infos": {}
},
"bank": {
"send_enabled": true
},
"distribution": {
"base_proposer_reward": "0.010000000000000000",
"bonus_proposer_reward": "0.040000000000000000",
"community_tax": "0.000000000000000000",
"delegator_starting_infos": [],
"delegator_withdraw_infos": [],
"fee_pool": {
"community_pool": []
},
"outstanding_rewards": [],
"previous_proposer": "",
"validator_accumulated_commissions": [],
"validator_current_rewards": [],
"validator_historical_rewards": [],
"validator_slash_events": [],
"withdraw_addr_enabled": true
},
"mint": {
"minter": {
"annual_provisions": "0.000000000000000000",
"inflation": "0.020000000000000000"
},
"params": {
"blocks_per_year": "6311520",
"goal_bonded": "0.670000000000000000",
"inflation_max": "0.130000000000000000",
"inflation_min": "0.010000000000000000",
"inflation_rate_change": "0.130000000000000000",
"mint_denom": "ukava"
}
},
"cdp": {
"cdps": [],
"debt_denom": "debt",
"deposits": [],
"gov_denom": "ukava",
"params": {
"circuit_breaker": false,
"collateral_params": [
{
"auction_size": "50000000000",
"conversion_factor": "8",
"debt_limit": [
{
"amount": "2000000000000",
"denom": "usdx"
}
],
"denom": "bnb",
"liquidation_penalty": "0.05",
"liquidation_ratio": "2.0",
"market_id": "bnb:usd",
"prefix": 1,
"stability_fee": "1.0000000007829977"
}
],
"debt_auction_threshold": "10000000000",
"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"
}
],
"savings_distribution_frequency": "120000000000",
"surplus_auction_threshold": "1000000000"
},
"previous_block_time": "1970-01-01T00:00:00Z",
"previous_distribution_time": "1970-01-01T00:00:00Z",
"starting_cdp_id": "1"
},
"gov": {
"deposit_params": {
"max_deposit_period": "600000000000",
"min_deposit": [
{
"amount": "200000000",
"denom": "ukava"
}
]
},
"deposits": null,
"proposals": null,
"starting_proposal_id": "1",
"tally_params": {
"quorum": "0.334000000000000000",
"threshold": "0.500000000000000000",
"veto": "0.334000000000000000"
},
"votes": null,
"voting_params": {
"voting_period": "3600000000000"
}
},
"staking": {
"delegations": null,
"exported": false,
"last_total_power": "0",
"last_validator_powers": null,
"params": {
"bond_denom": "ukava",
"max_entries": 7,
"max_validators": 100,
"unbonding_time": "3600000000000"
},
"redelegations": null,
"unbonding_delegations": null,
"validators": null
},
"supply": {
"supply": []
},
"crisis": {
"constant_fee": {
"amount": "1333000000",
"denom": "ukava"
}
},
"pricefeed": {
"params": {
"markets": [
{
"active": true,
"base_asset": "bnb",
"market_id": "bnb:usd",
"oracles": ["kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"],
"quote_asset": "usd"
}
]
},
"posted_prices": [
{
"expiry": "2020-04-20T00:00:00Z",
"market_id": "bnb:usd",
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"price": "12.2639061184"
}
]
},
"auction": {
"auctions": [],
"next_auction_id": "0",
"params": {
"bid_duration": "600000000000",
"max_auction_duration": "172800000000000",
"increment_surplus": "0.01",
"increment_debt": "0.01",
"increment_collateral": "0.01"
}
},
"bep3": {
"params": {
"bnb_deputy_address": "kava15wmww3chakqllq4n3ksm37lx36qz067gxe6f0k",
"min_block_lock": "80",
"max_block_lock": "600",
"supported_assets": [
{
"denom": "bnb",
"coin_id": "714",
"limit": "100000000000",
"active": true
}
]
}
},
"genutil": {
"gentxs": []
},
"validatorvesting": {
"previous_block_time": "1970-01-01T00:00:00Z"
},
"params": null
}
}

View File

@ -0,0 +1,29 @@
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "bep3/MsgClaimAtomicSwap",
"value": {
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"swap_id": "3CA20F0152F03B0AABE73E7AA1DDF78B9048EDE5A9A73846E1EF53BEBBFA4185",
"random_number": "E3D0A98B459F72231DA69C3BD771C1E721FEF4BE83C14B80DC805BA71019EEBE"
}
}
],
"fee": {
"amount": [],
"gas": "500000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "AsGjurKnae7kuQQawGO3FLzrjeLCRO2W6orV74fekVYo"
},
"signature": "aOM9ui+LOSw9GDHe2cKWXPno8Oa1dInphCCJE4WVC5Q/3Kv0j4a6fkfT7sR9uXEQX5rDN7CAH2WrmWxE7E6P7Q=="
}
],
"memo": ""
}
}

View File

@ -0,0 +1,41 @@
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "bep3/MsgCreateAtomicSwap",
"value": {
"from": "kava1sl8glhaa9f9tep0d9h8gdcfmwcatghtdrfcd2x",
"to": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"recipient_other_chain": "tbnb1etjxt3t5r9899w3tf7dnktdue5ngu4ncdwckvt",
"sender_other_chain": "tbnb10uypsspvl6jlxcx5xse02pag39l8xpe7a3468h",
"random_number_hash": "C0544B7F4B890A673EA3F61BDB4650FBFE2F3E56BDA1B397D6D592FCA7163C8C",
"timestamp": "1585252531",
"amount": [
{
"denom": "bnb",
"amount": "555555"
}
],
"expected_income": "555555bnb",
"height_span": "360",
"cross_chain": true
}
}
],
"fee": {
"amount": [],
"gas": "500000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "A6ExM8g1WQtVD7U5hg+whlWvPza6p24ABFqv6ofv0lQn"
},
"signature": "1bC1pe1oH20pScI2FimQ98VfvUA/galjDBNkaXcbZKYsE3ig7oGr322PraO2tO44OlY0AiZ1LCVZ15kCxnXO3w=="
}
],
"memo": ""
}
}

View File

@ -0,0 +1,28 @@
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "bep3/MsgRefundAtomicSwap",
"value": {
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"swap_id": "1B00244021EC239867E5B8C44BCD98E40F3148806A8C0A8FD3418872986BECBA"
}
}
],
"fee": {
"amount": [],
"gas": "500000"
},
"signatures": [
{
"pub_key": {
"type": "tendermint/PubKeySecp256k1",
"value": "AsGjurKnae7kuQQawGO3FLzrjeLCRO2W6orV74fekVYo"
},
"signature": "y/T0zLHuvk7oRgTqU9bc6wnWm5JHzhnEk/U1xSWVkxVD2NvC9MvI6lHjXy1iBzyn388x4U8DQ+sxsT/BBA1ehg=="
}
],
"memo": ""
}
}

View File

@ -0,0 +1,21 @@
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "bep3/MsgClaimAtomicSwap",
"value": {
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"swap_id": "3CA20F0152F03B0AABE73E7AA1DDF78B9048EDE5A9A73846E1EF53BEBBFA4185",
"random_number": "E3D0A98B459F72231DA69C3BD771C1E721FEF4BE83C14B80DC805BA71019EEBE"
}
}
],
"fee": {
"amount": [],
"gas": "500000"
},
"signatures": null,
"memo": ""
}
}

View File

@ -0,0 +1,15 @@
{
"base_req": {
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"memo": "",
"chain_id": "testing",
"account_number": "1",
"sequence": "0",
"gas": "500000",
"gas_adjustment": "1.0",
"simulate": false
},
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"swap_id": "3ca20f0152f03b0aabe73e7aa1ddf78b9048ede5a9a73846e1ef53bebbfa4185",
"random_number": "e3d0a98b459f72231da69c3bd771c1e721fef4be83c14b80dc805ba71019eebe"
}

View File

@ -0,0 +1,33 @@
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "bep3/MsgCreateAtomicSwap",
"value": {
"from": "kava1sl8glhaa9f9tep0d9h8gdcfmwcatghtdrfcd2x",
"to": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"recipient_other_chain": "tbnb1etjxt3t5r9899w3tf7dnktdue5ngu4ncdwckvt",
"sender_other_chain": "tbnb10uypsspvl6jlxcx5xse02pag39l8xpe7a3468h",
"random_number_hash": "C0544B7F4B890A673EA3F61BDB4650FBFE2F3E56BDA1B397D6D592FCA7163C8C",
"timestamp": "1585252531",
"amount": [
{
"denom": "bnb",
"amount": "555555"
}
],
"expected_income": "555555bnb",
"height_span": "360",
"cross_chain": true
}
}
],
"fee": {
"amount": [],
"gas": "500000"
},
"signatures": null,
"memo": ""
}
}

View File

@ -0,0 +1,27 @@
{
"base_req": {
"from": "kava1sl8glhaa9f9tep0d9h8gdcfmwcatghtdrfcd2x",
"memo": "",
"chain_id": "testing",
"account_number": "5",
"sequence": "0",
"gas": "500000",
"gas_adjustment": "1.0",
"simulate": false
},
"from": "kava1sl8glhaa9f9tep0d9h8gdcfmwcatghtdrfcd2x",
"to": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"recipient_other_chain": "tbnb1etjxt3t5r9899w3tf7dnktdue5ngu4ncdwckvt",
"sender_other_chain": "tbnb10uypsspvl6jlxcx5xse02pag39l8xpe7a3468h",
"random_number_hash": "c0544b7f4b890a673ea3f61bdb4650fbfe2f3e56bda1b397d6d592fca7163c8c",
"timestamp": "1585252531",
"amount": [
{
"denom": "bnb",
"amount": "555555"
}
],
"expected_income": "555555bnb",
"height_span": "360",
"cross_chain": true
}

View File

@ -0,0 +1,20 @@
{
"type": "cosmos-sdk/StdTx",
"value": {
"msg": [
{
"type": "bep3/MsgRefundAtomicSwap",
"value": {
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"swap_id": "1B00244021EC239867E5B8C44BCD98E40F3148806A8C0A8FD3418872986BECBA"
}
}
],
"fee": {
"amount": [],
"gas": "500000"
},
"signatures": null,
"memo": ""
}
}

View File

@ -0,0 +1,14 @@
{
"base_req": {
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"memo": "",
"chain_id": "testing",
"account_number": "1",
"sequence": "0",
"gas": "500000",
"gas_adjustment": "1.0",
"simulate": false
},
"from": "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p",
"swap_id": "1b00244021ec239867e5b8c44bcd98e40f3148806a8c0a8fd3418872986becba"
}

View File

@ -0,0 +1,259 @@
{
"genesis_time": "2020-04-23T16:32:31.393515Z",
"chain_id": "testing",
"consensus_params": {
"block": {
"max_bytes": "22020096",
"max_gas": "-1",
"time_iota_ms": "1000"
},
"evidence": {
"max_age_num_blocks": "100000",
"max_age_duration": "172800000000000"
},
"validator": {
"pub_key_types": [
"ed25519"
]
}
},
"app_hash": "",
"app_state": {
"auction": {
"auctions": [],
"next_auction_id": "1",
"params": {
"bid_duration": "600000000000",
"increment_collateral": "0.010000000000000000",
"increment_debt": "0.010000000000000000",
"increment_surplus": "0.010000000000000000",
"max_auction_duration": "172800000000000"
}
},
"auth": {
"accounts": [],
"params": {
"max_memo_characters": "256",
"sig_verify_cost_ed25519": "590",
"sig_verify_cost_secp256k1": "1000",
"tx_sig_limit": "7",
"tx_size_cost_per_byte": "10"
}
},
"bank": {
"send_enabled": true
},
"bep3": {
"assets_supplies": [],
"atomic_swaps": [],
"params": {
"bnb_deputy_address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
"max_block_lock": "600",
"min_block_lock": "80",
"supported_assets": [
{
"active": true,
"coin_id": "714",
"denom": "bnb",
"limit": "100000000000"
}
]
}
},
"cdp": {
"cdps": [],
"debt_denom": "debt",
"deposits": [],
"gov_denom": "ukava",
"params": {
"circuit_breaker": false,
"collateral_params": [
{
"auction_size": "50000000000",
"conversion_factor": "8",
"debt_limit": [
{
"amount": "2000000000000",
"denom": "usdx"
}
],
"denom": "bnb",
"liquidation_penalty": "0.05",
"liquidation_ratio": "2.0",
"market_id": "bnb:usd",
"prefix": 1,
"stability_fee": "1.0000000007829977"
}
],
"debt_auction_threshold": "10000000000",
"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"
}
],
"savings_distribution_frequency": "120000000000",
"surplus_auction_threshold": "1000000000"
},
"previous_block_time": "1970-01-01T00:00:00Z",
"previous_distribution_time": "1970-01-01T00:00:00Z",
"starting_cdp_id": "1"
},
"crisis": {
"constant_fee": {
"amount": "1333000000",
"denom": "ukava"
}
},
"distribution": {
"delegator_starting_infos": [],
"delegator_withdraw_infos": [],
"fee_pool": {
"community_pool": []
},
"outstanding_rewards": [],
"params": {
"base_proposer_reward": "0.010000000000000000",
"bonus_proposer_reward": "0.040000000000000000",
"community_tax": "0.000000000000000000",
"withdraw_addr_enabled": true
},
"previous_proposer": "",
"validator_accumulated_commissions": [],
"validator_current_rewards": [],
"validator_historical_rewards": [],
"validator_slash_events": []
},
"evidence": {
"evidence": [],
"params": {
"max_evidence_age": "120000000000"
}
},
"genutil": {
"gentxs": []
},
"gov": {
"deposit_params": {
"max_deposit_period": "600000000000",
"min_deposit": [
{
"amount": "200000000",
"denom": "ukava"
}
]
},
"deposits": null,
"proposals": null,
"starting_proposal_id": "1",
"tally_params": {
"quorum": "0.334000000000000000",
"threshold": "0.500000000000000000",
"veto": "0.334000000000000000"
},
"votes": null,
"voting_params": {
"voting_period": "600000000000"
}
},
"kavadist": {
"params": {
"active": true,
"periods": [
{
"end": "2021-03-28T15:20:00Z",
"inflation": "1.000000003022265980",
"start": "2020-03-28T15:20:00Z"
}
]
},
"previous_block_time": "1970-01-01T00:00:00Z"
},
"mint": {
"minter": {
"annual_provisions": "0.000000000000000000",
"inflation": "0.020000000000000000"
},
"params": {
"blocks_per_year": "6311520",
"goal_bonded": "0.670000000000000000",
"inflation_max": "0.130000000000000000",
"inflation_min": "0.010000000000000000",
"inflation_rate_change": "0.130000000000000000",
"mint_denom": "ukava"
}
},
"params": null,
"pricefeed": {
"params": {
"markets": [
{
"active": true,
"base_asset": "bnb",
"market_id": "bnb:usd",
"oracles": [
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
],
"quote_asset": "usd"
}
]
},
"posted_prices": [
{
"expiry": "2021-04-20T00:00:00Z",
"market_id": "bnb:usd",
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
"price": "12.2639061184"
}
]
},
"slashing": {
"missed_blocks": {},
"params": {
"downtime_jail_duration": "600000000000",
"min_signed_per_window": "0.010000000000000000",
"signed_blocks_window": "1000",
"slash_fraction_double_sign": "0.050000000000000000",
"slash_fraction_downtime": "0.000100000000000000"
},
"signing_infos": {}
},
"staking": {
"delegations": null,
"exported": false,
"last_total_power": "0",
"last_validator_powers": null,
"params": {
"bond_denom": "ukava",
"historical_entries": 0,
"max_entries": 7,
"max_validators": 100,
"unbonding_time": "3600000000000"
},
"redelegations": null,
"unbonding_delegations": null,
"validators": null
},
"supply": {
"supply": []
},
"validatorvesting": {
"previous_block_time": "1970-01-01T00:00:00Z"
}
}
}

19
go.mod
View File

@ -3,14 +3,15 @@ module github.com/kava-labs/kava
go 1.13
require (
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/cosmos/cosmos-sdk v0.38.3
github.com/gorilla/mux v1.7.3
github.com/raviqqe/liche v0.0.0-20191208214012-e144e0808a75 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.4.0
github.com/tendermint/go-amino v0.15.0
github.com/tendermint/tendermint v0.32.7
github.com/tendermint/tm-db v0.2.0
gopkg.in/yaml.v2 v2.2.4
github.com/spf13/cobra v0.0.6
github.com/spf13/viper v1.6.2
github.com/stretchr/testify v1.5.1
github.com/tendermint/go-amino v0.15.1
github.com/tendermint/tendermint v0.33.3
github.com/tendermint/tm-db v0.5.0
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 // indirect
gopkg.in/yaml.v2 v2.2.8
)

470
go.sum
View File

@ -1,56 +1,84 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/keyring v1.1.2 h1:JJauROcU6x6Nh9uZb+8JgXFvyo0GUESLo1ixhpA0Kmw=
github.com/99designs/keyring v1.1.2/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY=
github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw=
github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d h1:1aAija9gr0Hyv4KfQcRcwlmFIrhkDmIj2dz5bkg/s/8=
github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d/go.mod h1:icNx/6QdFblhsEjZehARqbNumymUT/ydwlLojFdv7Sk=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d h1:xG8Pj6Y6J760xwETNmMzmlt38QSwz0BLp1cZ09g27uw=
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
github.com/btcsuite/btcd v0.20.1-beta h1:Ik4hyJqN8Jfyv3S4AGBOmyouMsYE3EdYODkMbQjwPGw=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE=
github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d h1:yJzD/yFppdVCf6ApMkVy8cUxV0XrxdP9rVf6D87/Mng=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/cosmos-sdk v0.34.4-0.20190925161702-9d0bed8f4f4e h1:V8WpJTIAjajE2PE+1wWCG5LUYkWQal+aH6uqPUiZ9Qc=
github.com/cosmos/cosmos-sdk v0.34.4-0.20190925161702-9d0bed8f4f4e/go.mod h1:gwKdI16dOjylNYJkaHbcx0TcEIHyRs1xyc5qROmjCJE=
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010155330-64a27412505c h1:LM81MVa0CG0Q118dOp6f2Q0MoYIMRTi26zoyqZiL8sY=
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010155330-64a27412505c/go.mod h1:FxjFo2Y2ZuqUczTcVfnDUG413OgofBbxFNXA72eaUR4=
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1 h1:yb+E8HGzFnO0YwLS6OCBIAVWtN8KfCYoKeO9mgAmQN0=
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1/go.mod h1:IGBhkbOK1ebLqMWjtgo99zUxWHsA5IOb6N9CI8nHs0Y=
github.com/cosmos/cosmos-sdk v0.37.1 h1:mz5W3Au32VIPPtrY65dheVYeVDSFfS3eSSmuIj+cXsI=
github.com/cosmos/cosmos-sdk v0.37.4 h1:1ioXxkpiS+wOgaUbROeDIyuF7hciU5nti0TSyBmV2Ok=
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI=
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc=
github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY=
github.com/cosmos/cosmos-sdk v0.38.3 h1:qIBTiw+2T9POaSUJ5rvbBbXeq8C8btBlJxnSegPBd3Y=
github.com/cosmos/cosmos-sdk v0.38.3/go.mod h1:rzWOofbKfRt3wxiylmYWEFHnxxGj0coyqgWl2I9obAw=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU=
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4=
github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY=
github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI=
github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU=
github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -59,11 +87,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU=
github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
@ -72,72 +107,126 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129 h1:tT8iWCYw4uOem71yYA3htfH+LNopJvcqZQshm56G5L4=
github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU=
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0=
github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk=
github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM=
github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc=
github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o=
github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE=
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
@ -145,153 +234,197 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.8.5 h1:2ucYeik+NtUTg+IAiNQtoFC5ZGs5mIVidI7Ome0Br3Y=
github.com/klauspost/compress v1.8.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/cpuid v1.2.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/libp2p/go-buffer-pool v0.0.1 h1:9Rrn/H46cXjaA2HQ5Y8lyhOS1NhTkZ4yuEs2r3Eechg=
github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ=
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0=
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4=
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ=
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4=
github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A=
github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs=
github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs=
github.com/raviqqe/liche v0.0.0-20191208214012-e144e0808a75 h1:o0pGzJnfjk0E+CZg6jxQrADfk9WYO9fMuLOtSP4owtE=
github.com/raviqqe/liche v0.0.0-20191208214012-e144e0808a75/go.mod h1:/L9q8uCsB8BOWdzLK+6WIwkAlcMfKhFCZY0n8/CLHRY=
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk=
github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M=
github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stumble/gorocksdb v0.0.3 h1:9UU+QA1pqFYJuf9+5p7z1IqdE5k0mma4UAeu2wmX8kA=
github.com/stumble/gorocksdb v0.0.3/go.mod h1:v6IHdFBXk5DJ1K4FZ0xi+eY737quiiBxYtSWXadLybY=
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw=
github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs=
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok=
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8=
github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s=
github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U=
github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae h1:AOXNM7c2Vvo45SjAgeWF8Wy+NS7/NCqzRNpUc+HPAec=
github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk=
github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI=
github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk=
github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso=
github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk=
github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tendermint/iavl v0.12.4 h1:hd1woxUGISKkfUWBA4mmmTwOua6PQZTJM/F0FDrmMV8=
github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJkAUvEXT/o=
github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU=
github.com/tendermint/tendermint v0.32.3 h1:GEnWpGQ795h5oTFNbfBLsY0LW/CW2j6p6HtiYNfxsgg=
github.com/tendermint/tendermint v0.32.3/go.mod h1:ZK2c29jl1QRYznIRyRWRDsmm1yvtPzBRT00x4t1JToY=
github.com/tendermint/tendermint v0.32.5 h1:2hCLwuzfCKZxXSe/+iMEl+ChJWKJx6g/Wcvq3NMxVN4=
github.com/tendermint/tendermint v0.32.5/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8dh/Zg640FGyGtE=
github.com/tendermint/tendermint v0.32.6 h1:HozXi0USWvKrWuEh5ratnJV10ykkTy4nwXUi0UvPVzg=
github.com/tendermint/tendermint v0.32.6/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8dh/Zg640FGyGtE=
github.com/tendermint/tendermint v0.32.7 h1:Szu5Fm1L3pvn3t4uQxPAcP+7ndZEQKgLie/yokM56rU=
github.com/tendermint/tendermint v0.32.7/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8dh/Zg640FGyGtE=
github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0=
github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw=
github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ=
github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw=
github.com/tendermint/go-amino v0.15.1 h1:D2uk35eT4iTsvJd9jWIetzthE5C0/k2QmMFkCN+4JgQ=
github.com/tendermint/go-amino v0.15.1/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME=
github.com/tendermint/iavl v0.13.2 h1:O1m08/Ciy53l9IYmf75uIRVvrNsfjEbre8u/yCu/oqk=
github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1TuoljBcsQoGA=
github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk=
github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI=
github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk=
github.com/tendermint/tendermint v0.33.4 h1:NM3G9618yC5PaaxGrcAySc5ylc1PAANeIx42u2Re/jo=
github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY=
github.com/tendermint/tm-db v0.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ=
github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.5.0 h1:dhq+O9pmNZFF6qAXpasMO1xSm7dL4qEz2ylfZN8BG9w=
github.com/valyala/fasthttp v1.5.0/go.mod h1:eriCz9OhZjKCGfJ185a/IDgNl0bg9IbzfpcslMZXU1c=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8=
@ -299,90 +432,157 @@ github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWp
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a h1:YX8ljsm6wXlHZO+aRz9Exqr0evNhKRNe5K/gi+zKh4U=
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA=
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 h1:67iHsV9djwGdZpdZNbLuQj6FOzCaZe3w+vhLjn5AcFA=
google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=

View File

@ -1,44 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generator: Adobe Illustrator 24.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="725px" height="213px" viewBox="0 0 725 213" enable-background="new 0 0 725 213" xml:space="preserve">
viewBox="0 0 1186 360" style="enable-background:new 0 0 1186 360;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FF564F;}
</style>
<g>
<g opacity="0.2">
<polygon fill="#FF564F" points="30,213 111.625,106.851 30,0 20.246,38.336 72.809,106.851 22.664,171.296 "/>
</g>
<g>
<polygon fill="#FF564F" points="154.344,213 72.211,106.851 154.125,0 193,0 111.027,106.851 193.625,213 "/>
</g>
<g>
<rect fill="#FF564F" width="30" height="213"/>
</g>
<g>
<path fill="#FF564F" d="M288.04,39.144c-12.1,0-23.001,1.318-32.403,3.917c-9.313,2.58-18.633,6.056-27.701,10.332l-2.473,1.167
l9.252,25.495l2.977-1.405c7.314-3.451,14.881-6.231,22.489-8.262c7.549-2.01,16.08-3.028,25.355-3.028
c14.401,0,25.424,3.462,33.168,10.29C326.336,84.381,330,94.554,330,107.887v1.038c-6-1.637-12.337-2.99-18.681-4.032
c-7.742-1.272-16.909-1.918-27.45-1.918c-10.432,0-20.067,1.193-28.741,3.548c-8.765,2.384-16.429,5.904-22.829,10.463
c-6.528,4.646-11.531,10.538-15.07,17.512c-3.538,6.964-5.23,15.158-5.23,24.357v0.626c0,8.815,1.772,16.671,5.445,23.348
c3.632,6.604,8.441,12.239,14.38,16.745c5.909,4.489,12.749,7.867,20.369,10.039c7.543,2.152,15.284,3.244,23.028,3.244
c14.647,0,26.893-2.927,36.813-8.698c7.052-4.105,12.965-8.749,17.965-13.859V213h30V107.539c0-21.171-5.885-37.887-17.68-49.682
C329.902,45.439,311.703,39.144,288.04,39.144z M280.081,187.145c-5.101,0-10.005-0.691-14.581-2.057
c-4.535-1.347-8.585-3.293-12.042-5.786c-3.356-2.419-5.818-5.483-7.772-9.111c-1.935-3.584-2.686-7.661-2.686-12.118v-0.626
c0-8.815,3.391-15.708,10.839-21.071c7.658-5.517,18.654-8.313,32.907-8.313c9.735,0,18.548,0.716,26.031,2.128
c6.664,1.263,12.223,2.613,18.223,4.023v12.649c0,5.912-1.437,11.356-3.999,16.185c-2.594,4.894-6.277,9.184-10.81,12.751
c-4.585,3.604-10.025,6.423-16.101,8.377C293.978,186.146,287.223,187.145,280.081,187.145z"/>
<polygon fill="#FF564F" points="473.281,171.271 420.85,42 387.678,42 461.364,213 484.532,213 558.564,42 526.015,42 "/>
<path fill="#FF564F" d="M707.237,57.857c-12.418-12.418-30.707-18.714-54.369-18.714c-12.1,0-22.999,1.318-32.4,3.917
c-9.313,2.58-18.631,6.056-27.7,10.332l-2.472,1.167l9.252,25.495l2.978-1.405c7.314-3.451,14.881-6.231,22.489-8.262
c7.549-2.01,16.08-3.028,25.355-3.028c14.401,0,25.507,3.462,33.251,10.29C691.253,84.381,695,94.554,695,107.887v1.038
c-6-1.637-12.42-2.99-18.764-4.032c-7.742-1.272-16.951-1.918-27.491-1.918c-10.432,0-20.088,1.193-28.762,3.548
c-8.765,2.384-16.439,5.904-22.839,10.463c-6.528,4.646-11.453,10.538-14.992,17.512c-3.538,6.964-5.152,15.158-5.152,24.357
v0.626c0,8.815,1.689,16.671,5.362,23.348c3.632,6.604,8.399,12.239,14.339,16.745c5.909,4.489,12.728,7.867,20.348,10.039
c7.543,2.152,15.273,3.244,23.017,3.244c14.647,0,26.971-2.927,36.891-8.698c7.052-4.105,13.043-8.749,18.043-13.859V213h30
V107.539C725,86.368,719.032,69.652,707.237,57.857z M644.915,187.145c-5.101,0-10.005-0.691-14.581-2.057
c-4.535-1.347-8.585-3.293-12.042-5.786c-3.356-2.419-6.235-5.483-8.189-9.111c-1.935-3.584-3.103-7.661-3.103-12.118v-0.626
c0-8.815,3.808-15.708,11.256-21.071c7.658-5.517,18.862-8.313,33.116-8.313c9.735,0,18.735,0.716,26.218,2.128
c6.664,1.263,12.41,2.613,18.41,4.023v12.649c0,5.912-1.52,11.356-4.082,16.185c-2.594,4.894-6.318,9.184-10.852,12.751
c-4.585,3.604-10.046,6.423-16.122,8.377C658.833,186.146,652.057,187.145,644.915,187.145z"/>
</g>
<polygon class="st0" points="843.7,63.3 909.9,63.3 794.7,349.6 718.2,349.6 603,63.3 669.2,63.3 756.3,280.6 "/>
<rect x="9" y="4.1" class="st0" width="60.3" height="345.6"/>
<polygon class="st0" points="234.2,349.7 102.1,176.9 234.2,4.1 309.9,4.1 179.5,176.9 309.9,349.7 "/>
<path class="st0" d="M454.2,55.7c-66,0-115.7,36.5-119.3,90.8l-0.4,6.4h59.2l1-4.7c5.5-25.5,26.3-39.5,58.5-39.5
c35,0,55.1,17.6,55.1,48.4v17.7L434,179c-66.8,3.9-107.2,36.6-107.2,87.4c0,53.6,40.2,89.5,99.3,89.5c35.2,0,60.9-9.6,81.9-31.1
V351h60V152.5C568,93.7,522.6,55.7,454.2,55.7z M508.2,221.2V239c-3.9,38.9-30.8,63.3-72,65.3c-15.9,0.8-29.6-3.5-38.6-12.1
c-7.1-6.7-10.8-15.7-10.8-25.9c0-22.8,19.4-37.1,53.5-39.3L508.2,221.2z"/>
<path class="st0" d="M1063.2,55.7c-66,0-115.7,36.5-119.3,90.8l-0.4,6.4h59.2l1-4.7c5.5-25.5,26.3-39.5,58.5-39.5
c35,0,55.1,17.6,55.1,48.4v17.7l-74.2,4.2c-66.8,3.9-107.2,36.6-107.2,87.4c0,53.6,40.2,89.5,99.3,89.5c35.2,0,60.9-9.6,81.9-31.1
V351h60V152.5C1177,93.7,1131.6,55.7,1063.2,55.7z M1117.3,221.2V239c-3.9,38.9-30.8,63.3-72,65.3c-15.9,0.8-29.6-3.5-38.6-12.1
c-7.1-6.7-10.8-15.7-10.8-25.9c0-22.8,19.4-37.1,53.5-39.3L1117.3,221.2z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,25 @@
///////////////////////////////////////////////////////////////////////////////////////
///// IF YOU FOLLOW THESE INSTRUCTIONS ALL THE TESTS SHOULD PASS THE FIRST TIME ///////
///////////////////////////////////////////////////////////////////////////////////////
Instructions on how to run the `dredd` tests
(Prerequisite) Make sure that you have the latest versions of `node` and `npm` and `npx` installed. Then install `dredd` globally using the following command folder:
`npm install dredd --global`
(Running tests) Run `make test_dredd` from the `kava` directory.
This builds the `test.go` file, creates the genesis state for the blockchain, starts the blockchain, starts the rest server, sends the required transactions to the blockchain, runs all the `dredd` tests, shuts
down the blockchain, cleans up, and propagates up an error code if the tests do not all pass.
**IMPORTANT** - It takes about 3 minutes for the script to run and complete:
**When you run the script 62 tests should pass and zero should fail**
(Shutdown) If the script fails or you stop it midway using `ctrl-c` then you should manually stop the blockchain and rest server using the following script. If you let it complete
it will automatically shut down the blockchain, rest server and clean up.
`./stopchain.sh`
**DEBUGGING NOTE**: If you start getting `Validator set is different` errors then you need to try starting the chain from scratch (do NOT just use `unsafe-reset-all`, instead use `./stopchain.sh` and then `./run_all_tests.sh`)

View File

@ -0,0 +1,124 @@
#! /bin/bash
# kill kava if it is already running
pgrep kvd | xargs kill
pgrep kvcli | xargs kill
# TODO import from development environment in envkey
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
faucet="chief access utility giant burger winner jar false naive mobile often perfect advice village love enroll embark bacon under flock harbor render father since"
# address: kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz
# address: kavavaloper1ls82zzghsx0exkpr52m8vht5jqs3un0c5j2c04
# variables for home directories for kvd and kvcli
kvdHome=/tmp/kvdHome
kvcliHome=/tmp/kvcliHome
genesis=$kvdHome/config/genesis.json
swaggerFile=swagger-ui/testnet-4000/swagger-testnet-4000.yaml
# Remove any existing data directory
rm -rf $kvdHome
rm -rf $kvcliHome
# make the directories
mkdir /tmp/kvdHome
mkdir /tmp/kvcliHome
# create validator key
printf "$password\n$validatorMnemonic\n" | kvcli keys add vlad --recover --home $kvcliHome
# create faucet key
printf "$password\n$faucet\n" | kvcli --home $kvcliHome keys add faucet --recover --home $kvcliHome
# function used to show that it is still loading
showLoading() {
mypid=$!
loadingText=$1
echo -ne "$loadingText\r"
while kill -0 $mypid 2>/dev/null; do
echo -ne "$loadingText.\r"
sleep 0.5
echo -ne "$loadingText..\r"
sleep 0.5
echo -ne "$loadingText...\r"
sleep 0.5
echo -ne "\r\033[K"
echo -ne "$loadingText\r"
sleep 0.5
done
echo "$loadingText...finished"
}
# Create new data directory
{
kvd --home $kvdHome init --chain-id=testing vlad # doesn't need to be the same as the validator
} > /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
# add faucet account to genesis
kvd --home $kvdHome add-genesis-account $(kvcli --home $kvcliHome keys show faucet -a) 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 collect-gentxs
} > /dev/null 2>&1
# set blocktime to ~1s
jq '.app_state.mint.params.blocks_per_year = "31540000"' $genesis > $genesis.tmp && mv $genesis.tmp $genesis
# update pricefeed information
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
# start the blockchain in the background, wait until it starts making blocks
{
kvd start --home $kvdHome & kvdPid="$!"
} > /dev/null 2>&1
printf "\n"
sleep 10 & showLoading "Starting rest server, please wait"
# start the rest server. Use ./stopchain.sh to stop both rest server and the blockchain
{
kvcli rest-server --laddr tcp://127.0.0.1:1317 --chain-id=testing --home $kvcliHome & kvcliPid="$!"
} > /dev/null 2>&1
printf "\n"
sleep 10 & showLoading "Preparing blockchain setup transactions, please wait"
printf "\n"
# build the go setup test file
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"
printf "\n"
printf "Blockchain setup completed"
printf "\n\n"
############################
# Now run the dredd tests
############################
dredd $swaggerFile localhost:1317 2>&1 | tee output & showLoading "Running dredd tests"
########################################################
# Now run the check the return code from the dredd command.
# If 0 then all test passed OK, otherwise some failed and propagate the error
########################################################
# check that the error code was zero
if [ $? -eq 0 ]
then
# check that all the tests passed (ie zero failing)
if [[ $(cat output | grep "0 failing") ]]
then
# check for no errors
if [[ $(cat output | grep "0 errors") ]]
then
echo "Success"
rm setuptest & showLoading "Cleaning up go binary"
# kill the kvd and kvcli processes (blockchain and rest api)
pgrep kvd | xargs kill
pgrep kvcli | xargs kill & showLoading "Stopping blockchain"
rm -f output
exit 0
fi
fi
fi
# otherwise return an error code and redirect stderr to stdout so user sees the error output
echo "Failure" >&2
rm setuptest & showLoading "Cleaning up go binary"
# kill the kvd and kvcli processes (blockchain and rest api)
pgrep kvd | xargs kill
pgrep kvcli | xargs kill & showLoading "Stopping blockchain"
rm -f output
exit 1

View File

@ -0,0 +1,481 @@
package main
import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"time"
"github.com/cosmos/cosmos-sdk/client/keys"
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/x/auth"
authrest "github.com/cosmos/cosmos-sdk/x/auth/client/rest"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client/utils"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp"
"github.com/kava-labs/kava/x/pricefeed"
"github.com/tendermint/go-amino"
tmtime "github.com/tendermint/tendermint/types/time"
)
func init() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
app.SetBip44CoinType(config)
config.Seal()
}
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()
sendDeposit()
sendVote()
sendDelegation()
sendUndelegation()
sendCoins()
sendProposal()
sendDeposit()
sendVote()
sendDelegation()
sendUndelegation()
sendCoins()
// create an XRP cdp and send to blockchain
sendXrpCdp()
// create a BTC cdp and send to blockchain
sendBtcCdp()
// reduce the price of BTC to trigger an auction
sendMsgPostPrice()
}
// lower the price of xrp to trigger an auction
func sendMsgPostPrice() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addr, err := sdk.AccAddressFromBech32(address) // validator address
if err != nil {
panic(err)
}
price, err := sdk.NewDecFromStr("1")
if err != nil {
panic(err)
}
// set the expiry time
expiry := tmtime.Now().Add(time.Second * 100000)
// create a cdp message to send to the blockchain
// from, assetcode, price, expiry
msg := pricefeed.NewMsgPostPrice(
addr,
"btc:usd",
price,
expiry,
)
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// cast to the generic msg type
msgToSend := []sdk.Msg{msg}
// send the message to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase)
}
func sendBtcCdp() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addr, err := sdk.AccAddressFromBech32(address) // validator address
if err != nil {
panic(err)
}
// create a cdp message to send to the blockchain
// sender, collateral, principal
msg := cdp.NewMsgCreateCDP(
addr,
sdk.NewCoins(sdk.NewInt64Coin("btc", 200000000)),
sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000)),
)
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// cast to the generic msg type
msgToSend := []sdk.Msg{msg}
// send the message to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase)
}
func sendXrpCdp() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addr, err := sdk.AccAddressFromBech32(address) // validator address
if err != nil {
panic(err)
}
// create a cdp message to send to the blockchain
// sender, collateral, principal
msg := cdp.NewMsgCreateCDP(
addr,
sdk.NewCoins(sdk.NewInt64Coin("xrp", 200000000)),
sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000)),
)
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// cast to the generic msg type
msgToSend := []sdk.Msg{msg}
// send the message to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase)
}
func sendProposal() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
proposalContent := gov.ContentFromProposalType("A Test Title", "A test description on this proposal.", gov.ProposalTypeText)
addr, err := sdk.AccAddressFromBech32(address) // validator address
if err != nil {
panic(err)
}
// create a message to send to the blockchain
msg := gov.NewMsgSubmitProposal(
proposalContent,
sdk.NewCoins(sdk.NewInt64Coin("stake", 1000)),
addr,
)
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// SEND THE PROPOSAL
// cast to the generic msg type
msgToSend := []sdk.Msg{msg}
// send the PROPOSAL message to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase)
}
func sendDeposit() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addr, err := sdk.AccAddressFromBech32(address) // validator
if err != nil {
panic(err)
}
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// NOW SEND THE DEPOSIT
// 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
depositToSend := []sdk.Msg{deposit}
sendMsgToBlockchain(cdc, address, keyname, password, depositToSend, keybase)
}
func sendVote() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addr, err := sdk.AccAddressFromBech32(address) // validator
if err != nil {
panic(err)
}
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// 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
// send a vote to the blockchain
voteToSend := []sdk.Msg{vote}
sendMsgToBlockchain(cdc, address, keyname, password, voteToSend, keybase)
}
// this should send coins from one address to another
func sendCoins() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addrFrom, err := sdk.AccAddressFromBech32(address) // validator
if err != nil {
panic(err)
}
addrTo, err := sdk.AccAddressFromBech32("kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz") // TODO IMPORTANT this is the faucet address
if err != nil {
panic(err)
}
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// 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
coinsToSend := []sdk.Msg{coins}
// NOW SEND THE COINS
// send the coin message to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, coinsToSend, keybase)
}
func getTestAddress() (address string) {
// the test address - TODO IMPORTANT make sure this lines up 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
return keyname, password
}
// this should send a delegation
func sendDelegation() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addrFrom, err := sdk.AccAddressFromBech32(address) // validator
if err != nil {
panic(err)
}
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// get the validator address for delegation
valAddr, err := sdk.ValAddressFromBech32("kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0") // **FAUCET**
if err != nil {
panic(err)
}
// create delegation amount
delAmount := sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000000)
delegation := staking.NewMsgDelegate(addrFrom, valAddr, delAmount)
delegationToSend := []sdk.Msg{delegation}
// send the delegation to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, delegationToSend, keybase)
}
// this should send a MsgUndelegate
func sendUndelegation() {
// get the address
address := getTestAddress()
// get the keyname and password
keyname, password := getKeynameAndPassword()
addrFrom, err := sdk.AccAddressFromBech32(address) // validator
if err != nil {
panic(err)
}
// helper methods for transactions
cdc := app.MakeCodec() // make codec for the app
// get the keybase
keybase := getKeybase()
// get the validator address for delegation
valAddr, err := sdk.ValAddressFromBech32("kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0") // **FAUCET**
if err != nil {
panic(err)
}
// create delegation amount
undelAmount := sdk.NewInt64Coin(sdk.DefaultBondDenom, 1000000)
undelegation := staking.NewMsgUndelegate(addrFrom, valAddr, undelAmount)
delegationToSend := []sdk.Msg{undelegation}
// send the delegation to the blockchain
sendMsgToBlockchain(cdc, address, keyname, password, delegationToSend, keybase)
}
func getKeybase() crkeys.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])
if err != nil {
panic(err)
}
return keybase
}
// sendMsgToBlockchain sends a message to the blockchain via the rest api
func sendMsgToBlockchain(cdc *amino.Codec, address string, keyname string,
password string, msg []sdk.Msg, keybase crkeys.Keybase) {
// 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").
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)
if err != nil {
panic(err)
}
// fmt.Printf("txBytes: %s", txBytes)
// need to convert the Amino encoded version back to an actual go struct
var tx auth.StdTx
cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx) // might be unmarshal binary bare
// now we re-marshall it again into json
jsonBytes, err := cdc.MarshalJSON(
authrest.BroadcastReq{
Tx: tx,
Mode: "block",
},
)
fmt.Printf("%s", bytes.NewBuffer(jsonBytes))
if err != nil {
panic(err)
}
// fmt.Println("post body: ", string(jsonBytes))
resp, err := http.Post("http://localhost:1317/txs", "application/json", bytes.NewBuffer(jsonBytes))
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Printf("\n\nBody:\n\n")
fmt.Println(string(body))
}
// getAccountNumberAndSequenceNumber gets an account number and sequence number from the blockchain
func getAccountNumberAndSequenceNumber(cdc *amino.Codec, address string) (accountNumber uint64, sequenceNumber uint64) {
// we need to setup the account number and sequence in order to have a valid transaction
resp, err := http.Get("http://localhost:1317/auth/accounts/" + address)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
var bodyUnmarshalled sdkrest.ResponseWithHeight
err = cdc.UnmarshalJSON(body, &bodyUnmarshalled)
if err != nil {
panic(err)
}
var account authexported.Account
err = cdc.UnmarshalJSON(bodyUnmarshalled.Result, &account)
if err != nil {
panic(err)
}
return account.GetAccountNumber(), account.GetSequence()
}

7
rest_test/stopchain.sh Executable file
View File

@ -0,0 +1,7 @@
#! /bin/bash
# THIS IS A BASH SCRIPT THAT STOPS THE BLOCKCHAIN AND THE REST API
echo "Stopping blockchain"
pgrep kvd | xargs kill
pgrep kvcli | xargs kill
echo "COMPLETED"

View File

@ -41,3 +41,5 @@ AWS Batch allows for "array jobs" which are a way of specifying many duplicates
- click on the compute environment name, to get details, then click the link ECS Cluster Name to get details on the actual machines running
- for array jobs, click the job name to get details of the individual jobs
## Sims - TODO

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,3 @@
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/auction/keeper
// ALIASGEN: github.com/kava-labs/kava/x/auction/types
package auction
import (
@ -10,22 +5,13 @@ import (
"github.com/kava-labs/kava/x/auction/types"
)
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/auction/keeper
// ALIASGEN: github.com/kava-labs/kava/x/auction/types
const (
DefaultCodespace = types.DefaultCodespace
CodeInvalidInitialAuctionID = types.CodeInvalidInitialAuctionID
CodeInvalidModulePermissions = types.CodeInvalidModulePermissions
CodeUnrecognizedAuctionType = types.CodeUnrecognizedAuctionType
CodeAuctionNotFound = types.CodeAuctionNotFound
CodeAuctionHasNotExpired = types.CodeAuctionHasNotExpired
CodeAuctionHasExpired = types.CodeAuctionHasExpired
CodeInvalidBidDenom = types.CodeInvalidBidDenom
CodeInvalidLotDenom = types.CodeInvalidLotDenom
CodeBidTooSmall = types.CodeBidTooSmall
CodeBidTooLarge = types.CodeBidTooLarge
CodeLotTooSmall = types.CodeLotTooSmall
CodeLotTooLarge = types.CodeLotTooLarge
CodeCollateralAuctionIsInReversePhase = types.CodeCollateralAuctionIsInReversePhase
CodeCollateralAuctionIsInForwardPhase = types.CodeCollateralAuctionIsInForwardPhase
EventTypeAuctionStart = types.EventTypeAuctionStart
EventTypeAuctionBid = types.EventTypeAuctionBid
EventTypeAuctionClose = types.EventTypeAuctionClose
@ -55,6 +41,7 @@ var (
// functions aliases
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
RegisterInvariants = keeper.RegisterInvariants
NewSurplusAuction = types.NewSurplusAuction
NewDebtAuction = types.NewDebtAuction
NewCollateralAuction = types.NewCollateralAuction

View File

@ -6,8 +6,8 @@ import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/kava-labs/kava/x/auction/types"
@ -21,7 +21,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
Short: "Querying commands for the auction module",
}
auctionQueryCmd.AddCommand(client.GetCommands(
auctionQueryCmd.AddCommand(flags.GetCommands(
QueryGetAuctionCmd(queryRoute, cdc),
QueryGetAuctionsCmd(queryRoute, cdc),
QueryParamsCmd(queryRoute, cdc),

View File

@ -1,14 +1,15 @@
package cli
import (
"bufio"
"fmt"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
@ -25,7 +26,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
Short: "auction transactions subcommands",
}
auctionTxCmd.AddCommand(client.PostCommands(
auctionTxCmd.AddCommand(flags.PostCommands(
GetCmdPlaceBid(cdc),
)...)
@ -45,8 +46,9 @@ $ %s tx %s bid 34 1000usdx --from myKeyName
`, version.ClientName, types.ModuleName)),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc))
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
id, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {

View File

@ -22,7 +22,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper
for _, a := range gs.Auctions {
keeper.SetAuction(ctx, a)
// find the total coins that should be present in the module account
totalAuctionCoins.Add(a.GetModuleAccountCoins())
totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()...)
}
// check if the module account exists

View File

@ -29,6 +29,12 @@ func TestInitGenesis(t *testing.T) {
tApp := app.NewTestApp()
keeper := tApp.GetAuctionKeeper()
ctx := tApp.NewContext(true, abci.Header{})
// setup module account
supplyKeeper := tApp.GetSupplyKeeper()
moduleAcc := supplyKeeper.GetModuleAccount(ctx, auction.ModuleName)
require.NoError(t, moduleAcc.SetCoins(testAuction.GetModuleAccountCoins()))
supplyKeeper.SetModuleAccount(ctx, moduleAcc)
// create genesis
gs := auction.NewGenesisState(
10,
@ -38,7 +44,7 @@ func TestInitGenesis(t *testing.T) {
// run init
require.NotPanics(t, func() {
auction.InitGenesis(ctx, keeper, tApp.GetSupplyKeeper(), gs)
auction.InitGenesis(ctx, keeper, supplyKeeper, gs)
})
// check state is as expected
@ -59,7 +65,7 @@ func TestInitGenesis(t *testing.T) {
return false
})
})
t.Run("invalid", func(t *testing.T) {
t.Run("invalid (invalid nextAuctionID)", func(t *testing.T) {
// setup keepers
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{})
@ -71,6 +77,24 @@ func TestInitGenesis(t *testing.T) {
auction.GenesisAuctions{testAuction},
)
// check init fails
require.Panics(t, func() {
auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs)
})
})
t.Run("invalid (missing mod account coins)", func(t *testing.T) {
// setup keepers
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{})
// create invalid genesis
gs := auction.NewGenesisState(
10,
auction.DefaultParams(),
auction.GenesisAuctions{testAuction},
)
// invalid as there is no module account setup
// check init fails
require.Panics(t, func() {
auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs)

View File

@ -1,32 +1,31 @@
package auction
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/auction/types"
)
// NewHandler returns a function to handle all "auction" type messages.
func NewHandler(keeper Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case MsgPlaceBid:
return handleMsgPlaceBid(ctx, keeper, msg)
default:
return sdk.ErrUnknownRequest(fmt.Sprintf("Unrecognized auction msg type: %T", msg)).Result()
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
}
}
}
func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Result {
func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) (*sdk.Result, error) {
err := keeper.PlaceBid(ctx, msg.AuctionID, msg.Bidder, msg.Amount)
if err != nil {
return err.Result()
return nil, err
}
ctx.EventManager().EmitEvent(
@ -37,7 +36,7 @@ func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Resu
),
)
return sdk.Result{
return &sdk.Result{
Events: ctx.EventManager().Events(),
}
}, nil
}

View File

@ -5,18 +5,19 @@ import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/auction/types"
)
// StartSurplusAuction starts a new surplus (forward) auction.
func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) {
func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, error) {
auction := types.NewSurplusAuction(
seller,
lot,
bidDenom,
types.DistantFuture)
types.DistantFuture,
)
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
if err != nil {
@ -41,7 +42,7 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin
}
// StartDebtAuction starts a new debt (reverse) auction.
func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, sdk.Error) {
func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, error) {
auction := types.NewDebtAuction(
buyer,
@ -53,7 +54,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
// This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker.
macc := k.supplyKeeper.GetModuleAccount(ctx, buyer)
if !macc.HasPermission(supply.Minter) {
return 0, types.ErrInvalidModulePermissions(k.codespace, supply.Minter)
return 0, sdkerrors.Wrap(types.ErrInvalidModulePermissions, supply.Minter)
}
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, buyer, types.ModuleName, sdk.NewCoins(debt))
@ -79,8 +80,10 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in
}
// StartCollateralAuction starts a new collateral (2-phase) auction.
func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, sdk.Error) {
func (k Keeper) StartCollateralAuction(
ctx sdk.Context, seller string, lot, maxBid sdk.Coin,
lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin,
) (uint64, error) {
weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights)
if err != nil {
return 0, err
@ -91,7 +94,8 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C
types.DistantFuture,
maxBid,
weightedAddresses,
debt)
debt,
)
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
if err != nil {
@ -120,42 +124,41 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C
}
// PlaceBid places a bid on any auction.
func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) sdk.Error {
func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) error {
auction, found := k.GetAuction(ctx, auctionID)
if !found {
return types.ErrAuctionNotFound(k.codespace, auctionID)
return sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", auctionID)
}
// validation common to all auctions
if ctx.BlockTime().After(auction.GetEndTime()) {
return types.ErrAuctionHasExpired(k.codespace, auctionID)
return sdkerrors.Wrapf(types.ErrAuctionHasExpired, "%d", auctionID)
}
// move coins and return updated auction
var err sdk.Error
var updatedAuction types.Auction
var (
err error
updatedAuction types.Auction
)
switch a := auction.(type) {
case types.SurplusAuction:
if updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount); err != nil {
return err
}
updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount)
case types.DebtAuction:
if updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount); err != nil {
return err
}
updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount)
case types.CollateralAuction:
if !a.IsReversePhase() {
updatedAuction, err = k.PlaceForwardBidCollateral(ctx, a, bidder, newAmount)
} else {
updatedAuction, err = k.PlaceReverseBidCollateral(ctx, a, bidder, newAmount)
}
default:
err = sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auction.GetType())
}
if err != nil {
return err
}
default:
return types.ErrUnrecognizedAuctionType(k.codespace)
}
k.SetAuction(ctx, updatedAuction)
@ -163,10 +166,10 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres
}
// PlaceBidSurplus places a forward bid on a surplus auction, moving coins and returning the updated auction.
func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, sdk.Error) {
func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, error) {
// Validate new bid
if bid.Denom != a.Bid.Denom {
return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom)
return a, sdkerrors.Wrapf(types.ErrInvalidBidDenom, "%s ≠ %s)", bid.Denom, a.Bid.Denom)
}
minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
sdk.MaxInt(
@ -175,7 +178,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
),
)
if bid.Amount.LT(minNewBidAmt) {
return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt))
return a, sdkerrors.Wrapf(types.ErrBidTooSmall, "%s ≤ %s%s", bid, minNewBidAmt, a.Bid.Denom)
}
// New bidder pays back old bidder
@ -223,13 +226,13 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder
}
// PlaceForwardBidCollateral places a forward bid on a collateral auction, moving coins and returning the updated auction.
func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, sdk.Error) {
func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, error) {
// Validate new bid
if bid.Denom != a.Bid.Denom {
return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom)
return a, sdkerrors.Wrapf(types.ErrInvalidBidDenom, "%s ≠ %s", bid.Denom, a.Bid.Denom)
}
if a.IsReversePhase() {
return a, types.ErrCollateralAuctionIsInReversePhase(k.codespace, a.ID)
return a, sdkerrors.Wrapf(types.ErrCollateralAuctionIsInReversePhase, "%d", a.ID)
}
minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
sdk.MaxInt(
@ -239,10 +242,10 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
)
minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment %
if bid.Amount.LT(minNewBidAmt) {
return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt))
return a, sdkerrors.Wrapf(types.ErrBidTooSmall, "%s ≤ %s%s", bid, minNewBidAmt, a.Bid.Denom)
}
if a.MaxBid.IsLT(bid) {
return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid)
return a, sdkerrors.Wrapf(types.ErrBidTooLarge, "%s > %s", bid, a.MaxBid)
}
// New bidder pays back old bidder
@ -299,13 +302,13 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc
}
// PlaceReverseBidCollateral places a reverse bid on a collateral auction, moving coins and returning the updated auction.
func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, sdk.Error) {
func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, error) {
// Validate new bid
if lot.Denom != a.Lot.Denom {
return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom)
return a, sdkerrors.Wrapf(types.ErrInvalidLotDenom, lot.Denom, a.Lot.Denom)
}
if !a.IsReversePhase() {
return a, types.ErrCollateralAuctionIsInForwardPhase(k.codespace, a.ID)
return a, sdkerrors.Wrapf(types.ErrCollateralAuctionIsInForwardPhase, "%d", a.ID)
}
maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
sdk.MaxInt(
@ -314,10 +317,10 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc
),
)
if lot.Amount.GT(maxNewLotAmt) {
return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt))
return a, sdkerrors.Wrapf(types.ErrLotTooLarge, "%s > %s%s", lot, maxNewLotAmt, a.Lot.Denom)
}
if lot.IsNegative() {
return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt()))
return a, sdkerrors.Wrapf(types.ErrLotTooSmall, "%s ≤ %s%s", lot, sdk.ZeroInt(), a.Lot.Denom)
}
// New bidder pays back old bidder
@ -368,10 +371,10 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc
}
// PlaceBidDebt places a reverse bid on a debt auction, moving coins and returning the updated auction.
func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, sdk.Error) {
func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, error) {
// Validate new bid
if lot.Denom != a.Lot.Denom {
return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom)
return a, sdkerrors.Wrapf(types.ErrInvalidLotDenom, lot.Denom, a.Lot.Denom)
}
maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
sdk.MaxInt(
@ -380,10 +383,10 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
),
)
if lot.Amount.GT(maxNewLotAmt) {
return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt))
return a, sdkerrors.Wrapf(types.ErrLotTooLarge, "%s > %s%s", lot, maxNewLotAmt, a.Lot.Denom)
}
if lot.IsNegative() {
return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt()))
return a, sdkerrors.Wrapf(types.ErrLotTooSmall, "%s ≤ %s%s", lot, sdk.ZeroInt(), a.Lot.Denom)
}
// New bidder pays back old bidder
@ -434,15 +437,15 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac
}
// CloseAuction closes an auction and distributes funds to the highest bidder.
func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error {
func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) error {
auction, found := k.GetAuction(ctx, auctionID)
if !found {
return types.ErrAuctionNotFound(k.codespace, auctionID)
return sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", auctionID)
}
if ctx.BlockTime().Before(auction.GetEndTime()) {
return types.ErrAuctionHasNotExpired(k.codespace, ctx.BlockTime(), auction.GetEndTime())
return sdkerrors.Wrapf(types.ErrAuctionHasNotExpired, "block time %s, auction end time %s", ctx.BlockTime().UTC(), auction.GetEndTime().UTC())
}
// payout to the last bidder
@ -460,7 +463,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error {
return err
}
default:
return types.ErrUnrecognizedAuctionType(k.codespace)
return sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType())
}
k.DeleteAuction(ctx, auctionID)
@ -475,7 +478,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error {
}
// PayoutDebtAuction pays out the proceeds for a debt auction, first minting the coins.
func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Error {
func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) error {
err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot))
if err != nil {
return err
@ -494,7 +497,7 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Erro
}
// PayoutSurplusAuction pays out the proceeds for a surplus auction.
func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sdk.Error {
func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) error {
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot))
if err != nil {
return err
@ -503,7 +506,7 @@ func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sd
}
// PayoutCollateralAuction pays out the proceeds for a collateral auction.
func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) sdk.Error {
func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) error {
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot))
if err != nil {
return err
@ -518,7 +521,7 @@ func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAucti
}
// CloseExpiredAuctions finds all auctions that are past (or at) their ending times and closes them, paying out to the highest bidder.
func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) sdk.Error {
func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) error {
var expiredAuctions []uint64
k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool {
expiredAuctions = append(expiredAuctions, id)
@ -542,10 +545,10 @@ func earliestTime(t1, t2 time.Time) time.Time {
}
// splitCoinIntoWeightedBuckets divides up some amount of coins according to some weights.
func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, sdk.Error) {
func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, error) {
for _, bucket := range buckets {
if bucket.IsNegative() {
return nil, sdk.ErrInternal("cannot split coin into bucket with negative weight")
return nil, fmt.Errorf("cannot split %s into bucket with negative weight (%s)", coin.String(), bucket.String())
}
}
amounts := splitIntIntoWeightedBuckets(coin.Amount, buckets)

View File

@ -267,30 +267,31 @@ func TestStartSurplusAuction(t *testing.T) {
blockTime time.Time
args args
expectPass bool
expPanic bool
}{
{
"normal",
someTime,
args{cdp.LiquidatorMacc, c("stable", 10), "gov"},
true,
true, false,
},
{
"no module account",
someTime,
args{"nonExistentModule", c("stable", 10), "gov"},
false,
false, true,
},
{
"not enough coins",
someTime,
args{cdp.LiquidatorMacc, c("stable", 101), "gov"},
false,
false, false,
},
{
"incorrect denom",
someTime,
args{cdp.LiquidatorMacc, c("notacoin", 10), "gov"},
false,
false, false,
},
}
for _, tc := range testCases {
@ -308,7 +309,15 @@ func TestStartSurplusAuction(t *testing.T) {
keeper := tApp.GetAuctionKeeper()
// run function under test
id, err := keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom)
var (
id uint64
err error
)
if tc.expPanic {
require.Panics(t, func() { _, _ = keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) }, tc.name)
} else {
id, err = keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom)
}
// check
sk := tApp.GetSupplyKeeper()
@ -316,11 +325,11 @@ func TestStartSurplusAuction(t *testing.T) {
actualAuc, found := keeper.GetAuction(ctx, id)
if tc.expectPass {
require.NoError(t, err)
require.NoError(t, err, tc.name)
// check coins moved
require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins)
require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins, tc.name)
// check auction in store and is correct
require.True(t, found)
require.True(t, found, tc.name)
expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{
ID: id,
Initiator: tc.args.seller,
@ -331,13 +340,13 @@ func TestStartSurplusAuction(t *testing.T) {
EndTime: types.DistantFuture,
MaxEndTime: types.DistantFuture,
}})
require.Equal(t, expectedAuction, actualAuc)
} else {
require.Error(t, err)
require.Equal(t, expectedAuction, actualAuc, tc.name)
} else if !tc.expPanic && !tc.expectPass {
require.Error(t, err, tc.name)
// check coins not moved
require.Equal(t, initialLiquidatorCoins, liquidatorCoins)
require.Equal(t, initialLiquidatorCoins, liquidatorCoins, tc.name)
// check auction not in store
require.False(t, found)
require.False(t, found, tc.name)
}
})
}

View File

@ -1,6 +1,7 @@
package keeper_test
import (
"errors"
"strings"
"testing"
"time"
@ -55,319 +56,347 @@ func TestAuctionBidding(t *testing.T) {
auctionArgs auctionArgs
setupBids []bidArgs
bidArgs bidArgs
expectedError sdk.CodeType
expectedError error
expectedEndTime time.Time
expectedBidder sdk.AccAddress
expectedBid sdk.Coin
expectpass bool
expectPass bool
expectPanic bool
}{
{
"basic: auction doesn't exist",
auctionArgs{Surplus, "", c("token1", 1), c("token2", 1), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token2", 10)},
types.CodeAuctionNotFound,
types.ErrAuctionNotFound,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
false,
true,
},
{
"basic: closed auction",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token2", 10)},
types.CodeAuctionHasExpired,
types.ErrAuctionHasExpired,
types.DistantFuture,
nil,
c("token2", 0),
false,
false,
},
{
"surplus: normal",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token2", 10)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
true,
false,
},
{
"surplus: second bidder",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
[]bidArgs{{buyer, c("token2", 10)}},
bidArgs{secondBuyer, c("token2", 11)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
secondBuyer,
c("token2", 11),
true,
false,
},
{
"surplus: invalid bid denom",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidBidDenom,
types.ErrInvalidBidDenom,
types.DistantFuture,
nil, // surplus auctions are created with initial bidder as a nil address
c("token2", 0),
false,
false,
},
{
"surplus: invalid bid (less than)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
[]bidArgs{{buyer, c("token2", 100)}},
bidArgs{buyer, c("token2", 99)},
types.CodeBidTooSmall,
types.ErrBidTooSmall,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 100),
false,
false,
},
{
"surplus: invalid bid (equal)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token2", 0)}, // min bid is technically 0 at default 5%, but it's capped at 1
types.CodeBidTooSmall,
types.ErrBidTooSmall,
types.DistantFuture,
nil, // surplus auctions are created with initial bidder as a nil address
c("token2", 0),
false,
false,
},
{
"surplus: invalid bid (less than min increment)",
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
[]bidArgs{{buyer, c("token2", 100)}},
bidArgs{buyer, c("token2", 104)}, // min bid is 105 at default 5%
types.CodeBidTooSmall,
types.ErrBidTooSmall,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 100),
false,
false,
},
{
"debt: normal",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
nil,
bidArgs{buyer, c("token1", 10)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 100),
true,
false,
},
{
"debt: second bidder",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
[]bidArgs{{buyer, c("token1", 10)}},
bidArgs{secondBuyer, c("token1", 9)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
secondBuyer,
c("token2", 100),
true,
false,
},
{
"debt: invalid lot denom",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidLotDenom,
types.ErrInvalidLotDenom,
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false,
false,
},
{
"debt: invalid lot size (larger)",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token1", 21)},
types.CodeLotTooLarge,
types.ErrLotTooLarge,
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false,
false,
},
{
"debt: invalid lot size (equal)",
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token1", 20)},
types.CodeLotTooLarge,
types.ErrLotTooLarge,
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false,
false,
},
{
"debt: invalid lot size (larger than min increment)",
auctionArgs{Debt, modName, c("token1", 60), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}},
nil,
bidArgs{buyer, c("token1", 58)}, // max lot at default 5% is 57
types.CodeLotTooLarge,
types.ErrLotTooLarge,
types.DistantFuture,
supply.NewModuleAddress(modName),
c("token2", 100),
false,
false, false,
},
{
"collateral [forward]: normal",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("token2", 10)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
true,
false,
},
{
"collateral [forward]: second bidder",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 10)}},
bidArgs{secondBuyer, c("token2", 11)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
secondBuyer,
c("token2", 11),
true,
false,
},
{
"collateral [forward]: invalid bid denom",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("badtoken", 10)},
types.CodeInvalidBidDenom,
types.ErrInvalidBidDenom,
types.DistantFuture,
nil,
c("token2", 0),
false,
false,
},
{
"collateral [forward]: invalid bid size (smaller)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 10)}},
bidArgs{buyer, c("token2", 9)},
types.CodeBidTooSmall,
types.ErrBidTooSmall,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 10),
false,
false,
},
{
"collateral [forward]: invalid bid size (equal)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("token2", 0)},
types.CodeBidTooSmall,
types.ErrBidTooSmall,
types.DistantFuture,
nil,
c("token2", 0),
false,
false,
},
{
"collateral [forward]: invalid bid size (less than min increment)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}},
bidArgs{buyer, c("token2", 51)},
types.CodeBidTooSmall,
types.ErrBidTooSmall,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false,
false,
},
{
"collateral [forward]: less than min increment but equal to maxBid",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 99)}},
bidArgs{buyer, c("token2", 100)}, // min bid at default 5% is 104
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 100),
true,
false,
},
{
"collateral [forward]: invalid bid size (greater than max)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
nil,
bidArgs{buyer, c("token2", 101)},
types.CodeBidTooLarge,
types.ErrBidTooLarge,
types.DistantFuture,
nil,
c("token2", 0),
false,
false,
},
{
"collateral [reverse]: normal",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 15)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
true,
false,
},
{
"collateral [reverse]: second bidder",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}, {buyer, c("token1", 15)}}, // put auction into reverse phase, and add a reverse phase bid
bidArgs{secondBuyer, c("token1", 14)},
sdk.CodeType(0),
nil,
someTime.Add(types.DefaultBidDuration),
secondBuyer,
c("token2", 50),
true,
false,
},
{
"collateral [reverse]: invalid lot denom",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("badtoken", 15)},
types.CodeInvalidLotDenom,
types.ErrInvalidLotDenom,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false,
false,
},
{
"collateral [reverse]: invalid lot size (greater)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 21)},
types.CodeLotTooLarge,
types.ErrLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false,
false,
},
{
"collateral [reverse]: invalid lot size (equal)",
auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 20)},
types.CodeLotTooLarge,
types.ErrLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false,
false,
},
{
"collateral [reverse]: invalid lot size (larger than min increment)",
auctionArgs{Collateral, modName, c("token1", 60), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
[]bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase
bidArgs{buyer, c("token1", 58)}, // max lot at default 5% is 57
types.CodeLotTooLarge,
types.ErrLotTooLarge,
someTime.Add(types.DefaultBidDuration),
buyer,
c("token2", 50),
false,
false,
},
}
for _, tc := range tests {
@ -394,17 +423,26 @@ func TestAuctionBidding(t *testing.T) {
// Start Auction
var id uint64
var err sdk.Error
var err error
switch tc.auctionArgs.auctionType {
case Surplus:
id, _ = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom)
if tc.expectPanic {
require.Panics(t, func() {
id, err = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom)
})
} else {
id, err = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom)
}
case Debt:
id, _ = keeper.StartDebtAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.bid, tc.auctionArgs.lot, tc.auctionArgs.debt)
id, err = keeper.StartDebtAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.bid, tc.auctionArgs.lot, tc.auctionArgs.debt)
case Collateral:
id, _ = keeper.StartCollateralAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid, tc.auctionArgs.addresses, tc.auctionArgs.weights, tc.auctionArgs.debt) // seller, lot, maxBid, otherPerson
id, err = keeper.StartCollateralAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid, tc.auctionArgs.addresses, tc.auctionArgs.weights, tc.auctionArgs.debt) // seller, lot, maxBid, otherPerson
default:
t.Fail()
}
require.NoError(t, err)
// Place setup bids
for _, b := range tc.setupBids {
require.NoError(t, keeper.PlaceBid(ctx, id, b.bidder, b.amount))
@ -428,7 +466,7 @@ func TestAuctionBidding(t *testing.T) {
err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.bidArgs.amount)
// Check success/failure
if tc.expectpass {
if tc.expectPass {
require.Nil(t, err)
// Check auction was found
newAuction, found := keeper.GetAuction(ctx, id)
@ -445,7 +483,8 @@ func TestAuctionBidding(t *testing.T) {
case Debt:
bidAmt = oldAuction.GetBid()
case Collateral:
collatAuction, _ := oldAuction.(types.CollateralAuction)
collatAuction, ok := oldAuction.(types.CollateralAuction)
require.True(t, ok, tc.name)
if collatAuction.IsReversePhase() {
bidAmt = oldAuction.GetBid()
}
@ -453,18 +492,18 @@ func TestAuctionBidding(t *testing.T) {
if oldBidder.Equals(tc.bidArgs.bidder) { // same bidder
require.Equal(t, newBidderOldCoins.Sub(cs(bidAmt.Sub(oldAuction.GetBid()))), bank.GetCoins(ctx, tc.bidArgs.bidder))
} else {
require.Equal(t, cs(newBidderOldCoins.Sub(cs(bidAmt))...), bank.GetCoins(ctx, tc.bidArgs.bidder)) // wrapping in cs() to avoid comparing nil and empty coins
require.Equal(t, newBidderOldCoins.Sub(cs(bidAmt)), bank.GetCoins(ctx, tc.bidArgs.bidder)) // wrapping in cs() to avoid comparing nil and empty coins
if oldBidder.Equals(supply.NewModuleAddress(oldAuction.GetInitiator())) { // handle checking debt coins for case debt auction has had no bids placed yet TODO make this less confusing
require.Equal(t, cs(oldBidderOldCoins.Add(cs(oldAuction.GetBid()))...).Add(cs(c("debt", oldAuction.GetBid().Amount.Int64()))), bank.GetCoins(ctx, oldBidder))
require.Equal(t, oldBidderOldCoins.Add(oldAuction.GetBid()).Add(c("debt", oldAuction.GetBid().Amount.Int64())), bank.GetCoins(ctx, oldBidder))
} else {
require.Equal(t, cs(oldBidderOldCoins.Add(cs(oldAuction.GetBid()))...), bank.GetCoins(ctx, oldBidder))
require.Equal(t, cs(oldBidderOldCoins.Add(oldAuction.GetBid())...), bank.GetCoins(ctx, oldBidder))
}
}
} else {
// Check expected error code type
require.NotNil(t, err, "PlaceBid did not return an error") // catch nil values before they cause a panic below
require.Equal(t, tc.expectedError, err.Result().Code)
require.True(t, errors.Is(err, tc.expectedError))
// Check auction values
newAuction, found := keeper.GetAuction(ctx, id)

View File

@ -0,0 +1,143 @@
package keeper
import (
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/auction/types"
)
// RegisterInvariants registers all staking invariants
func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) {
ir.RegisterRoute(types.ModuleName, "module-account",
ModuleAccountInvariants(k))
ir.RegisterRoute(types.ModuleName, "valid-auctions",
ValidAuctionInvariant(k))
ir.RegisterRoute(types.ModuleName, "valid-index",
ValidIndexInvariant(k))
}
// ModuleAccountInvariants checks that the module account's coins matches those stored in auctions
func ModuleAccountInvariants(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
totalAuctionCoins := sdk.NewCoins()
k.IterateAuctions(ctx, func(auction types.Auction) bool {
a, ok := auction.(types.GenesisAuction)
if !ok {
panic("stored auction type does not fulfill GenesisAuction interface")
}
totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()...)
return false
})
moduleAccCoins := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName).GetCoins()
broken := !moduleAccCoins.IsEqual(totalAuctionCoins)
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"module account",
fmt.Sprintf(
"\texpected ModuleAccount coins: %s\n"+
"\tactual ModuleAccount coins: %s\n",
totalAuctionCoins, moduleAccCoins),
)
return invariantMessage, broken
}
}
// ValidAuctionInvariant verifies that all auctions in the store are independently valid
func ValidAuctionInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
var validationErr error
var invalidAuction types.Auction
k.IterateAuctions(ctx, func(auction types.Auction) bool {
a, ok := auction.(types.GenesisAuction)
if !ok {
panic("stored auction type does not fulfill GenesisAuction interface")
}
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 a.GetEndTime().Before(currentTime) {
validationErr = fmt.Errorf("endTime after current block time (%s)", currentTime)
invalidAuction = a
return true
}
}
if err := a.Validate(); err != nil {
validationErr = err
invalidAuction = a
return true
}
return false
})
broken := validationErr != nil
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"valid auctions",
fmt.Sprintf(
"\tfound invalid auction, reason: %s\n"+
"\tauction:\n\t%s\n",
validationErr, invalidAuction),
)
return invariantMessage, broken
}
}
// ValidIndexInvariant checks that all auctions in the store are also in the index and vice versa.
func ValidIndexInvariant(k Keeper) sdk.Invariant {
return func(ctx sdk.Context) (string, bool) {
/* Method:
- check all the auction IDs in the index have a corresponding auction in the store
- index is now valid but there could be extra auction in the store
- check for these extra auctions by checking num items in the store equals that of index (store keys are always unique)
- doesn't check the IDs in the auction structs match the IDs in the keys
*/
// Check all auction IDs in the index are in the auction store
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
indexIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
defer indexIterator.Close()
var indexLength int
for ; indexIterator.Valid(); indexIterator.Next() {
indexLength++
idBytes := indexIterator.Value()
auctionBytes := store.Get(idBytes)
if auctionBytes == nil {
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"valid index",
fmt.Sprintf("\tauction with ID '%d' found in index but not in store", types.Uint64FromBytes(idBytes)))
return invariantMessage, true
}
}
// Check length of auction store matches the length of the index
storeIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
defer storeIterator.Close()
var storeLength int
for ; storeIterator.Valid(); storeIterator.Next() {
storeLength++
}
if storeLength != indexLength {
invariantMessage := sdk.FormatInvariant(
types.ModuleName,
"valid index",
fmt.Sprintf("\tmismatched number of items in auction store (%d) and index (%d)", storeLength, indexLength))
return invariantMessage, true
}
return "", false
}
}

View File

@ -18,7 +18,6 @@ type Keeper struct {
storeKey sdk.StoreKey
cdc *codec.Codec
paramSubspace subspace.Subspace
codespace sdk.CodespaceType
}
// NewKeeper returns a new auction keeper.
@ -26,11 +25,16 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.Suppl
if addr := supplyKeeper.GetModuleAddress(types.ModuleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
}
if !paramstore.HasKeyTable() {
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
}
return Keeper{
supplyKeeper: supplyKeeper,
storeKey: storeKey,
cdc: cdc,
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
paramSubspace: paramstore,
}
}
@ -46,17 +50,17 @@ func (k Keeper) SetNextAuctionID(ctx sdk.Context, id uint64) {
}
// GetNextAuctionID reads the next available global ID from store
func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, sdk.Error) {
func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, error) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.NextAuctionIDKey)
if bz == nil {
return 0, types.ErrInvalidInitialAuctionID(k.codespace)
return 0, types.ErrInvalidInitialAuctionID
}
return types.Uint64FromBytes(bz), nil
}
// IncrementNextAuctionID increments the next auction ID in the store by 1.
func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error {
func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) error {
id, err := k.GetNextAuctionID(ctx)
if err != nil {
return err
@ -66,7 +70,7 @@ func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error {
}
// StoreNewAuction stores an auction, adding a new ID
func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, sdk.Error) {
func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, error) {
newAuctionID, err := k.GetNextAuctionID(ctx)
if err != nil {
return 0, err

View File

@ -1,16 +1,18 @@
package keeper
import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/tendermint/abci/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/auction/types"
)
// NewQuerier is the module level router for state queries
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) {
switch path[0] {
case types.QueryGetAuction:
return queryAuction(ctx, req, keeper)
@ -19,35 +21,35 @@ func NewQuerier(keeper Keeper) sdk.Querier {
case types.QueryGetParams:
return queryGetParams(ctx, req, keeper)
default:
return nil, sdk.ErrUnknownRequest("unknown auction query endpoint")
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
}
}
}
func queryAuction(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
func queryAuction(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Decode request
var requestParams types.QueryAuctionParams
err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams)
err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
// Lookup auction
auction, found := keeper.GetAuction(ctx, requestParams.AuctionID)
if !found {
return nil, types.ErrAuctionNotFound(types.DefaultCodespace, requestParams.AuctionID)
return nil, sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", requestParams.AuctionID)
}
// Encode results
bz, err := codec.MarshalJSONIndent(keeper.cdc, auction)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Get all auctions
auctionsList := types.Auctions{}
keeper.IterateAuctions(ctx, func(a types.Auction) bool {
@ -58,21 +60,21 @@ func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byt
// Encode Results
bz, err := codec.MarshalJSONIndent(keeper.cdc, auctionsList)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query params in the auction store
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Get params
params := keeper.GetParams(ctx)
// Encode results
bz, err := codec.MarshalJSONIndent(keeper.cdc, params)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil

View File

@ -12,6 +12,7 @@ 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"
@ -24,7 +25,7 @@ import (
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ module.AppModuleSimulation = AppModuleSimulation{}
_ module.AppModuleSimulation = AppModule{}
)
// AppModuleBasic implements the sdk.AppModuleBasic interface.
@ -72,46 +73,29 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
//____________________________________________________________________________
// AppModuleSimulation defines the module simulation functions used by the auction module.
type AppModuleSimulation struct{}
// RegisterStoreDecoder registers a decoder for auction module's types
func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
sdr[StoreKey] = simulation.DecodeStore
}
// GenerateGenesisState creates a randomized GenState of the auction module
func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
// RandomizedParams creates randomized auction param changes for the simulator.
func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange {
return simulation.ParamChanges(r)
}
//____________________________________________________________________________
// AppModule implements the sdk.AppModule interface.
type AppModule struct {
AppModuleBasic
AppModuleSimulation
keeper Keeper
accountKeeper auth.AccountKeeper
supplyKeeper types.SupplyKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule {
func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper types.SupplyKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: keeper,
accountKeeper: accountKeeper,
supplyKeeper: supplyKeeper,
}
}
// RegisterInvariants performs a no-op.
func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
// RegisterInvariants registers the module invariants.
func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
RegisterInvariants(ir, am.keeper)
}
// Route module message route name
func (AppModule) Route() string {
@ -157,3 +141,30 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}
//____________________________________________________________________________
// GenerateGenesisState creates a randomized GenState of the auction module
func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
// ProposalContents doesn't return any content functions for governance proposals.
func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent {
return nil
}
// RandomizedParams returns nil because auction has no params.
func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange {
return simulation.ParamChanges(r)
}
// RegisterStoreDecoder registers a decoder for auction module's types
func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
sdr[StoreKey] = simulation.DecodeStore
}
// WeightedOperations returns the all the auction module operations with their respective weights.
func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper)
}

View File

@ -1,12 +1,33 @@
package simulation
import (
"bytes"
"encoding/binary"
"fmt"
"github.com/tendermint/tendermint/libs/kv"
"github.com/cosmos/cosmos-sdk/codec"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/kava-labs/kava/x/auction/types"
)
// DecodeStore unmarshals the KVPair's Value to the corresponding auction type
func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string {
// TODO implement this
return ""
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
switch {
case bytes.Equal(kvA.Key[:1], types.AuctionKeyPrefix):
var auctionA, auctionB types.Auction
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &auctionA)
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &auctionB)
return fmt.Sprintf("%v\n%v", auctionA, auctionB)
case bytes.Equal(kvA.Key[:1], types.AuctionByTimeKeyPrefix),
bytes.Equal(kvA.Key[:1], types.NextAuctionIDKey):
auctionIDA := binary.BigEndian.Uint64(kvA.Value)
auctionIDB := binary.BigEndian.Uint64(kvB.Value)
return fmt.Sprintf("%d\n%d", auctionIDA, auctionIDB)
default:
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
}
}

View File

@ -0,0 +1,57 @@
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"
"github.com/kava-labs/kava/x/auction/types"
)
func makeTestCodec() (cdc *codec.Codec) {
cdc = codec.New()
sdk.RegisterCodec(cdc)
types.RegisterCodec(cdc)
return
}
func TestDecodeDistributionStore(t *testing.T) {
cdc := makeTestCodec()
oneCoin := sdk.NewCoin("coin", sdk.OneInt())
auction := types.NewSurplusAuction("me", oneCoin, "coin", time.Now().UTC())
kvPairs := kv.Pairs{
kv.Pair{Key: types.AuctionKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&auction)},
kv.Pair{Key: types.AuctionByTimeKeyPrefix, Value: sdk.Uint64ToBigEndian(2)},
kv.Pair{Key: types.NextAuctionIDKey, Value: sdk.Uint64ToBigEndian(10)},
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
}
tests := []struct {
name string
expectedLog string
}{
{"Auction", fmt.Sprintf("%v\n%v", auction, auction)},
{"AuctionByTime", "2\n2"},
{"NextAuctionI", "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)
}
})
}
}

View File

@ -2,21 +2,170 @@ 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/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/auction/types"
cdptypes "github.com/kava-labs/kava/x/cdp/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
// MaxBidDuration is a crude way of ensuring that BidDuration ≤ MaxAuctionDuration for all generated params
MaxBidDuration time.Duration = AverageBlockTime * 50
)
func GenBidDuration(r *rand.Rand) time.Duration {
d, err := RandomPositiveDuration(r, 0, MaxBidDuration)
if err != nil {
panic(err)
}
return d
}
func GenMaxAuctionDuration(r *rand.Rand) time.Duration {
d, err := RandomPositiveDuration(r, MaxBidDuration, AverageBlockTime*200)
if err != nil {
panic(err)
}
return d
}
func GenIncrementCollateral(r *rand.Rand) sdk.Dec {
return simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("1"))
}
var GenIncrementDebt = GenIncrementCollateral
var GenIncrementSurplus = GenIncrementCollateral
// RandomizedGenState generates a random GenesisState for auction
func RandomizedGenState(simState *module.SimulationState) {
// TODO implement this fully
// - randomly generating the genesis params
// - overwriting with genesis provided to simulation
auctionGenesis := types.DefaultGenesisState()
p := types.NewParams(
GenMaxAuctionDuration(simState.Rand),
GenBidDuration(simState.Rand),
GenIncrementSurplus(simState.Rand),
GenIncrementDebt(simState.Rand),
GenIncrementCollateral(simState.Rand),
)
if err := p.Validate(); err != nil {
panic(err)
}
auctionGenesis := types.NewGenesisState(
types.DefaultNextAuctionID,
p,
nil,
)
// Add auctions
auctions := types.GenesisAuctions{
types.NewDebtAuction(
cdptypes.LiquidatorMacc, // using cdp account rather than generic test one to avoid having to set permissions on the supply keeper
sdk.NewInt64Coin("usdx", 100),
sdk.NewInt64Coin("ukava", 1000000000000),
simState.GenTimestamp.Add(time.Hour*5),
sdk.NewInt64Coin("debt", 100), // same as usdx
),
}
var startingID = auctionGenesis.NextAuctionID
var ok bool
var totalAuctionCoins sdk.Coins
for i, a := range auctions {
auctions[i], ok = a.WithID(uint64(i) + startingID).(types.GenesisAuction)
if !ok {
panic("can't convert Auction to GenesisAuction")
}
totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()...)
}
auctionGenesis.NextAuctionID = startingID + uint64(len(auctions))
auctionGenesis.Auctions = append(auctionGenesis.Auctions, auctions...)
// Also need to update the auction module account (to reflect the coins held in the auctions)
var authGenesis auth.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis)
auctionModAcc, found := getAccount(authGenesis.Accounts, supply.NewModuleAddress(types.ModuleName))
if !found {
auctionModAcc = supply.NewEmptyModuleAccount(types.ModuleName)
}
if err := auctionModAcc.SetCoins(totalAuctionCoins); err != nil {
panic(err)
}
authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, auctionModAcc)
// TODO adding bidder coins as well - this should be moved elsewhere
bidder, found := getAccount(authGenesis.Accounts, simState.Accounts[0].Address) // 0 is the bidder // FIXME
if !found {
panic("bidder not found")
}
bidderCoins := sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000000))
if err := bidder.SetCoins(bidder.GetCoins().Add(bidderCoins...)); err != nil {
panic(err)
}
authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, bidder)
simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis)
// Update the supply genesis state to reflect the new coins
// TODO find some way for this to happen automatically / move it elsewhere
var supplyGenesis supply.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis)
supplyGenesis.Supply = supplyGenesis.Supply.Add(totalAuctionCoins...).Add(bidderCoins...)
simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis)
// TODO liquidator mod account doesn't need to be initialized for this example
// - it just mints kava, doesn't need a starting balance
// - and supply.GetModuleAccount creates one if it doesn't exist
// Note: this line prints out the auction genesis state, not just the auction parameters. Some sdk modules print out just the parameters.
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, auctionGenesis))
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(auctionGenesis)
}
// Return an account from a list of accounts that matches an address.
func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) {
for _, a := range accounts {
if a.GetAddress().Equals(addr) {
return a, true
}
}
return nil, false
}
// In a list of accounts, replace the first account found with the same address. If not found, append the account.
func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount {
newAccounts := accounts
for i, a := range accounts {
if a.GetAddress().Equals(acc.GetAddress()) {
newAccounts[i] = acc
return newAccounts
}
}
return append(newAccounts, acc)
}
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
}

View File

@ -0,0 +1,251 @@
package simulation
import (
"errors"
"fmt"
"math/big"
"math/rand"
"time"
"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"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/simulation"
appparams "github.com/kava-labs/kava/app/params"
"github.com/kava-labs/kava/x/auction/keeper"
"github.com/kava-labs/kava/x/auction/types"
)
var (
errorNotEnoughCoins = errors.New("account doesn't have enough coins")
errorCantReceiveBids = errors.New("auction can't receive bids (lot = 0 in reverse auction)")
)
// Simulation operation weights constants
const (
OpWeightMsgPlaceBid = "op_weight_msg_place_bid"
)
// 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,
) simulation.WeightedOperations {
var weightMsgPlaceBid int
appParams.GetOrGenerate(cdc, OpWeightMsgPlaceBid, &weightMsgPlaceBid, nil,
func(_ *rand.Rand) {
weightMsgPlaceBid = appparams.DefaultWeightMsgPlaceBid
},
)
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgPlaceBid,
SimulateMsgPlaceBid(ak, k),
),
}
}
// SimulateMsgPlaceBid returns a function that runs a random state change on the module keeper.
// There's two error paths
// - return a OpMessage, but nil error - this will log a message but keep running the simulation
// - return an error - this will stop the simulation
func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper 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) {
// get open auctions
openAuctions := types.Auctions{}
keeper.IterateAuctions(ctx, func(a types.Auction) bool {
openAuctions = append(openAuctions, a)
return false
})
// shuffle auctions slice so that bids are evenly distributed across auctions
r.Shuffle(len(openAuctions), func(i, j int) {
openAuctions[i], openAuctions[j] = openAuctions[j], openAuctions[i]
})
// search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction)
blockTime := ctx.BlockHeader().Time
params := keeper.GetParams(ctx)
bidder, openAuction, found := findValidAccountAuctionPair(accs, openAuctions, func(acc simulation.Account, auc types.Auction) bool {
account := ak.GetAccount(ctx, acc.Address)
_, err := generateBidAmount(r, params, auc, account, blockTime)
if err == errorNotEnoughCoins || err == errorCantReceiveBids {
return false // keep searching
} else if err != nil {
panic(err) // raise errors
}
return true // found valid pair
})
if !found {
return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation (no valid auction and bidder)", "", false, nil), nil, nil
}
bidderAcc := ak.GetAccount(ctx, bidder.Address)
if bidderAcc == nil {
return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", bidder.Address)
}
// pick a bid amount for the chosen auction and bidder
amount, err := generateBidAmount(r, params, openAuction, bidderAcc, blockTime)
if err != nil { // shouldn't happen given the checks above
return simulation.NoOpMsg(types.ModuleName), nil, err
}
// create and deliver a tx
msg := types.NewMsgPlaceBid(openAuction.GetID(), bidder.Address, amount)
tx := helpers.GenTx(
[]sdk.Msg{msg},
sdk.NewCoins(), // TODO pick a random amount fees
helpers.DefaultGenTxGas,
chainID,
[]uint64{bidderAcc.GetAccountNumber()},
[]uint64{bidderAcc.GetSequence()},
bidder.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
}
}
func generateBidAmount(
r *rand.Rand, params types.Params, auc types.Auction,
bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) {
bidderBalance := bidder.SpendableCoins(blockTime)
switch a := auc.(type) {
case types.DebtAuction:
// Check bidder has enough (stable coin) to pay in
if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin
return sdk.Coin{}, errorNotEnoughCoins
}
// Check auction can still receive new bids
if a.Lot.Amount.Equal(sdk.ZeroInt()) {
return sdk.Coin{}, errorCantReceiveBids
}
// Generate a new lot amount (gov coin)
maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementDebt).RoundInt(),
),
)
amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above
if err != nil {
panic(err)
}
return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin
case types.SurplusAuction:
// Check the bidder has enough (gov coin) to pay in
minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementSurplus).RoundInt(),
),
)
if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { // gov coin
return sdk.Coin{}, errorNotEnoughCoins
}
// Generate a new bid amount (gov coin)
amt, err := RandIntInclusive(r, minNewBidAmt, bidderBalance.AmountOf(a.Bid.Denom))
if err != nil {
panic(err)
}
return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin
case types.CollateralAuction:
// Check the bidder has enough (stable coin) to pay in
minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementCollateral).RoundInt(),
),
)
minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment %
if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) {
return sdk.Coin{}, errorNotEnoughCoins
}
// Check auction can still receive new bids
if a.IsReversePhase() && a.Lot.Amount.Equal(sdk.ZeroInt()) {
return sdk.Coin{}, errorCantReceiveBids
}
// Generate a new bid amount (collateral coin in reverse phase)
if a.IsReversePhase() {
maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost
sdk.MaxInt(
sdk.NewInt(1),
sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementCollateral).RoundInt(),
),
)
amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above
if err != nil {
panic(err)
}
return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin
// Generate a new bid amount (stable coin in forward phase)
} else {
amt, err := RandIntInclusive(r, minNewBidAmt, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount))
if err != nil {
panic(err)
}
// when the bidder has enough coins, pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase
if r.Intn(2) == 0 && bidderBalance.AmountOf(a.Bid.Denom).GTE(a.MaxBid.Amount) { // 50%
amt = a.MaxBid.Amount
}
return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin
}
default:
return sdk.Coin{}, fmt.Errorf("unknown auction type")
}
}
// findValidAccountAuctionPair finds an auction and account for which the callback func returns true
func findValidAccountAuctionPair(accounts []simulation.Account, auctions types.Auctions, cb func(simulation.Account, types.Auction) bool) (simulation.Account, types.Auction, bool) {
for _, auc := range auctions {
for _, acc := range accounts {
if isValid := cb(acc, auc); isValid {
return acc, auc, true
}
}
}
return simulation.Account{}, nil, false
}
// RandIntInclusive 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
}

View File

@ -1,14 +1,46 @@
package simulation
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/x/auction/types"
)
// 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{}
// Note: params are encoded to JSON before being stored in the param store. These param changes
// update the raw values in the store so values need to be JSON. This is why values that are represented
// as strings in JSON (such as time.Duration) have the escaped quotes.
// TODO should we encode the values properly with ModuleCdc.MustMarshalJSON()?
return []simulation.ParamChange{
simulation.NewSimParamChange(types.ModuleName, string(types.KeyBidDuration),
func(r *rand.Rand) string {
return fmt.Sprintf("%d", GenBidDuration(r))
},
),
simulation.NewSimParamChange(types.ModuleName, string(types.KeyMaxAuctionDuration),
func(r *rand.Rand) string {
return fmt.Sprintf("%d", GenMaxAuctionDuration(r))
},
),
simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementCollateral),
func(r *rand.Rand) string {
return fmt.Sprintf("%d", GenIncrementCollateral(r))
},
),
simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementDebt),
func(r *rand.Rand) string {
return fmt.Sprintf("%d", GenIncrementDebt(r))
},
),
simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementSurplus),
func(r *rand.Rand) string {
return fmt.Sprintf("%d", GenIncrementSurplus(r))
},
),
}
}

View File

@ -69,6 +69,12 @@ func (a BaseAuction) Validate() error {
if a.EndTime.After(a.MaxEndTime) {
return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime)
}
if !a.Lot.IsValid() {
return fmt.Errorf("invalid lot: %s", a.Lot)
}
if !a.Bid.IsValid() {
return fmt.Errorf("invalid bid: %s", a.Bid)
}
return nil
}
@ -148,6 +154,13 @@ func (a DebtAuction) GetModuleAccountCoins() sdk.Coins {
// GetPhase returns the direction of a debt auction, which never changes.
func (a DebtAuction) GetPhase() string { return "reverse" }
func (a DebtAuction) Validate() error {
if !a.CorrespondingDebt.IsValid() {
return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt)
}
return a.BaseAuction.Validate()
}
// NewDebtAuction returns a new debt auction.
func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction {
// Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator)
@ -191,7 +204,7 @@ func (a CollateralAuction) GetType() string { return "collateral" }
// It is used in genesis initialize the module account correctly.
func (a CollateralAuction) GetModuleAccountCoins() sdk.Coins {
// a.Bid is paid out on bids, so is never stored in the module account
return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt))
return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt)...)
}
// IsReversePhase returns whether the auction has switched over to reverse phase or not.
@ -208,6 +221,19 @@ func (a CollateralAuction) GetPhase() string {
return "forward"
}
func (a CollateralAuction) Validate() error {
if !a.CorrespondingDebt.IsValid() {
return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt)
}
if !a.MaxBid.IsValid() {
return fmt.Errorf("invalid max bid: %s", a.MaxBid)
}
if err := a.LotReturns.Validate(); err != nil {
return fmt.Errorf("invalid lot returns: %w", err)
}
return a.BaseAuction.Validate()
}
func (a CollateralAuction) String() string {
return fmt.Sprintf(`Auction %d:
Initiator: %s
@ -250,17 +276,26 @@ type WeightedAddresses struct {
}
// NewWeightedAddresses returns a new list addresses with weights.
func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) {
if len(addrs) != len(weights) {
return WeightedAddresses{}, sdk.ErrInternal("number of addresses doesn't match number of weights")
}
for _, w := range weights {
if w.IsNegative() {
return WeightedAddresses{}, sdk.ErrInternal("weights contain a negative amount")
}
}
return WeightedAddresses{
func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, error) {
wa := WeightedAddresses{
Addresses: addrs,
Weights: weights,
}, nil
}
if err := wa.Validate(); err != nil {
return WeightedAddresses{}, err
}
return wa, nil
}
// Validate checks for that the weights are not negative and that lengths match.
func (wa WeightedAddresses) Validate() error {
if len(wa.Addresses) != len(wa.Weights) {
return fmt.Errorf("number of addresses doesn't match number of weights, %d ≠ %d", len(wa.Addresses), len(wa.Weights))
}
for _, w := range wa.Weights {
if w.IsNegative() {
return fmt.Errorf("weights contain a negative amount: %s", w)
}
}
return nil
}

View File

@ -1,98 +1,36 @@
// DONTCOVER
package types
import (
"fmt"
"time"
import sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
// DONTCOVER
var (
// ErrInvalidInitialAuctionID error for when the initial auction ID hasn't been set
ErrInvalidInitialAuctionID = sdkerrors.Register(ModuleName, 2, "initial auction ID hasn't been set")
// ErrInvalidModulePermissions error for when module doesn't have valid permissions
ErrInvalidModulePermissions = sdkerrors.Register(ModuleName, 3, "module does not have required permission")
// ErrUnrecognizedAuctionType error for unrecognized auction type
ErrUnrecognizedAuctionType = sdkerrors.Register(ModuleName, 4, "unrecognized auction type")
// ErrAuctionNotFound error for when an auction is not found
ErrAuctionNotFound = sdkerrors.Register(ModuleName, 5, "auction not found")
// ErrAuctionHasNotExpired error for attempting to close an auction that has not passed its end time
ErrAuctionHasNotExpired = sdkerrors.Register(ModuleName, 6, "auction can't be closed as curent block time has not passed auction end time")
// ErrAuctionHasExpired error for when an auction is closed and unavailable for bidding
ErrAuctionHasExpired = sdkerrors.Register(ModuleName, 7, "auction has closed")
// ErrInvalidBidDenom error for when bid denom doesn't match auction bid denom
ErrInvalidBidDenom = sdkerrors.Register(ModuleName, 8, "bid denom doesn't match auction bid denom")
// ErrInvalidLotDenom error for when lot denom doesn't match auction lot denom
ErrInvalidLotDenom = sdkerrors.Register(ModuleName, 9, "lot denom doesn't match auction lot denom")
// ErrBidTooSmall error for when bid is not greater than auction's min bid amount
ErrBidTooSmall = sdkerrors.Register(ModuleName, 10, "bid is not greater than auction's min new bid amount")
// ErrBidTooLarge error for when bid is larger than auction's maximum allowed bid
ErrBidTooLarge = sdkerrors.Register(ModuleName, 11, "bid is greater than auction's max bid")
// ErrLotTooSmall error for when lot is less than zero
ErrLotTooSmall = sdkerrors.Register(ModuleName, 12, "lot is not greater than auction's min new lot amount")
// ErrLotTooLarge error for when lot is not smaller than auction's max new lot amount
ErrLotTooLarge = sdkerrors.Register(ModuleName, 13, "lot is greater than auction's max new lot amount")
// ErrCollateralAuctionIsInReversePhase error for when attempting to place a forward bid on a collateral auction in reverse phase
ErrCollateralAuctionIsInReversePhase = sdkerrors.Register(ModuleName, 14, "invalid bid: auction is in reverse phase")
// ErrCollateralAuctionIsInForwardPhase error for when attempting to place a reverse bid on a collateral auction in forward phase
ErrCollateralAuctionIsInForwardPhase = sdkerrors.Register(ModuleName, 15, "invalid bid: auction is in forward phase")
)
// Error codes specific to auction module
const (
DefaultCodespace sdk.CodespaceType = ModuleName
CodeInvalidInitialAuctionID sdk.CodeType = 1
CodeInvalidModulePermissions sdk.CodeType = 2
CodeUnrecognizedAuctionType sdk.CodeType = 3
CodeAuctionNotFound sdk.CodeType = 4
CodeAuctionHasNotExpired sdk.CodeType = 5
CodeAuctionHasExpired sdk.CodeType = 6
CodeInvalidBidDenom sdk.CodeType = 7
CodeInvalidLotDenom sdk.CodeType = 8
CodeBidTooSmall sdk.CodeType = 9
CodeBidTooLarge sdk.CodeType = 10
CodeLotTooSmall sdk.CodeType = 11
CodeLotTooLarge sdk.CodeType = 12
CodeCollateralAuctionIsInReversePhase sdk.CodeType = 13
CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 14
)
// ErrInvalidInitialAuctionID error for when the initial auction ID hasn't been set
func ErrInvalidInitialAuctionID(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidInitialAuctionID, fmt.Sprintf("initial auction ID hasn't been set"))
}
// ErrInvalidModulePermissions error for when module doesn't have valid permissions
func ErrInvalidModulePermissions(codespace sdk.CodespaceType, permission string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidModulePermissions, fmt.Sprintf("module does not have required permission '%s'", permission))
}
// ErrUnrecognizedAuctionType error for unrecognized auction type
func ErrUnrecognizedAuctionType(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeUnrecognizedAuctionType, fmt.Sprintf("unrecognized auction type"))
}
// ErrAuctionNotFound error for when an auction is not found
func ErrAuctionNotFound(codespace sdk.CodespaceType, id uint64) sdk.Error {
return sdk.NewError(codespace, CodeAuctionNotFound, fmt.Sprintf("auction %d was not found", id))
}
// ErrAuctionHasNotExpired error for attempting to close an auction that has not passed its end time
func ErrAuctionHasNotExpired(codespace sdk.CodespaceType, blockTime time.Time, endTime time.Time) sdk.Error {
return sdk.NewError(codespace, CodeAuctionHasNotExpired, fmt.Sprintf("auction can't be closed as curent block time (%v) has not passed auction end time (%v)", blockTime, endTime))
}
// ErrAuctionHasExpired error for when an auction is closed and unavailable for bidding
func ErrAuctionHasExpired(codespace sdk.CodespaceType, id uint64) sdk.Error {
return sdk.NewError(codespace, CodeAuctionHasExpired, fmt.Sprintf("auction %d has closed", id))
}
// ErrInvalidBidDenom error for when bid denom doesn't match auction bid denom
func ErrInvalidBidDenom(codespace sdk.CodespaceType, bidDenom string, auctionBidDenom string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidBidDenom, fmt.Sprintf("bid denom %s doesn't match auction bid denom %s", bidDenom, auctionBidDenom))
}
// ErrInvalidLotDenom error for when lot denom doesn't match auction lot denom
func ErrInvalidLotDenom(codespace sdk.CodespaceType, lotDenom string, auctionLotDenom string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidLotDenom, fmt.Sprintf("lot denom %s doesn't match auction lot denom %s", lotDenom, auctionLotDenom))
}
// ErrBidTooSmall error for when bid is not greater than auction's min bid amount
func ErrBidTooSmall(codespace sdk.CodespaceType, bid sdk.Coin, minBid sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeBidTooSmall, fmt.Sprintf("bid %s is not greater than auction's min new bid amount %s", bid.String(), minBid.String()))
}
// ErrBidTooLarge error for when bid is larger than auction's maximum allowed bid
func ErrBidTooLarge(codespace sdk.CodespaceType, bid sdk.Coin, maxBid sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeBidTooLarge, fmt.Sprintf("bid %s is greater than auction's max bid %s", bid.String(), maxBid.String()))
}
// ErrLotToosmall error for when lot is less than zero
func ErrLotTooSmall(codespace sdk.CodespaceType, lot sdk.Coin, minLot sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeLotTooSmall, fmt.Sprintf("lot %s is not greater than auction's min new lot amount %s", lot.String(), minLot.String()))
}
// ErrLotTooLarge error for when lot is not smaller than auction's max new lot amount
func ErrLotTooLarge(codespace sdk.CodespaceType, lot sdk.Coin, maxLot sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeLotTooLarge, fmt.Sprintf("lot %s is greater than auction's max new lot amount %s", lot.String(), maxLot.String()))
}
// ErrCollateralAuctionIsInReversePhase error for when attempting to place a forward bid on a collateral auction in reverse phase
func ErrCollateralAuctionIsInReversePhase(codespace sdk.CodespaceType, id uint64) sdk.Error {
return sdk.NewError(codespace, CodeCollateralAuctionIsInReversePhase, fmt.Sprintf("invalid bid - auction %d is in reverse phase", id))
}
// ErrCollateralAuctionIsInForwardPhase error for when attempting to place a reverse bid on a collateral auction in forward phase
func ErrCollateralAuctionIsInForwardPhase(codespace sdk.CodespaceType, id uint64) sdk.Error {
return sdk.NewError(codespace, CodeCollateralAuctionIsInForwardPhase, fmt.Sprintf("invalid bid - auction %d is in forward phase", id))
}

View File

@ -10,10 +10,10 @@ 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) sdk.Error
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error
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) sdk.Error
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error
}

View File

@ -2,7 +2,9 @@ package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// ensure Msg interface compliance at compile time
@ -31,12 +33,12 @@ func (msg MsgPlaceBid) Route() string { return RouterKey }
func (msg MsgPlaceBid) Type() string { return "place_bid" }
// ValidateBasic does a simple validation check that doesn't require access to state.
func (msg MsgPlaceBid) ValidateBasic() sdk.Error {
func (msg MsgPlaceBid) ValidateBasic() error {
if msg.Bidder.Empty() {
return sdk.ErrInvalidAddress("invalid (empty) bidder address")
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "bidder address cannot be empty")
}
if !msg.Amount.IsValid() {
return sdk.ErrInvalidCoins(fmt.Sprintf("invalid bid amount: %s", msg.Amount))
return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "bid amount %s", msg.Amount)
}
return nil
}
@ -51,3 +53,12 @@ func (msg MsgPlaceBid) GetSignBytes() []byte {
func (msg MsgPlaceBid) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.Bidder}
}
func (msg MsgPlaceBid) String() string {
// String implements the Stringer interface
return fmt.Sprintf(`Place Bid Message:
Auction ID: %d
Bidder: %s
Amount: %s
`, msg.AuctionID, msg.Bidder, msg.Amount)
}

View File

@ -2,13 +2,17 @@ package types
import (
"bytes"
"errors"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
)
var emptyDec = sdk.Dec{}
// Defaults for auction params
const (
// DefaultMaxAuctionDuration max length of auction
@ -69,11 +73,11 @@ func ParamKeyTable() subspace.KeyTable {
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs.
func (p *Params) ParamSetPairs() subspace.ParamSetPairs {
return subspace.ParamSetPairs{
{Key: KeyBidDuration, Value: &p.BidDuration},
{Key: KeyMaxAuctionDuration, Value: &p.MaxAuctionDuration},
{Key: KeyIncrementSurplus, Value: &p.IncrementSurplus},
{Key: KeyIncrementDebt, Value: &p.IncrementDebt},
{Key: KeyIncrementCollateral, Value: &p.IncrementCollateral},
params.NewParamSetPair(KeyBidDuration, &p.BidDuration, validateBidDurationParam),
params.NewParamSetPair(KeyMaxAuctionDuration, &p.MaxAuctionDuration, validateMaxAuctionDurationParam),
params.NewParamSetPair(KeyIncrementSurplus, &p.IncrementSurplus, validateIncrementSurplusParam),
params.NewParamSetPair(KeyIncrementDebt, &p.IncrementDebt, validateIncrementDebtParam),
params.NewParamSetPair(KeyIncrementCollateral, &p.IncrementCollateral, validateIncrementCollateralParam),
}
}
@ -97,26 +101,102 @@ func (p Params) String() string {
// Validate checks that the parameters have valid values.
func (p Params) Validate() error {
if p.BidDuration < 0 {
return sdk.ErrInternal("bid duration cannot be negative")
if err := validateBidDurationParam(p.BidDuration); err != nil {
return err
}
if p.MaxAuctionDuration < 0 {
return sdk.ErrInternal("max auction duration cannot be negative")
if err := validateMaxAuctionDurationParam(p.MaxAuctionDuration); err != nil {
return err
}
if p.BidDuration > p.MaxAuctionDuration {
return sdk.ErrInternal("bid duration param cannot be larger than max auction duration")
return errors.New("bid duration param cannot be larger than max auction duration")
}
if p.IncrementSurplus == (sdk.Dec{}) || p.IncrementDebt == (sdk.Dec{}) || p.IncrementCollateral == (sdk.Dec{}) {
return sdk.ErrInternal("auction increment values cannot be nil")
if err := validateIncrementSurplusParam(p.IncrementSurplus); err != nil {
return err
}
if p.IncrementSurplus.IsNegative() {
return sdk.ErrInternal("surplus auction increment cannot be less than zero")
if err := validateIncrementDebtParam(p.IncrementDebt); err != nil {
return err
}
if p.IncrementDebt.IsNegative() {
return sdk.ErrInternal("debt auction increment cannot be less than zero")
return validateIncrementCollateralParam(p.IncrementCollateral)
}
func validateBidDurationParam(i interface{}) error {
bidDuration, ok := i.(time.Duration)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if p.IncrementCollateral.IsNegative() {
return sdk.ErrInternal("collateral auction increment cannot be less than zero")
if bidDuration < 0 {
return fmt.Errorf("bid duration cannot be negative %d", bidDuration)
}
return nil
}
func validateMaxAuctionDurationParam(i interface{}) error {
maxAuctionDuration, ok := i.(time.Duration)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if maxAuctionDuration < 0 {
return fmt.Errorf("max auction duration cannot be negative %d", maxAuctionDuration)
}
return nil
}
func validateIncrementSurplusParam(i interface{}) error {
incrementSurplus, ok := i.(sdk.Dec)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if incrementSurplus == emptyDec || incrementSurplus.IsNil() {
return errors.New("surplus auction increment cannot be nil or empty")
}
if incrementSurplus.IsNegative() {
return fmt.Errorf("surplus auction increment cannot be less than zero %s", incrementSurplus)
}
return nil
}
func validateIncrementDebtParam(i interface{}) error {
incrementDebt, ok := i.(sdk.Dec)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if incrementDebt == emptyDec || incrementDebt.IsNil() {
return errors.New("debt auction increment cannot be nil or empty")
}
if incrementDebt.IsNegative() {
return fmt.Errorf("debt auction increment cannot be less than zero %s", incrementDebt)
}
return nil
}
func validateIncrementCollateralParam(i interface{}) error {
incrementCollateral, ok := i.(sdk.Dec)
if !ok {
return fmt.Errorf("invalid parameter type: %T", i)
}
if incrementCollateral == emptyDec || incrementCollateral.IsNil() {
return errors.New("collateral auction increment cannot be nil or empty")
}
if incrementCollateral.IsNegative() {
return fmt.Errorf("collateral auction increment cannot be less than zero %s", incrementCollateral)
}
return nil
}

18
x/bep3/abci.go Normal file
View File

@ -0,0 +1,18 @@
package bep3
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
// BeginBlocker runs at the start of every block
func BeginBlocker(ctx sdk.Context, k Keeper) {
err := k.UpdateExpiredAtomicSwaps(ctx)
if err != nil {
panic(err)
}
err = k.DeleteClosedAtomicSwapsFromLongtermStorage(ctx)
if err != nil {
panic(err)
}
}

244
x/bep3/abci_test.go Normal file
View File

@ -0,0 +1,244 @@
package bep3_test
import (
"testing"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtime "github.com/tendermint/tendermint/types/time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3"
)
type ABCITestSuite struct {
suite.Suite
keeper bep3.Keeper
app app.TestApp
ctx sdk.Context
addrs []sdk.AccAddress
swapIDs []tmbytes.HexBytes
randomNumbers []tmbytes.HexBytes
}
func (suite *ABCITestSuite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
// Set up auth GenesisState
_, addrs := app.GeneratePrivKeyAddressPairs(11)
coins := []sdk.Coins{}
for j := 0; j < 11; j++ {
coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000)))
}
authGS := app.NewAuthGenState(addrs, coins)
// Initialize test app
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(addrs[0]))
suite.ctx = ctx
suite.app = tApp
suite.addrs = addrs
suite.ResetKeeper()
}
func (suite *ABCITestSuite) ResetKeeper() {
suite.keeper = suite.app.GetBep3Keeper()
var swapIDs []tmbytes.HexBytes
var randomNumbers []tmbytes.HexBytes
for i := 0; i < 10; i++ {
// Set up atomic swap variables
expireHeight := int64(360)
amount := cs(c("bnb", int64(100)))
timestamp := ts(i)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
// Create atomic swap and check err to confirm creation
err := suite.keeper.CreateAtomicSwap(suite.ctx, randomNumberHash, timestamp, expireHeight,
suite.addrs[0], suite.addrs[i], TestSenderOtherChain, TestRecipientOtherChain,
amount, amount.String(), true)
suite.Nil(err)
// Store swap's calculated ID and secret random number
swapID := bep3.CalculateSwapID(randomNumberHash, suite.addrs[0], TestSenderOtherChain)
swapIDs = append(swapIDs, swapID)
randomNumbers = append(randomNumbers, randomNumber.Bytes())
}
suite.swapIDs = swapIDs
suite.randomNumbers = randomNumbers
}
func (suite *ABCITestSuite) TestBeginBlocker_UpdateExpiredAtomicSwaps() {
testCases := []struct {
name string
firstCtx sdk.Context
secondCtx sdk.Context
expectedStatus bep3.SwapStatus
expectInStorage bool
}{
{
name: "normal",
firstCtx: suite.ctx,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 10),
expectedStatus: bep3.Open,
expectInStorage: true,
},
{
name: "after expiration",
firstCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 410),
expectedStatus: bep3.Expired,
expectInStorage: true,
},
{
name: "after completion",
firstCtx: suite.ctx,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 10),
expectedStatus: bep3.Completed,
expectInStorage: true,
},
{
name: "after deletion",
firstCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400 + bep3.DefaultLongtermStorageDuration),
expectedStatus: bep3.NULL,
expectInStorage: false,
},
}
for _, tc := range testCases {
// Reset keeper and run the initial begin blocker
suite.ResetKeeper()
suite.Run(tc.name, func() {
bep3.BeginBlocker(tc.firstCtx, suite.keeper)
switch tc.expectedStatus {
case bep3.Completed:
for i, swapID := range suite.swapIDs {
err := suite.keeper.ClaimAtomicSwap(tc.firstCtx, suite.addrs[5], swapID, suite.randomNumbers[i])
suite.Nil(err)
}
case bep3.NULL:
for _, swapID := range suite.swapIDs {
err := suite.keeper.RefundAtomicSwap(tc.firstCtx, suite.addrs[5], swapID)
suite.Nil(err)
}
}
// Run the second begin blocker
bep3.BeginBlocker(tc.secondCtx, suite.keeper)
// Check each swap's availibility and status
for _, swapID := range suite.swapIDs {
storedSwap, found := suite.keeper.GetAtomicSwap(tc.secondCtx, swapID)
if tc.expectInStorage {
suite.True(found)
} else {
suite.False(found)
}
suite.Equal(tc.expectedStatus, storedSwap.Status)
}
})
}
}
func (suite *ABCITestSuite) TestBeginBlocker_DeleteClosedAtomicSwapsFromLongtermStorage() {
type Action int
const (
NULL Action = 0x00
Refund Action = 0x01
Claim Action = 0x02
)
testCases := []struct {
name string
firstCtx sdk.Context
action Action
secondCtx sdk.Context
expectInStorage bool
}{
{
name: "no action with long storage duration",
firstCtx: suite.ctx,
action: NULL,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + bep3.DefaultLongtermStorageDuration),
expectInStorage: true,
},
{
name: "claim with short storage duration",
firstCtx: suite.ctx,
action: Claim,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 5000),
expectInStorage: true,
},
{
name: "claim with long storage duration",
firstCtx: suite.ctx,
action: Claim,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + bep3.DefaultLongtermStorageDuration),
expectInStorage: false,
},
{
name: "refund with short storage duration",
firstCtx: suite.ctx,
action: Refund,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 5000),
expectInStorage: true,
},
{
name: "refund with long storage duration",
firstCtx: suite.ctx,
action: Refund,
secondCtx: suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + bep3.DefaultLongtermStorageDuration),
expectInStorage: false,
},
}
for _, tc := range testCases {
// Reset keeper and run the initial begin blocker
suite.ResetKeeper()
suite.Run(tc.name, func() {
bep3.BeginBlocker(tc.firstCtx, suite.keeper)
switch tc.action {
case Claim:
for i, swapID := range suite.swapIDs {
err := suite.keeper.ClaimAtomicSwap(tc.firstCtx, suite.addrs[5], swapID, suite.randomNumbers[i])
suite.Nil(err)
}
case Refund:
for _, swapID := range suite.swapIDs {
swap, _ := suite.keeper.GetAtomicSwap(tc.firstCtx, swapID)
refundCtx := suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + swap.ExpireHeight)
bep3.BeginBlocker(refundCtx, suite.keeper)
err := suite.keeper.RefundAtomicSwap(refundCtx, suite.addrs[5], swapID)
suite.Nil(err)
// Add expire height to second ctx block height
tc.secondCtx = tc.secondCtx.WithBlockHeight(tc.secondCtx.BlockHeight() + swap.ExpireHeight)
}
}
// Run the second begin blocker
bep3.BeginBlocker(tc.secondCtx, suite.keeper)
// Check each swap's availibility and status
for _, swapID := range suite.swapIDs {
_, found := suite.keeper.GetAtomicSwap(tc.secondCtx, swapID)
if tc.expectInStorage {
suite.True(found)
} else {
suite.False(found)
}
}
})
}
}
func TestABCITestSuite(t *testing.T) {
suite.Run(t, new(ABCITestSuite))
}

141
x/bep3/alias.go Normal file
View File

@ -0,0 +1,141 @@
package bep3
import (
"github.com/kava-labs/kava/x/bep3/client/rest"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
)
const (
AddrByteCount = types.AddrByteCount
AttributeKeyAmount = types.AttributeKeyAmount
AttributeKeyAtomicSwapID = types.AttributeKeyAtomicSwapID
AttributeKeyAtomicSwapIDs = types.AttributeKeyAtomicSwapIDs
AttributeKeyClaimSender = types.AttributeKeyClaimSender
AttributeKeyDirection = types.AttributeKeyDirection
AttributeKeyExpectedIncome = types.AttributeKeyExpectedIncome
AttributeKeyExpireHeight = types.AttributeKeyExpireHeight
AttributeKeyRandomNumber = types.AttributeKeyRandomNumber
AttributeKeyRandomNumberHash = types.AttributeKeyRandomNumberHash
AttributeKeyRecipient = types.AttributeKeyRecipient
AttributeKeyRefundSender = types.AttributeKeyRefundSender
AttributeKeySender = types.AttributeKeySender
AttributeKeySenderOtherChain = types.AttributeKeySenderOtherChain
AttributeKeyTimestamp = types.AttributeKeyTimestamp
AttributeValueCategory = types.AttributeValueCategory
CalcSwapID = types.CalcSwapID
ClaimAtomicSwap = types.ClaimAtomicSwap
Completed = types.Completed
CreateAtomicSwap = types.CreateAtomicSwap
DefaultLongtermStorageDuration = types.DefaultLongtermStorageDuration
DefaultParamspace = types.DefaultParamspace
DepositAtomicSwap = types.DepositAtomicSwap
EventTypeClaimAtomicSwap = types.EventTypeClaimAtomicSwap
EventTypeCreateAtomicSwap = types.EventTypeCreateAtomicSwap
EventTypeRefundAtomicSwap = types.EventTypeRefundAtomicSwap
EventTypeSwapsExpired = types.EventTypeSwapsExpired
Expired = types.Expired
INVALID = types.INVALID
Incoming = types.Incoming
Int64Size = types.Int64Size
MaxExpectedIncomeLength = types.MaxExpectedIncomeLength
MaxOtherChainAddrLength = types.MaxOtherChainAddrLength
ModuleName = types.ModuleName
NULL = types.NULL
Open = types.Open
Outgoing = types.Outgoing
QuerierRoute = types.QuerierRoute
QueryGetAssetSupply = types.QueryGetAssetSupply
QueryGetAtomicSwap = types.QueryGetAtomicSwap
QueryGetAtomicSwaps = types.QueryGetAtomicSwaps
QueryGetParams = types.QueryGetParams
RandomNumberHashLength = types.RandomNumberHashLength
RandomNumberLength = types.RandomNumberLength
RefundAtomicSwap = types.RefundAtomicSwap
RouterKey = types.RouterKey
StoreKey = types.StoreKey
SwapIDLength = types.SwapIDLength
)
var (
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
RegisterRoutes = rest.RegisterRoutes
BytesToHex = types.BytesToHex
CalculateRandomHash = types.CalculateRandomHash
CalculateSwapID = types.CalculateSwapID
DefaultGenesisState = types.DefaultGenesisState
DefaultParams = types.DefaultParams
ErrAssetNotActive = types.ErrAssetNotActive
ErrAssetNotSupported = types.ErrAssetNotSupported
ErrAssetSupplyNotFound = types.ErrAssetSupplyNotFound
ErrAtomicSwapAlreadyExists = types.ErrAtomicSwapAlreadyExists
ErrAtomicSwapNotFound = types.ErrAtomicSwapNotFound
ErrExceedsAvailableSupply = types.ErrExceedsAvailableSupply
ErrExceedsSupplyLimit = types.ErrExceedsSupplyLimit
ErrInvalidClaimSecret = types.ErrInvalidClaimSecret
ErrInvalidCurrentSupply = types.ErrInvalidCurrentSupply
ErrInvalidHeightSpan = types.ErrInvalidHeightSpan
ErrInvalidIncomingSupply = types.ErrInvalidIncomingSupply
ErrInvalidOutgoingSupply = types.ErrInvalidOutgoingSupply
ErrInvalidTimestamp = types.ErrInvalidTimestamp
ErrSwapNotClaimable = types.ErrSwapNotClaimable
ErrSwapNotRefundable = types.ErrSwapNotRefundable
GenerateSecureRandomNumber = types.GenerateSecureRandomNumber
GetAtomicSwapByHeightKey = types.GetAtomicSwapByHeightKey
HexToBytes = types.HexToBytes
NewAssetSupply = types.NewAssetSupply
NewAtomicSwap = types.NewAtomicSwap
NewGenesisState = types.NewGenesisState
NewMsgClaimAtomicSwap = types.NewMsgClaimAtomicSwap
NewMsgCreateAtomicSwap = types.NewMsgCreateAtomicSwap
NewMsgRefundAtomicSwap = types.NewMsgRefundAtomicSwap
NewParams = types.NewParams
NewQueryAssetSupply = types.NewQueryAssetSupply
NewQueryAtomicSwapByID = types.NewQueryAtomicSwapByID
NewQueryAtomicSwaps = types.NewQueryAtomicSwaps
NewSwapDirectionFromString = types.NewSwapDirectionFromString
NewSwapStatusFromString = types.NewSwapStatusFromString
ParamKeyTable = types.ParamKeyTable
RegisterCodec = types.RegisterCodec
Uint64FromBytes = types.Uint64FromBytes
Uint64ToBytes = types.Uint64ToBytes
// variable aliases
AbsoluteMaximumBlockLock = types.AbsoluteMaximumBlockLock
AbsoluteMinimumBlockLock = types.AbsoluteMinimumBlockLock
AssetSupplyKeyPrefix = types.AssetSupplyKeyPrefix
AtomicSwapByBlockPrefix = types.AtomicSwapByBlockPrefix
AtomicSwapCoinsAccAddr = types.AtomicSwapCoinsAccAddr
AtomicSwapKeyPrefix = types.AtomicSwapKeyPrefix
AtomicSwapLongtermStoragePrefix = types.AtomicSwapLongtermStoragePrefix
DefaultMaxBlockLock = types.DefaultMaxBlockLock
DefaultMinBlockLock = types.DefaultMinBlockLock
DefaultSupportedAssets = types.DefaultSupportedAssets
KeyBnbDeputyAddress = types.KeyBnbDeputyAddress
KeyMaxBlockLock = types.KeyMaxBlockLock
KeyMinBlockLock = types.KeyMinBlockLock
KeySupportedAssets = types.KeySupportedAssets
ModuleCdc = types.ModuleCdc
)
type (
Keeper = keeper.Keeper
AssetParam = types.AssetParam
AssetParams = types.AssetParams
AssetSupplies = types.AssetSupplies
AssetSupply = types.AssetSupply
AtomicSwap = types.AtomicSwap
AtomicSwaps = types.AtomicSwaps
GenesisState = types.GenesisState
MsgClaimAtomicSwap = types.MsgClaimAtomicSwap
MsgCreateAtomicSwap = types.MsgCreateAtomicSwap
MsgRefundAtomicSwap = types.MsgRefundAtomicSwap
Params = types.Params
QueryAssetSupply = types.QueryAssetSupply
QueryAtomicSwapByID = types.QueryAtomicSwapByID
QueryAtomicSwaps = types.QueryAtomicSwaps
SupplyKeeper = types.SupplyKeeper
SwapDirection = types.SwapDirection
SwapStatus = types.SwapStatus
)

225
x/bep3/client/cli/query.go Normal file
View File

@ -0,0 +1,225 @@
package cli
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/spf13/cobra"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/x/bep3/types"
)
// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
// Group bep3 queries under a subcommand
bep3QueryCmd := &cobra.Command{
Use: "bep3",
Short: "Querying commands for the bep3 module",
}
bep3QueryCmd.AddCommand(flags.GetCommands(
QueryCalcSwapIDCmd(queryRoute, cdc),
QueryCalcRandomNumberHashCmd(queryRoute, cdc),
QueryGetAtomicSwapCmd(queryRoute, cdc),
QueryGetAssetSupplyCmd(queryRoute, cdc),
QueryGetAtomicSwapsCmd(queryRoute, cdc),
QueryParamsCmd(queryRoute, cdc),
)...)
return bep3QueryCmd
}
// QueryCalcRandomNumberHashCmd calculates the random number hash for a number and timestamp
func QueryCalcRandomNumberHashCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "calc-rnh [unix-timestamp]",
Short: "calculates an example random number hash from an optional timestamp",
Example: "bep3 calc-rnh now",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
userTimestamp := "now"
if len(args) > 0 {
userTimestamp = args[0]
}
// Timestamp defaults to time.Now() unless it's explicitly set
var timestamp int64
if strings.Compare(userTimestamp, "now") == 0 {
timestamp = tmtime.Now().Unix()
} else {
userTimestamp, err := strconv.ParseInt(userTimestamp, 10, 64)
if err != nil {
return err
}
timestamp = userTimestamp
}
// Load hex-encoded cryptographically strong pseudo-random number
randomNumber, err := types.GenerateSecureRandomNumber()
if err != nil {
return err
}
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
// Prepare random number, timestamp, and hash for output
randomNumberStr := fmt.Sprintf("Random number: %s\n", randomNumber)
timestampStr := fmt.Sprintf("Timestamp: %d\n", timestamp)
randomNumberHashStr := fmt.Sprintf("Random number hash: %s", hex.EncodeToString(randomNumberHash))
output := []string{randomNumberStr, timestampStr, randomNumberHashStr}
return cliCtx.PrintOutput(strings.Join(output, ""))
},
}
}
// QueryCalcSwapIDCmd calculates the swapID for a random number hash, sender, and sender other chain
func QueryCalcSwapIDCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "calc-swapid [random-number-hash] [sender] [sender-other-chain]",
Short: "calculate swap ID for the given random number hash, sender, and sender other chain",
Example: "bep3 calc-swapid 0677bd8a303dd981810f34d8e5cc6507f13b391899b84d3c1be6c6045a17d747 kava15qdefkmwswysgg4qxgcqpqr35k3m49pkx2jdfnw bnb1ud3q90r98l3mhd87kswv3h8cgrymzeljct8qn7",
Args: cobra.MinimumNArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Parse query params
randomNumberHash, err := types.HexToBytes(args[0])
if err != nil {
return err
}
sender := sdk.AccAddress(args[1])
senderOtherChain := args[2]
// Calculate swap ID and convert to human-readable string
swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain)
return cliCtx.PrintOutput(hex.EncodeToString(swapID))
},
}
}
// QueryGetAssetSupplyCmd queries as asset's current in swap supply, active, supply, and supply limit
func QueryGetAssetSupplyCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "supply [denom]",
Short: "get information about an asset's supply",
Example: "bep3 supply bnb",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Prepare query params
bz, err := cdc.MarshalJSON(types.NewQueryAssetSupply([]byte(args[0])))
if err != nil {
return err
}
// Execute query
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAssetSupply), bz)
if err != nil {
return err
}
// Decode and print results
var assetSupply types.AssetSupply
cdc.MustUnmarshalJSON(res, &assetSupply)
return cliCtx.PrintOutput(assetSupply)
},
}
}
// QueryGetAtomicSwapCmd queries an AtomicSwap by swapID
func QueryGetAtomicSwapCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "swap [swap-id]",
Short: "get atomic swap information",
Example: "bep3 swap 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Decode swapID's hex encoded string to []byte
swapID, err := types.HexToBytes(args[0])
if err != nil {
return err
}
// Prepare query params
bz, err := cdc.MarshalJSON(types.NewQueryAtomicSwapByID(swapID))
if err != nil {
return err
}
// Execute query
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAtomicSwap), bz)
if err != nil {
return err
}
var atomicSwap types.AtomicSwap
cdc.MustUnmarshalJSON(res, &atomicSwap)
cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(atomicSwap.String())
},
}
}
// QueryGetAtomicSwapsCmd queries AtomicSwaps in the store
func QueryGetAtomicSwapsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "swaps",
Short: "get a list of active atomic swaps",
Example: "bep3 swaps",
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAtomicSwaps), nil)
if err != nil {
return err
}
var atomicSwaps types.AtomicSwaps
cdc.MustUnmarshalJSON(res, &atomicSwaps)
if len(atomicSwaps) == 0 {
return fmt.Errorf("There are currently no atomic swaps")
}
cliCtx = cliCtx.WithHeight(height)
return cliCtx.PrintOutput(atomicSwaps.String())
},
}
}
// QueryParamsCmd queries the bep3 module parameters
func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "params",
Short: "get the bep3 module parameters",
Example: "bep3 params",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
cliCtx := context.NewCLIContext().WithCodec(cdc)
// Query
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetParams)
res, _, err := cliCtx.QueryWithData(route, nil)
if err != nil {
return err
}
// Decode and print results
var out types.Params
cdc.MustUnmarshalJSON(res, &out)
return cliCtx.PrintOutput(out)
},
}
}

185
x/bep3/client/cli/tx.go Normal file
View File

@ -0,0 +1,185 @@
package cli
import (
"bufio"
"encoding/hex"
"fmt"
"strconv"
"strings"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/spf13/cobra"
tmtime "github.com/tendermint/tendermint/types/time"
)
// GetTxCmd returns the transaction commands for this module
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
bep3TxCmd := &cobra.Command{
Use: "bep3",
Short: "bep3 transactions subcommands",
}
bep3TxCmd.AddCommand(flags.PostCommands(
GetCmdCreateAtomicSwap(cdc),
GetCmdClaimAtomicSwap(cdc),
GetCmdRefundAtomicSwap(cdc),
)...)
return bep3TxCmd
}
// GetCmdCreateAtomicSwap cli command for creating atomic swaps
func GetCmdCreateAtomicSwap(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "create [to] [recipient-other-chain] [sender-other-chain] [timestamp] [coins] [expected-income] [height-span] [cross-chain]",
Short: "create a new atomic swap",
Example: fmt.Sprintf("%s tx %s create kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7 bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7 now 100bnb 100bnb 360 true --from validator",
version.ClientName, types.ModuleName),
Args: cobra.ExactArgs(8),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
from := cliCtx.GetFromAddress() // same as KavaExecutor.DeputyAddress (for cross-chain)
to, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
recipientOtherChain := args[1] // same as OtherExecutor.DeputyAddress
senderOtherChain := args[2]
// Timestamp defaults to time.Now() unless it's explicitly set
var timestamp int64
if strings.Compare(args[3], "now") == 0 {
timestamp = tmtime.Now().Unix()
} else {
timestamp, err = strconv.ParseInt(args[3], 10, 64)
if err != nil {
return err
}
}
// Generate cryptographically strong pseudo-random number
randomNumber, err := types.GenerateSecureRandomNumber()
if err != nil {
return err
}
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
// Print random number, timestamp, and hash to user's console
fmt.Printf("\nRandom number: %s\n", hex.EncodeToString(randomNumber.Bytes()))
fmt.Printf("Timestamp: %d\n", timestamp)
fmt.Printf("Random number hash: %s\n\n", hex.EncodeToString(randomNumberHash))
coins, err := sdk.ParseCoins(args[4])
if err != nil {
return err
}
expectedIncome := args[5]
heightSpan, err := strconv.ParseInt(args[6], 10, 64)
if err != nil {
return err
}
crossChain, err := strconv.ParseBool(args[7])
if err != nil {
return err
}
msg := types.NewMsgCreateAtomicSwap(
from, to, recipientOtherChain, senderOtherChain, randomNumberHash,
timestamp, coins, expectedIncome, heightSpan, crossChain,
)
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
// GetCmdClaimAtomicSwap cli command for claiming an atomic swap
func GetCmdClaimAtomicSwap(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "claim [swap-id] [random-number]",
Short: "claim coins in an atomic swap using the secret number",
Example: fmt.Sprintf("%s tx %s claim 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af 56f13e6a5cd397447f8b5f8c82fdb5bbf56127db75269f5cc14e50acd8ac9a4c --from accA", version.ClientName, types.ModuleName),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
from := cliCtx.GetFromAddress()
swapID, err := types.HexToBytes(args[0])
if err != nil {
return err
}
if len(strings.TrimSpace(args[1])) == 0 {
return fmt.Errorf("random-number cannot be empty")
}
randomNumber, err := types.HexToBytes(args[1])
if err != nil {
return err
}
msg := types.NewMsgClaimAtomicSwap(from, swapID, randomNumber)
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}
// GetCmdRefundAtomicSwap cli command for claiming an atomic swap
func GetCmdRefundAtomicSwap(cdc *codec.Codec) *cobra.Command {
return &cobra.Command{
Use: "refund [swap-id]",
Short: "refund the coins in an atomic swap",
Example: fmt.Sprintf("%s tx %s refund 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af --from accA", version.ClientName, types.ModuleName),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc))
from := cliCtx.GetFromAddress()
swapID, err := types.HexToBytes(args[0])
if err != nil {
return err
}
msg := types.NewMsgRefundAtomicSwap(from, swapID)
err = msg.ValidateBasic()
if err != nil {
return err
}
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
},
}
}

163
x/bep3/client/rest/query.go Normal file
View File

@ -0,0 +1,163 @@
package rest
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/kava-labs/kava/x/bep3/types"
)
const restSwapID = "swap-id"
const restDenom = "denom"
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/swap/{%s}", types.ModuleName, restSwapID), queryAtomicSwapHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/swaps", types.ModuleName), queryAtomicSwapsHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/supply/{%s}", types.ModuleName, restDenom), queryAssetSupplyHandlerFn(cliCtx)).Methods("GET")
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
}
func queryAtomicSwapHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse the query height
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
// Prepare params for querier
vars := mux.Vars(r)
if len(vars[restSwapID]) == 0 {
err := fmt.Errorf("%s required but not specified", restSwapID)
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
swapID, err := types.HexToBytes(vars[restSwapID])
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
bz, err := cliCtx.Codec.MarshalJSON(types.QueryAtomicSwapByID{SwapID: swapID})
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// Query
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("/custom/%s/%s", types.ModuleName, types.QueryGetAtomicSwap), bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
// Decode and return results
cliCtx = cliCtx.WithHeight(height)
var swap types.AtomicSwap
err = cliCtx.Codec.UnmarshalJSON(res, &swap)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, cliCtx.Codec.MustMarshalJSON(swap))
}
}
func queryAtomicSwapsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse the query height
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
route := fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetAtomicSwaps)
res, height, err := cliCtx.QueryWithData(route, nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
// Decode and return results
cliCtx = cliCtx.WithHeight(height)
var swaps types.AtomicSwaps
err = cliCtx.Codec.UnmarshalJSON(res, &swaps)
if err != nil {
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
return
}
// using empty slice so json returns [] instead of null when there's no swaps
sliceSwaps := types.AtomicSwaps{}
for _, s := range swaps {
sliceSwaps = append(sliceSwaps, s)
}
rest.PostProcessResponse(w, cliCtx, cliCtx.Codec.MustMarshalJSON(sliceSwaps))
}
}
func queryAssetSupplyHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Parse the query height
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
// Prepare params for querier
vars := mux.Vars(r)
denom := []byte(vars[restDenom])
params := types.NewQueryAssetSupply(denom)
bz, err := cliCtx.Codec.MarshalJSON(params)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// Query
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("/custom/%s/%s", types.ModuleName, types.QueryGetAssetSupply), bz)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
// Decode and return results
cliCtx = cliCtx.WithHeight(height)
var assetSupply types.AssetSupply
err = cliCtx.Codec.UnmarshalJSON(res, &assetSupply)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
rest.PostProcessResponse(w, cliCtx, cliCtx.Codec.MustMarshalJSON(assetSupply))
}
}
func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
if !ok {
return
}
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryGetParams)
res, height, err := cliCtx.QueryWithData(route, nil)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
cliCtx = cliCtx.WithHeight(height)
rest.PostProcessResponse(w, cliCtx, res)
}
}

View File

@ -0,0 +1,47 @@
package rest
import (
"github.com/gorilla/mux"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
)
// RegisterRoutes registers bep3-related REST handlers to a router
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
registerQueryRoutes(cliCtx, r)
registerTxRoutes(cliCtx, r)
}
// PostCreateSwapReq defines the properties of a swap create request's body
type PostCreateSwapReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
From sdk.AccAddress `json:"from" yaml:"from"`
To sdk.AccAddress `json:"to" yaml:"to"`
RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"`
SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"`
RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"`
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
Amount sdk.Coins `json:"amount" yaml:"amount"`
ExpectedIncome string `json:"expected_income" yaml:"expected_income"`
HeightSpan int64 `json:"height_span" yaml:"height_span"`
CrossChain bool `json:"cross_chain" yaml:"cross_chain"`
}
// PostClaimSwapReq defines the properties of a swap claim request's body
type PostClaimSwapReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
From sdk.AccAddress `json:"from" yaml:"from"`
SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"`
RandomNumber tmbytes.HexBytes `json:"random_number" yaml:"random_number"`
}
// PostRefundSwapReq defines the properties of swap refund request's body
type PostRefundSwapReq struct {
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
From sdk.AccAddress `json:"from" yaml:"from"`
SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"`
}

110
x/bep3/client/rest/tx.go Normal file
View File

@ -0,0 +1,110 @@
package rest
import (
"fmt"
"net/http"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
"github.com/gorilla/mux"
"github.com/kava-labs/kava/x/bep3/types"
)
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc(fmt.Sprintf("/%s/swap/create", types.ModuleName), postCreateHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/%s/swap/claim", types.ModuleName), postClaimHandlerFn(cliCtx)).Methods("POST")
r.HandleFunc(fmt.Sprintf("/%s/swap/refund", types.ModuleName), postRefundHandlerFn(cliCtx)).Methods("POST")
}
func postCreateHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Decode PUT request body
var req PostCreateSwapReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}
req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}
// Create and return msg
msg := types.NewMsgCreateAtomicSwap(
req.From,
req.To,
req.RecipientOtherChain,
req.SenderOtherChain,
req.RandomNumberHash,
req.Timestamp,
req.Amount,
req.ExpectedIncome,
req.HeightSpan,
req.CrossChain,
)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}
func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Decode PUT request body
var req PostClaimSwapReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}
req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}
// Create and return msg
msg := types.NewMsgClaimAtomicSwap(
req.From,
req.SwapID,
req.RandomNumber,
)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}
func postRefundHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Decode PUT request body
var req PostRefundSwapReq
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
return
}
req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}
senderAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
// Create and return msg
msg := types.NewMsgRefundAtomicSwap(
senderAddr,
req.SwapID,
)
if err := msg.ValidateBasic(); err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
}
}

122
x/bep3/genesis.go Normal file
View File

@ -0,0 +1,122 @@
package bep3
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// InitGenesis initializes the store state from a genesis state.
func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper SupplyKeeper, gs GenesisState) {
if err := gs.Validate(); err != nil {
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
}
keeper.SetParams(ctx, gs.Params)
// Initialize supported assets
for _, asset := range gs.Params.SupportedAssets {
zeroCoin := sdk.NewCoin(asset.Denom, sdk.NewInt(0))
supply := NewAssetSupply(asset.Denom, zeroCoin, zeroCoin, zeroCoin, sdk.NewCoin(asset.Denom, asset.Limit))
keeper.SetAssetSupply(ctx, supply, []byte(asset.Denom))
}
// Increment an asset's incoming, current, and outgoing supply
// It it required that assets are supported but they do not have to be active
for _, supply := range gs.AssetSupplies {
// Asset must be supported but does not have to be active
coin, found := keeper.GetAssetByDenom(ctx, supply.Denom)
if !found {
panic(fmt.Sprintf("invalid asset supply: %s is not a supported asset", coin.Denom))
}
if !coin.Limit.Equal(supply.Limit.Amount) {
panic(fmt.Sprintf("supported asset limit %s does not equal asset supply %s", coin.Limit, supply.Limit.Amount))
}
// Increment current, incoming, and outgoing asset supplies
err := keeper.IncrementCurrentAssetSupply(ctx, supply.CurrentSupply)
if err != nil {
panic(err)
}
err = keeper.IncrementIncomingAssetSupply(ctx, supply.IncomingSupply)
if err != nil {
panic(err)
}
err = keeper.IncrementOutgoingAssetSupply(ctx, supply.OutgoingSupply)
if err != nil {
panic(err)
}
}
var incomingSupplies sdk.Coins
var outgoingSupplies sdk.Coins
for _, swap := range gs.AtomicSwaps {
if swap.Validate() != nil {
panic(fmt.Sprintf("invalid swap %s", swap.GetSwapID()))
}
// Atomic swap assets must be both supported and active
err := keeper.ValidateLiveAsset(ctx, swap.Amount[0])
if err != nil {
panic(err)
}
keeper.SetAtomicSwap(ctx, swap)
// Add swap to block index or longterm storage based on swap.Status
// Increment incoming or outgoing supply based on swap.Direction
switch swap.Direction {
case Incoming:
switch swap.Status {
case Open:
// This index expires unclaimed swaps
keeper.InsertIntoByBlockIndex(ctx, swap)
incomingSupplies = incomingSupplies.Add(swap.Amount...)
case Expired:
incomingSupplies = incomingSupplies.Add(swap.Amount...)
case Completed:
// This index stores swaps until deletion
keeper.InsertIntoLongtermStorage(ctx, swap)
default:
panic(fmt.Sprintf("swap %s has invalid status %s", swap.GetSwapID(), swap.Status.String()))
}
case Outgoing:
switch swap.Status {
case Open:
keeper.InsertIntoByBlockIndex(ctx, swap)
outgoingSupplies = outgoingSupplies.Add(swap.Amount...)
case Expired:
outgoingSupplies = outgoingSupplies.Add(swap.Amount...)
case Completed:
keeper.InsertIntoLongtermStorage(ctx, swap)
default:
panic(fmt.Sprintf("swap %s has invalid status %s", swap.GetSwapID(), swap.Status.String()))
}
default:
panic(fmt.Sprintf("swap %s has invalid direction %s", swap.GetSwapID(), swap.Direction.String()))
}
}
// Asset's given incoming/outgoing supply much match the amount of coins in incoming/outgoing atomic swaps
supplies := keeper.GetAllAssetSupplies(ctx)
for _, supply := range supplies {
incomingSupply := incomingSupplies.AmountOf(supply.Denom)
if !supply.IncomingSupply.Amount.Equal(incomingSupply) {
panic(fmt.Sprintf("asset's incoming supply %s does not match amount %s in incoming atomic swaps",
supply.IncomingSupply, incomingSupply))
}
outgoingSupply := outgoingSupplies.AmountOf(supply.Denom)
if !supply.OutgoingSupply.Amount.Equal(outgoingSupply) {
panic(fmt.Sprintf("asset's outgoing supply %s does not match amount %s in outgoing atomic swaps",
supply.OutgoingSupply, outgoingSupply))
}
}
}
// ExportGenesis writes the current store values to a genesis file, which can be imported again with InitGenesis
func ExportGenesis(ctx sdk.Context, k Keeper) (data GenesisState) {
params := k.GetParams(ctx)
swaps := k.GetAllAtomicSwaps(ctx)
assets := k.GetAllAssetSupplies(ctx)
return NewGenesisState(params, swaps, assets)
}

298
x/bep3/genesis_test.go Normal file
View File

@ -0,0 +1,298 @@
package bep3_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3"
)
type GenesisTestSuite struct {
suite.Suite
app app.TestApp
ctx sdk.Context
keeper bep3.Keeper
addrs []sdk.AccAddress
}
func (suite *GenesisTestSuite) SetupTest() {
tApp := app.NewTestApp()
suite.ctx = tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
suite.keeper = tApp.GetBep3Keeper()
suite.app = tApp
_, addrs := app.GeneratePrivKeyAddressPairs(3)
suite.addrs = addrs
}
func (suite *GenesisTestSuite) TestGenesisState() {
type GenState func() app.GenesisState
testCases := []struct {
name string
genState GenState
expectPass bool
}{
{
name: "default",
genState: func() app.GenesisState {
return NewBep3GenStateMulti(suite.addrs[0])
},
expectPass: true,
},
{
name: "import atomic swaps and asset supplies",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
_, addrs := app.GeneratePrivKeyAddressPairs(3)
var swaps bep3.AtomicSwaps
var supplies bep3.AssetSupplies
for i := 0; i < 3; i++ {
swap, supply := loadSwapAndSupply(addrs[i], i)
swaps = append(swaps, swap)
supplies = append(supplies, supply)
}
gs.AtomicSwaps = swaps
gs.AssetSupplies = supplies
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: true,
},
{
name: "incoming supply doesn't match amount in incoming atomic swaps",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
_, addrs := app.GeneratePrivKeyAddressPairs(1)
swap, _ := loadSwapAndSupply(addrs[0], 2)
gs.AtomicSwaps = bep3.AtomicSwaps{swap}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "current supply above limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
assetParam, _ := suite.keeper.GetAssetByDenom(suite.ctx, "bnb")
gs.AssetSupplies = bep3.AssetSupplies{
bep3.AssetSupply{
Denom: "bnb",
IncomingSupply: c("bnb", 0),
OutgoingSupply: c("bnb", 0),
CurrentSupply: c("bnb", assetParam.Limit.Add(i(1)).Int64()),
Limit: c("bnb", assetParam.Limit.Int64()),
},
}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "incoming supply above limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
// Set up overlimit amount
assetParam, _ := suite.keeper.GetAssetByDenom(suite.ctx, "bnb")
overLimitAmount := assetParam.Limit.Add(i(1))
// Set up an atomic swap with amount equal to the currently asset supply
_, addrs := app.GeneratePrivKeyAddressPairs(2)
timestamp := ts(0)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
swap := bep3.NewAtomicSwap(cs(c("bnb", overLimitAmount.Int64())), randomNumberHash,
int64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
TestRecipientOtherChain, 0, bep3.Open, true, bep3.Incoming)
gs.AtomicSwaps = bep3.AtomicSwaps{swap}
// Set up asset supply with overlimit current supply
gs.AssetSupplies = bep3.AssetSupplies{
bep3.AssetSupply{
Denom: "bnb",
IncomingSupply: c("bnb", assetParam.Limit.Add(i(1)).Int64()),
OutgoingSupply: c("bnb", 0),
CurrentSupply: c("bnb", 0),
Limit: c("bnb", assetParam.Limit.Int64()),
},
}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "incoming supply + current supply above limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
// Set up overlimit amount
assetParam, _ := suite.keeper.GetAssetByDenom(suite.ctx, "bnb")
halfLimit := assetParam.Limit.Int64() / 2
overHalfLimit := halfLimit + 1
// Set up an atomic swap with amount equal to the currently asset supply
_, addrs := app.GeneratePrivKeyAddressPairs(2)
timestamp := ts(0)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
swap := bep3.NewAtomicSwap(cs(c("bnb", halfLimit)), randomNumberHash,
int64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
TestRecipientOtherChain, 0, bep3.Open, true, bep3.Incoming)
gs.AtomicSwaps = bep3.AtomicSwaps{swap}
// Set up asset supply with overlimit current supply
gs.AssetSupplies = bep3.AssetSupplies{
bep3.AssetSupply{
Denom: "bnb",
IncomingSupply: c("bnb", halfLimit),
OutgoingSupply: c("bnb", 0),
CurrentSupply: c("bnb", overHalfLimit),
Limit: c("bnb", assetParam.Limit.Int64()),
},
}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "asset supply denom is not a supported asset",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.AssetSupplies = bep3.AssetSupplies{
bep3.AssetSupply{
Denom: "fake",
IncomingSupply: c("fake", 0),
OutgoingSupply: c("fake", 0),
CurrentSupply: c("fake", 0),
Limit: c("fake", StandardSupplyLimit.Int64()),
},
}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "atomic swap asset type is unsupported",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
_, addrs := app.GeneratePrivKeyAddressPairs(2)
timestamp := ts(0)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
swap := bep3.NewAtomicSwap(cs(c("fake", 500000)), randomNumberHash,
int64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
TestRecipientOtherChain, 0, bep3.Open, true, bep3.Incoming)
gs.AtomicSwaps = bep3.AtomicSwaps{swap}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "atomic swap status is invalid",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
_, addrs := app.GeneratePrivKeyAddressPairs(2)
timestamp := ts(0)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
swap := bep3.NewAtomicSwap(cs(c("bnb", 5000)), randomNumberHash,
int64(360), timestamp, suite.addrs[0], addrs[1], TestSenderOtherChain,
TestRecipientOtherChain, 0, bep3.NULL, true, bep3.Incoming)
gs.AtomicSwaps = bep3.AtomicSwaps{swap}
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "minimum block lock below limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.MinBlockLock = 1
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "minimum block lock above limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.MinBlockLock = 500000
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "maximum block lock below limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.MaxBlockLock = 1
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "maximum block lock above limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.MaxBlockLock = 100000000
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "empty supported asset denom",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.SupportedAssets[0].Denom = ""
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "negative supported asset limit",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.SupportedAssets[0].Limit = i(-100)
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
{
name: "duplicate supported asset denom",
genState: func() app.GenesisState {
gs := baseGenState(suite.addrs[0])
gs.Params.SupportedAssets[1].Denom = "bnb"
return app.GenesisState{"bep3": bep3.ModuleCdc.MustMarshalJSON(gs)}
},
expectPass: false,
},
}
for _, tc := range testCases {
suite.SetupTest()
if tc.expectPass {
suite.Run(tc.name, func() {
suite.NotPanics(func() {
suite.app.InitializeFromGenesisStates(tc.genState())
})
})
} else {
suite.Run(tc.name, func() {
suite.Panics(func() {
suite.app.InitializeFromGenesisStates(tc.genState())
})
})
}
}
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

87
x/bep3/handler.go Normal file
View File

@ -0,0 +1,87 @@
package bep3
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// NewHandler creates an sdk.Handler for all the bep3 type messages
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
ctx = ctx.WithEventManager(sdk.NewEventManager())
switch msg := msg.(type) {
case MsgCreateAtomicSwap:
return handleMsgCreateAtomicSwap(ctx, k, msg)
case MsgClaimAtomicSwap:
return handleMsgClaimAtomicSwap(ctx, k, msg)
case MsgRefundAtomicSwap:
return handleMsgRefundAtomicSwap(ctx, k, msg)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
}
}
}
// handleMsgCreateAtomicSwap handles requests to create a new AtomicSwap
func handleMsgCreateAtomicSwap(ctx sdk.Context, k Keeper, msg MsgCreateAtomicSwap) (*sdk.Result, error) {
err := k.CreateAtomicSwap(ctx, msg.RandomNumberHash, msg.Timestamp, msg.HeightSpan, msg.From, msg.To,
msg.SenderOtherChain, msg.RecipientOtherChain, msg.Amount, msg.ExpectedIncome, msg.CrossChain)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.From.String()),
),
)
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
// handleMsgClaimAtomicSwap handles requests to claim funds in an active AtomicSwap
func handleMsgClaimAtomicSwap(ctx sdk.Context, k Keeper, msg MsgClaimAtomicSwap) (*sdk.Result, error) {
err := k.ClaimAtomicSwap(ctx, msg.From, msg.SwapID, msg.RandomNumber)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.From.String()),
),
)
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}
// handleMsgRefundAtomicSwap handles requests to refund an active AtomicSwap
func handleMsgRefundAtomicSwap(ctx sdk.Context, k Keeper, msg MsgRefundAtomicSwap) (*sdk.Result, error) {
err := k.RefundAtomicSwap(ctx, msg.From, msg.SwapID)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
sdk.EventTypeMessage,
sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory),
sdk.NewAttribute(sdk.AttributeKeySender, msg.From.String()),
),
)
return &sdk.Result{
Events: ctx.EventManager().Events(),
}, nil
}

134
x/bep3/handler_test.go Normal file
View File

@ -0,0 +1,134 @@
package bep3_test
import (
"testing"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtime "github.com/tendermint/tendermint/types/time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3"
)
type HandlerTestSuite struct {
suite.Suite
ctx sdk.Context
app app.TestApp
handler sdk.Handler
keeper bep3.Keeper
addrs []sdk.AccAddress
}
func (suite *HandlerTestSuite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
keeper := tApp.GetBep3Keeper()
// Set up genesis state and initialize
_, addrs := app.GeneratePrivKeyAddressPairs(3)
coins := []sdk.Coins{}
for j := 0; j < 3; j++ {
coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000)))
}
authGS := app.NewAuthGenState(addrs, coins)
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(addrs[0]))
suite.addrs = addrs
suite.handler = bep3.NewHandler(keeper)
suite.keeper = keeper
suite.app = tApp
suite.ctx = ctx
}
func (suite *HandlerTestSuite) AddAtomicSwap() (tmbytes.HexBytes, tmbytes.HexBytes) {
expireHeight := int64(360)
amount := cs(c("bnb", int64(50000)))
timestamp := ts(0)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
// Create atomic swap and check err to confirm creation
err := suite.keeper.CreateAtomicSwap(suite.ctx, randomNumberHash, timestamp, expireHeight,
suite.addrs[0], suite.addrs[1], TestSenderOtherChain, TestRecipientOtherChain,
amount, amount.String(), true)
suite.Nil(err)
swapID := bep3.CalculateSwapID(randomNumberHash, suite.addrs[0], TestSenderOtherChain)
return swapID, randomNumber.Bytes()
}
func (suite *HandlerTestSuite) TestMsgCreateAtomicSwap() {
amount := cs(c("bnb", int64(10000)))
timestamp := ts(0)
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
msg := bep3.NewMsgCreateAtomicSwap(
suite.addrs[0], suite.addrs[2], TestRecipientOtherChain, TestSenderOtherChain,
randomNumberHash, timestamp, amount, amount.String(), int64(300), true)
res, err := suite.handler(suite.ctx, msg)
suite.Require().NoError(err)
suite.Require().NotNil(res)
}
func (suite *HandlerTestSuite) TestMsgClaimAtomicSwap() {
// Attempt claim msg on fake atomic swap
badRandomNumber, _ := bep3.GenerateSecureRandomNumber()
badRandomNumberHash := bep3.CalculateRandomHash(badRandomNumber.Bytes(), ts(0))
badSwapID := bep3.CalculateSwapID(badRandomNumberHash, suite.addrs[0], TestSenderOtherChain)
badMsg := bep3.NewMsgClaimAtomicSwap(suite.addrs[0], badSwapID, badRandomNumber.Bytes())
badRes, err := suite.handler(suite.ctx, badMsg)
suite.Require().Error(err)
suite.Require().Nil(badRes)
// Add an atomic swap before attempting new claim msg
swapID, randomNumber := suite.AddAtomicSwap()
msg := bep3.NewMsgClaimAtomicSwap(suite.addrs[0], swapID, randomNumber)
res, err := suite.handler(suite.ctx, msg)
suite.Require().NoError(err)
suite.Require().NotNil(res)
}
func (suite *HandlerTestSuite) TestMsgRefundAtomicSwap() {
// Attempt refund msg on fake atomic swap
badRandomNumber, _ := bep3.GenerateSecureRandomNumber()
badRandomNumberHash := bep3.CalculateRandomHash(badRandomNumber.Bytes(), ts(0))
badSwapID := bep3.CalculateSwapID(badRandomNumberHash, suite.addrs[0], TestSenderOtherChain)
badMsg := bep3.NewMsgRefundAtomicSwap(suite.addrs[0], badSwapID)
badRes, err := suite.handler(suite.ctx, badMsg)
suite.Require().Error(err)
suite.Require().Nil(badRes)
// Add an atomic swap and build refund msg
swapID, _ := suite.AddAtomicSwap()
msg := bep3.NewMsgRefundAtomicSwap(suite.addrs[0], swapID)
// Attempt to refund active atomic swap
res1, err := suite.handler(suite.ctx, msg)
suite.Require().Error(err)
suite.Require().Nil(res1)
// Expire the atomic swap with begin blocker and attempt refund
laterCtx := suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400)
bep3.BeginBlocker(laterCtx, suite.keeper)
res2, err := suite.handler(laterCtx, msg)
suite.Require().NoError(err)
suite.Require().NotNil(res2)
}
func (suite *HandlerTestSuite) TestInvalidMsg() {
res, err := suite.handler(suite.ctx, sdk.NewTestMsg())
suite.Require().Error(err)
suite.Require().Nil(res)
}
func TestHandlerTestSuite(t *testing.T) {
suite.Run(t, new(HandlerTestSuite))
}

View File

@ -0,0 +1,87 @@
package bep3_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3"
)
const (
TestSenderOtherChain = "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7"
TestRecipientOtherChain = "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7"
TestDeputy = "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj"
TestUser = "kava1vry5lhegzlulehuutcr7nmdlmktw88awp0a39p"
)
var (
StandardSupplyLimit = i(100000000000)
DenomMap = map[int]string{0: "btc", 1: "eth", 2: "bnb", 3: "xrp", 4: "dai"}
)
func i(in int64) sdk.Int { return sdk.NewInt(in) }
func d(de int64) sdk.Dec { return sdk.NewDec(de) }
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
func ts(minOffset int) int64 { return tmtime.Now().Add(time.Duration(minOffset) * time.Minute).Unix() }
func NewBep3GenStateMulti(deputy sdk.AccAddress) app.GenesisState {
bep3Genesis := baseGenState(deputy)
return app.GenesisState{bep3.ModuleName: bep3.ModuleCdc.MustMarshalJSON(bep3Genesis)}
}
func baseGenState(deputy sdk.AccAddress) bep3.GenesisState {
bep3Genesis := bep3.GenesisState{
Params: bep3.Params{
BnbDeputyAddress: deputy,
MinBlockLock: bep3.DefaultMinBlockLock, // 80
MaxBlockLock: bep3.DefaultMaxBlockLock, // 360
SupportedAssets: bep3.AssetParams{
bep3.AssetParam{
Denom: "btc",
CoinID: 714,
Limit: StandardSupplyLimit,
Active: true,
},
bep3.AssetParam{
Denom: "eth",
CoinID: 999999,
Limit: StandardSupplyLimit,
Active: true,
},
bep3.AssetParam{
Denom: "bnb",
CoinID: 99999,
Limit: StandardSupplyLimit,
Active: true,
},
bep3.AssetParam{
Denom: "inc",
CoinID: 9999,
Limit: i(100),
Active: false,
},
},
},
}
return bep3Genesis
}
func loadSwapAndSupply(addr sdk.AccAddress, index int) (bep3.AtomicSwap, bep3.AssetSupply) {
coin := c(DenomMap[index], 50000)
expireOffset := int64((index * 15) + 360) // Default expire height + offet to match timestamp
timestamp := ts(index) // One minute apart
randomNumber, _ := bep3.GenerateSecureRandomNumber()
randomNumberHash := bep3.CalculateRandomHash(randomNumber.Bytes(), timestamp)
swap := bep3.NewAtomicSwap(cs(coin), randomNumberHash,
expireOffset, timestamp, addr, addr, TestSenderOtherChain,
TestRecipientOtherChain, 0, bep3.Open, true, bep3.Incoming)
supply := bep3.NewAssetSupply(coin.Denom, coin, c(coin.Denom, 0),
c(coin.Denom, 0), c(coin.Denom, StandardSupplyLimit.Int64()))
return swap, supply
}

115
x/bep3/keeper/asset.go Normal file
View File

@ -0,0 +1,115 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/bep3/types"
)
// IncrementCurrentAssetSupply increments an asset's supply by the coin
func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Resulting current supply must be under asset's limit
if !supply.Limit.IsGTE(supply.CurrentSupply.Add(coin)) {
return sdkerrors.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, supply.CurrentSupply, supply.Limit)
}
supply.CurrentSupply = supply.CurrentSupply.Add(coin)
k.SetAssetSupply(ctx, supply, []byte(coin.Denom))
return nil
}
// DecrementCurrentAssetSupply decrement an asset's supply by the coin
func (k Keeper) DecrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Resulting current supply must be greater than or equal to 0
// Use sdk.Int instead of sdk.Coin to prevent panic if true
if supply.CurrentSupply.Amount.Sub(coin.Amount).IsNegative() {
return sdkerrors.Wrapf(types.ErrInvalidCurrentSupply, "decrease %s, asset supply %s", coin, supply.CurrentSupply)
}
supply.CurrentSupply = supply.CurrentSupply.Sub(coin)
k.SetAssetSupply(ctx, supply, []byte(coin.Denom))
return nil
}
// IncrementIncomingAssetSupply increments an asset's incoming supply
func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Result of (current + incoming + amount) must be under asset's limit
totalSupply := supply.CurrentSupply.Add(supply.IncomingSupply)
if !supply.Limit.IsGTE(totalSupply.Add(coin)) {
return sdkerrors.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, totalSupply, supply.Limit)
}
supply.IncomingSupply = supply.IncomingSupply.Add(coin)
k.SetAssetSupply(ctx, supply, []byte(coin.Denom))
return nil
}
// DecrementIncomingAssetSupply decrements an asset's incoming supply
func (k Keeper) DecrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Resulting incoming supply must be greater than or equal to 0
// Use sdk.Int instead of sdk.Coin to prevent panic if true
if supply.IncomingSupply.Amount.Sub(coin.Amount).IsNegative() {
return sdkerrors.Wrapf(types.ErrInvalidIncomingSupply, "decrease %s, incoming supply %s", coin, supply.IncomingSupply)
}
supply.IncomingSupply = supply.IncomingSupply.Sub(coin)
k.SetAssetSupply(ctx, supply, []byte(coin.Denom))
return nil
}
// IncrementOutgoingAssetSupply increments an asset's outoing supply
func (k Keeper) IncrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Result of (outgoing + amount) must be less than current supply
if !supply.CurrentSupply.IsGTE(supply.OutgoingSupply.Add(coin)) {
return sdkerrors.Wrapf(types.ErrExceedsAvailableSupply, "swap amount %s, available supply %s", coin,
supply.CurrentSupply.Amount.Sub(supply.OutgoingSupply.Amount))
}
supply.OutgoingSupply = supply.OutgoingSupply.Add(coin)
k.SetAssetSupply(ctx, supply, []byte(coin.Denom))
return nil
}
// DecrementOutgoingAssetSupply decrements an asset's outoing supply
func (k Keeper) DecrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
// Resulting outgoing supply must be greater than or equal to 0
// Use sdk.Int instead of sdk.Coin to prevent panic if true
if supply.OutgoingSupply.Amount.Sub(coin.Amount).IsNegative() {
return sdkerrors.Wrapf(types.ErrInvalidOutgoingSupply, "decrease %s, outgoing supply %s", coin, supply.OutgoingSupply)
}
supply.OutgoingSupply = supply.OutgoingSupply.Sub(coin)
k.SetAssetSupply(ctx, supply, []byte(coin.Denom))
return nil
}

415
x/bep3/keeper/asset_test.go Normal file
View File

@ -0,0 +1,415 @@
package keeper_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
type AssetTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
}
func (suite *AssetTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
// Initialize test app and set context
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
// Initialize genesis state
deputy, _ := sdk.AccAddressFromBech32(TestDeputy)
tApp.InitializeFromGenesisStates(NewBep3GenStateMulti(deputy))
keeper := tApp.GetBep3Keeper()
// Set asset supply with standard value for testing
supply := types.AssetSupply{
Denom: "bnb",
IncomingSupply: c("bnb", 5),
OutgoingSupply: c("bnb", 5),
CurrentSupply: c("bnb", 40),
Limit: c("bnb", 50),
}
keeper.SetAssetSupply(ctx, supply, []byte(supply.Denom))
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
return
}
func (suite *AssetTestSuite) TestIncrementCurrentAssetSupply() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 5),
},
true,
},
{
"equal limit",
args{
coin: c("bnb", 10),
},
true,
},
{
"exceeds limit",
args{
coin: c("bnb", 11),
},
false,
},
{
"unsupported asset",
args{
coin: c("xyz", 5),
},
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
supplyKeyPrefix := []byte(tc.args.coin.Denom)
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coin)
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
if tc.expectPass {
suite.True(found)
suite.NoError(err)
suite.Equal(preSupply.CurrentSupply.Add(tc.args.coin), postSupply.CurrentSupply)
} else {
suite.Error(err)
suite.Equal(preSupply.CurrentSupply, postSupply.CurrentSupply)
}
})
}
}
func (suite *AssetTestSuite) TestDecrementCurrentAssetSupply() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 30),
},
true,
},
{
"equal current",
args{
coin: c("bnb", 40),
},
true,
},
{
"exceeds current",
args{
coin: c("bnb", 41),
},
false,
},
{
"unsupported asset",
args{
coin: c("xyz", 30),
},
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
supplyKeyPrefix := []byte(tc.args.coin.Denom)
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
err := suite.keeper.DecrementCurrentAssetSupply(suite.ctx, tc.args.coin)
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
if tc.expectPass {
suite.True(found)
suite.NoError(err)
suite.True(preSupply.CurrentSupply.Sub(tc.args.coin).IsEqual(postSupply.CurrentSupply))
} else {
suite.Error(err)
suite.Equal(preSupply.CurrentSupply, postSupply.CurrentSupply)
}
})
}
}
func (suite *AssetTestSuite) TestIncrementIncomingAssetSupply() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 2),
},
true,
},
{
"incoming + current = limit",
args{
coin: c("bnb", 5),
},
true,
},
{
"incoming + current > limit",
args{
coin: c("bnb", 6),
},
false,
},
{
"unsupported asset",
args{
coin: c("xyz", 2),
},
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
supplyKeyPrefix := []byte(tc.args.coin.Denom)
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
err := suite.keeper.IncrementIncomingAssetSupply(suite.ctx, tc.args.coin)
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
if tc.expectPass {
suite.True(found)
suite.NoError(err)
suite.Equal(preSupply.IncomingSupply.Add(tc.args.coin), postSupply.IncomingSupply)
} else {
suite.Error(err)
suite.Equal(preSupply.IncomingSupply, postSupply.IncomingSupply)
}
})
}
}
func (suite *AssetTestSuite) TestDecrementIncomingAssetSupply() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 4),
},
true,
},
{
"equal incoming",
args{
coin: c("bnb", 5),
},
true,
},
{
"exceeds incoming",
args{
coin: c("bnb", 6),
},
false,
},
{
"unsupported asset",
args{
coin: c("xyz", 4),
},
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
supplyKeyPrefix := []byte(tc.args.coin.Denom)
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
err := suite.keeper.DecrementIncomingAssetSupply(suite.ctx, tc.args.coin)
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
if tc.expectPass {
suite.True(found)
suite.NoError(err)
suite.True(preSupply.IncomingSupply.Sub(tc.args.coin).IsEqual(postSupply.IncomingSupply))
} else {
suite.Error(err)
suite.Equal(preSupply.IncomingSupply, postSupply.IncomingSupply)
}
})
}
}
func (suite *AssetTestSuite) TestIncrementOutgoingAssetSupply() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 30),
},
true,
},
{
"outgoing + amount = current",
args{
coin: c("bnb", 35),
},
true,
},
{
"outoing + amount > current",
args{
coin: c("bnb", 36),
},
false,
},
{
"unsupported asset",
args{
coin: c("xyz", 30),
},
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
supplyKeyPrefix := []byte(tc.args.coin.Denom)
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
err := suite.keeper.IncrementOutgoingAssetSupply(suite.ctx, tc.args.coin)
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
if tc.expectPass {
suite.True(found)
suite.NoError(err)
suite.Equal(preSupply.OutgoingSupply.Add(tc.args.coin), postSupply.OutgoingSupply)
} else {
suite.Error(err)
suite.Equal(preSupply.OutgoingSupply, postSupply.OutgoingSupply)
}
})
}
}
func (suite *AssetTestSuite) TestDecrementOutgoingAssetSupply() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 4),
},
true,
},
{
"equal outgoing",
args{
coin: c("bnb", 5),
},
true,
},
{
"exceeds outgoing",
args{
coin: c("bnb", 6),
},
false,
},
{
"unsupported asset",
args{
coin: c("xyz", 4),
},
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
supplyKeyPrefix := []byte(tc.args.coin.Denom)
preSupply, found := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
err := suite.keeper.DecrementOutgoingAssetSupply(suite.ctx, tc.args.coin)
postSupply, _ := suite.keeper.GetAssetSupply(suite.ctx, supplyKeyPrefix)
if tc.expectPass {
suite.True(found)
suite.NoError(err)
suite.True(preSupply.OutgoingSupply.Sub(tc.args.coin).IsEqual(postSupply.OutgoingSupply))
} else {
suite.Error(err)
suite.Equal(preSupply.OutgoingSupply, postSupply.OutgoingSupply)
}
})
}
}
func TestAssetTestSuite(t *testing.T) {
suite.Run(t, new(AssetTestSuite))
}

View File

@ -0,0 +1,94 @@
package keeper_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/tendermint/tendermint/crypto"
tmtime "github.com/tendermint/tendermint/types/time"
)
const (
TestSenderOtherChain = "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7"
TestRecipientOtherChain = "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7"
TestDeputy = "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj"
)
var (
StandardSupplyLimit = i(100000000000)
DenomMap = map[int]string{0: "btc", 1: "eth", 2: "bnb", 3: "xrp", 4: "dai"}
TestUser1 = sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser1")))
TestUser2 = sdk.AccAddress(crypto.AddressHash([]byte("KavaTestUser2")))
)
func i(in int64) sdk.Int { return sdk.NewInt(in) }
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
func ts(minOffset int) int64 { return tmtime.Now().Add(time.Duration(minOffset) * time.Minute).Unix() }
func NewBep3GenStateMulti(deputy sdk.AccAddress) app.GenesisState {
bep3Genesis := types.GenesisState{
Params: bep3.Params{
BnbDeputyAddress: deputy,
MinBlockLock: types.DefaultMinBlockLock, // 80
MaxBlockLock: types.DefaultMaxBlockLock, // 360
SupportedAssets: types.AssetParams{
types.AssetParam{
Denom: "bnb",
CoinID: 714,
Limit: StandardSupplyLimit,
Active: true,
},
types.AssetParam{
Denom: "inc",
CoinID: 9999,
Limit: i(100),
Active: false,
},
},
},
}
return app.GenesisState{bep3.ModuleName: bep3.ModuleCdc.MustMarshalJSON(bep3Genesis)}
}
func atomicSwaps(ctx sdk.Context, count int) types.AtomicSwaps {
var swaps types.AtomicSwaps
for i := 0; i < count; i++ {
swap := atomicSwap(ctx, i)
swaps = append(swaps, swap)
}
return swaps
}
func atomicSwap(ctx sdk.Context, index int) types.AtomicSwap {
expireOffset := int64((index * 15) + 360) // Default expire height + offet to match timestamp
timestamp := ts(index) // One minute apart
randomNumber, _ := types.GenerateSecureRandomNumber()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
return types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash,
ctx.BlockHeight()+expireOffset, timestamp, TestUser1, TestUser2,
TestSenderOtherChain, TestRecipientOtherChain, 0, types.Open, true,
types.Incoming)
}
func assetSupplies(count int) types.AssetSupplies {
if count > 5 { // Max 5 asset supplies
return types.AssetSupplies{}
}
var supplies types.AssetSupplies
for i := 0; i < count; i++ {
supply := assetSupply(DenomMap[i])
supplies = append(supplies, supply)
}
return supplies
}
func assetSupply(denom string) types.AssetSupply {
return types.NewAssetSupply(denom, c(denom, 0), c(denom, 0), c(denom, 0), c(denom, 10000))
}

226
x/bep3/keeper/keeper.go Normal file
View File

@ -0,0 +1,226 @@
package keeper
import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params/subspace"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/tendermint/tendermint/libs/log"
)
// Keeper of the bep3 store
type Keeper struct {
key sdk.StoreKey
cdc *codec.Codec
paramSubspace subspace.Subspace
supplyKeeper types.SupplyKeeper
}
// 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))
}
if !paramstore.HasKeyTable() {
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
}
keeper := Keeper{
key: key,
cdc: cdc,
paramSubspace: paramstore,
supplyKeeper: sk,
}
return keeper
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// ------------------------------------------
// Atomic Swaps
// ------------------------------------------
// SetAtomicSwap puts the AtomicSwap into the store, and updates any indexes.
func (k Keeper) SetAtomicSwap(ctx sdk.Context, atomicSwap types.AtomicSwap) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(atomicSwap)
store.Set(atomicSwap.GetSwapID(), bz)
}
// GetAtomicSwap gets an AtomicSwap from the store.
func (k Keeper) GetAtomicSwap(ctx sdk.Context, swapID []byte) (types.AtomicSwap, bool) {
var atomicSwap types.AtomicSwap
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
bz := store.Get(swapID)
if bz == nil {
return atomicSwap, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &atomicSwap)
return atomicSwap, true
}
// RemoveAtomicSwap removes an AtomicSwap from the AtomicSwapKeyPrefix.
func (k Keeper) RemoveAtomicSwap(ctx sdk.Context, swapID []byte) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
store.Delete(swapID)
}
// IterateAtomicSwaps provides an iterator over all stored AtomicSwaps.
// For each AtomicSwap, cb will be called. If cb returns true, the iterator will close and stop.
func (k Keeper) IterateAtomicSwaps(ctx sdk.Context, cb func(atomicSwap types.AtomicSwap) (stop bool)) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.AtomicSwapKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var atomicSwap types.AtomicSwap
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &atomicSwap)
if cb(atomicSwap) {
break
}
}
}
// GetAllAtomicSwaps returns all AtomicSwaps from the store
func (k Keeper) GetAllAtomicSwaps(ctx sdk.Context) (atomicSwaps types.AtomicSwaps) {
k.IterateAtomicSwaps(ctx, func(atomicSwap types.AtomicSwap) bool {
atomicSwaps = append(atomicSwaps, atomicSwap)
return false
})
return
}
// ------------------------------------------
// Atomic Swap Block Index
// ------------------------------------------
// InsertIntoByBlockIndex adds a swap ID and expiration time into the byBlock index.
func (k Keeper) InsertIntoByBlockIndex(ctx sdk.Context, atomicSwap types.AtomicSwap) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapByBlockPrefix)
store.Set(types.GetAtomicSwapByHeightKey(atomicSwap.ExpireHeight, atomicSwap.GetSwapID()), atomicSwap.GetSwapID())
}
// RemoveFromByBlockIndex removes an AtomicSwap from the byBlock index.
func (k Keeper) RemoveFromByBlockIndex(ctx sdk.Context, atomicSwap types.AtomicSwap) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapByBlockPrefix)
store.Delete(types.GetAtomicSwapByHeightKey(atomicSwap.ExpireHeight, atomicSwap.GetSwapID()))
}
// IterateAtomicSwapsByBlock provides an iterator over AtomicSwaps ordered by AtomicSwap expiration block
// For each AtomicSwap cb will be called. If cb returns true the iterator will close and stop.
func (k Keeper) IterateAtomicSwapsByBlock(ctx sdk.Context, inclusiveCutoffTime uint64, cb func(swapID []byte) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapByBlockPrefix)
iterator := store.Iterator(
nil, // start at the very start of the prefix store
sdk.PrefixEndBytes(types.Uint64ToBytes(inclusiveCutoffTime)), // end of range
)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
id := iterator.Value()
if cb(id) {
break
}
}
}
// ------------------------------------------
// Atomic Swap Longterm Storage Index
// ------------------------------------------
// InsertIntoLongtermStorage adds a swap ID and deletion time into the longterm storage index.
// Completed swaps are stored for 1 week.
func (k Keeper) InsertIntoLongtermStorage(ctx sdk.Context, atomicSwap types.AtomicSwap) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapLongtermStoragePrefix)
store.Set(types.GetAtomicSwapByHeightKey(atomicSwap.ClosedBlock+types.DefaultLongtermStorageDuration,
atomicSwap.GetSwapID()), atomicSwap.GetSwapID())
}
// RemoveFromLongtermStorage removes a swap from the into the longterm storage index
func (k Keeper) RemoveFromLongtermStorage(ctx sdk.Context, atomicSwap types.AtomicSwap) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapLongtermStoragePrefix)
store.Delete(types.GetAtomicSwapByHeightKey(atomicSwap.ClosedBlock+types.DefaultLongtermStorageDuration,
atomicSwap.GetSwapID()))
}
// IterateAtomicSwapsLongtermStorage provides an iterator over AtomicSwaps ordered by deletion height.
// For each AtomicSwap cb will be called. If cb returns true the iterator will close and stop.
func (k Keeper) IterateAtomicSwapsLongtermStorage(ctx sdk.Context, inclusiveCutoffTime uint64,
cb func(swapID []byte) (stop bool)) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AtomicSwapLongtermStoragePrefix)
iterator := store.Iterator(
nil, // start at the very start of the prefix store
sdk.PrefixEndBytes(types.Uint64ToBytes(inclusiveCutoffTime)), // end of range
)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
id := iterator.Value()
if cb(id) {
break
}
}
}
// ------------------------------------------
// Asset Supplies
// ------------------------------------------
// GetAssetSupply gets an asset's current supply from the store.
func (k Keeper) GetAssetSupply(ctx sdk.Context, denom []byte) (types.AssetSupply, bool) {
var supply types.AssetSupply
store := prefix.NewStore(ctx.KVStore(k.key), types.AssetSupplyKeyPrefix)
bz := store.Get(denom)
if bz == nil {
return types.AssetSupply{}, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &supply)
return supply, true
}
// SetAssetSupply updates an asset's current active supply
func (k Keeper) SetAssetSupply(ctx sdk.Context, supply types.AssetSupply, denom []byte) {
store := prefix.NewStore(ctx.KVStore(k.key), types.AssetSupplyKeyPrefix)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(supply)
store.Set(denom, bz)
}
// IterateAssetSupplies provides an iterator over current asset supplies.
// For each asset supply, cb will be called. If cb returns true, the iterator will close and stop.
func (k Keeper) IterateAssetSupplies(ctx sdk.Context, cb func(supply types.AssetSupply) (stop bool)) {
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.key), types.AssetSupplyKeyPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var supply types.AssetSupply
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &supply)
if cb(supply) {
break
}
}
}
// GetAllAssetSupplies returns current asset supplies from the store as an array of sdk.Coin
func (k Keeper) GetAllAssetSupplies(ctx sdk.Context) (supplies types.AssetSupplies) {
k.IterateAssetSupplies(ctx, func(supply types.AssetSupply) bool {
supplies = append(supplies, supply)
return false
})
return
}

View File

@ -0,0 +1,370 @@
package keeper_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
const LongtermStorageDuration = 86400
type KeeperTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
}
func (suite *KeeperTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
suite.ResetChain()
return
}
func (suite *KeeperTestSuite) ResetChain() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
keeper := tApp.GetBep3Keeper()
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
}
func (suite *KeeperTestSuite) TestGetSetAtomicSwap() {
suite.ResetChain()
// Set new atomic swap
atomicSwap := atomicSwap(suite.ctx, 1)
suite.keeper.SetAtomicSwap(suite.ctx, atomicSwap)
// Check atomic swap in store
s, found := suite.keeper.GetAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
suite.True(found)
suite.Equal(atomicSwap, s)
// Check fake atomic swap not in store
fakeSwapID := types.CalculateSwapID(atomicSwap.RandomNumberHash, TestUser2, "otheraddress")
_, found = suite.keeper.GetAtomicSwap(suite.ctx, fakeSwapID)
suite.False(found)
}
func (suite *KeeperTestSuite) TestRemoveAtomicSwap() {
suite.ResetChain()
// Set new atomic swap
atomicSwap := atomicSwap(suite.ctx, 1)
suite.keeper.SetAtomicSwap(suite.ctx, atomicSwap)
// Check atomic swap in store
s, found := suite.keeper.GetAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
suite.True(found)
suite.Equal(atomicSwap, s)
suite.keeper.RemoveAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
// Check atomic swap not in store
_, found = suite.keeper.GetAtomicSwap(suite.ctx, atomicSwap.GetSwapID())
suite.False(found)
}
func (suite *KeeperTestSuite) TestIterateAtomicSwaps() {
suite.ResetChain()
// Set atomic swaps
atomicSwaps := atomicSwaps(suite.ctx, 4)
for _, s := range atomicSwaps {
suite.keeper.SetAtomicSwap(suite.ctx, s)
}
// Read each atomic swap from the store
var readAtomicSwaps types.AtomicSwaps
suite.keeper.IterateAtomicSwaps(suite.ctx, func(a types.AtomicSwap) bool {
readAtomicSwaps = append(readAtomicSwaps, a)
return false
})
// Check expected values
suite.Equal(len(atomicSwaps), len(readAtomicSwaps))
}
func (suite *KeeperTestSuite) TestGetAllAtomicSwaps() {
suite.ResetChain()
// Set atomic swaps
atomicSwaps := atomicSwaps(suite.ctx, 4)
for _, s := range atomicSwaps {
suite.keeper.SetAtomicSwap(suite.ctx, s)
}
// Get and check atomic swaps
res := suite.keeper.GetAllAtomicSwaps(suite.ctx)
suite.Equal(4, len(res))
}
func (suite *KeeperTestSuite) TestInsertIntoByBlockIndex() {
suite.ResetChain()
// Set new atomic swap in by block index
atomicSwap := atomicSwap(suite.ctx, 1)
suite.keeper.InsertIntoByBlockIndex(suite.ctx, atomicSwap)
// Block index lacks getter methods, must use iteration to get count of swaps in store
var swapIDs [][]byte
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, uint64(atomicSwap.ExpireHeight+1), func(id []byte) bool {
swapIDs = append(swapIDs, id)
return false
})
suite.Equal(len(swapIDs), 1)
// Marshal the expected swapID
cdc := suite.app.Codec()
res, _ := cdc.MarshalBinaryBare(atomicSwap.GetSwapID())
expectedSwapID := res[1:]
suite.Equal(expectedSwapID, swapIDs[0])
}
func (suite *KeeperTestSuite) TestRemoveFromByBlockIndex() {
suite.ResetChain()
// Set new atomic swap in by block index
atomicSwap := atomicSwap(suite.ctx, 1)
suite.keeper.InsertIntoByBlockIndex(suite.ctx, atomicSwap)
// Check stored data in block index
var swapIDsPre [][]byte
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, uint64(atomicSwap.ExpireHeight+1), func(id []byte) bool {
swapIDsPre = append(swapIDsPre, id)
return false
})
suite.Equal(len(swapIDsPre), 1)
suite.keeper.RemoveFromByBlockIndex(suite.ctx, atomicSwap)
// Check stored data not in block index
var swapIDsPost [][]byte
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, uint64(atomicSwap.ExpireHeight+1), func(id []byte) bool {
swapIDsPost = append(swapIDsPost, id)
return false
})
suite.Equal(len(swapIDsPost), 0)
}
func (suite *KeeperTestSuite) TestIterateAtomicSwapsByBlock() {
suite.ResetChain()
type args struct {
blockCtx sdk.Context
swap types.AtomicSwap
}
var testCases []args
for i := 0; i < 8; i++ {
// Set up context 100 blocks apart
blockCtx := suite.ctx.WithBlockHeight(int64(i) * 100)
// Initialize a new atomic swap (different randomNumberHash = different swap IDs)
timestamp := tmtime.Now().Add(time.Duration(i) * time.Minute).Unix()
randomNumber, _ := types.GenerateSecureRandomNumber()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
atomicSwap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash,
blockCtx.BlockHeight(), timestamp, TestUser1, TestUser2,
TestSenderOtherChain, TestRecipientOtherChain, 0, types.Open,
true, types.Incoming)
// Insert into block index
suite.keeper.InsertIntoByBlockIndex(blockCtx, atomicSwap)
// Add to local block index
testCases = append(testCases, args{blockCtx, atomicSwap})
}
// Set up the expected swap IDs for a given cutoff block
cutoffBlock := int64(450)
var expectedSwapIDs [][]byte
for _, tc := range testCases {
if tc.blockCtx.BlockHeight() < cutoffBlock || tc.blockCtx.BlockHeight() == cutoffBlock {
expectedSwapIDs = append(expectedSwapIDs, tc.swap.GetSwapID())
}
}
// Read the swap IDs from store for a given cutoff block
var readSwapIDs [][]byte
suite.keeper.IterateAtomicSwapsByBlock(suite.ctx, uint64(cutoffBlock), func(id []byte) bool {
readSwapIDs = append(readSwapIDs, id)
return false
})
suite.Equal(expectedSwapIDs, readSwapIDs)
}
func (suite *KeeperTestSuite) TestInsertIntoLongtermStorage() {
suite.ResetChain()
// Set atomic swap in longterm storage
atomicSwap := atomicSwap(suite.ctx, 1)
atomicSwap.ClosedBlock = suite.ctx.BlockHeight()
suite.keeper.InsertIntoLongtermStorage(suite.ctx, atomicSwap)
// Longterm storage lacks getter methods, must use iteration to get count of swaps in store
var swapIDs [][]byte
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(atomicSwap.ClosedBlock+LongtermStorageDuration), func(id []byte) bool {
swapIDs = append(swapIDs, id)
return false
})
suite.Equal(len(swapIDs), 1)
// Marshal the expected swapID
cdc := suite.app.Codec()
res, _ := cdc.MarshalBinaryBare(atomicSwap.GetSwapID())
expectedSwapID := res[1:]
suite.Equal(expectedSwapID, swapIDs[0])
}
func (suite *KeeperTestSuite) TestRemoveFromLongtermStorage() {
suite.ResetChain()
// Set atomic swap in longterm storage
atomicSwap := atomicSwap(suite.ctx, 1)
atomicSwap.ClosedBlock = suite.ctx.BlockHeight()
suite.keeper.InsertIntoLongtermStorage(suite.ctx, atomicSwap)
// Longterm storage lacks getter methods, must use iteration to get count of swaps in store
var swapIDs [][]byte
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(atomicSwap.ClosedBlock+LongtermStorageDuration), func(id []byte) bool {
swapIDs = append(swapIDs, id)
return false
})
suite.Equal(len(swapIDs), 1)
suite.keeper.RemoveFromLongtermStorage(suite.ctx, atomicSwap)
// Check stored data not in block index
var swapIDsPost [][]byte
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(atomicSwap.ClosedBlock+LongtermStorageDuration), func(id []byte) bool {
swapIDsPost = append(swapIDsPost, id)
return false
})
suite.Equal(len(swapIDsPost), 0)
}
func (suite *KeeperTestSuite) TestIterateAtomicSwapsLongtermStorage() {
suite.ResetChain()
// Set up atomic swaps with stagged closed blocks
var swaps types.AtomicSwaps
for i := 0; i < 8; i++ {
timestamp := tmtime.Now().Unix()
randomNumber, _ := types.GenerateSecureRandomNumber()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
atomicSwap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash,
suite.ctx.BlockHeight(), timestamp, TestUser1, TestUser2,
TestSenderOtherChain, TestRecipientOtherChain, 100, types.Open,
true, types.Incoming)
// Set closed block staggered by 100 blocks and insert into longterm storage
atomicSwap.ClosedBlock = int64(i) * 100
suite.keeper.InsertIntoLongtermStorage(suite.ctx, atomicSwap)
// Add to local longterm storage
swaps = append(swaps, atomicSwap)
}
// Set up the expected swap IDs for a given cutoff block.
cutoffBlock := int64(LongtermStorageDuration + 350)
var expectedSwapIDs [][]byte
for _, swap := range swaps {
if swap.ClosedBlock+LongtermStorageDuration < cutoffBlock ||
swap.ClosedBlock+LongtermStorageDuration == cutoffBlock {
expectedSwapIDs = append(expectedSwapIDs, swap.GetSwapID())
}
}
// Read the swap IDs from store for a given cutoff block
var readSwapIDs [][]byte
suite.keeper.IterateAtomicSwapsLongtermStorage(suite.ctx, uint64(cutoffBlock), func(id []byte) bool {
readSwapIDs = append(readSwapIDs, id)
return false
})
// At the cutoff block, iteration should return half of the swap IDs
suite.Equal(len(swaps)/2, len(expectedSwapIDs))
suite.Equal(len(swaps)/2, len(readSwapIDs))
// Should be the same IDs
suite.Equal(expectedSwapIDs, readSwapIDs)
}
func (suite *KeeperTestSuite) TestGetSetAssetSupply() {
suite.ResetChain()
denom := "bnb"
// Put asset supply in store
assetSupply := types.NewAssetSupply(denom, c(denom, 0), c(denom, 0), c(denom, 50000), c(denom, 100000))
suite.keeper.SetAssetSupply(suite.ctx, assetSupply, []byte(denom))
// Check asset in store
storedAssetSupply, found := suite.keeper.GetAssetSupply(suite.ctx, []byte(denom))
suite.True(found)
suite.Equal(assetSupply, storedAssetSupply)
// Check fake asset supply not in store
fakeDenom := "xyz"
_, found = suite.keeper.GetAssetSupply(suite.ctx, []byte(fakeDenom))
suite.False(found)
}
func (suite *KeeperTestSuite) TestIterateAssetSupplies() {
suite.ResetChain()
// Set asset supplies
supplies := assetSupplies(5)
for _, supply := range supplies {
suite.keeper.SetAssetSupply(suite.ctx, supply, []byte(supply.Denom))
}
// Read each asset supply from the store
var readSupplies types.AssetSupplies
suite.keeper.IterateAssetSupplies(suite.ctx, func(a types.AssetSupply) bool {
readSupplies = append(readSupplies, a)
return false
})
// Check expected values
for i := 0; i < len(supplies); i++ {
suite.Contains(readSupplies, supplies[i])
}
}
func (suite *KeeperTestSuite) TestGetAllAssetSupplies() {
suite.ResetChain()
// Set asset supplies
count := 3
supplies := assetSupplies(count)
for _, supply := range supplies {
suite.keeper.SetAssetSupply(suite.ctx, supply, []byte(supply.Denom))
}
// Get all asset supplies
readSupplies := suite.keeper.GetAllAssetSupplies(suite.ctx)
suite.Equal(count, len(readSupplies))
// Check expected values
for i := 0; i < count; i++ {
suite.Contains(readSupplies, supplies[i])
}
}
func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

77
x/bep3/keeper/params.go Normal file
View File

@ -0,0 +1,77 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/bep3/types"
)
// GetParams returns the total set of bep3 parameters.
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
k.paramSubspace.GetParamSet(ctx, &params)
return params
}
// SetParams sets the bep3 parameters to the param space.
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSubspace.SetParamSet(ctx, &params)
}
// GetBnbDeputyAddress returns the Bnbchain's deputy address
func (k Keeper) GetBnbDeputyAddress(ctx sdk.Context) sdk.AccAddress {
params := k.GetParams(ctx)
return params.BnbDeputyAddress
}
// GetMaxBlockLock returns the maximum block lock
func (k Keeper) GetMaxBlockLock(ctx sdk.Context) int64 {
params := k.GetParams(ctx)
return params.MaxBlockLock
}
// GetMinBlockLock returns the minimum block lock
func (k Keeper) GetMinBlockLock(ctx sdk.Context) int64 {
params := k.GetParams(ctx)
return params.MinBlockLock
}
// GetAssets returns a list containing all supported assets
func (k Keeper) GetAssets(ctx sdk.Context) (types.AssetParams, bool) {
params := k.GetParams(ctx)
return params.SupportedAssets, len(params.SupportedAssets) > 0
}
// GetAssetByDenom returns an asset by its denom
func (k Keeper) GetAssetByDenom(ctx sdk.Context, denom string) (types.AssetParam, bool) {
params := k.GetParams(ctx)
for _, asset := range params.SupportedAssets {
if asset.Denom == denom {
return asset, true
}
}
return types.AssetParam{}, false
}
// GetAssetByCoinID returns an asset by its denom
func (k Keeper) GetAssetByCoinID(ctx sdk.Context, coinID int) (types.AssetParam, bool) {
params := k.GetParams(ctx)
for _, asset := range params.SupportedAssets {
if asset.CoinID == coinID {
return asset, true
}
}
return types.AssetParam{}, false
}
// ValidateLiveAsset checks if an asset is both supported and active
func (k Keeper) ValidateLiveAsset(ctx sdk.Context, coin sdk.Coin) error {
asset, found := k.GetAssetByDenom(ctx, coin.Denom)
if !found {
return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom)
}
if !asset.Active {
return sdkerrors.Wrap(types.ErrAssetNotActive, asset.Denom)
}
return nil
}

View File

@ -0,0 +1,142 @@
package keeper_test
import (
"errors"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
type ParamsTestSuite struct {
suite.Suite
keeper keeper.Keeper
addrs []sdk.AccAddress
app app.TestApp
ctx sdk.Context
}
func (suite *ParamsTestSuite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
_, addrs := app.GeneratePrivKeyAddressPairs(10)
tApp.InitializeFromGenesisStates(NewBep3GenStateMulti(addrs[0]))
suite.keeper = tApp.GetBep3Keeper()
suite.ctx = ctx
suite.addrs = addrs
}
func (suite *ParamsTestSuite) TestGetSetBnbDeputyAddress() {
params := suite.keeper.GetParams(suite.ctx)
params.BnbDeputyAddress = suite.addrs[1]
suite.NotPanics(func() { suite.keeper.SetParams(suite.ctx, params) })
params = suite.keeper.GetParams(suite.ctx)
suite.Equal(suite.addrs[1], params.BnbDeputyAddress)
addr := suite.keeper.GetBnbDeputyAddress(suite.ctx)
suite.Equal(suite.addrs[1], addr)
}
func (suite *ParamsTestSuite) TestGetMaxBlockLock() {
params := suite.keeper.GetParams(suite.ctx)
maxBlockLock := params.MaxBlockLock
res := suite.keeper.GetMaxBlockLock(suite.ctx)
suite.Equal(maxBlockLock, res)
}
func (suite *ParamsTestSuite) TestGetMinBlockLock() {
params := suite.keeper.GetParams(suite.ctx)
minBlockLock := params.MinBlockLock
res := suite.keeper.GetMinBlockLock(suite.ctx)
suite.Equal(minBlockLock, res)
}
func (suite *ParamsTestSuite) TestGetAssets() {
params := suite.keeper.GetParams(suite.ctx)
assets := params.SupportedAssets
res, found := suite.keeper.GetAssets(suite.ctx)
suite.True(found)
suite.Equal(assets, res)
}
func (suite *ParamsTestSuite) TestGetAssetByDenom() {
params := suite.keeper.GetParams(suite.ctx)
asset := params.SupportedAssets[0]
res, found := suite.keeper.GetAssetByDenom(suite.ctx, asset.Denom)
suite.True(found)
suite.Equal(asset, res)
}
func (suite *ParamsTestSuite) TestGetAssetByCoinID() {
params := suite.keeper.GetParams(suite.ctx)
asset := params.SupportedAssets[0]
res, found := suite.keeper.GetAssetByCoinID(suite.ctx, asset.CoinID)
suite.True(found)
suite.Equal(asset, res)
}
func (suite *AssetTestSuite) TestValidateLiveAsset() {
type args struct {
coin sdk.Coin
}
testCases := []struct {
name string
args args
expectedError error
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 1),
},
nil,
true,
},
{
"asset not supported",
args{
coin: c("bad", 1),
},
types.ErrAssetNotSupported,
false,
},
{
"asset not active",
args{
coin: c("inc", 1),
},
types.ErrAssetNotActive,
false,
},
}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
err := suite.keeper.ValidateLiveAsset(suite.ctx, tc.args.coin)
if tc.expectPass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
suite.Require().True(errors.Is(err, tc.expectedError))
}
})
}
}
func TestParamsTestSuite(t *testing.T) {
suite.Run(t, new(ParamsTestSuite))
}

103
x/bep3/keeper/querier.go Normal file
View File

@ -0,0 +1,103 @@
package keeper
import (
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/bep3/types"
)
// NewQuerier is the module level router for state queries
func NewQuerier(keeper Keeper) sdk.Querier {
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) {
switch path[0] {
case types.QueryGetAssetSupply:
return queryAssetSupply(ctx, req, keeper)
case types.QueryGetAtomicSwap:
return queryAtomicSwap(ctx, req, keeper)
case types.QueryGetAtomicSwaps:
return queryAtomicSwaps(ctx, req, keeper)
case types.QueryGetParams:
return queryGetParams(ctx, req, keeper)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
}
}
}
func queryAssetSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Decode request
var requestParams types.QueryAssetSupply
err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
assetSupply, found := keeper.GetAssetSupply(ctx, []byte(requestParams.Denom))
if !found {
return nil, sdkerrors.Wrap(types.ErrAssetSupplyNotFound, string(requestParams.Denom))
}
// Encode results
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, assetSupply)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
func queryAtomicSwap(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Decode request
var requestParams types.QueryAtomicSwapByID
err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
// Lookup atomic swap
atomicSwap, found := keeper.GetAtomicSwap(ctx, requestParams.SwapID)
if !found {
return nil, sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%d", requestParams.SwapID)
}
// Encode results
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, atomicSwap)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
func queryAtomicSwaps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err error) {
var swaps types.AtomicSwaps
keeper.IterateAtomicSwaps(ctx, func(s types.AtomicSwap) bool {
swaps = append(swaps, s)
return false
})
bz, err2 := codec.MarshalJSONIndent(types.ModuleCdc, swaps)
if err2 != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}
// query params in the bep3 store
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
// Get params
params := keeper.GetParams(ctx)
// Encode results
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, params)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
}
return bz, nil
}

View File

@ -0,0 +1,170 @@
package keeper_test
import (
"encoding/hex"
"strings"
"testing"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtime "github.com/tendermint/tendermint/types/time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
)
const (
custom = "custom"
)
type QuerierTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
querier sdk.Querier
addrs []sdk.AccAddress
isSupplyDenom map[string]bool
swapIDs []tmbytes.HexBytes
isSwapID map[string]bool
}
func (suite *QuerierTestSuite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
// Set up auth GenesisState
_, addrs := app.GeneratePrivKeyAddressPairs(10)
coins := []sdk.Coins{}
for j := 0; j < 10; j++ {
coins = append(coins, cs(c("bnb", 10000000000), c("ukava", 10000000000)))
}
authGS := app.NewAuthGenState(addrs, coins)
tApp.InitializeFromGenesisStates(
authGS,
NewBep3GenStateMulti(addrs[0]),
)
suite.ctx = ctx
suite.app = tApp
suite.keeper = tApp.GetBep3Keeper()
suite.querier = keeper.NewQuerier(suite.keeper)
suite.addrs = addrs
// Create atomic swaps and save IDs
var swapIDs []tmbytes.HexBytes
isSwapID := make(map[string]bool)
for i := 0; i < 10; i++ {
// Set up atomic swap variables
expireHeight := int64(360)
amount := cs(c("bnb", 100))
timestamp := ts(0)
randomNumber, _ := types.GenerateSecureRandomNumber()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
// Create atomic swap and check err
err := suite.keeper.CreateAtomicSwap(suite.ctx, randomNumberHash, timestamp, expireHeight,
addrs[0], suite.addrs[i], TestSenderOtherChain, TestRecipientOtherChain, amount,
amount.String(), true)
suite.Nil(err)
// Calculate swap ID and save
swapID := types.CalculateSwapID(randomNumberHash, addrs[0], TestSenderOtherChain)
swapIDs = append(swapIDs, swapID)
isSwapID[hex.EncodeToString(swapID)] = true
}
suite.swapIDs = swapIDs
suite.isSwapID = isSwapID
}
func (suite *QuerierTestSuite) TestQueryAssetSupply() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
denom := "bnb"
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAssetSupply}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryAssetSupply(tmbytes.HexBytes(denom))),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryGetAssetSupply}, query)
suite.Nil(err)
suite.NotNil(bz)
// Unmarshal the bytes into type asset supply
var supply types.AssetSupply
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &supply))
expectedSupply := types.NewAssetSupply(denom, c(denom, 1000),
c(denom, 0), c(denom, 0), c(denom, StandardSupplyLimit.Int64()))
suite.Equal(supply, expectedSupply)
}
func (suite *QuerierTestSuite) TestQueryAtomicSwap() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAtomicSwap}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryAtomicSwapByID(suite.swapIDs[0])),
}
// Execute query and check the []byte result
bz, err := suite.querier(ctx, []string{types.QueryGetAtomicSwap}, query)
suite.Nil(err)
suite.NotNil(bz)
// Unmarshal the bytes into type atomic swap
var swap types.AtomicSwap
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &swap))
// Check the returned atomic swap's ID
suite.True(suite.isSwapID[hex.EncodeToString(swap.GetSwapID())])
}
func (suite *QuerierTestSuite) TestQueryAtomicSwaps() {
ctx := suite.ctx.WithIsCheckTx(false)
// Set up request query
query := abci.RequestQuery{
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAtomicSwaps}, "/"),
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryAtomicSwaps(1, 100)),
}
bz, err := suite.querier(ctx, []string{types.QueryGetAtomicSwaps}, query)
suite.Nil(err)
suite.NotNil(bz)
var swaps types.AtomicSwaps
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &swaps))
suite.Equal(len(suite.swapIDs), len(swaps))
for _, swap := range swaps {
suite.True(suite.isSwapID[hex.EncodeToString(swap.GetSwapID())])
}
}
func (suite *QuerierTestSuite) TestQueryParams() {
ctx := suite.ctx.WithIsCheckTx(false)
bz, err := suite.querier(ctx, []string{types.QueryGetParams}, abci.RequestQuery{})
suite.Nil(err)
suite.NotNil(bz)
var p types.Params
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &p))
bep3GenesisState := NewBep3GenStateMulti(suite.addrs[0])
gs := types.GenesisState{}
types.ModuleCdc.UnmarshalJSON(bep3GenesisState["bep3"], &gs)
suite.Equal(gs.Params, p)
}
func TestQuerierTestSuite(t *testing.T) {
suite.Run(t, new(QuerierTestSuite))
}

274
x/bep3/keeper/swap.go Normal file
View File

@ -0,0 +1,274 @@
package keeper
import (
"bytes"
"encoding/hex"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/kava-labs/kava/x/bep3/types"
)
// CreateAtomicSwap creates a new AtomicSwap
func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, timestamp int64, heightSpan int64,
sender sdk.AccAddress, recipient sdk.AccAddress, senderOtherChain, recipientOtherChain string,
amount sdk.Coins, expectedIncome string, crossChain bool) error {
// Confirm that this is not a duplicate swap
swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain)
_, found := k.GetAtomicSwap(ctx, swapID)
if found {
return sdkerrors.Wrap(types.ErrAtomicSwapAlreadyExists, hex.EncodeToString(swapID))
}
// The heightSpan period should be more than 10 minutes and less than one week
// Assume average block time interval is 10 second. 10 mins = 60 blocks, 1 week = 60480 blocks
if heightSpan < k.GetMinBlockLock(ctx) || heightSpan > k.GetMaxBlockLock(ctx) {
return sdkerrors.Wrapf(types.ErrInvalidHeightSpan, "height span %d, range %d - %d", heightSpan, k.GetMinBlockLock(ctx), k.GetMaxBlockLock(ctx))
}
// Unix timestamp must be in range [-15 mins, 30 mins] of the current time
pastTimestampLimit := ctx.BlockTime().Add(time.Duration(-15) * time.Minute).Unix()
futureTimestampLimit := ctx.BlockTime().Add(time.Duration(30) * time.Minute).Unix()
if timestamp < pastTimestampLimit || timestamp >= futureTimestampLimit {
return sdkerrors.Wrap(types.ErrInvalidTimestamp, time.Unix(timestamp, 0).UTC().String())
}
if len(amount) != 1 {
return fmt.Errorf("amount must contain exactly one coin")
}
err := k.ValidateLiveAsset(ctx, amount[0])
if err != nil {
return err
}
var direction types.SwapDirection
deputy := k.GetBnbDeputyAddress(ctx)
if sender.Equals(deputy) {
direction = types.Incoming
} else {
direction = types.Outgoing
}
switch direction {
case types.Incoming:
err = k.IncrementIncomingAssetSupply(ctx, amount[0])
case types.Outgoing:
err = k.IncrementOutgoingAssetSupply(ctx, amount[0])
default:
err = fmt.Errorf("invalid swap direction: %s", direction.String())
}
if err != nil {
return err
}
// Transfer coins to module
err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount)
if err != nil {
return err
}
// Store the details of the swap
atomicSwap := types.NewAtomicSwap(amount, randomNumberHash, ctx.BlockHeight()+heightSpan,
timestamp, sender, recipient, senderOtherChain, recipientOtherChain, 0, types.Open,
crossChain, direction)
k.SetAtomicSwap(ctx, atomicSwap)
k.InsertIntoByBlockIndex(ctx, atomicSwap)
// Emit 'create_atomic_swap' event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCreateAtomicSwap,
sdk.NewAttribute(types.AttributeKeySender, atomicSwap.Sender.String()),
sdk.NewAttribute(types.AttributeKeyRecipient, atomicSwap.Recipient.String()),
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())),
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)),
sdk.NewAttribute(types.AttributeKeyTimestamp, fmt.Sprintf("%d", atomicSwap.Timestamp)),
sdk.NewAttribute(types.AttributeKeySenderOtherChain, atomicSwap.SenderOtherChain),
sdk.NewAttribute(types.AttributeKeyExpireHeight, fmt.Sprintf("%d", atomicSwap.ExpireHeight)),
sdk.NewAttribute(types.AttributeKeyAmount, atomicSwap.Amount[0].String()),
sdk.NewAttribute(types.AttributeKeyExpectedIncome, expectedIncome),
sdk.NewAttribute(types.AttributeKeyDirection, atomicSwap.Direction.String()),
),
)
return nil
}
// ClaimAtomicSwap validates a claim attempt, and if successful, sends the escrowed amount and closes the AtomicSwap
func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte, randomNumber []byte) error {
atomicSwap, found := k.GetAtomicSwap(ctx, swapID)
if !found {
return sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%s", swapID)
}
// Only open atomic swaps can be claimed
if atomicSwap.Status != types.Open {
return types.ErrSwapNotClaimable
}
// Calculate hashed secret using submitted number
hashedSubmittedNumber := types.CalculateRandomHash(randomNumber, atomicSwap.Timestamp)
hashedSecret := types.CalculateSwapID(hashedSubmittedNumber, atomicSwap.Sender, atomicSwap.SenderOtherChain)
// Confirm that secret unlocks the atomic swap
if !bytes.Equal(hashedSecret, atomicSwap.GetSwapID()) {
return sdkerrors.Wrapf(types.ErrInvalidClaimSecret, "%s ≠ %s", hex.EncodeToString(hashedSecret), hex.EncodeToString(atomicSwap.GetSwapID()))
}
var err error
switch atomicSwap.Direction {
case types.Incoming:
err := k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
break
}
err = k.IncrementCurrentAssetSupply(ctx, atomicSwap.Amount[0])
case types.Outgoing:
err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
break
}
err = k.DecrementCurrentAssetSupply(ctx, atomicSwap.Amount[0])
default:
err = fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String())
}
if err != nil {
return err
}
// Send intended recipient coins
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount)
if err != nil {
return err
}
// Complete swap
atomicSwap.Status = types.Completed
atomicSwap.ClosedBlock = ctx.BlockHeight()
k.SetAtomicSwap(ctx, atomicSwap)
// Remove from byBlock index and transition to longterm storage
k.RemoveFromByBlockIndex(ctx, atomicSwap)
k.InsertIntoLongtermStorage(ctx, atomicSwap)
// Emit 'claim_atomic_swap' event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaimAtomicSwap,
sdk.NewAttribute(types.AttributeKeyClaimSender, from.String()),
sdk.NewAttribute(types.AttributeKeyRecipient, atomicSwap.Recipient.String()),
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())),
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)),
sdk.NewAttribute(types.AttributeKeyRandomNumber, hex.EncodeToString(randomNumber)),
),
)
return nil
}
// RefundAtomicSwap refunds an AtomicSwap, sending assets to the original sender and closing the AtomicSwap
func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte) error {
atomicSwap, found := k.GetAtomicSwap(ctx, swapID)
if !found {
return sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%s", swapID)
}
// Only expired swaps may be refunded
if atomicSwap.Status != types.Expired {
return types.ErrSwapNotRefundable
}
var err error
switch atomicSwap.Direction {
case types.Incoming:
err = k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
case types.Outgoing:
err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
default:
err = fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String())
}
if err != nil {
return err
}
// Refund coins to original swap sender
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount)
if err != nil {
return err
}
// Complete swap
atomicSwap.Status = types.Completed
atomicSwap.ClosedBlock = ctx.BlockHeight()
k.SetAtomicSwap(ctx, atomicSwap)
// Transition to longterm storage
k.InsertIntoLongtermStorage(ctx, atomicSwap)
// Emit 'refund_atomic_swap' event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRefundAtomicSwap,
sdk.NewAttribute(types.AttributeKeyRefundSender, fmt.Sprintf("%s", from)),
sdk.NewAttribute(types.AttributeKeySender, fmt.Sprintf("%s", atomicSwap.Sender)),
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, fmt.Sprintf("%s", hex.EncodeToString(atomicSwap.GetSwapID()))),
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, fmt.Sprintf("%s", hex.EncodeToString(atomicSwap.RandomNumberHash))),
),
)
return nil
}
// UpdateExpiredAtomicSwaps finds all AtomicSwaps that are past (or at) their ending times and expires them.
func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) error {
var expiredSwaps [][]byte
k.IterateAtomicSwapsByBlock(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool {
expiredSwaps = append(expiredSwaps, id)
return false
})
// Expire incomplete swaps (claimed swaps have already been removed from byBlock index)
var expiredSwapIDs []string
for _, id := range expiredSwaps {
atomicSwap, _ := k.GetAtomicSwap(ctx, id)
atomicSwap.Status = types.Expired
k.SetAtomicSwap(ctx, atomicSwap)
k.RemoveFromByBlockIndex(ctx, atomicSwap)
expiredSwapIDs = append(expiredSwapIDs, hex.EncodeToString(atomicSwap.GetSwapID()))
}
// Emit 'swaps_expired' event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSwapsExpired,
sdk.NewAttribute(types.AttributeKeyAtomicSwapIDs, fmt.Sprintf("%s", expiredSwapIDs)),
sdk.NewAttribute(types.AttributeExpirationBlock, fmt.Sprintf("%d", ctx.BlockHeight())),
),
)
return nil
}
// DeleteClosedAtomicSwapsFromLongtermStorage removes swaps one week after completion
func (k Keeper) DeleteClosedAtomicSwapsFromLongtermStorage(ctx sdk.Context) error {
var swapsToDelete [][]byte
k.IterateAtomicSwapsLongtermStorage(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool {
swapsToDelete = append(swapsToDelete, id)
return false
})
// Delete closed atomic swaps
for _, id := range swapsToDelete {
swap, _ := k.GetAtomicSwap(ctx, id)
k.RemoveAtomicSwap(ctx, swap.GetSwapID())
k.RemoveFromLongtermStorage(ctx, swap)
}
return nil
}

699
x/bep3/keeper/swap_test.go Normal file
View File

@ -0,0 +1,699 @@
package keeper_test
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtime "github.com/tendermint/tendermint/types/time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
)
type AtomicSwapTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
deputy sdk.AccAddress
addrs []sdk.AccAddress
timestamps []int64
randomNumberHashes []tmbytes.HexBytes
swapIDs []tmbytes.HexBytes
randomNumbers []tmbytes.HexBytes
}
const (
STARING_BNB_BALANCE = int64(1000000000)
BNB_DENOM = "bnb"
)
func (suite *AtomicSwapTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
// Initialize test app and set context
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
// Create and load 20 accounts with bnb tokens
coins := []sdk.Coins{}
for i := 0; i < 20; i++ {
coins = append(coins, cs(c(BNB_DENOM, STARING_BNB_BALANCE)))
}
_, addrs := app.GeneratePrivKeyAddressPairs(20)
deputy := addrs[0]
authGS := app.NewAuthGenState(addrs, coins)
// Initialize genesis state
tApp.InitializeFromGenesisStates(authGS, NewBep3GenStateMulti(deputy))
suite.app = tApp
suite.ctx = ctx
suite.deputy = deputy
suite.addrs = addrs
suite.keeper = suite.app.GetBep3Keeper()
suite.GenerateSwapDetails()
}
func (suite *AtomicSwapTestSuite) GenerateSwapDetails() {
var timestamps []int64
var randomNumberHashes []tmbytes.HexBytes
var randomNumbers []tmbytes.HexBytes
for i := 0; i < 10; i++ {
// Set up atomic swap details
timestamp := ts(i)
randomNumber, _ := types.GenerateSecureRandomNumber()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
timestamps = append(timestamps, timestamp)
randomNumberHashes = append(randomNumberHashes, randomNumberHash)
randomNumbers = append(randomNumbers, randomNumber.Bytes())
}
suite.timestamps = timestamps
suite.randomNumberHashes = randomNumberHashes
suite.randomNumbers = randomNumbers
}
func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() {
currentTmTime := tmtime.Now()
type args struct {
randomNumberHash []byte
timestamp int64
heightSpan int64
sender sdk.AccAddress
recipient sdk.AccAddress
senderOtherChain string
recipientOtherChain string
coins sdk.Coins
expectedIncome string
crossChain bool
direction types.SwapDirection
}
testCases := []struct {
name string
blockTime time.Time
args args
expectPass bool
shouldBeFound bool
}{
{
"incoming swap",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[0],
timestamp: suite.timestamps[0],
heightSpan: int64(360),
sender: suite.deputy,
recipient: suite.addrs[1],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: fmt.Sprintf("50000%s", BNB_DENOM),
crossChain: true,
direction: types.Incoming,
},
true,
true,
},
{
"outgoing swap",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[0],
timestamp: suite.timestamps[0],
heightSpan: int64(360),
sender: suite.addrs[1],
recipient: suite.addrs[2],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: fmt.Sprintf("50000%s", BNB_DENOM),
crossChain: true,
direction: types.Outgoing,
},
true,
true,
},
{
"unsupported asset",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[1],
timestamp: suite.timestamps[1],
heightSpan: int64(360),
sender: suite.deputy,
recipient: suite.addrs[2],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c("xyz", 50000)),
expectedIncome: "50000xyz",
crossChain: true,
direction: types.Incoming,
},
false,
false,
},
{
"past timestamp",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[2],
timestamp: suite.timestamps[2] - 2000,
heightSpan: int64(360),
sender: suite.deputy,
recipient: suite.addrs[3],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: fmt.Sprintf("50000%s", BNB_DENOM),
crossChain: true,
direction: types.Incoming,
},
false,
false,
},
{
"future timestamp",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[3],
timestamp: suite.timestamps[3] + 5000,
heightSpan: int64(360),
sender: suite.deputy,
recipient: suite.addrs[4],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: fmt.Sprintf("50000%s", BNB_DENOM),
crossChain: true,
direction: types.Incoming,
},
false,
false,
},
{
"small height span",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[4],
timestamp: suite.timestamps[4],
heightSpan: int64(5),
sender: suite.deputy,
recipient: suite.addrs[5],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: fmt.Sprintf("50000%s", BNB_DENOM),
crossChain: true,
direction: types.Incoming,
},
false,
false,
},
{
"big height span",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[5],
timestamp: suite.timestamps[5],
heightSpan: int64(1000000),
sender: suite.deputy,
recipient: suite.addrs[6],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: fmt.Sprintf("50000%s", BNB_DENOM),
crossChain: true,
direction: types.Incoming,
},
false,
false,
},
{
"zero amount",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[6],
timestamp: suite.timestamps[6],
heightSpan: int64(360),
sender: suite.deputy,
recipient: suite.addrs[7],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 0)),
expectedIncome: fmt.Sprintf("0%s", BNB_DENOM),
crossChain: true,
direction: types.Incoming,
},
false,
false,
},
{
"duplicate swap",
currentTmTime,
args{
randomNumberHash: suite.randomNumberHashes[0],
timestamp: suite.timestamps[0],
heightSpan: int64(360),
sender: suite.deputy,
recipient: suite.addrs[1],
senderOtherChain: TestSenderOtherChain,
recipientOtherChain: TestRecipientOtherChain,
coins: cs(c(BNB_DENOM, 50000)),
expectedIncome: "50000bnb",
crossChain: true,
direction: types.Incoming,
},
false,
true,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// Increment current asset supply to support outgoing swaps
if tc.args.direction == types.Outgoing {
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, tc.args.coins[0])
suite.Nil(err)
}
// Load asset denom (required for zero coins test case)
var swapAssetDenom string
if len(tc.args.coins) == 1 {
swapAssetDenom = tc.args.coins[0].Denom
} else {
swapAssetDenom = BNB_DENOM
}
// Load sender's account prior to swap creation
ak := suite.app.GetAccountKeeper()
senderAccPre := ak.GetAccount(suite.ctx, tc.args.sender)
senderBalancePre := senderAccPre.GetCoins().AmountOf(swapAssetDenom)
assetSupplyPre, _ := suite.keeper.GetAssetSupply(suite.ctx, []byte(swapAssetDenom))
// Create atomic swap
err := suite.keeper.CreateAtomicSwap(suite.ctx, tc.args.randomNumberHash, tc.args.timestamp,
tc.args.heightSpan, tc.args.sender, tc.args.recipient, tc.args.senderOtherChain,
tc.args.recipientOtherChain, tc.args.coins, tc.args.expectedIncome, tc.args.crossChain)
// Load sender's account after swap creation
senderAccPost := ak.GetAccount(suite.ctx, tc.args.sender)
senderBalancePost := senderAccPost.GetCoins().AmountOf(swapAssetDenom)
assetSupplyPost, _ := suite.keeper.GetAssetSupply(suite.ctx, []byte(swapAssetDenom))
// Load expected swap ID
expectedSwapID := types.CalculateSwapID(tc.args.randomNumberHash, tc.args.sender, tc.args.senderOtherChain)
if tc.expectPass {
suite.NoError(err)
// Check coins moved
suite.Equal(senderBalancePre.Sub(tc.args.coins[0].Amount), senderBalancePost)
// Check incoming/outgoing asset supply increased
switch tc.args.direction {
case types.Incoming:
suite.Equal(assetSupplyPre.IncomingSupply.Add(tc.args.coins[0]), assetSupplyPost.IncomingSupply)
case types.Outgoing:
suite.Equal(assetSupplyPre.OutgoingSupply.Add(tc.args.coins[0]), assetSupplyPost.OutgoingSupply)
default:
suite.Fail("should not have invalid direction")
}
// Check swap in store
actualSwap, found := suite.keeper.GetAtomicSwap(suite.ctx, expectedSwapID)
suite.True(found)
suite.NotNil(actualSwap)
// Confirm swap contents
expectedSwap :=
types.AtomicSwap{
Amount: tc.args.coins,
RandomNumberHash: tc.args.randomNumberHash,
ExpireHeight: suite.ctx.BlockHeight() + tc.args.heightSpan,
Timestamp: tc.args.timestamp,
Sender: tc.args.sender,
Recipient: tc.args.recipient,
SenderOtherChain: tc.args.senderOtherChain,
RecipientOtherChain: tc.args.recipientOtherChain,
ClosedBlock: 0,
Status: types.Open,
CrossChain: tc.args.crossChain,
Direction: tc.args.direction,
}
suite.Equal(expectedSwap, actualSwap)
} else {
suite.Error(err)
// Check coins not moved
suite.Equal(senderBalancePre, senderBalancePost)
// Check incoming/outgoing asset supply not increased
switch tc.args.direction {
case types.Incoming:
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
case types.Outgoing:
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
default:
suite.Fail("should not have invalid direction")
}
// Check if swap found in store
_, found := suite.keeper.GetAtomicSwap(suite.ctx, expectedSwapID)
if !tc.shouldBeFound {
suite.False(found)
} else {
suite.True(found)
}
}
})
}
}
func (suite *AtomicSwapTestSuite) TestClaimAtomicSwap() {
suite.SetupTest()
invalidRandomNumber, _ := types.GenerateSecureRandomNumber()
type args struct {
swapID []byte
randomNumber []byte
direction types.SwapDirection
}
testCases := []struct {
name string
claimCtx sdk.Context
args args
expectPass bool
}{
{
"normal incoming swap",
suite.ctx,
args{
swapID: []byte{},
randomNumber: []byte{},
direction: types.Incoming,
},
true,
},
{
"normal outgoing swap",
suite.ctx,
args{
swapID: []byte{},
randomNumber: []byte{},
direction: types.Outgoing,
},
true,
},
{
"invalid random number",
suite.ctx,
args{
swapID: []byte{},
randomNumber: invalidRandomNumber.Bytes(),
direction: types.Incoming,
},
false,
},
{
"wrong swap ID",
suite.ctx,
args{
swapID: types.CalculateSwapID(suite.randomNumberHashes[3], suite.addrs[6], TestRecipientOtherChain),
randomNumber: []byte{},
direction: types.Outgoing,
},
false,
},
{
"past expiration",
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 2000),
args{
swapID: []byte{},
randomNumber: []byte{},
direction: types.Incoming,
},
false,
},
}
for i, tc := range testCases {
suite.GenerateSwapDetails()
suite.Run(tc.name, func() {
expectedRecipient := suite.addrs[5]
expectedClaimAmount := cs(c(BNB_DENOM, 50000))
sender := suite.deputy
// Set sender to other and increment current asset supply for outgoing swap
if tc.args.direction == types.Outgoing {
sender = suite.addrs[6]
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, expectedClaimAmount[0])
suite.Nil(err)
}
// Create atomic swap
err := suite.keeper.CreateAtomicSwap(suite.ctx, suite.randomNumberHashes[i], suite.timestamps[i],
int64(360), sender, expectedRecipient, TestSenderOtherChain, TestRecipientOtherChain,
expectedClaimAmount, expectedClaimAmount.String(), true)
suite.NoError(err)
realSwapID := types.CalculateSwapID(suite.randomNumberHashes[i], sender, TestSenderOtherChain)
// If args contains an invalid swap ID claim attempt will use it instead of the real swap ID
var claimSwapID []byte
if len(tc.args.swapID) == 0 {
claimSwapID = realSwapID
} else {
claimSwapID = tc.args.swapID
}
// If args contains an invalid random number claim attempt will use it instead of the real random number
var claimRandomNumber []byte
if len(tc.args.randomNumber) == 0 {
claimRandomNumber = suite.randomNumbers[i]
} else {
claimRandomNumber = tc.args.randomNumber
}
// Run the beginblocker before attempting claim
bep3.BeginBlocker(tc.claimCtx, suite.keeper)
// Load expected recipient's account prior to claim attempt
ak := suite.app.GetAccountKeeper()
expectedRecipientAccPre := ak.GetAccount(tc.claimCtx, expectedRecipient)
expectedRecipientBalancePre := expectedRecipientAccPre.GetCoins().AmountOf(expectedClaimAmount[0].Denom)
// Load asset supplies prior to claim attempt
assetSupplyPre, _ := suite.keeper.GetAssetSupply(tc.claimCtx, []byte(expectedClaimAmount[0].Denom))
// Attempt to claim atomic swap
err = suite.keeper.ClaimAtomicSwap(tc.claimCtx, expectedRecipient, claimSwapID, claimRandomNumber)
// Load expected recipient's account after the claim attempt
expectedRecipientAccPost := ak.GetAccount(tc.claimCtx, expectedRecipient)
expectedRecipientBalancePost := expectedRecipientAccPost.GetCoins().AmountOf(expectedClaimAmount[0].Denom)
// Load asset supplies after the claim attempt
assetSupplyPost, _ := suite.keeper.GetAssetSupply(tc.claimCtx, []byte(expectedClaimAmount[0].Denom))
if tc.expectPass {
suite.NoError(err)
// Check coins moved
suite.Equal(expectedRecipientBalancePre.Add(expectedClaimAmount[0].Amount), expectedRecipientBalancePost)
// Check asset supply changes
switch tc.args.direction {
case types.Incoming:
// Check incoming supply decreased
suite.True(assetSupplyPre.IncomingSupply.Amount.Sub(expectedClaimAmount[0].Amount).Equal(assetSupplyPost.IncomingSupply.Amount))
// Check current supply increased
suite.Equal(assetSupplyPre.CurrentSupply.Add(expectedClaimAmount[0]), assetSupplyPost.CurrentSupply)
// Check outgoing supply not changed
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
case types.Outgoing:
// Check incoming supply not changed
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
// Check current supply decreased
suite.Equal(assetSupplyPre.CurrentSupply.Sub(expectedClaimAmount[0]), assetSupplyPost.CurrentSupply)
// Check outgoing supply decreased
suite.True(assetSupplyPre.OutgoingSupply.Sub(expectedClaimAmount[0]).IsEqual(assetSupplyPost.OutgoingSupply))
default:
suite.Fail("should not have invalid direction")
}
} else {
suite.Error(err)
// Check coins not moved
suite.Equal(expectedRecipientBalancePre, expectedRecipientBalancePost)
// Check asset supply has not changed
switch tc.args.direction {
case types.Incoming:
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
case types.Outgoing:
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
default:
suite.Fail("should not have invalid direction")
}
}
})
}
}
func (suite *AtomicSwapTestSuite) TestRefundAtomicSwap() {
suite.SetupTest()
type args struct {
swapID []byte
direction types.SwapDirection
}
testCases := []struct {
name string
refundCtx sdk.Context
args args
expectPass bool
}{
{
"normal incoming swap",
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
args{
swapID: []byte{},
direction: types.Incoming,
},
true,
},
{
"normal outgoing swap",
suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400),
args{
swapID: []byte{},
direction: types.Outgoing,
},
true,
},
{
"before expiration",
suite.ctx,
args{
swapID: []byte{},
direction: types.Incoming,
},
false,
},
{
"wrong swapID",
suite.ctx,
args{
swapID: types.CalculateSwapID(suite.randomNumberHashes[6], suite.addrs[1], TestRecipientOtherChain),
direction: types.Incoming,
},
false,
},
}
for i, tc := range testCases {
suite.GenerateSwapDetails()
suite.Run(tc.name, func() {
// Create atomic swap
expectedRefundAmount := cs(c(BNB_DENOM, 50000))
sender := suite.deputy
// Set sender to other and increment current asset supply for outgoing swap
if tc.args.direction == types.Outgoing {
sender = suite.addrs[6]
err := suite.keeper.IncrementCurrentAssetSupply(suite.ctx, expectedRefundAmount[0])
suite.Nil(err)
}
err := suite.keeper.CreateAtomicSwap(suite.ctx, suite.randomNumberHashes[i], suite.timestamps[i],
int64(360), sender, suite.addrs[8], TestSenderOtherChain, TestRecipientOtherChain,
expectedRefundAmount, expectedRefundAmount.String(), true)
suite.NoError(err)
realSwapID := types.CalculateSwapID(suite.randomNumberHashes[i], sender, TestSenderOtherChain)
// If args contains an invalid swap ID refund attempt will use it instead of the real swap ID
var refundSwapID []byte
if len(tc.args.swapID) == 0 {
refundSwapID = realSwapID
} else {
refundSwapID = tc.args.swapID
}
// Run the beginblocker before attempting refund
bep3.BeginBlocker(tc.refundCtx, suite.keeper)
// Load sender's account prior to swap refund
ak := suite.app.GetAccountKeeper()
originalSenderAccPre := ak.GetAccount(tc.refundCtx, sender)
originalSenderBalancePre := originalSenderAccPre.GetCoins().AmountOf(expectedRefundAmount[0].Denom)
// Load asset supply prior to swap refund
assetSupplyPre, _ := suite.keeper.GetAssetSupply(tc.refundCtx, []byte(expectedRefundAmount[0].Denom))
// Attempt to refund atomic swap
err = suite.keeper.RefundAtomicSwap(tc.refundCtx, sender, refundSwapID)
// Load sender's account after refund
originalSenderAccPost := ak.GetAccount(tc.refundCtx, sender)
originalSenderBalancePost := originalSenderAccPost.GetCoins().AmountOf(expectedRefundAmount[0].Denom)
// Load asset supply after to swap refund
assetSupplyPost, _ := suite.keeper.GetAssetSupply(tc.refundCtx, []byte(expectedRefundAmount[0].Denom))
if tc.expectPass {
suite.NoError(err)
// Check coins moved
suite.Equal(originalSenderBalancePre.Add(expectedRefundAmount[0].Amount), originalSenderBalancePost)
// Check asset supply changes
switch tc.args.direction {
case types.Incoming:
// Check incoming supply decreased
suite.True(assetSupplyPre.IncomingSupply.Sub(expectedRefundAmount[0]).IsEqual(assetSupplyPost.IncomingSupply))
// Check current, outgoing supply not changed
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
case types.Outgoing:
// Check incoming, current supply not changed
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
// Check outgoing supply decreased
suite.True(assetSupplyPre.OutgoingSupply.Sub(expectedRefundAmount[0]).IsEqual(assetSupplyPost.OutgoingSupply))
default:
suite.Fail("should not have invalid direction")
}
} else {
suite.Error(err)
// Check coins not moved
suite.Equal(originalSenderBalancePre, originalSenderBalancePost)
// Check asset supply has not changed
switch tc.args.direction {
case types.Incoming:
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
case types.Outgoing:
suite.Equal(assetSupplyPre.IncomingSupply, assetSupplyPost.IncomingSupply)
suite.Equal(assetSupplyPre.CurrentSupply, assetSupplyPost.CurrentSupply)
suite.Equal(assetSupplyPre.OutgoingSupply, assetSupplyPost.OutgoingSupply)
default:
suite.Fail("should not have invalid direction")
}
}
})
}
}
func TestAtomicSwapTestSuite(t *testing.T) {
suite.Run(t, new(AtomicSwapTestSuite))
}

172
x/bep3/module.go Normal file
View File

@ -0,0 +1,172 @@
package bep3
import (
"encoding/json"
"math/rand"
"github.com/cosmos/cosmos-sdk/client/context"
"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"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
"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"
)
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ module.AppModuleSimulation = AppModule{}
)
// AppModuleBasic defines the basic application module used by the bep3 module.
type AppModuleBasic struct{}
// Name returns the bep3 module's name.
func (AppModuleBasic) Name() string {
return ModuleName
}
// RegisterCodec registers the bep3 module's types for the given codec.
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
RegisterCodec(cdc)
}
// DefaultGenesis returns default genesis state as raw bytes for the bep3
// module.
func (AppModuleBasic) DefaultGenesis() json.RawMessage {
return ModuleCdc.MustMarshalJSON(DefaultGenesisState())
}
// ValidateGenesis performs genesis state validation for the bep3 module.
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
var gs GenesisState
err := ModuleCdc.UnmarshalJSON(bz, &gs)
if err != nil {
return err
}
return gs.Validate()
}
// RegisterRESTRoutes registers the REST routes for the bep3 module.
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
rest.RegisterRoutes(ctx, rtr)
}
// GetTxCmd returns the root tx command for the bep3 module.
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
return cli.GetTxCmd(cdc)
}
// GetQueryCmd returns no root query command for the bep3 module.
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
return cli.GetQueryCmd(StoreKey, cdc)
}
//____________________________________________________________________________
// AppModule implements the sdk.AppModule interface.
type AppModule struct {
AppModuleBasic
keeper Keeper
accountKeeper auth.AccountKeeper
supplyKeeper SupplyKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper SupplyKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: keeper,
accountKeeper: accountKeeper,
supplyKeeper: supplyKeeper,
}
}
// Name returns the bep3 module's name.
func (AppModule) Name() string {
return ModuleName
}
// RegisterInvariants registers the bep3 module invariants.
func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
// Route returns the message routing key for the bep3 module.
func (AppModule) Route() string {
return RouterKey
}
// NewHandler returns an sdk.Handler for the bep3 module.
func (am AppModule) NewHandler() sdk.Handler {
return NewHandler(am.keeper)
}
// QuerierRoute returns the bep3 module's querier route name.
func (AppModule) QuerierRoute() string {
return QuerierRoute
}
// NewQuerierHandler returns the bep3 module sdk.Querier.
func (am AppModule) NewQuerierHandler() sdk.Querier {
return NewQuerier(am.keeper)
}
// InitGenesis performs genesis initialization for the bep3 module. It returns
// no validator updates.
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
var genesisState GenesisState
ModuleCdc.MustUnmarshalJSON(data, &genesisState)
InitGenesis(ctx, am.keeper, am.supplyKeeper, genesisState)
return []abci.ValidatorUpdate{}
}
// ExportGenesis returns the exported genesis state as raw bytes for the bep3
// module.
func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
gs := ExportGenesis(ctx, am.keeper)
return ModuleCdc.MustMarshalJSON(gs)
}
// BeginBlock returns the begin blocker for the bep3 module.
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper)
}
// EndBlock returns the end blocker for the bep3 module. It returns no validator updates.
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}
//____________________________________________________________________________
// GenerateGenesisState creates a randomized GenState of the bep3 module
func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
// ProposalContents doesn't return any content functions for governance proposals.
func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent {
return nil
}
// RandomizedParams returns nil because bep3 has no params.
func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange {
return simulation.ParamChanges(r)
}
// RegisterStoreDecoder registers a decoder for bep3 module's types
func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
sdr[StoreKey] = simulation.DecodeStore
}
// WeightedOperations returns the all the bep3 module operations with their respective weights.
func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper)
}

View File

@ -0,0 +1,39 @@
package simulation
import (
"bytes"
"fmt"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/kv"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/kava-labs/kava/x/bep3/types"
)
// DecodeStore unmarshals the KVPair's Value to the module's corresponding type
func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string {
switch {
case bytes.Equal(kvA.Key[:1], types.AtomicSwapKeyPrefix):
var swapA, swapB types.AtomicSwap
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &swapA)
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &swapB)
return fmt.Sprintf("%v\n%v", swapA, swapB)
case bytes.Equal(kvA.Key[:1], types.AssetSupplyKeyPrefix):
var supplyA, supplyB types.AssetSupply
cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &supplyA)
cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &supplyB)
return fmt.Sprintf("%s\n%s", supplyA, supplyB)
case bytes.Equal(kvA.Key[:1], types.AtomicSwapByBlockPrefix),
bytes.Equal(kvA.Key[:1], types.AtomicSwapLongtermStoragePrefix):
var bytesA tmbytes.HexBytes = kvA.Value
var bytesB tmbytes.HexBytes = kvA.Value
return fmt.Sprintf("%s\n%s", bytesA.String(), bytesB.String())
default:
panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1]))
}
}

View File

@ -0,0 +1,62 @@
package simulation
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
"github.com/tendermint/tendermint/libs/kv"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/bep3/types"
)
func makeTestCodec() (cdc *codec.Codec) {
cdc = codec.New()
sdk.RegisterCodec(cdc)
types.RegisterCodec(cdc)
return
}
func TestDecodeDistributionStore(t *testing.T) {
cdc := makeTestCodec()
oneCoin := sdk.NewCoin("coin", sdk.OneInt())
swap := types.NewAtomicSwap(sdk.Coins{oneCoin}, nil, 10, 100, nil, nil, "otherChainSender", "otherChainRec", 200, types.Completed, true, types.Outgoing)
supply := types.AssetSupply{Denom: "coin", IncomingSupply: oneCoin, OutgoingSupply: oneCoin, CurrentSupply: oneCoin, Limit: oneCoin}
bz := tmbytes.HexBytes([]byte{1, 2})
kvPairs := kv.Pairs{
kv.Pair{Key: types.AtomicSwapKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(swap)},
kv.Pair{Key: types.AssetSupplyKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(supply)},
kv.Pair{Key: types.AtomicSwapByBlockPrefix, Value: bz},
kv.Pair{Key: types.AtomicSwapByBlockPrefix, Value: bz},
kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}},
}
tests := []struct {
name string
expectedLog string
}{
{"AtomicSwap", fmt.Sprintf("%v\n%v", swap, swap)},
{"AssetSupply", fmt.Sprintf("%v\n%v", supply, supply)},
{"AtomicSwapByBlock", fmt.Sprintf("%s\n%s", bz, bz)},
{"AtomicSwapLongtermStorage", fmt.Sprintf("%s\n%s", bz, bz)},
{"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)
}
})
}
}

View File

@ -0,0 +1,168 @@
package simulation
import (
"fmt"
"math/rand"
"strings"
"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"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/bep3/types"
)
// Simulation parameter constants
const (
BnbDeputyAddress = "bnb_deputy_address"
MinBlockLock = "min_block_lock"
MaxBlockLock = "max_block_lock"
SupportedAssets = "supported_assets"
)
var (
MaxSupplyLimit = sdk.NewInt(1000000000000)
accs []simulation.Account
ConsistentDenoms = [3]string{"bnb", "xrp", "btc"}
)
// GenRandBnbDeputy randomized BnbDeputyAddress
func GenRandBnbDeputy(r *rand.Rand) simulation.Account {
acc, _ := simulation.RandomAcc(r, accs)
return acc
}
// GenMinBlockLock randomized MinBlockLock
func GenMinBlockLock(r *rand.Rand) int64 {
min := int(types.AbsoluteMinimumBlockLock)
max := int(types.AbsoluteMaximumBlockLock)
return int64(r.Intn(max-min) + min)
}
// GenMaxBlockLock randomized MaxBlockLock
func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 {
min := int(minBlockLock)
max := int(types.AbsoluteMaximumBlockLock)
return int64(r.Intn(max-min) + min)
}
// GenSupportedAssets gets randomized SupportedAssets
func GenSupportedAssets(r *rand.Rand) types.AssetParams {
var assets types.AssetParams
numAssets := (r.Intn(10) + 1)
for i := 0; i < numAssets; i++ {
denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3)))
asset := genSupportedAsset(r, denom)
assets = append(assets, asset)
}
// Add bnb, btc, or xrp as a supported asset for interactions with other modules
stableAsset := genSupportedAsset(r, ConsistentDenoms[r.Intn(3)])
assets = append(assets, stableAsset)
return assets
}
func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam {
coinID, _ := simulation.RandPositiveInt(r, sdk.NewInt(100000))
limit, _ := simulation.RandPositiveInt(r, MaxSupplyLimit)
return types.AssetParam{
Denom: denom,
CoinID: int(coinID.Int64()),
Limit: limit,
Active: true,
}
}
// RandomizedGenState generates a random GenesisState
func RandomizedGenState(simState *module.SimulationState) {
accs = simState.Accounts
bep3Genesis := loadRandomBep3GenState(simState)
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, bep3Genesis))
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(bep3Genesis)
authGenesis, totalCoins := loadAuthGenState(simState, bep3Genesis)
simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis)
// Update supply to match amount of coins in auth
var supplyGenesis supply.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis)
for _, deputyCoin := range totalCoins {
supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin...)
}
simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis)
}
func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState {
bnbDeputy := GenRandBnbDeputy(simState.Rand)
// min/max block lock are hardcoded to 50/100 for expected -NumBlocks=100
minBlockLock := int64(types.AbsoluteMinimumBlockLock)
maxBlockLock := minBlockLock * 2
var supportedAssets types.AssetParams
simState.AppParams.GetOrGenerate(
simState.Cdc, SupportedAssets, &supportedAssets, simState.Rand,
func(r *rand.Rand) { supportedAssets = GenSupportedAssets(r) },
)
bep3Genesis := types.GenesisState{
Params: types.Params{
BnbDeputyAddress: bnbDeputy.Address,
MinBlockLock: minBlockLock,
MaxBlockLock: maxBlockLock,
SupportedAssets: supportedAssets,
},
}
return bep3Genesis
}
func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.GenesisState) (
auth.GenesisState, []sdk.Coins) {
var authGenesis auth.GenesisState
simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis)
deputy, found := getAccount(authGenesis.Accounts, bep3Genesis.Params.BnbDeputyAddress)
if !found {
panic("deputy address not found in available accounts")
}
// Load total limit of each supported asset to deputy's account
var totalCoins []sdk.Coins
for _, asset := range bep3Genesis.Params.SupportedAssets {
assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.Limit))
if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin...)); err != nil {
panic(err)
}
totalCoins = append(totalCoins, assetCoin)
}
authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, deputy)
return authGenesis, totalCoins
}
// Return an account from a list of accounts that matches an address.
func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) {
for _, a := range accounts {
if a.GetAddress().Equals(addr) {
return a, true
}
}
return nil, false
}
// In a list of accounts, replace the first account found with the same address. If not found, append the account.
func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount {
newAccounts := accounts
for i, a := range accounts {
if a.GetAddress().Equals(acc.GetAddress()) {
newAccounts[i] = acc
return newAccounts
}
}
return append(newAccounts, acc)
}

View File

@ -0,0 +1,210 @@
package simulation
import (
"fmt"
"math"
"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"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/simulation"
appparams "github.com/kava-labs/kava/app/params"
"github.com/kava-labs/kava/x/bep3/keeper"
"github.com/kava-labs/kava/x/bep3/types"
)
var (
noOpMsg = simulation.NoOpMsg(types.ModuleName)
)
// Simulation operation weights constants
const (
OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_swap"
)
// 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,
) simulation.WeightedOperations {
var weightCreateAtomicSwap int
appParams.GetOrGenerate(cdc, OpWeightMsgCreateAtomicSwap, &weightCreateAtomicSwap, nil,
func(_ *rand.Rand) {
weightCreateAtomicSwap = appparams.DefaultWeightMsgCreateAtomicSwap
},
)
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightCreateAtomicSwap,
SimulateMsgCreateAtomicSwap(ak, k),
),
}
}
// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values
func SimulateMsgCreateAtomicSwap(ak auth.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 {
return noOpMsg, nil, nil
}
recipient, _ := simulation.RandomAcc(r, accs)
recipientOtherChain := simulation.RandStringOfLength(r, 43)
senderOtherChain := simulation.RandStringOfLength(r, 43)
// Generate cryptographically strong pseudo-random number
randomNumber, err := simulation.RandPositiveInt(r, sdk.NewInt(math.MaxInt64))
if err != nil {
return noOpMsg, nil, err
}
// Must use current blocktime instead of 'now' since initial blocktime was randomly generated
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)
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
}
coin := sdk.NewCoin(asset.Denom, amount)
coins := sdk.NewCoins(coin)
expectedIncome := coin.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,
)
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{senderAcc.GetAccountNumber()},
[]uint64{senderAcc.GetSequence()},
sender.PrivKey,
)
_, result, err := app.Deliver(tx)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
// If created, 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 {
// Claim future operation
executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2)
futureOp = simulation.FutureOperation{
BlockHeight: int(executionBlock),
Op: operationClaimAtomicSwap(ak, k, swapID, randomNumber.BigInt().Bytes()),
}
} else {
// Refund future operation
executionBlock := ctx.BlockHeight() + msg.HeightSpan
futureOp = simulation.FutureOperation{
BlockHeight: int(executionBlock),
Op: operationRefundAtomicSwap(ak, k, swapID),
}
}
return simulation.NewOperationMsg(msg, true, result.Log), []simulation.FutureOperation{futureOp}, nil
}
}
func operationClaimAtomicSwap(ak auth.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) {
simAccount, _ := simulation.RandomAcc(r, accs)
acc := ak.GetAccount(ctx, simAccount.Address)
msg := types.NewMsgClaimAtomicSwap(acc.GetAddress(), swapID, randomNumber)
fees, err := simulation.RandomFees(r, ctx, acc.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{acc.GetAccountNumber()},
[]uint64{acc.GetSequence()},
simAccount.PrivKey,
)
_, result, err := app.Deliver(tx)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, result.Log), nil, nil
}
}
func operationRefundAtomicSwap(ak auth.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) {
simAccount, _ := simulation.RandomAcc(r, accs)
acc := ak.GetAccount(ctx, simAccount.Address)
msg := types.NewMsgRefundAtomicSwap(acc.GetAddress(), swapID)
fees, err := simulation.RandomFees(r, ctx, acc.SpendableCoins(ctx.BlockTime()))
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
tx := helpers.GenTx(
[]sdk.Msg{msg},
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{acc.GetAccountNumber()},
[]uint64{acc.GetSequence()},
simAccount.PrivKey,
)
_, result, err := app.Deliver(tx)
if err != nil {
return simulation.NoOpMsg(types.ModuleName), nil, err
}
return simulation.NewOperationMsg(msg, true, result.Log), nil, nil
}
}

View File

@ -0,0 +1,46 @@
package simulation
import (
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/x/bep3/types"
)
const (
keyBnbDeputyAddress = "BnbDeputyAddress"
keyMinBlockLock = "MinBlockLock"
keyMaxBlockLock = "MaxBlockLock"
keySupportedAssets = "SupportedAssets"
)
// ParamChanges defines the parameters that can be modified by param change proposals
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
// We generate MinBlockLock first because the result is required by GenMaxBlockLock()
minBlockLockVal := GenMinBlockLock(r)
return []simulation.ParamChange{
simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress,
func(r *rand.Rand) string {
return fmt.Sprintf("\"%s\"", GenRandBnbDeputy(r).Address)
},
),
simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock,
func(r *rand.Rand) string {
return fmt.Sprintf("\"%d\"", minBlockLockVal)
},
),
simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock,
func(r *rand.Rand) string {
return fmt.Sprintf("\"%d\"", GenMaxBlockLock(r, minBlockLockVal))
},
),
simulation.NewSimParamChange(types.ModuleName, keySupportedAssets,
func(r *rand.Rand) string {
return fmt.Sprintf("\"%v\"", GenSupportedAssets(r))
},
),
}
}

Some files were not shown because too many files have changed in this diff Show More