[R4R] Testnet-5k proposal (#404)

* R4R: BEP3 module (#370)

* bep3 module scaffold from cosmos/scaffold

* Populated types, keeper with HTLT msgs, module params, and scaffolding for keys, and genesis

* added KavaHTLT struct, UpdateHTLT struct, resolved compilation errors

* refactored kavaHTLT struct <-> msgs

* Implemented params, refactored UpdateKavaHTLT to UpdateKHTLT interface

* Updated keeper with byTimeIndex methods

* HTLT creation flow

* adjustments in prep for repo config updates

* App moudle updated for bep3, MsgCreateHTLT tested, HTLT keeper methods tested

* Updated bep3 params to match spec

* tests for MsgRefundHTLT, MsgDepositHTLT, MsgClaimHTLT

* AddHtlt cli cmd, queryHtlts cmd, added conversion funcs for binance -> cosmos types, refactored MsgCreateHTLT from binance.AccAddress to sdk.AccAddress

* working edits related to bep3-deputy compatibility

* removed binance-chain go sdk dependency

* updated msg ValidateBasic() return to sdk.Error type

* implement MsgCalculateSwapID

* added MsgCalculateSwapID test, updated randomNumberHash type to []byte

* removed binance type conversions

* msg codec registration

* clean /types directory

* CLI cmds:create htlt, query htlt

* update keeper logic

* handle MsgCreateHTLT

* implement htlt type, msg types

* implement global chain types

* update querier

* added go-ethereum to go mod

* refactor QuerySwap to QueryHTLT

* update HTLTMsg to MsgCreateHTLT

* implemented htlt deposit

* add token transfer to MsgCreateHTLT

* implement refund, claim client txs

* add refund/claim cmds to tx cmd

* commiting go.sum for build

* implemented keeper claim logic

* add RandomNumberHash to create-htlt event

* implement refund keeper logic

* AddHTLT updated to CreateHTLT

* added params keeper

* updated params to single chain, added sample genesis file

* implemented htlt keeper param checks

* removed go-ethereum dependency

* updated go.sum

* housekeeping on keeper tests

* updated cli tx cmds

* ran go.tidy

* remove links from module readme

* updated coin construction in tests

* added expectedIncome checks in ValidateBasic()

* made ValidateAsset() more robust

* update param format for tests

* added basic HTLTByTime index

* implement abci, fix expectedIncome validation

* byTime index updated to blocks, added swap ID & expiration block to htlt

* added not-expired check to HTLT claims

* cross-chain mint/burn logic, htlt string type refactored to []byte

* fix bnb_deputy_address param

* remove abci panic

* cmn.HexBytes, byTime index iteration update, claim-mint logic update

* update genesis example

* general codebase cleaning

* renamed HTLT to AtomicSwap

* staging for PR

* updated naming conventions

* refactor + revisions

* removed code related to deposits & swap block index

* added timestamp validation comment

* post-refactor housekeeping

* post refactor housekeeping (keeper)

* remove GenesisAtomicSwap type

* refactor asset supply logic

* BeginBlocker expires swaps automatically

* param asset.limit type updated to sdk.Int

* remove claimed swaps from block index

* fix DefaultDeputyAddress

* removed BaseSwap

* revisions

* total genesis coins

* updated tx examples

* Automatically update fees for risky cdps (#381)

* wip: sketch implementation

* adding initial function to calcuate risky fees for cdps

* adding todo comment to fix the function arguments

* changing the function arguments

* adding multiplication, print error, change types

* get the number of periods, add comments and questios for code review

* adding specification notes

* removing old comment

* replace collateral with collateral denom

* remove todo and clarify comment

* Update x/cdp/keeper/fees.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/keeper/fees.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update go.sum

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/keeper/fees.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/keeper/fees.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/keeper/fees.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/abci.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* updating fees

* error handling and propogation

* fix collat denom variable

* fix build issues, error, variable names, parameter type

* Update x/cdp/abci.go

Use `err` as name instead of `e`

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/keeper/fees.go

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* fixing error variable name

* changing call to method to compute risky cdps fees

* changing the calcualation to select risky cdps to be based on normalized ratio

* adding skeleton for test methods, adding skeleton helper function for creating cdps for use in tests

* fixing function call to helper method

* fix assignment, calling function that returns two variables instead of one

* adding comment and fixing call to create cdps

* adding create cdps function and updating / fixing the test. one of the expected tests is failing, need to figure out why that is. added a todo question to note it

* logging the cdp object before and after updating. it seems that the fees are not set / written before or after

* adding interim changed

* updated

* fixing normalized ratio

* code cleanup

* changing print of accumulated fees

* removing debug code

* remove completed todo

* remove old variable

* remove spewing print statement

* remove dead todo

* adding note about prices for future

* remove dead code

* try to fix test

* fix types

* fix types

* fix context in call

* changing back as new version breaks a test

* fix

* cleanup, removing logging and old code

* remove dead code

* removing changes to cdp test

* Update x/cdp/keeper/fees.go

Remove old comment as suggested

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update x/cdp/spec/04_begin_block.md

Fix typo as suggested

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

Co-authored-by: Kevin Davis <karzak@users.noreply.github.com>

* Add Savings Rate (#365)

* fix: ensure cdp module accounts created at gensis

* feat: add savings rate

* chore: update alias

* fix: update default test param values

* chore: update spec for savings rate

* fix: add distribution time to genesis state

* fix: iterate over accounts using callback function

* feat: use seprate mod account for savings rate

* fix: remove mod account coins from total supply

* address review comments

* fix: genesis function initialization

* fix: update alias

* add comment about maintaining module account list

* feat: add genesis example

* R4R: bep3 module upgrades (#388)

* bep3 module scaffold from cosmos/scaffold

* Populated types, keeper with HTLT msgs, module params, and scaffolding for keys, and genesis

* added KavaHTLT struct, UpdateHTLT struct, resolved compilation errors

* refactored kavaHTLT struct <-> msgs

* Implemented params, refactored UpdateKavaHTLT to UpdateKHTLT interface

* Updated keeper with byTimeIndex methods

* HTLT creation flow

* adjustments in prep for repo config updates

* App moudle updated for bep3, MsgCreateHTLT tested, HTLT keeper methods tested

* Updated bep3 params to match spec

* tests for MsgRefundHTLT, MsgDepositHTLT, MsgClaimHTLT

* AddHtlt cli cmd, queryHtlts cmd, added conversion funcs for binance -> cosmos types, refactored MsgCreateHTLT from binance.AccAddress to sdk.AccAddress

* working edits related to bep3-deputy compatibility

* removed binance-chain go sdk dependency

* updated msg ValidateBasic() return to sdk.Error type

* implement MsgCalculateSwapID

* added MsgCalculateSwapID test, updated randomNumberHash type to []byte

* removed binance type conversions

* msg codec registration

* clean /types directory

* CLI cmds:create htlt, query htlt

* update keeper logic

* handle MsgCreateHTLT

* implement htlt type, msg types

* implement global chain types

* update querier

* added go-ethereum to go mod

* refactor QuerySwap to QueryHTLT

* update HTLTMsg to MsgCreateHTLT

* implemented htlt deposit

* add token transfer to MsgCreateHTLT

* implement refund, claim client txs

* add refund/claim cmds to tx cmd

* commiting go.sum for build

* implemented keeper claim logic

* add RandomNumberHash to create-htlt event

* implement refund keeper logic

* AddHTLT updated to CreateHTLT

* added params keeper

* updated params to single chain, added sample genesis file

* implemented htlt keeper param checks

* removed go-ethereum dependency

* updated go.sum

* housekeeping on keeper tests

* updated cli tx cmds

* ran go.tidy

* remove links from module readme

* updated coin construction in tests

* added expectedIncome checks in ValidateBasic()

* made ValidateAsset() more robust

* update param format for tests

* added basic HTLTByTime index

* implement abci, fix expectedIncome validation

* byTime index updated to blocks, added swap ID & expiration block to htlt

* added not-expired check to HTLT claims

* cross-chain mint/burn logic, htlt string type refactored to []byte

* fix bnb_deputy_address param

* remove abci panic

* cmn.HexBytes, byTime index iteration update, claim-mint logic update

* update genesis example

* general codebase cleaning

* renamed HTLT to AtomicSwap

* staging for PR

* updated naming conventions

* refactor + revisions

* removed code related to deposits & swap block index

* added timestamp validation comment

* post-refactor housekeeping

* post refactor housekeeping (keeper)

* remove GenesisAtomicSwap type

* refactor asset supply logic

* BeginBlocker expires swaps automatically

* param asset.limit type updated to sdk.Int

* remove claimed swaps from block index

* fix DefaultDeputyAddress

* removed BaseSwap

* revisions

* total genesis coins

* updated tx examples

* timestamp to unix

* add past timestamp limit

* update random number byte encoding

* add recipient_other_chain to AtomicSwap

* add TODO for timestamp arg parsing

* generate secure random numbers

* update tx cli

* keeper tests

* add bnb token

* bep3 params test set up, test CreateAtomicSwap

* swap table tests

* Revert "bep3 params test set up, test CreateAtomicSwap"

This reverts commits containing tests.

* use tmtime.Now()

* Kava distribution module (#387)

* wip: kavadist module structure

* feat: implement minting logic

* wip: sketch module

* wip: module level code

* wip: bug fixes

* wip: add tests

* wip: resolve todos and tidy

* fix: remove unused file

* address review comments

* fix: update genesis for guide (#394)

* add kava_dist to sample genesis file (#396)

* R4R: BEP3 module test suite (#395)

* refactor secure rng

* refactor common tests, implement keeper tests

* implement asset tests

* implement params, querier tests

* implement keeper swap tests

* refactor import naming conventions

* implement core types tests

* improve keeper swap tests

* implement genesis types test

* implement params test + revisions

* implement duplicate swap test

* implement duplicate swap ID test

* R4R: BEP3 additional features + module test suite (#397)

* update and reorder errors

* implement swap deletion block delay

* add swap deletion block delay, set up tests

* add secure random number gen

* implement AtomicSwapLongtermStorage index

* fix syntax error

* abci test updates

* implement handler test

* implement core genesis tests

* update asset supply logic

* implement functional asset supply

* pretty print atomic swaps

* requested revisions

* fix test suite post merge

* implement and integrate asset supply tests

* update import genesis, add storage duration param

* implement swap deletion ABCI test

* go mod tidy

* remove duplicated interface assertion

* add new bep3 param to contrib genesis file

* remove btc from supported assets

* revisions: LongtermStorageDuration param

* revisions: suite ctx, fix genesis, update contrib

* implement AssetSupply type, store key, keeper

* integrate supply and swaps; genesis, tests

* remove legacy comments

* requested revisions

* update alias

* Swagger Rest Automating Testing With Dredd  (#390)

* swagger testing and mods

* fixed a test

* fixed withdraw address

* adding script to start the chain

* fixed val rewards test

* fix outstanding rewards test

* fixed rewards

* hooks skeleton

* adding test file and hooks

* updates on the hooks working

* now creates a transaction and sends to the chain via rest api successfully

* small fix - now works

* 34 tests now passing successfully

* fix print statement error

* instructions on how to run the tests

* changing function names when to run the hooks

* adding instructions on how to setup and run the dredd tests

* removing large error output file

* removing binary file

* removing more output logging files

* creating a vote on a proposal to send to the blockchain

* adding instructions on how to setup chain

* adding function to get account number and sequence number

* adding send msg to blockchain method

* posting vote tx to blockchain - successfully prepares and sends vote to endpoint but endpoint returns 'inactive proposal with id'

* successfully depositing 600 stake to a proposal

* successfully depositing onto a proposal and then voting on it

* got another governance test working now after submitting a vote to the blockchain

* updating instructions on how to run

* fixed another voting test

* fixed deposits test

* fixed another gov test

* fix print line

* fix circle ci build issue with println

* improving instructions on how to build and run the hooks and dredd tests

* improving instructions on how to build and run the hooks and dredd tests

* finally fixed param change governance proposal test

* trying to unskip tests wip

* fixed gov/proposals test

* fixed another test

* fixed a slashing test

* fixed another redelegation test

* fixed another unbonding delegation test

* fixed more staking tests

* fixed another staking test

* fixed another test

* fixed more tests. 50 now passing, 15 failing

* fixed mislabeled variable

* managed to fix unjail test

* fixed bank acct transfers test

* change certain types from number to string to match the output, typo fix

* another typo fix

* fixed delegation test

* finally figured out and fixed the latest blocks types mismatch - fixed the test

* fixed staking delegators validators test

* removed and noted unimplemented tests from yaml file. fixed blocks height test

* fixed transcations test

* adding functionality to send transfer of coins to blockchain, and to send delegations

* updating the yaml to line up with a valid message format

* added delegation method

* adding test results showing 57 are now passing and only 5 failing

* remove test yaml file from pull req

* testing file updates

* adding test memo

* added undelegation hook method - fixed unbonding delegation test

* fixed the get tx from hash test

* adding not if you encounter validator set is different errors how to fix. 59 tests now passsing, 3 failing

* adding test results showing 59 passing, 3 failing

* finally fixed encode test - 60 tests now passing only 2 failing

* adding test results 60 passing 1 failing

* more test updates

* finally fixed decode test - 61 tests now passing only 1 failing

* test results 61 tests passing 1 failing

* remove dead code

* all 62 tests are now passingga swagger.yaml  0 failing

* used for testing and generating transactions and testing hooks

* updating run instructions

* more instructions updates

* updating the test file

* adding note on reading from a file

* refactoring code and cleanup

* refactoring getting the keybase

* code cleanup for address, keyname methods, remove unused code

* more code cleanup around addresses

* updating the instructions on how to run the dredd tests

* adding comment

* adding additional requirements to the go.mod dependency file

* remove hardcoded home directory, read using os golang library

* increase timeout in example run script

* remove hardcoded home directory

* reordering commands to get rid of errors if key directory is deleted

* changing to use temporary directory

* updating dredd timeout time

* finally managed to get the script wroking using a temporary directory instead of the default

* adding notes and comments

* changing to use a temp directory instead of default directory

* remove un-needed file

* rename debugging tools folder

* adding instructions to install dredd and npm

* Update swagger-ui/startchain.sh

Send output to dev null not to console

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* Update swagger-ui/startchain.sh

Send output to dev null not to console

Co-Authored-By: Kevin Davis <karzak@users.noreply.github.com>

* adding new version of test.go to setup the chain

* adding todo to update instructions for new workflow

* updating script to start and setup the chain

* updating the transaction hash test

* update the start chain script to setup the chain correctly

* add the script to stop the chain and the rest server

* updated the instructions for the new workflow so that all the tests pass the first time

* updated the instructions on how to run the tests

* update instructions for printing logs or not

* updating the startchain script to add messages when starting the rest server and preparing transactions

* adding print messages when stop chain is completed

* updating test results to just include test output and not the debug log statements

* cleaning up the messages that are printed to the user

* moving files to their own directory

* build go test file and remove previous binary

* move instructions

* updating instructions now that test file is auto built

* building, running dredd tests, propagating error code, shut down blockchain all in one script

* fix object type to array type for block latests

* cleaning up the script

* rename script as it now does all the setup, test running, shutdown, and cleanup

* update instructions for new workflow

* adding a shell script to call from the makefile

* adding a make command to build and run all the dredd tests

* update instructions to run using make

* updated code review comment

* minor update to instructions

* update remove file command so doesn't print an error if the file has already been deleted

* renaming folder and test

* adjust code comment

* removing example test results

* updating instructions to remove reference to the test results

* remove old hooks file

* remove obsolete code comment

* remove swagger file, will change references to the other one

* remove shell script, will now use the one called from make instead

* renaming as underscore messes up go build

* clean up script, fix return code issues

* cleanup output file

* fix object to array issue

* add comments to explain functionality

* use variables for kvd home and kvcli home, check for errors

* change the kvcli home directory. need to take this from command line

* take kvcli from command line parameter to golang file

* take kvcli directory from command line parameter

Co-authored-by: John Maheswaran <john@kava.io>
Co-authored-by: Kevin Davis <karzak@users.noreply.github.com>

* R4R: Update BEP3 rest endpoints + format example requests (#402)

* update and reorder errors

* implement swap deletion block delay

* add swap deletion block delay, set up tests

* add secure random number gen

* implement AtomicSwapLongtermStorage index

* fix syntax error

* abci test updates

* implement handler test

* implement core genesis tests

* update asset supply logic

* implement functional asset supply

* pretty print atomic swaps

* requested revisions

* fix test suite post merge

* implement and integrate asset supply tests

* update import genesis, add storage duration param

* implement swap deletion ABCI test

* go mod tidy

* remove duplicated interface assertion

* add new bep3 param to contrib genesis file

* remove btc from supported assets

* revisions: LongtermStorageDuration param

* revisions: suite ctx, fix genesis, update contrib

* implement AssetSupply type, store key, keeper

* integrate supply and swaps; genesis, tests

* remove legacy comments

* requested revisions

* update alias

* rest queries

* implement BEP3 REST txs

* draft rest server readme + example json files

* tested all swap rest examples

* implement query swaps rest endpoint

* feat: update genesis examples

* fix: use post instead of put (#405)

Co-authored-by: Denali Marsh <denali@kava.io>
Co-authored-by: John Maheswaran <jmaheswaran@users.noreply.github.com>
Co-authored-by: John Maheswaran <john@kava.io>
This commit is contained in:
Kevin Davis 2020-03-27 22:54:00 -04:00 committed by GitHub
parent 7eede47769
commit 82f637649c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
111 changed files with 10262 additions and 515 deletions

View File

@ -125,6 +125,9 @@ test-all: build
test:
@go test ./...
test_dredd:
rest_test/./run_all_tests_from_make.sh
# 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:

View File

@ -5,7 +5,9 @@ import (
"os"
"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/kavadist"
"github.com/kava-labs/kava/x/pricefeed"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
@ -62,6 +64,8 @@ var (
auction.AppModuleBasic{},
cdp.AppModuleBasic{},
pricefeed.AppModuleBasic{},
bep3.AppModuleBasic{},
kavadist.AppModuleBasic{},
)
// module account permissions
@ -76,6 +80,9 @@ 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},
}
)
@ -105,6 +112,8 @@ type App struct {
auctionKeeper auction.Keeper
cdpKeeper cdp.Keeper
pricefeedKeeper pricefeed.Keeper
bep3Keeper bep3.Keeper
kavadistKeeper kavadist.Keeper
// the module manager
mm *module.Manager
@ -128,7 +137,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
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,
auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey,
kavadist.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
@ -153,6 +163,8 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
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)
// add keepers
app.accountKeeper = auth.NewAccountKeeper(
@ -229,7 +241,6 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
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],
@ -242,7 +253,21 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
app.pricefeedKeeper,
app.auctionKeeper,
app.supplyKeeper,
app.accountKeeper,
cdp.DefaultCodespace)
app.bep3Keeper = bep3.NewKeeper(
app.cdc,
keys[bep3.StoreKey],
app.supplyKeeper,
bep3Subspace,
bep3.DefaultCodespace)
app.kavadistKeeper = kavadist.NewKeeper(
app.cdc,
keys[kavadist.StoreKey],
kavadistSubspace,
app.supplyKeeper,
kavadist.DefaultCodespace,
)
// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
@ -264,15 +289,16 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
auction.NewAppModule(app.auctionKeeper, app.supplyKeeper),
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper),
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper, app.supplyKeeper),
pricefeed.NewAppModule(app.pricefeedKeeper),
bep3.NewAppModule(app.bep3Keeper, app.supplyKeeper),
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
)
// 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)
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, bep3.ModuleName)
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName)
@ -285,7 +311,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
auth.ModuleName, 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, // TODO is this order ok?
pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok?
)
app.mm.RegisterInvariants(&app.crisisKeeper)
@ -305,9 +331,10 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
distr.NewAppModule(app.distrKeeper, app.supplyKeeper),
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?
cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper, app.supplyKeeper), // TODO how is the order be decided here? Is this order correct?
pricefeed.NewAppModule(app.pricefeedKeeper),
auction.NewAppModule(app.auctionKeeper, app.supplyKeeper),
kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper),
)
app.sm.RegisterStoreDecoders()

View File

@ -28,7 +28,9 @@ 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/kavadist"
"github.com/kava-labs/kava/x/pricefeed"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
)
@ -67,6 +69,8 @@ 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 }
// This calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states
func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp {

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,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,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"
}

6
go.mod
View File

@ -3,14 +3,18 @@ module github.com/kava-labs/kava
go 1.13
require (
github.com/btcsuite/btcd v0.20.1-beta // indirect
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect
github.com/gogo/protobuf v1.3.0
github.com/gorilla/mux v1.7.3
github.com/raviqqe/liche v0.0.0-20191208214012-e144e0808a75 // indirect
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa
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
golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 // indirect
gopkg.in/yaml.v2 v2.2.4
)

66
go.sum
View File

@ -18,11 +18,13 @@ github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
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=
@ -36,16 +38,11 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
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/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.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc=
github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY=
github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI=
@ -59,8 +56,6 @@ 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/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=
@ -72,8 +67,6 @@ 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=
@ -81,7 +74,6 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV
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=
@ -145,11 +137,6 @@ 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=
@ -158,19 +145,11 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
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/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/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@ -187,8 +166,6 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
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/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/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -212,21 +189,13 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
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/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/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/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/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=
@ -242,7 +211,6 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL
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.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=
@ -272,26 +240,15 @@ github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoM
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/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/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=
@ -307,7 +264,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
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-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-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -320,9 +276,7 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r
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-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-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=
@ -338,12 +292,8 @@ golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5h
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-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-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=
@ -363,7 +313,6 @@ google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoA
google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
@ -378,10 +327,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

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,133 @@
#! /bin/bash
# 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
# 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
# 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
# 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 swagger-ui/swagger.yaml 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,349 @@
package main
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"os"
"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/tendermint/go-amino"
)
func init() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
app.SetBip44CoinType(config)
config.Seal()
}
func main() {
sendProposal()
sendDeposit()
sendVote()
sendDelegation()
sendUndelegation()
sendCoins()
sendProposal()
sendDeposit()
sendVote()
sendDelegation()
sendUndelegation()
sendCoins()
}
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)
txBldr := auth.NewTxBuilderFromCLI().
WithTxEncoder(authclient.GetTxEncoder(cdc)).WithChainID("testing").
WithKeybase(keybase).WithAccountNumber(accountNumber).
WithSequence(sequenceNumber)
// 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",
},
)
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"

File diff suppressed because it is too large Load Diff

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

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

@ -0,0 +1,242 @@
package bep3_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
tmtime "github.com/tendermint/tendermint/types/time"
"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 []cmn.HexBytes
randomNumbers []cmn.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 []cmn.HexBytes
var randomNumbers []cmn.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))
}

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

@ -0,0 +1,160 @@
// nolint
// DO NOT EDIT - generated by aliasgen tool (github.com/rhuairahrighairidh/aliasgen)
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
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
CodeAssetNotActive = types.CodeAssetNotActive
CodeAssetNotSupported = types.CodeAssetNotSupported
CodeAssetSupplyNotFound = types.CodeAssetSupplyNotFound
CodeAtomicSwapAlreadyExists = types.CodeAtomicSwapAlreadyExists
CodeAtomicSwapNotFound = types.CodeAtomicSwapNotFound
CodeExceedsAvailableSupply = types.CodeExceedsAvailableSupply
CodeExceedsSupplyLimit = types.CodeExceedsSupplyLimit
CodeInvalidClaimSecret = types.CodeInvalidClaimSecret
CodeInvalidCurrentSupply = types.CodeInvalidCurrentSupply
CodeInvalidHeightSpan = types.CodeInvalidHeightSpan
CodeInvalidIncomingSupply = types.CodeInvalidIncomingSupply
CodeInvalidOutgoingSupply = types.CodeInvalidOutgoingSupply
CodeInvalidTimestamp = types.CodeInvalidTimestamp
CodeSwapNotClaimable = types.CodeSwapNotClaimable
CodeSwapNotRefundable = types.CodeSwapNotRefundable
Completed = types.Completed
CreateAtomicSwap = types.CreateAtomicSwap
DefaultCodespace = types.DefaultCodespace
DefaultLongtermStorageDuration = types.DefaultLongtermStorageDuration
DefaultParamspace = types.DefaultParamspace
DepositAtomicSwap = types.DepositAtomicSwap
EventTypeClaimAtomicSwap = types.EventTypeClaimAtomicSwap
EventTypeCreateAtomicSwap = types.EventTypeCreateAtomicSwap
EventTypeDepositAtomicSwap = types.EventTypeDepositAtomicSwap
EventTypeRefundAtomicSwap = types.EventTypeRefundAtomicSwap
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
CodeType = types.CodeType
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
Swap = types.Swap
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"
"github.com/cosmos/cosmos-sdk/client/context"
"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(client.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)
},
}
}

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

@ -0,0 +1,181 @@
package cli
import (
"encoding/hex"
"fmt"
"strconv"
"strings"
"github.com/cosmos/cosmos-sdk/client"
"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/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(client.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 {
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI().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 {
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI().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 {
cliCtx := context.NewCLIContext().WithCodec(cdc)
txBldr := auth.NewTxBuilderFromCLI().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})
},
}
}

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

@ -0,0 +1,164 @@
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,46 @@
package rest
import (
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/context"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
cmn "github.com/tendermint/tendermint/libs/common"
)
// 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 cmn.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 cmn.HexBytes `json:"swap_id" yaml:"swap_id"`
RandomNumber cmn.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 cmn.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))
}

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

@ -0,0 +1,89 @@
package bep3
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// 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 {
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:
errMsg := fmt.Sprintf("unrecognized %s message type: %T", ModuleName, msg)
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}
// handleMsgCreateAtomicSwap handles requests to create a new AtomicSwap
func handleMsgCreateAtomicSwap(ctx sdk.Context, k Keeper, msg MsgCreateAtomicSwap) sdk.Result {
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 err.Result()
}
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(),
}
}
// handleMsgClaimAtomicSwap handles requests to claim funds in an active AtomicSwap
func handleMsgClaimAtomicSwap(ctx sdk.Context, k Keeper, msg MsgClaimAtomicSwap) sdk.Result {
err := k.ClaimAtomicSwap(ctx, msg.From, msg.SwapID, msg.RandomNumber)
if err != nil {
return err.Result()
}
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(),
}
}
// handleMsgRefundAtomicSwap handles requests to refund an active AtomicSwap
func handleMsgRefundAtomicSwap(ctx sdk.Context, k Keeper, msg MsgRefundAtomicSwap) sdk.Result {
err := k.RefundAtomicSwap(ctx, msg.From, msg.SwapID)
if err != nil {
return err.Result()
}
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(),
}
}

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

@ -0,0 +1,132 @@
package bep3_test
import (
"encoding/hex"
"fmt"
"strings"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
tmtime "github.com/tendermint/tendermint/types/time"
"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() (cmn.HexBytes, cmn.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 := suite.handler(suite.ctx, msg)
suite.True(res.IsOK())
}
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 := suite.handler(suite.ctx, badMsg)
suite.False(badRes.IsOK())
suite.True(strings.Contains(badRes.Log, fmt.Sprintf("AtomicSwap %s was not found", hex.EncodeToString(badSwapID))))
// Add an atomic swap before attempting new claim msg
swapID, randomNumber := suite.AddAtomicSwap()
msg := bep3.NewMsgClaimAtomicSwap(suite.addrs[0], swapID, randomNumber)
res := suite.handler(suite.ctx, msg)
suite.True(res.IsOK())
}
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 := suite.handler(suite.ctx, badMsg)
suite.False(badRes.IsOK())
suite.True(strings.Contains(badRes.Log, fmt.Sprintf("AtomicSwap %s was not found", hex.EncodeToString(badSwapID))))
// 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 := suite.handler(suite.ctx, msg)
suite.True(strings.Contains(res1.Log, "atomic swap is still active and cannot be refunded"))
suite.False(res1.IsOK())
// Expire the atomic swap with begin blocker and attempt refund
laterCtx := suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400)
bep3.BeginBlocker(laterCtx, suite.keeper)
res2 := suite.handler(laterCtx, msg)
suite.True(res2.IsOK())
}
func (suite *HandlerTestSuite) TestInvalidMsg() {
res := suite.handler(suite.ctx, sdk.NewTestMsg())
suite.False(res.IsOK())
suite.True(strings.Contains(res.Log, "unrecognized bep3 message type"))
}
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
}

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

@ -0,0 +1,113 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"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) sdk.Error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return types.ErrAssetNotSupported(k.codespace, coin.Denom)
}
// Resulting current supply must be under asset's limit
if !supply.Limit.IsGTE(supply.CurrentSupply.Add(coin)) {
return types.ErrExceedsSupplyLimit(k.codespace, 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) sdk.Error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return types.ErrAssetNotSupported(k.codespace, 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 types.ErrInvalidCurrentSupply(k.codespace, 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) sdk.Error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return types.ErrAssetNotSupported(k.codespace, 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 types.ErrExceedsSupplyLimit(k.codespace, 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) sdk.Error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return types.ErrAssetNotSupported(k.codespace, 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 types.ErrInvalidIncomingSupply(k.codespace, 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) sdk.Error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return types.ErrAssetNotSupported(k.codespace, coin.Denom)
}
// Result of (outgoing + amount) must be less than current supply
if !supply.CurrentSupply.IsGTE(supply.OutgoingSupply.Add(coin)) {
return types.ErrExceedsAvailableSupply(k.codespace, 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) sdk.Error {
supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom))
if !found {
return types.ErrAssetNotSupported(k.codespace, 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 types.ErrInvalidOutgoingSupply(k.codespace, 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))
}

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

@ -0,0 +1,223 @@
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
codespace sdk.CodespaceType
}
// NewKeeper creates a bep3 keeper
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.SupplyKeeper, paramstore subspace.Subspace, codespace sdk.CodespaceType) Keeper {
if addr := sk.GetModuleAddress(types.ModuleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
}
keeper := Keeper{
key: key,
cdc: cdc,
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
supplyKeeper: sk,
codespace: codespace,
}
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))
}

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

@ -0,0 +1,75 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"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) sdk.Error {
asset, found := k.GetAssetByDenom(ctx, coin.Denom)
if !found {
return types.ErrAssetNotSupported(k.codespace, coin.Denom)
}
if !asset.Active {
return types.ErrAssetNotActive(k.codespace, asset.Denom)
}
return nil
}

View File

@ -0,0 +1,141 @@
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 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 sdk.CodeType
expectPass bool
}{
{
"normal",
args{
coin: c("bnb", 1),
},
sdk.CodeType(0),
true,
},
{
"asset not supported",
args{
coin: c("bad", 1),
},
types.CodeAssetNotSupported,
false,
},
{
"asset not active",
args{
coin: c("inc", 1),
},
types.CodeAssetNotActive,
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.NoError(err)
} else {
suite.Error(err)
suite.Equal(tc.expectedError, err.Result().Code)
}
})
}
}
func TestParamsTestSuite(t *testing.T) {
suite.Run(t, new(ParamsTestSuite))
}

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

@ -0,0 +1,100 @@
package keeper
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/bep3/types"
abci "github.com/tendermint/tendermint/abci/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) {
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, sdk.ErrUnknownRequest("unknown bep3 query endpoint")
}
}
}
func queryAssetSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
// Decode request
var requestParams types.QueryAssetSupply
err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
assetSupply, found := keeper.GetAssetSupply(ctx, []byte(requestParams.Denom))
if !found {
return nil, sdk.ErrInternal("Not found")
}
// Encode results
bz, err := codec.MarshalJSONIndent(keeper.cdc, assetSupply)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
func queryAtomicSwap(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
// Decode request
var requestParams types.QueryAtomicSwapByID
err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams)
if err != nil {
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
}
// Lookup atomic swap
atomicSwap, found := keeper.GetAtomicSwap(ctx, requestParams.SwapID)
if !found {
return nil, sdk.ErrInternal("Not found")
}
// Encode results
bz, err := codec.MarshalJSONIndent(keeper.cdc, atomicSwap)
if err != nil {
return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error()))
}
return bz, nil
}
func queryAtomicSwaps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var swaps types.AtomicSwaps
keeper.IterateAtomicSwaps(ctx, func(s types.AtomicSwap) bool {
swaps = append(swaps, s)
return false
})
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, swaps)
if err2 != nil {
return nil, sdk.ErrInternal("could not marshal result to JSON")
}
return bz, nil
}
// query params in the bep3 store
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.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 bz, nil
}

View File

@ -0,0 +1,167 @@
package keeper_test
import (
"encoding/hex"
"strings"
"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"
cmn "github.com/tendermint/tendermint/libs/common"
tmtime "github.com/tendermint/tendermint/types/time"
)
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 []cmn.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 []cmn.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(cmn.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))
}

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

@ -0,0 +1,266 @@
package keeper
import (
"bytes"
"encoding/hex"
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"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) sdk.Error {
// Confirm that this is not a duplicate swap
swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain)
_, found := k.GetAtomicSwap(ctx, swapID)
if found {
return types.ErrAtomicSwapAlreadyExists(k.codespace, 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 types.ErrInvalidHeightSpan(k.codespace, 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 types.ErrInvalidTimestamp(k.codespace)
}
// Sanity check on recipient address
if recipient.Empty() {
return sdk.ErrInvalidAddress("invalid (empty) recipient address")
}
if len(amount) != 1 {
return sdk.ErrInternal("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])
if err != nil {
return err
}
case types.Outgoing:
err := k.IncrementOutgoingAssetSupply(ctx, amount[0])
if err != nil {
return err
}
default:
return sdk.ErrInternal("invalid swap direction")
}
// 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)
// Emit 'create_atomic_swap' event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCreateAtomicSwap,
sdk.NewAttribute(types.AttributeKeySender, fmt.Sprintf("%s", atomicSwap.Sender)),
sdk.NewAttribute(types.AttributeKeyRecipient, fmt.Sprintf("%s", atomicSwap.Recipient)),
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, fmt.Sprintf("%s", hex.EncodeToString(atomicSwap.GetSwapID()))),
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, fmt.Sprintf("%s", hex.EncodeToString(atomicSwap.RandomNumberHash))),
sdk.NewAttribute(types.AttributeKeyTimestamp, fmt.Sprintf("%d", atomicSwap.Timestamp)),
sdk.NewAttribute(types.AttributeKeySenderOtherChain, fmt.Sprintf("%s", atomicSwap.SenderOtherChain)),
sdk.NewAttribute(types.AttributeKeyExpireHeight, fmt.Sprintf("%d", atomicSwap.ExpireHeight)),
sdk.NewAttribute(types.AttributeKeyAmount, fmt.Sprintf("%s", atomicSwap.Amount[0].String())),
sdk.NewAttribute(types.AttributeKeyExpectedIncome, fmt.Sprintf("%s", expectedIncome)),
sdk.NewAttribute(types.AttributeKeyDirection, fmt.Sprintf("%s", atomicSwap.Direction.String())),
),
)
k.SetAtomicSwap(ctx, atomicSwap)
k.InsertIntoByBlockIndex(ctx, atomicSwap)
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) sdk.Error {
atomicSwap, found := k.GetAtomicSwap(ctx, swapID)
if !found {
return types.ErrAtomicSwapNotFound(k.codespace, swapID)
}
// Only open atomic swaps can be claimed
if atomicSwap.Status != types.Open {
return types.ErrSwapNotClaimable(k.codespace)
}
// 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 types.ErrInvalidClaimSecret(k.codespace, hashedSecret, atomicSwap.GetSwapID())
}
switch atomicSwap.Direction {
case types.Incoming:
err := k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
return err
}
err = k.IncrementCurrentAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
return err
}
case types.Outgoing:
err := k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
return err
}
err = k.DecrementCurrentAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
return err
}
default:
return sdk.ErrInternal("invalid swap direction")
}
// Send intended recipient coins
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount)
if err != nil {
return err
}
// Emit 'claim_atomic_swap' event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeClaimAtomicSwap,
sdk.NewAttribute(types.AttributeKeyClaimSender, fmt.Sprintf("%s", from)),
sdk.NewAttribute(types.AttributeKeyRecipient, fmt.Sprintf("%s", atomicSwap.Recipient)),
sdk.NewAttribute(types.AttributeKeyAtomicSwapID, fmt.Sprintf("%s", hex.EncodeToString(atomicSwap.GetSwapID()))),
sdk.NewAttribute(types.AttributeKeyRandomNumberHash, fmt.Sprintf("%s", hex.EncodeToString(atomicSwap.RandomNumberHash))),
sdk.NewAttribute(types.AttributeKeyRandomNumber, fmt.Sprintf("%s", hex.EncodeToString(randomNumber))),
),
)
// 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)
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) sdk.Error {
atomicSwap, found := k.GetAtomicSwap(ctx, swapID)
if !found {
return types.ErrAtomicSwapNotFound(k.codespace, swapID)
}
// Only expired swaps may be refunded
if atomicSwap.Status != types.Expired {
return types.ErrSwapNotRefundable(k.codespace)
}
switch atomicSwap.Direction {
case types.Incoming:
err := k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
return err
}
case types.Outgoing:
err := k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0])
if err != nil {
return err
}
default:
return sdk.ErrInternal("invalid swap direction")
}
// Refund coins to original swap sender
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount)
if err != nil {
return err
}
// 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))),
),
)
// Complete swap
atomicSwap.Status = types.Completed
atomicSwap.ClosedBlock = ctx.BlockHeight()
k.SetAtomicSwap(ctx, atomicSwap)
// Transition to longterm storage
k.InsertIntoLongtermStorage(ctx, atomicSwap)
return nil
}
// UpdateExpiredAtomicSwaps finds all AtomicSwaps that are past (or at) their ending times and expires them.
func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) sdk.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)
for _, id := range expiredSwaps {
swap, _ := k.GetAtomicSwap(ctx, id)
swap.Status = types.Expired
k.SetAtomicSwap(ctx, swap)
k.RemoveFromByBlockIndex(ctx, swap)
}
return nil
}
// DeleteClosedAtomicSwapsFromLongtermStorage removes swaps one week after completion
func (k Keeper) DeleteClosedAtomicSwapsFromLongtermStorage(ctx sdk.Context) sdk.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
}

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

@ -0,0 +1,696 @@
package keeper_test
import (
"fmt"
"testing"
"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"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
tmtime "github.com/tendermint/tendermint/types/time"
)
type AtomicSwapTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
deputy sdk.AccAddress
addrs []sdk.AccAddress
timestamps []int64
randomNumberHashes []cmn.HexBytes
swapIDs []cmn.HexBytes
randomNumbers []cmn.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 []cmn.HexBytes
var randomNumbers []cmn.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.Swap(
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))
}

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

@ -0,0 +1,136 @@
package bep3
import (
"encoding/json"
"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/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"
)
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
)
// 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
supplyKeeper SupplyKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(keeper Keeper, supplyKeeper SupplyKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: keeper,
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{}
}

17
x/bep3/spec/README.md Normal file
View File

@ -0,0 +1,17 @@
# bep3 module specification
## Abstract
<!-- TODO: Create a abstract definition of what this module does, what functionality does it enable and how it can be used. -->
## Contents
// TODO: Create the below files if they are needed.
<!-- 1. **[Concepts](01_concepts.md)**
2. **[State](02_state.md)**
3. **[Messages](03_messages.md)**
4. **[Begin-Block](04_begin_block.md)**
5. **[End-Block](06_end_bloc.md)**
6. **[05_hooks](06_hooks.md)**
7. **[Events](07_events.md)**
8. **[Parameters](08_params.md)** -->

50
x/bep3/types/asset.go Normal file
View File

@ -0,0 +1,50 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// AssetSupply contains information about an asset's supply
type AssetSupply struct {
Denom string `json:"denom" yaml:"denom"`
IncomingSupply sdk.Coin `json:"incoming_supply" yaml:"incoming_supply"`
OutgoingSupply sdk.Coin `json:"outgoing_supply" yaml:"outgoing_supply"`
CurrentSupply sdk.Coin `json:"current_supply" yaml:"current_supply"`
Limit sdk.Coin `json:"limit" yaml:"limit"`
}
// NewAssetSupply initializes a new AssetSupply
func NewAssetSupply(denom string, incomingSupply, outgoingSupply, currentSupply, limit sdk.Coin) AssetSupply {
return AssetSupply{
Denom: denom,
IncomingSupply: incomingSupply,
OutgoingSupply: outgoingSupply,
CurrentSupply: currentSupply,
Limit: limit,
}
}
// String implements stringer
func (a AssetSupply) String() string {
return fmt.Sprintf("Asset Supply"+
"\n Denom: %s"+
"\n Incoming supply: %s"+
"\n Outgoing supply: %s"+
"\n Current supply: %s"+
"\n Limit: %s"+
a.Denom, a.IncomingSupply, a.OutgoingSupply, a.CurrentSupply, a.Limit)
}
// AssetSupplies is a slice of AssetSupply
type AssetSupplies []AssetSupply
// String implements stringer
func (supplies AssetSupplies) String() string {
out := ""
for _, supply := range supplies {
out += supply.String() + "\n"
}
return out
}

20
x/bep3/types/codec.go Normal file
View File

@ -0,0 +1,20 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
)
var ModuleCdc = codec.New()
func init() {
RegisterCodec(ModuleCdc)
}
// RegisterCodec registers concrete types on amino
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*Swap)(nil), nil)
cdc.RegisterConcrete(MsgCreateAtomicSwap{}, "bep3/MsgCreateAtomicSwap", nil)
cdc.RegisterConcrete(MsgRefundAtomicSwap{}, "bep3/MsgRefundAtomicSwap", nil)
cdc.RegisterConcrete(MsgClaimAtomicSwap{}, "bep3/MsgClaimAtomicSwap", nil)
}

View File

@ -0,0 +1,35 @@
package types_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/bep3/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
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 atomicSwaps(count int) types.AtomicSwaps {
var swaps types.AtomicSwaps
for i := 0; i < count; i++ {
swap := atomicSwap(i)
swaps = append(swaps, swap)
}
return swaps
}
func atomicSwap(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)
swap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash, expireOffset, timestamp, kavaAddrs[0],
kavaAddrs[1], binanceAddrs[0].String(), binanceAddrs[1].String(), 0, types.Open, true, types.Incoming)
return swap
}

118
x/bep3/types/errors.go Normal file
View File

@ -0,0 +1,118 @@
package types
import (
"encoding/hex"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
cmn "github.com/tendermint/tendermint/libs/common"
)
// CodeType is the local code type
type CodeType = sdk.CodeType
const (
// DefaultCodespace default bep3 codespace
DefaultCodespace sdk.CodespaceType = ModuleName
CodeInvalidTimestamp CodeType = 1
CodeInvalidHeightSpan CodeType = 2
CodeAssetNotSupported CodeType = 3
CodeAssetNotActive CodeType = 4
CodeAssetSupplyNotFound CodeType = 5
CodeExceedsSupplyLimit CodeType = 6
CodeExceedsAvailableSupply CodeType = 7
CodeInvalidCurrentSupply CodeType = 8
CodeInvalidIncomingSupply CodeType = 9
CodeInvalidOutgoingSupply CodeType = 10
CodeInvalidClaimSecret CodeType = 11
CodeAtomicSwapAlreadyExists CodeType = 12
CodeAtomicSwapNotFound CodeType = 13
CodeSwapNotRefundable CodeType = 14
CodeSwapNotClaimable CodeType = 15
)
// ErrInvalidTimestamp error for when an timestamp is outside of bounds. Assumes block time of 10 seconds.
func ErrInvalidTimestamp(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeInvalidTimestamp, fmt.Sprintf("Timestamp can neither be 15 minutes ahead of the current time, nor 30 minutes later"))
}
// ErrInvalidHeightSpan error a proposed height span is outside of lock time range
func ErrInvalidHeightSpan(codespace sdk.CodespaceType, heightspan int64, minLockTime int64, maxLockTime int64) sdk.Error {
return sdk.NewError(codespace, CodeInvalidHeightSpan, fmt.Sprintf("height span %d is outside acceptable range %d - %d", heightspan, minLockTime, maxLockTime))
}
// ErrAssetNotSupported error for when an asset is not supported
func ErrAssetNotSupported(codespace sdk.CodespaceType, denom string) sdk.Error {
return sdk.NewError(codespace, CodeAssetNotSupported, fmt.Sprintf("asset %s is not on the list of supported assets", denom))
}
// ErrAssetNotActive error for when an asset is currently inactive
func ErrAssetNotActive(codespace sdk.CodespaceType, denom string) sdk.Error {
return sdk.NewError(codespace, CodeAssetNotActive, fmt.Sprintf("asset %s is currently inactive", denom))
}
// ErrAssetSupplyNotFound error for when an asset's supply is not found in the store
func ErrAssetSupplyNotFound(codespace sdk.CodespaceType, denom string) sdk.Error {
return sdk.NewError(codespace, CodeAssetSupplyNotFound, fmt.Sprintf("%s asset supply not found in store", denom))
}
// ErrExceedsSupplyLimit error for when the proposed supply increase would put the supply above limit
func ErrExceedsSupplyLimit(codespace sdk.CodespaceType, increase, current, limit sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeExceedsSupplyLimit,
fmt.Sprintf("a supply increase of %s puts current asset supply %s over supply limit %s", increase, current, limit))
}
// ErrExceedsAvailableSupply error for when the proposed outgoing amount exceeds the total available supply
func ErrExceedsAvailableSupply(codespace sdk.CodespaceType, increase sdk.Coin, available sdk.Int) sdk.Error {
return sdk.NewError(codespace, CodeExceedsAvailableSupply,
fmt.Sprintf("an outgoing swap with amount %s exceeds total available supply %s",
increase, sdk.NewCoin(increase.Denom, available)))
}
// ErrInvalidCurrentSupply error for when the proposed decrease would result in a negative current supply
func ErrInvalidCurrentSupply(codespace sdk.CodespaceType, decrease, current sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeInvalidCurrentSupply,
fmt.Sprintf("a supply decrease of %s puts current asset supply %s below 0", decrease, current))
}
// ErrInvalidIncomingSupply error for when the proposed decrease would result in a negative incoming supply
func ErrInvalidIncomingSupply(codespace sdk.CodespaceType, decrease, incoming sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeInvalidIncomingSupply,
fmt.Sprintf("a supply decrease of %s puts incoming asset supply %s below 0", decrease, incoming))
}
// ErrInvalidOutgoingSupply error for when the proposed decrease would result in a negative outgoing supply
func ErrInvalidOutgoingSupply(codespace sdk.CodespaceType, decrease, outgoing sdk.Coin) sdk.Error {
return sdk.NewError(codespace, CodeInvalidOutgoingSupply,
fmt.Sprintf("a supply decrease of %s puts outgoing asset supply %s below 0", decrease, outgoing))
}
// ErrInvalidClaimSecret error when a submitted secret doesn't match an AtomicSwap's swapID
func ErrInvalidClaimSecret(codespace sdk.CodespaceType, submittedSecret []byte, swapID []byte) sdk.Error {
return sdk.NewError(codespace, CodeInvalidClaimSecret,
fmt.Sprintf("hashed claim attempt %s does not match %s",
hex.EncodeToString(submittedSecret),
hex.EncodeToString(swapID),
),
)
}
// ErrAtomicSwapAlreadyExists error for when an AtomicSwap with this swapID already exists
func ErrAtomicSwapAlreadyExists(codespace sdk.CodespaceType, swapID cmn.HexBytes) sdk.Error {
return sdk.NewError(codespace, CodeAtomicSwapAlreadyExists, fmt.Sprintf("atomic swap %s already exists", swapID))
}
// ErrAtomicSwapNotFound error for when an atomic swap is not found
func ErrAtomicSwapNotFound(codespace sdk.CodespaceType, id []byte) sdk.Error {
return sdk.NewError(codespace, CodeAtomicSwapNotFound, fmt.Sprintf("AtomicSwap %s was not found", hex.EncodeToString(id)))
}
// ErrSwapNotRefundable error for when an AtomicSwap has not expired and cannot be refunded
func ErrSwapNotRefundable(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeSwapNotRefundable, fmt.Sprintf("atomic swap is still active and cannot be refunded"))
}
// ErrSwapNotClaimable error for when an atomic swap is not open and cannot be claimed
func ErrSwapNotClaimable(codespace sdk.CodespaceType) sdk.Error {
return sdk.NewError(codespace, CodeSwapNotClaimable, fmt.Sprintf("atomic swap is not claimable"))
}

30
x/bep3/types/events.go Normal file
View File

@ -0,0 +1,30 @@
package types
// bep3 module event types
const (
EventTypeCreateAtomicSwap = "createAtomicSwap"
EventTypeDepositAtomicSwap = "depositAtomicSwap"
EventTypeClaimAtomicSwap = "claimAtomicSwap"
EventTypeRefundAtomicSwap = "refundAtomicSwap"
// Common
AttributeKeySender = "sender"
AttributeKeyRecipient = "recipient"
AttributeKeyAtomicSwapID = "atomic_swap_id"
AttributeKeyRandomNumberHash = "random_number_hash"
AttributeKeyTimestamp = "timestamp"
AttributeKeySenderOtherChain = "sender_other_chain"
AttributeKeyExpireHeight = "expire_height"
AttributeKeyAmount = "amount"
AttributeKeyExpectedIncome = "expected_income"
AttributeKeyDirection = "direction"
// Claim
AttributeKeyClaimSender = "claim_sender"
AttributeKeyRandomNumber = "random_number"
// Refund
AttributeKeyRefundSender = "refund_sender"
AttributeValueCategory = ModuleName
)

View File

@ -0,0 +1,19 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
// SupplyKeeper defines the expected supply Keeper
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
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
}

66
x/bep3/types/genesis.go Normal file
View File

@ -0,0 +1,66 @@
package types
import (
"bytes"
"encoding/hex"
"fmt"
)
// GenesisState - all bep3 state that must be provided at genesis
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
AtomicSwaps AtomicSwaps `json:"atomic_swaps" yaml:"atomic_swaps"`
AssetSupplies AssetSupplies `json:"assets_supplies" yaml:"assets_supplies"`
}
// NewGenesisState creates a new GenesisState object
func NewGenesisState(params Params, swaps AtomicSwaps, supplies AssetSupplies) GenesisState {
return GenesisState{
Params: params,
AtomicSwaps: swaps,
AssetSupplies: supplies,
}
}
// DefaultGenesisState - default GenesisState used by Cosmos Hub
func DefaultGenesisState() GenesisState {
return NewGenesisState(
DefaultParams(),
AtomicSwaps{},
AssetSupplies{},
)
}
// Equal checks whether two GenesisState structs are equivalent.
func (gs GenesisState) Equal(gs2 GenesisState) bool {
b1 := ModuleCdc.MustMarshalBinaryBare(gs)
b2 := ModuleCdc.MustMarshalBinaryBare(gs2)
return bytes.Equal(b1, b2)
}
// IsEmpty returns true if a GenesisState is empty.
func (gs GenesisState) IsEmpty() bool {
return gs.Equal(GenesisState{})
}
// Validate validates genesis inputs. It returns error if validation of any input fails.
func (gs GenesisState) Validate() error {
if err := gs.Params.Validate(); err != nil {
return err
}
denoms := map[string]bool{}
for _, a := range gs.AssetSupplies {
if denoms[a.Denom] {
return fmt.Errorf("found duplicate asset denom %s", a.Denom)
}
denoms[a.Denom] = true
}
ids := map[string]bool{}
for _, a := range gs.AtomicSwaps {
if ids[hex.EncodeToString(a.GetSwapID())] {
return fmt.Errorf("found duplicate atomic swap ID %s", hex.EncodeToString(a.GetSwapID()))
}
ids[hex.EncodeToString(a.GetSwapID())] = true
}
return nil
}

View File

@ -0,0 +1,104 @@
package types_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
)
type GenesisTestSuite struct {
suite.Suite
swaps types.AtomicSwaps
supplies types.AssetSupplies
}
func (suite *GenesisTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
count := 10
suite.swaps = atomicSwaps(count)
incomingSupply := int64(count * 50000)
supply := types.NewAssetSupply("bnb", c("bnb", incomingSupply),
c("bnb", 0), c("bnb", 0), c("bnb", 100000000000))
suite.supplies = types.AssetSupplies{supply}
}
func (suite *GenesisTestSuite) TestValidate() {
type args struct {
swaps types.AtomicSwaps
supplies types.AssetSupplies
}
testCases := []struct {
name string
args args
expectPass bool
}{
{
"default",
args{
swaps: types.AtomicSwaps{},
supplies: types.AssetSupplies{},
},
true,
},
{
"with swaps",
args{
swaps: suite.swaps,
supplies: types.AssetSupplies{},
},
true,
},
{
"with supplies",
args{
swaps: types.AtomicSwaps{},
supplies: suite.supplies,
},
true,
},
{
"duplicate swaps",
args{
swaps: types.AtomicSwaps{suite.swaps[2], suite.swaps[2]},
supplies: types.AssetSupplies{},
},
false,
},
{
"duplicate supplies",
args{
swaps: types.AtomicSwaps{},
supplies: types.AssetSupplies{suite.supplies[0], suite.supplies[0]},
},
false,
}}
for _, tc := range testCases {
suite.SetupTest()
suite.Run(tc.name, func() {
var gs types.GenesisState
if tc.name == "default" {
gs = types.DefaultGenesisState()
} else {
gs = types.NewGenesisState(types.DefaultParams(), tc.args.swaps, tc.args.supplies)
}
err := gs.Validate()
if tc.expectPass {
suite.Nil(err)
} else {
suite.Error(err)
}
})
}
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

48
x/bep3/types/hash.go Normal file
View File

@ -0,0 +1,48 @@
package types
import (
"crypto/rand"
"encoding/binary"
"errors"
"math/big"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto/tmhash"
)
// GenerateSecureRandomNumber generates cryptographically strong pseudo-random number
func GenerateSecureRandomNumber() (*big.Int, error) {
max := new(big.Int)
max.Exp(big.NewInt(2), big.NewInt(256), nil) // 256-bits integer i.e. 2^256
// Generate number between 0 - max
randomNumber, err := rand.Int(rand.Reader, max)
if err != nil {
return big.NewInt(0), errors.New("random number generation error")
}
// Catch random numbers that encode to hexadecimal poorly
if len(randomNumber.Text(16)) != 64 {
return GenerateSecureRandomNumber()
}
return randomNumber, nil
}
// CalculateRandomHash calculates the hash of a number and timestamp
func CalculateRandomHash(randomNumber []byte, timestamp int64) []byte {
data := make([]byte, RandomNumberLength+Int64Size)
copy(data[:RandomNumberLength], randomNumber)
binary.BigEndian.PutUint64(data[RandomNumberLength:], uint64(timestamp))
return tmhash.Sum(data)
}
// CalculateSwapID calculates the hash of a RandomNumberHash, sdk.AccAddress, and string
func CalculateSwapID(randomNumberHash []byte, sender sdk.AccAddress, senderOtherChain string) []byte {
senderOtherChain = strings.ToLower(senderOtherChain)
data := randomNumberHash
data = append(data, []byte(sender)...)
data = append(data, []byte(senderOtherChain)...)
return tmhash.Sum(data)
}

61
x/bep3/types/hash_test.go Normal file
View File

@ -0,0 +1,61 @@
package types_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
)
type HashTestSuite struct {
suite.Suite
addrs []sdk.AccAddress
timestamps []int64
}
func (suite *HashTestSuite) SetupTest() {
// Generate 10 addresses
_, addrs := app.GeneratePrivKeyAddressPairs(10)
// Generate 10 timestamps
var timestamps []int64
for i := 0; i < 10; i++ {
timestamps = append(timestamps, ts(i))
}
suite.addrs = addrs
suite.timestamps = timestamps
return
}
func (suite *HashTestSuite) TestGenerateSecureRandomNumber() {
secureRandomNumber, err := types.GenerateSecureRandomNumber()
suite.Nil(err)
suite.NotNil(secureRandomNumber)
suite.Equal(64, len(secureRandomNumber.Text(16)))
}
func (suite *HashTestSuite) TestCalculateRandomHash() {
randomNumber, _ := types.GenerateSecureRandomNumber()
hash := types.CalculateRandomHash(randomNumber.Bytes(), suite.timestamps[0])
suite.NotNil(hash)
suite.Equal(32, len(hash))
}
func (suite *HashTestSuite) TestCalculateSwapID() {
randomNumber, _ := types.GenerateSecureRandomNumber()
hash := types.CalculateRandomHash(randomNumber.Bytes(), suite.timestamps[3])
swapID := types.CalculateSwapID(hash, suite.addrs[3], suite.addrs[5].String())
suite.NotNil(swapID)
suite.Equal(32, len(swapID))
diffHash := types.CalculateRandomHash(randomNumber.Bytes(), suite.timestamps[2])
diffSwapID := types.CalculateSwapID(diffHash, suite.addrs[3], suite.addrs[5].String())
suite.NotEqual(swapID, diffSwapID)
}
func TestHashTestSuite(t *testing.T) {
suite.Run(t, new(HashTestSuite))
}

67
x/bep3/types/keys.go Normal file
View File

@ -0,0 +1,67 @@
package types
import (
"encoding/binary"
"encoding/hex"
)
const (
// ModuleName is the name of the module
ModuleName = "bep3"
// StoreKey to be used when creating the KVStore
StoreKey = ModuleName
// RouterKey to be used for routing msgs
RouterKey = ModuleName
// QuerierRoute is the querier route for bep3
QuerierRoute = ModuleName
// DefaultParamspace default namestore
DefaultParamspace = ModuleName
)
// DefaultLongtermStorageDuration is 1 week (assuming a block time of 7 seconds)
const DefaultLongtermStorageDuration int64 = 86400
// Key prefixes
var (
AtomicSwapKeyPrefix = []byte{0x00} // prefix for keys that store AtomicSwaps
AtomicSwapByBlockPrefix = []byte{0x01} // prefix for keys of the AtomicSwapsByBlock index
AssetSupplyKeyPrefix = []byte{0x02} // prefix for keys that store global asset supply counts
AtomicSwapLongtermStoragePrefix = []byte{0x03} // prefix for keys of the AtomicSwapLongtermStorage index
)
// GetAtomicSwapByHeightKey is used by the AtomicSwapByBlock index and AtomicSwapLongtermStorage index
func GetAtomicSwapByHeightKey(height int64, swapID []byte) []byte {
return append(Uint64ToBytes(uint64(height)), swapID...)
}
// Uint64ToBytes converts a uint64 into fixed length bytes for use in store keys.
func Uint64ToBytes(id uint64) []byte {
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, uint64(id))
return bz
}
// Uint64FromBytes converts some fixed length bytes back into a uint64.
func Uint64FromBytes(bz []byte) uint64 {
return binary.BigEndian.Uint64(bz)
}
// BytesToHex converts data from []byte to a hex-encoded string
func BytesToHex(data []byte) string {
encodedData := make([]byte, hex.EncodedLen(len(data)))
hex.Encode(encodedData, data)
return string(encodedData)
}
// HexToBytes converts data from a hex-encoded string to []bytes
func HexToBytes(data string) ([]byte, error) {
decodedData, err := hex.DecodeString(data)
if err != nil {
return []byte{}, err
}
return decodedData, nil
}

260
x/bep3/types/msg.go Normal file
View File

@ -0,0 +1,260 @@
package types
import (
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tendermint/libs/common"
)
const (
CreateAtomicSwap = "createAtomicSwap"
DepositAtomicSwap = "depositAtomicSwap"
ClaimAtomicSwap = "claimAtomicSwap"
RefundAtomicSwap = "refundAtomicSwap"
CalcSwapID = "calcSwapID"
Int64Size = 8
RandomNumberHashLength = 32
RandomNumberLength = 32
AddrByteCount = 20
MaxOtherChainAddrLength = 64
SwapIDLength = 32
MaxExpectedIncomeLength = 64
)
var (
// kava prefix address: [INSERT BEP3-DEPUTY ADDRESS]
// tkava prefix address: [INSERT BEP3-DEPUTY ADDRESS]
AtomicSwapCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("KavaAtomicSwapCoins")))
)
// MsgCreateAtomicSwap contains an AtomicSwap struct
type MsgCreateAtomicSwap struct {
From sdk.AccAddress `json:"from"`
To sdk.AccAddress `json:"to"`
RecipientOtherChain string `json:"recipient_other_chain"`
SenderOtherChain string `json:"sender_other_chain"`
RandomNumberHash cmn.HexBytes `json:"random_number_hash"`
Timestamp int64 `json:"timestamp"`
Amount sdk.Coins `json:"amount"`
ExpectedIncome string `json:"expected_income"`
HeightSpan int64 `json:"height_span"`
CrossChain bool `json:"cross_chain"`
}
// NewMsgCreateAtomicSwap initializes a new MsgCreateAtomicSwap
func NewMsgCreateAtomicSwap(from sdk.AccAddress, to sdk.AccAddress, recipientOtherChain,
senderOtherChain string, randomNumberHash cmn.HexBytes, timestamp int64,
amount sdk.Coins, expectedIncome string, heightSpan int64, crossChain bool) MsgCreateAtomicSwap {
return MsgCreateAtomicSwap{
From: from,
To: to,
RecipientOtherChain: recipientOtherChain,
SenderOtherChain: senderOtherChain,
RandomNumberHash: randomNumberHash,
Timestamp: timestamp,
Amount: amount,
ExpectedIncome: expectedIncome,
HeightSpan: heightSpan,
CrossChain: crossChain,
}
}
// Route establishes the route for the MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) Route() string { return RouterKey }
// Type is the name of MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) Type() string { return CreateAtomicSwap }
// String prints the MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) String() string {
return fmt.Sprintf("AtomicSwap{%v#%v#%v#%v#%v#%v#%v#%v#%v#%v}",
msg.From, msg.To, msg.RecipientOtherChain, msg.SenderOtherChain,
msg.RandomNumberHash, msg.Timestamp, msg.Amount, msg.ExpectedIncome,
msg.HeightSpan, msg.CrossChain)
}
// GetInvolvedAddresses gets the addresses involved in a MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) GetInvolvedAddresses() []sdk.AccAddress {
return append(msg.GetSigners(), AtomicSwapCoinsAccAddr)
}
// GetSigners gets the signers of a MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.From}
}
// ValidateBasic validates the MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) ValidateBasic() sdk.Error {
if len(msg.From) != AddrByteCount {
return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From)))
}
if len(msg.To) != AddrByteCount {
return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.To)))
}
if !msg.CrossChain && len(msg.RecipientOtherChain) != 0 {
return sdk.ErrInternal(fmt.Sprintf("must leave recipient address on other chain to empty for single chain swap"))
}
if !msg.CrossChain && len(msg.SenderOtherChain) != 0 {
return sdk.ErrInternal(fmt.Sprintf("must leave sender address on other chain to empty for single chain swap"))
}
if msg.CrossChain && len(msg.RecipientOtherChain) == 0 {
return sdk.ErrInternal(fmt.Sprintf("missing recipient address on other chain for cross chain swap"))
}
if len(msg.RecipientOtherChain) > MaxOtherChainAddrLength {
return sdk.ErrInternal(fmt.Sprintf("the length of recipient address on other chain should be less than %d", MaxOtherChainAddrLength))
}
if len(msg.SenderOtherChain) > MaxOtherChainAddrLength {
return sdk.ErrInternal(fmt.Sprintf("the length of sender address on other chain should be less than %d", MaxOtherChainAddrLength))
}
if len(msg.RandomNumberHash) != RandomNumberHashLength {
return sdk.ErrInternal(fmt.Sprintf("the length of random number hash should be %d", RandomNumberHashLength))
}
if msg.Timestamp <= 0 {
return sdk.ErrInternal("timestamp must be positive")
}
if !msg.Amount.IsAllPositive() {
return sdk.ErrInternal(fmt.Sprintf("the swapped out coin must be positive"))
}
if len(msg.ExpectedIncome) > MaxExpectedIncomeLength {
return sdk.ErrInternal(fmt.Sprintf("the length of expected income should be less than %d", MaxExpectedIncomeLength))
}
expectedIncomeCoins, err := sdk.ParseCoins(msg.ExpectedIncome)
if err != nil || expectedIncomeCoins == nil {
return sdk.ErrInternal(fmt.Sprintf("expected income %s must be in valid format e.g. 10000ukava", msg.ExpectedIncome))
}
if expectedIncomeCoins.IsAnyGT(msg.Amount) {
return sdk.ErrInternal(fmt.Sprintf("expected income %s cannot be greater than amount %s", msg.ExpectedIncome, msg.Amount.String()))
}
if msg.HeightSpan <= 0 {
return sdk.ErrInternal("height span must be positive")
}
return nil
}
// GetSignBytes gets the sign bytes of a MsgCreateAtomicSwap
func (msg MsgCreateAtomicSwap) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// MsgClaimAtomicSwap defines a AtomicSwap claim
type MsgClaimAtomicSwap struct {
From sdk.AccAddress `json:"from"`
SwapID cmn.HexBytes `json:"swap_id"`
RandomNumber cmn.HexBytes `json:"random_number"`
}
// NewMsgClaimAtomicSwap initializes a new MsgClaimAtomicSwap
func NewMsgClaimAtomicSwap(from sdk.AccAddress, swapID, randomNumber []byte) MsgClaimAtomicSwap {
return MsgClaimAtomicSwap{
From: from,
SwapID: swapID,
RandomNumber: randomNumber,
}
}
// Route establishes the route for the MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) Route() string { return RouterKey }
// Type is the name of MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) Type() string { return ClaimAtomicSwap }
// String prints the MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) String() string {
return fmt.Sprintf("claimAtomicSwap{%v#%v#%v}", msg.From, msg.SwapID, msg.RandomNumber)
}
// GetInvolvedAddresses gets the addresses involved in a MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) GetInvolvedAddresses() []sdk.AccAddress {
return append(msg.GetSigners(), AtomicSwapCoinsAccAddr)
}
// GetSigners gets the signers of a MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.From}
}
// ValidateBasic validates the MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) ValidateBasic() sdk.Error {
if len(msg.From) != AddrByteCount {
return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From)))
}
if len(msg.SwapID) != SwapIDLength {
return sdk.ErrInternal(fmt.Sprintf("the length of swapID should be %d", SwapIDLength))
}
if len(msg.RandomNumber) == 0 {
return sdk.ErrInternal("the length of random number cannot be 0")
}
return nil
}
// GetSignBytes gets the sign bytes of a MsgClaimAtomicSwap
func (msg MsgClaimAtomicSwap) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}
// MsgRefundAtomicSwap defines a refund msg
type MsgRefundAtomicSwap struct {
From sdk.AccAddress `json:"from"`
SwapID cmn.HexBytes `json:"swap_id"`
}
// NewMsgRefundAtomicSwap initializes a new MsgRefundAtomicSwap
func NewMsgRefundAtomicSwap(from sdk.AccAddress, swapID []byte) MsgRefundAtomicSwap {
return MsgRefundAtomicSwap{
From: from,
SwapID: swapID,
}
}
// Route establishes the route for the MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) Route() string { return RouterKey }
// Type is the name of MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) Type() string { return RefundAtomicSwap }
// String prints the MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) String() string {
return fmt.Sprintf("refundAtomicSwap{%v#%v}", msg.From, msg.SwapID)
}
// GetInvolvedAddresses gets the addresses involved in a MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) GetInvolvedAddresses() []sdk.AccAddress {
return append(msg.GetSigners(), AtomicSwapCoinsAccAddr)
}
// GetSigners gets the signers of a MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) GetSigners() []sdk.AccAddress {
return []sdk.AccAddress{msg.From}
}
// ValidateBasic validates the MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) ValidateBasic() sdk.Error {
if len(msg.From) != AddrByteCount {
return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From)))
}
if len(msg.SwapID) != SwapIDLength {
return sdk.ErrInternal(fmt.Sprintf("the length of swapID should be %d", SwapIDLength))
}
return nil
}
// GetSignBytes gets the sign bytes of a MsgRefundAtomicSwap
func (msg MsgRefundAtomicSwap) GetSignBytes() []byte {
b, err := json.Marshal(msg)
if err != nil {
panic(err)
}
return b
}

122
x/bep3/types/msg_test.go Normal file
View File

@ -0,0 +1,122 @@
package types_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/require"
"github.com/tendermint/tendermint/crypto"
cmn "github.com/tendermint/tendermint/libs/common"
)
var (
coinsSingle = sdk.NewCoins(sdk.NewInt64Coin("bnb", int64(50000)))
coinsZero = sdk.Coins{sdk.Coin{}}
binanceAddrs = []sdk.AccAddress{
sdk.AccAddress(crypto.AddressHash([]byte("BinanceTest1"))),
sdk.AccAddress(crypto.AddressHash([]byte("BinanceTest2"))),
}
kavaAddrs = []sdk.AccAddress{
sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))),
sdk.AccAddress(crypto.AddressHash([]byte("KavaTest2"))),
}
randomNumberBytes = []byte{15}
timestampInt64 = int64(100)
randomNumberHash = types.CalculateRandomHash(randomNumberBytes, timestampInt64)
)
func TestMsgCreateAtomicSwap(t *testing.T) {
tests := []struct {
description string
from sdk.AccAddress
to sdk.AccAddress
recipientOtherChain string
senderOtherChain string
randomNumberHash cmn.HexBytes
timestamp int64
amount sdk.Coins
expectedIncome string
heightSpan int64
crossChain bool
expectPass bool
}{
{"normal", binanceAddrs[0], kavaAddrs[0], "", "", randomNumberHash, timestampInt64, coinsSingle, "50000bnb", 500, false, true},
{"cross-chain", binanceAddrs[0], kavaAddrs[0], kavaAddrs[0].String(), binanceAddrs[0].String(), randomNumberHash, timestampInt64, coinsSingle, "50000bnb", 80000, true, true},
{"with other chain fields", binanceAddrs[0], kavaAddrs[0], kavaAddrs[0].String(), binanceAddrs[0].String(), randomNumberHash, timestampInt64, coinsSingle, "50000bnb", 500, false, false},
{"cross-cross no other chain fields", binanceAddrs[0], kavaAddrs[0], "", "", randomNumberHash, timestampInt64, coinsSingle, "50000bnb", 500, true, false},
{"zero coins", binanceAddrs[0], kavaAddrs[0], "", "", randomNumberHash, timestampInt64, coinsZero, "50000bnb", 500, true, false},
}
for i, tc := range tests {
msg := types.NewMsgCreateAtomicSwap(
tc.from,
tc.to,
tc.recipientOtherChain,
tc.senderOtherChain,
tc.randomNumberHash,
tc.timestamp,
tc.amount,
tc.expectedIncome,
tc.heightSpan,
tc.crossChain,
)
if tc.expectPass {
require.NoError(t, msg.ValidateBasic(), "test: %v", i)
} else {
require.Error(t, msg.ValidateBasic(), "test: %v", i)
}
}
}
func TestMsgClaimAtomicSwap(t *testing.T) {
swapID := types.CalculateSwapID(randomNumberHash, binanceAddrs[0], "")
tests := []struct {
description string
from sdk.AccAddress
swapID cmn.HexBytes
randomNumber cmn.HexBytes
expectPass bool
}{
{"normal", binanceAddrs[0], swapID, randomNumberHash, true},
}
for i, tc := range tests {
msg := types.NewMsgClaimAtomicSwap(
tc.from,
tc.swapID,
tc.randomNumber,
)
if tc.expectPass {
require.NoError(t, msg.ValidateBasic(), "test: %v", i)
} else {
require.Error(t, msg.ValidateBasic(), "test: %v", i)
}
}
}
func TestMsgRefundAtomicSwap(t *testing.T) {
swapID := types.CalculateSwapID(randomNumberHash, binanceAddrs[0], "")
tests := []struct {
description string
from sdk.AccAddress
swapID cmn.HexBytes
expectPass bool
}{
{"normal", binanceAddrs[0], swapID, true},
}
for i, tc := range tests {
msg := types.NewMsgRefundAtomicSwap(
tc.from,
tc.swapID,
)
if tc.expectPass {
require.NoError(t, msg.ValidateBasic(), "test: %v", i)
} else {
require.Error(t, msg.ValidateBasic(), "test: %v", i)
}
}
}

146
x/bep3/types/params.go Normal file
View File

@ -0,0 +1,146 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/params"
)
// Parameter keys
var (
KeyBnbDeputyAddress = []byte("BnbDeputyAddress")
KeyMinBlockLock = []byte("MinBlockLock")
KeyMaxBlockLock = []byte("MaxBlockLock")
KeySupportedAssets = []byte("SupportedAssets")
AbsoluteMaximumBlockLock int64 = 10000
AbsoluteMinimumBlockLock int64 = 50
DefaultMinBlockLock int64 = 80
DefaultMaxBlockLock int64 = 600
DefaultSupportedAssets = AssetParams{
AssetParam{
Denom: "bnb",
CoinID: 714,
Limit: sdk.NewInt(100000000000),
Active: true,
},
}
)
// Params governance parameters for bep3 module
type Params struct {
BnbDeputyAddress sdk.AccAddress `json:"bnb_deputy_address" yaml:"bnb_deputy_address"` // Bnbchain deputy address
MinBlockLock int64 `json:"min_block_lock" yaml:"min_block_lock"` // AtomicSwap minimum block lock
MaxBlockLock int64 `json:"max_block_lock" yaml:"max_block_lock"` // AtomicSwap maximum block lock
SupportedAssets AssetParams `json:"supported_assets" yaml:"supported_assets"` // Supported assets
}
// String implements fmt.Stringer
func (p Params) String() string {
return fmt.Sprintf(`Params:
Bnbchain deputy address: %s,
Min block lock: %d,
Max block lock: %d,
Supported assets: %s`,
p.BnbDeputyAddress.String(), p.MinBlockLock, p.MaxBlockLock, p.SupportedAssets)
}
// NewParams returns a new params object
func NewParams(bnbDeputyAddress sdk.AccAddress, minBlockLock, maxBlockLock int64, supportedAssets AssetParams,
) Params {
return Params{
BnbDeputyAddress: bnbDeputyAddress,
MinBlockLock: minBlockLock,
MaxBlockLock: maxBlockLock,
SupportedAssets: supportedAssets,
}
}
// DefaultParams returns default params for bep3 module
func DefaultParams() Params {
defaultBnbDeputyAddress, _ := sdk.AccAddressFromBech32("kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj")
return NewParams(defaultBnbDeputyAddress, DefaultMinBlockLock, DefaultMaxBlockLock, DefaultSupportedAssets)
}
// AssetParam governance parameters for each asset within a supported chain
type AssetParam struct {
Denom string `json:"denom" yaml:"denom"` // name of the asset
CoinID int `json:"coin_id" yaml:"coin_id"` // internationally recognized coin ID
Limit sdk.Int `json:"limit" yaml:"limit"` // asset supply limit
Active bool `json:"active" yaml:"active"` // denotes if asset is available or paused
}
// String implements fmt.Stringer
func (ap AssetParam) String() string {
return fmt.Sprintf(`Asset:
Denom: %s
Coin ID: %d
Limit: %s
Active: %t`,
ap.Denom, ap.CoinID, ap.Limit.String(), ap.Active)
}
// AssetParams array of AssetParam
type AssetParams []AssetParam
// String implements fmt.Stringer
func (aps AssetParams) String() string {
out := "Asset Params\n"
for _, ap := range aps {
out += fmt.Sprintf("%s\n", ap)
}
return out
}
// ParamKeyTable Key declaration for parameters
func ParamKeyTable() params.KeyTable {
return params.NewKeyTable().RegisterParamSet(&Params{})
}
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs
// pairs of bep3 module's parameters.
// nolint
func (p *Params) ParamSetPairs() params.ParamSetPairs {
return params.ParamSetPairs{
{Key: KeyBnbDeputyAddress, Value: &p.BnbDeputyAddress},
{Key: KeyMinBlockLock, Value: &p.MinBlockLock},
{Key: KeyMaxBlockLock, Value: &p.MaxBlockLock},
{Key: KeySupportedAssets, Value: &p.SupportedAssets},
}
}
// Validate ensure that params have valid values
func (p Params) Validate() error {
if p.MinBlockLock < AbsoluteMinimumBlockLock {
return fmt.Errorf(fmt.Sprintf("minimum block lock cannot be less than %d", AbsoluteMinimumBlockLock))
}
if p.MinBlockLock >= p.MaxBlockLock {
return fmt.Errorf("maximum block lock must be greater than minimum block lock")
}
if p.MaxBlockLock > AbsoluteMaximumBlockLock {
return fmt.Errorf(fmt.Sprintf("maximum block lock cannot be greater than %d", AbsoluteMaximumBlockLock))
}
coinIDs := make(map[int]bool)
coinDenoms := make(map[string]bool)
for _, asset := range p.SupportedAssets {
if len(asset.Denom) == 0 {
return fmt.Errorf("asset denom cannot be empty")
}
if asset.CoinID < 0 {
return fmt.Errorf(fmt.Sprintf("asset %s must be a positive integer", asset.Denom))
}
if !asset.Limit.IsPositive() {
return fmt.Errorf(fmt.Sprintf("asset %s must have a positive supply limit", asset.Denom))
}
if coinDenoms[asset.Denom] {
return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate denom", asset.Denom))
}
coinDenoms[asset.Denom] = true
if coinIDs[asset.CoinID] {
return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate coin id %d", asset.Denom, asset.CoinID))
}
coinIDs[asset.CoinID] = true
}
return nil
}

217
x/bep3/types/params_test.go Normal file
View File

@ -0,0 +1,217 @@
package types_test
import (
"strings"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
)
type ParamsTestSuite struct {
suite.Suite
addr sdk.AccAddress
}
func (suite *ParamsTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
_, addrs := app.GeneratePrivKeyAddressPairs(1)
suite.addr = addrs[0]
return
}
func (suite *ParamsTestSuite) TestParamValidation() {
type LoadParams func() types.Params
type args struct {
bnbDeputyAddress sdk.AccAddress
minBlockLock int64
maxBlockLock int64
supportedAssets types.AssetParams
}
testCases := []struct {
name string
args args
expectPass bool
expectedErr string
}{
{
name: "default",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.DefaultSupportedAssets,
},
expectPass: true,
expectedErr: "",
},
{
name: "minimum block lock below limit",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: 1,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.DefaultSupportedAssets,
},
expectPass: false,
expectedErr: "minimum block lock cannot be less than",
},
{
name: "minimum block lock above limit",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: 500000,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.DefaultSupportedAssets,
},
expectPass: false,
expectedErr: "maximum block lock must be greater than minimum block lock",
},
{
name: "maximum block lock below limit",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: 1,
supportedAssets: types.DefaultSupportedAssets,
},
expectPass: false,
expectedErr: "maximum block lock must be greater than minimum block lock",
},
{
name: "maximum block lock above limit",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: 100000000,
supportedAssets: types.DefaultSupportedAssets,
},
expectPass: false,
expectedErr: "maximum block lock cannot be greater than",
},
{
name: "empty asset denom",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.AssetParams{
types.AssetParam{
Denom: "",
CoinID: 714,
Limit: sdk.NewInt(100000000000),
Active: true,
},
},
},
expectPass: false,
expectedErr: "asset denom cannot be empty",
},
{
name: "negative asset coin ID",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.AssetParams{
types.AssetParam{
Denom: "bnb",
CoinID: -1,
Limit: sdk.NewInt(100000000000),
Active: true,
},
},
},
expectPass: false,
expectedErr: "must be a positive integer",
},
{
name: "negative asset limit",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.AssetParams{
types.AssetParam{
Denom: "bnb",
CoinID: 714,
Limit: sdk.NewInt(-10000),
Active: true,
},
},
},
expectPass: false,
expectedErr: "must have a positive supply limit",
},
{
name: "duplicate asset denom",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.AssetParams{
types.AssetParam{
Denom: "bnb",
CoinID: 714,
Limit: sdk.NewInt(100000000000),
Active: true,
},
types.AssetParam{
Denom: "bnb",
CoinID: 114,
Limit: sdk.NewInt(500000000),
Active: false,
},
},
},
expectPass: false,
expectedErr: "cannot have duplicate denom",
},
{
name: "duplicate asset coin ID",
args: args{
bnbDeputyAddress: suite.addr,
minBlockLock: types.DefaultMinBlockLock,
maxBlockLock: types.DefaultMaxBlockLock,
supportedAssets: types.AssetParams{
types.AssetParam{
Denom: "bnb",
CoinID: 714,
Limit: sdk.NewInt(100000000000),
Active: true,
},
types.AssetParam{
Denom: "fake",
CoinID: 714,
Limit: sdk.NewInt(500000000),
Active: false,
},
},
},
expectPass: false,
expectedErr: "cannot have duplicate coin id",
},
}
for _, tc := range testCases {
params := types.NewParams(tc.args.bnbDeputyAddress, tc.args.minBlockLock,
tc.args.maxBlockLock, tc.args.supportedAssets)
err := params.Validate()
if tc.expectPass {
suite.Nil(err)
} else {
suite.NotNil(err)
suite.True(strings.Contains(err.Error(), tc.expectedErr))
}
}
}
func TestParamsTestSuite(t *testing.T) {
suite.Run(t, new(ParamsTestSuite))
}

54
x/bep3/types/querier.go Normal file
View File

@ -0,0 +1,54 @@
package types
import (
cmn "github.com/tendermint/tendermint/libs/common"
)
const (
// QueryGetAssetSupply command for getting info about an asset's supply
QueryGetAssetSupply = "supply"
// QueryGetAtomicSwap command for getting info about an atomic swap
QueryGetAtomicSwap = "swap"
// QueryGetAtomicSwaps command for getting a list of atomic swaps
QueryGetAtomicSwaps = "swaps"
// QueryGetParams command for getting module params
QueryGetParams = "parameters"
)
// QueryAssetSupply contains the params for query 'custom/bep3/supply'
type QueryAssetSupply struct {
Denom cmn.HexBytes `json:"denom" yaml:"denom"`
}
// NewQueryAssetSupply creates a new QueryAssetSupply
func NewQueryAssetSupply(denom cmn.HexBytes) QueryAssetSupply {
return QueryAssetSupply{
Denom: denom,
}
}
// QueryAtomicSwapByID contains the params for query 'custom/bep3/swap'
type QueryAtomicSwapByID struct {
SwapID cmn.HexBytes `json:"swap_id" yaml:"swap_id"`
}
// NewQueryAtomicSwapByID creates a new QueryAtomicSwapByID
func NewQueryAtomicSwapByID(swapBytes cmn.HexBytes) QueryAtomicSwapByID {
return QueryAtomicSwapByID{
SwapID: swapBytes,
}
}
// QueryAtomicSwaps contains the params for an AtomicSwaps query
type QueryAtomicSwaps struct {
Page int `json:"page" yaml:"page"`
Limit int `json:"limit" yaml:"limit"`
}
// NewQueryAtomicSwaps creates a new QueryAtomicSwaps
func NewQueryAtomicSwaps(page int, limit int) QueryAtomicSwaps {
return QueryAtomicSwaps{
Page: page,
Limit: limit,
}
}

219
x/bep3/types/swap.go Normal file
View File

@ -0,0 +1,219 @@
package types
import (
"encoding/hex"
"encoding/json"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
cmn "github.com/tendermint/tendermint/libs/common"
)
// Swap is an interface for handling common actions
type Swap interface {
GetSwapID() cmn.HexBytes
GetModuleAccountCoins() sdk.Coins
Validate() error
}
// AtomicSwap contains the information for an atomic swap
type AtomicSwap struct {
Swap `json:"swap" yaml:"swap"`
Amount sdk.Coins `json:"amount" yaml:"amount"`
RandomNumberHash cmn.HexBytes `json:"random_number_hash" yaml:"random_number_hash"`
ExpireHeight int64 `json:"expire_height" yaml:"expire_height"`
Timestamp int64 `json:"timestamp" yaml:"timestamp"`
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
Recipient sdk.AccAddress `json:"recipient" yaml:"recipient"`
SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"`
RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"`
ClosedBlock int64 `json:"closed_block" yaml:"closed_block"`
Status SwapStatus `json:"status" yaml:"status"`
CrossChain bool `json:"cross_chain" yaml:"cross_chain"`
Direction SwapDirection `json:"direction" yaml:"direction"`
}
// NewAtomicSwap returns a new AtomicSwap
func NewAtomicSwap(amount sdk.Coins, randomNumberHash cmn.HexBytes, expireHeight, timestamp int64, sender,
recipient sdk.AccAddress, senderOtherChain string, recipientOtherChain string, closedBlock int64,
status SwapStatus, crossChain bool, direction SwapDirection) AtomicSwap {
return AtomicSwap{
Amount: amount,
RandomNumberHash: randomNumberHash,
ExpireHeight: expireHeight,
Timestamp: timestamp,
Sender: sender,
Recipient: recipient,
SenderOtherChain: senderOtherChain,
RecipientOtherChain: recipientOtherChain,
ClosedBlock: closedBlock,
Status: status,
CrossChain: crossChain,
Direction: direction,
}
}
// GetSwapID calculates the ID of an atomic swap
func (a AtomicSwap) GetSwapID() cmn.HexBytes {
return CalculateSwapID(a.RandomNumberHash, a.Sender, a.SenderOtherChain)
}
// GetModuleAccountCoins returns the swap's amount as sdk.Coins
func (a AtomicSwap) GetModuleAccountCoins() sdk.Coins {
return sdk.NewCoins(a.Amount...)
}
// Validate verifies that recipient is not empty
func (a AtomicSwap) Validate() error {
if len(a.Sender) != AddrByteCount {
return fmt.Errorf(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Sender)))
}
if len(a.Recipient) != AddrByteCount {
return fmt.Errorf(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(a.Recipient)))
}
if len(a.RandomNumberHash) != RandomNumberHashLength {
return fmt.Errorf(fmt.Sprintf("the length of random number hash should be %d", RandomNumberHashLength))
}
if !a.Amount.IsAllPositive() {
return fmt.Errorf(fmt.Sprintf("the swapped out coin must be positive"))
}
return nil
}
// String implements stringer
func (a AtomicSwap) String() string {
return fmt.Sprintf("Atomic Swap"+
"\n ID: %s"+
"\n Status: %s"+
"\n Amount: %s"+
"\n Random number hash: %s"+
"\n Expire height: %d"+
"\n Timestamp: %d"+
"\n Sender: %s"+
"\n Recipient: %s"+
"\n Sender other chain: %s"+
"\n Recipient other chain: %s"+
"\n Closed block: %d"+
"\n Cross chain: %t"+
"\n Direction: %s",
a.GetSwapID(), a.Status.String(), a.Amount.String(),
hex.EncodeToString(a.RandomNumberHash), a.ExpireHeight,
a.Timestamp, a.Sender.String(), a.Recipient.String(),
a.SenderOtherChain, a.RecipientOtherChain, a.ClosedBlock,
a.CrossChain, a.Direction)
}
// AtomicSwaps is a slice of AtomicSwap
type AtomicSwaps []AtomicSwap
// String implements stringer
func (swaps AtomicSwaps) String() string {
out := ""
for _, swap := range swaps {
out += swap.String() + "\n"
}
return out
}
// SwapStatus is the status of an AtomicSwap
type SwapStatus byte
const (
NULL SwapStatus = 0x00
Open SwapStatus = 0x01
Completed SwapStatus = 0x02
Expired SwapStatus = 0x03
)
// NewSwapStatusFromString converts string to SwapStatus type
func NewSwapStatusFromString(str string) SwapStatus {
switch str {
case "Open", "open":
return Open
case "Completed", "completed":
return Completed
case "Expired", "expired":
return Expired
default:
return NULL
}
}
// String returns the string representation of a SwapStatus
func (status SwapStatus) String() string {
switch status {
case Open:
return "Open"
case Completed:
return "Completed"
case Expired:
return "Expired"
default:
return "NULL"
}
}
// MarshalJSON marshals the SwapStatus
func (status SwapStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// UnmarshalJSON unmarshals the SwapStatus
func (status *SwapStatus) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
*status = NewSwapStatusFromString(s)
return nil
}
// SwapDirection is the direction of an AtomicSwap
type SwapDirection byte
const (
INVALID SwapDirection = 0x00
Incoming SwapDirection = 0x01
Outgoing SwapDirection = 0x02
)
// NewSwapDirectionFromString converts string to SwapDirection type
func NewSwapDirectionFromString(str string) SwapDirection {
switch str {
case "Incoming", "incoming", "inc", "I", "i":
return Incoming
case "Outgoing", "outgoing", "out", "O", "o":
return Outgoing
default:
return INVALID
}
}
// String returns the string representation of a SwapDirection
func (direction SwapDirection) String() string {
switch direction {
case Incoming:
return "Incoming"
case Outgoing:
return "Outgoing"
default:
return "INVALID"
}
}
// MarshalJSON marshals the SwapDirection
func (direction SwapDirection) MarshalJSON() ([]byte, error) {
return json.Marshal(direction.String())
}
// UnmarshalJSON unmarshals the SwapDirection
func (direction *SwapDirection) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
*direction = NewSwapDirectionFromString(s)
return nil
}

140
x/bep3/types/swap_test.go Normal file
View File

@ -0,0 +1,140 @@
package types_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/bep3/types"
"github.com/stretchr/testify/suite"
cmn "github.com/tendermint/tendermint/libs/common"
)
type AtomicSwapTestSuite struct {
suite.Suite
addrs []sdk.AccAddress
timestamps []int64
randomNumberHashes []cmn.HexBytes
}
func (suite *AtomicSwapTestSuite) SetupTest() {
// Generate 10 addresses
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
_, addrs := app.GeneratePrivKeyAddressPairs(10)
// Generate 10 timestamps and random number hashes
var timestamps []int64
var randomNumberHashes []cmn.HexBytes
for i := 0; i < 10; i++ {
timestamp := ts(i)
randomNumber, _ := types.GenerateSecureRandomNumber()
randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp)
timestamps = append(timestamps, timestamp)
randomNumberHashes = append(randomNumberHashes, randomNumberHash)
}
suite.addrs = addrs
suite.timestamps = timestamps
suite.randomNumberHashes = randomNumberHashes
return
}
func (suite *AtomicSwapTestSuite) TestNewAtomicSwap() {
type args struct {
amount sdk.Coins
randomNumberHash cmn.HexBytes
expireHeight int64
timestamp int64
sender sdk.AccAddress
recipient sdk.AccAddress
recipientOtherChain string
senderOtherChain string
closedBlock int64
status types.SwapStatus
crossChain bool
direction types.SwapDirection
}
testCases := []struct {
description string
args args
expectPass bool
}{
{
"normal",
args{
amount: cs(c("bnb", 50000)),
randomNumberHash: suite.randomNumberHashes[0],
expireHeight: int64(360),
timestamp: suite.timestamps[0],
sender: suite.addrs[0],
recipient: suite.addrs[5],
recipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
senderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
closedBlock: 0,
status: types.Open,
crossChain: true,
direction: types.Incoming,
},
true,
},
{
"invalid random number hash length",
args{
amount: cs(c("bnb", 50000)),
randomNumberHash: suite.randomNumberHashes[1][0:20],
expireHeight: int64(360),
timestamp: suite.timestamps[1],
sender: suite.addrs[1],
recipient: suite.addrs[5],
recipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
senderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
closedBlock: 0,
status: types.Open,
crossChain: true,
direction: types.Incoming,
},
false,
},
{
"invalid amount",
args{
amount: cs(c("bnb", 0)),
randomNumberHash: suite.randomNumberHashes[2],
expireHeight: int64(360),
timestamp: suite.timestamps[2],
sender: suite.addrs[2],
recipient: suite.addrs[5],
recipientOtherChain: "bnb1urfermcg92dwq36572cx4xg84wpk3lfpksr5g7",
senderOtherChain: "bnb1uky3me9ggqypmrsvxk7ur6hqkzq7zmv4ed4ng7",
closedBlock: 0,
status: types.Open,
crossChain: true,
direction: types.Incoming,
},
false,
},
}
for _, tc := range testCases {
// Create atomic swap
swap := types.NewAtomicSwap(tc.args.amount, tc.args.randomNumberHash, tc.args.expireHeight,
tc.args.timestamp, tc.args.sender, tc.args.recipient, tc.args.senderOtherChain,
tc.args.recipientOtherChain, tc.args.closedBlock, tc.args.status, tc.args.crossChain,
tc.args.direction)
if tc.expectPass {
suite.Nil(swap.Validate())
suite.Equal(tc.args.amount, swap.GetModuleAccountCoins())
expectedSwapID := types.CalculateSwapID(tc.args.randomNumberHash, tc.args.sender, tc.args.senderOtherChain)
suite.Equal(cmn.HexBytes(expectedSwapID), swap.GetSwapID())
} else {
suite.Error(swap.Validate())
}
}
}
func TestAtomicSwapTestSuite(t *testing.T) {
suite.Run(t, new(AtomicSwapTestSuite))
}

View File

@ -15,13 +15,31 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
if !found {
previousBlockTime = ctx.BlockTime()
}
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
previousDistTime, found := k.GetPreviousSavingsDistribution(ctx)
if !found {
previousDistTime = ctx.BlockTime()
k.SetPreviousSavingsDistribution(ctx, previousDistTime)
}
blockTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
for _, cp := range params.CollateralParams {
for _, dp := range params.DebtParams {
k.HandleNewDebt(ctx, cp.Denom, dp.Denom, timeElapsed)
k.HandleNewDebt(ctx, cp.Denom, dp.Denom, blockTimeElapsed)
}
err := k.LiquidateCdps(ctx, cp.MarketID, cp.Denom, cp.LiquidationRatio)
// call our update fees method for the risky cdps
err := k.UpdateFeesForRiskyCdps(ctx, cp.Denom, cp.MarketID)
// handle if an error is returned then propagate up
if err != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeBeginBlockerFatal,
sdk.NewAttribute(sdk.AttributeKeyModule, fmt.Sprintf("%s", ModuleName)),
sdk.NewAttribute(types.AttributeKeyError, fmt.Sprintf("%s", err)),
),
)
}
err = k.LiquidateCdps(ctx, cp.MarketID, cp.Denom, cp.LiquidationRatio)
if err != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
@ -42,6 +60,22 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) {
),
)
}
distTimeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousDistTime.Unix())
if distTimeElapsed.GTE(sdk.NewInt(int64(params.SavingsDistributionFrequency.Seconds()))) {
for _, dp := range params.DebtParams {
err := k.DistributeSavingsRate(ctx, dp.Denom)
if err != nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
EventTypeBeginBlockerFatal,
sdk.NewAttribute(sdk.AttributeKeyModule, fmt.Sprintf("%s", ModuleName)),
sdk.NewAttribute(types.AttributeKeyError, fmt.Sprintf("%s", err)),
),
)
}
}
k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime())
}
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
return
}

View File

@ -1,8 +1,8 @@
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/cdp/types/
// ALIASGEN: github.com/kava-labs/kava/x/cdp/keeper/
// ALIASGEN: github.com/kava-labs/kava/x/cdp/keeper
// ALIASGEN: github.com/kava-labs/kava/x/cdp/types
package cdp
import (
@ -11,6 +11,7 @@ import (
)
const (
BaseDigitFactor = keeper.BaseDigitFactor
DefaultCodespace = types.DefaultCodespace
CodeCdpAlreadyExists = types.CodeCdpAlreadyExists
CodeCollateralLengthInvalid = types.CodeCollateralLengthInvalid
@ -47,7 +48,9 @@ const (
QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace
LiquidatorMacc = types.LiquidatorMacc
SavingsRateMacc = types.SavingsRateMacc
QueryGetCdp = types.QueryGetCdp
QueryGetCdpDeposits = types.QueryGetCdpDeposits
QueryGetCdps = types.QueryGetCdps
QueryGetCdpsByCollateralization = types.QueryGetCdpsByCollateralization
QueryGetParams = types.QueryGetParams
@ -58,7 +61,10 @@ const (
var (
// functions aliases
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
NewCDP = types.NewCDP
NewAugmentedCDP = types.NewAugmentedCDP
RegisterCodec = types.RegisterCodec
NewDeposit = types.NewDeposit
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
@ -105,45 +111,49 @@ var (
ParamKeyTable = types.ParamKeyTable
NewQueryCdpsParams = types.NewQueryCdpsParams
NewQueryCdpParams = types.NewQueryCdpParams
NewQueryCdpDeposits = types.NewQueryCdpDeposits
NewQueryCdpsByRatioParams = types.NewQueryCdpsByRatioParams
ValidSortableDec = types.ValidSortableDec
SortableDecBytes = types.SortableDecBytes
ParseDecBytes = types.ParseDecBytes
RelativePow = types.RelativePow
NewKeeper = keeper.NewKeeper
NewQuerier = keeper.NewQuerier
// variable aliases
ModuleCdc = types.ModuleCdc
CdpIDKeyPrefix = types.CdpIDKeyPrefix
CdpKeyPrefix = types.CdpKeyPrefix
CollateralRatioIndexPrefix = types.CollateralRatioIndexPrefix
CdpIDKey = types.CdpIDKey
DebtDenomKey = types.DebtDenomKey
GovDenomKey = types.GovDenomKey
DepositKeyPrefix = types.DepositKeyPrefix
PrincipalKeyPrefix = types.PrincipalKeyPrefix
PreviousBlockTimeKey = types.PreviousBlockTimeKey
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
KeyCollateralParams = types.KeyCollateralParams
KeyDebtParams = types.KeyDebtParams
KeyCircuitBreaker = types.KeyCircuitBreaker
KeyDebtThreshold = types.KeyDebtThreshold
KeySurplusThreshold = types.KeySurplusThreshold
DefaultGlobalDebt = types.DefaultGlobalDebt
DefaultCircuitBreaker = types.DefaultCircuitBreaker
DefaultCollateralParams = types.DefaultCollateralParams
DefaultDebtParams = types.DefaultDebtParams
DefaultCdpStartingID = types.DefaultCdpStartingID
DefaultDebtDenom = types.DefaultDebtDenom
DefaultGovDenom = types.DefaultGovDenom
DefaultSurplusThreshold = types.DefaultSurplusThreshold
DefaultDebtThreshold = types.DefaultDebtThreshold
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
MaxSortableDec = types.MaxSortableDec
ModuleCdc = types.ModuleCdc
CdpIDKeyPrefix = types.CdpIDKeyPrefix
CdpKeyPrefix = types.CdpKeyPrefix
CollateralRatioIndexPrefix = types.CollateralRatioIndexPrefix
CdpIDKey = types.CdpIDKey
DebtDenomKey = types.DebtDenomKey
GovDenomKey = types.GovDenomKey
DepositKeyPrefix = types.DepositKeyPrefix
PrincipalKeyPrefix = types.PrincipalKeyPrefix
PreviousBlockTimeKey = types.PreviousBlockTimeKey
PreviousDistributionTimeKey = types.PreviousDistributionTimeKey
KeyGlobalDebtLimit = types.KeyGlobalDebtLimit
KeyCollateralParams = types.KeyCollateralParams
KeyDebtParams = types.KeyDebtParams
KeyDistributionFrequency = types.KeyDistributionFrequency
KeyCircuitBreaker = types.KeyCircuitBreaker
KeyDebtThreshold = types.KeyDebtThreshold
KeySurplusThreshold = types.KeySurplusThreshold
DefaultGlobalDebt = types.DefaultGlobalDebt
DefaultCircuitBreaker = types.DefaultCircuitBreaker
DefaultCollateralParams = types.DefaultCollateralParams
DefaultDebtParams = types.DefaultDebtParams
DefaultCdpStartingID = types.DefaultCdpStartingID
DefaultDebtDenom = types.DefaultDebtDenom
DefaultGovDenom = types.DefaultGovDenom
DefaultSurplusThreshold = types.DefaultSurplusThreshold
DefaultDebtThreshold = types.DefaultDebtThreshold
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime
DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency
MaxSortableDec = types.MaxSortableDec
)
type (
Keeper = keeper.Keeper
CDP = types.CDP
CDPs = types.CDPs
AugmentedCDP = types.AugmentedCDP
@ -165,6 +175,6 @@ type (
DebtParams = types.DebtParams
QueryCdpsParams = types.QueryCdpsParams
QueryCdpParams = types.QueryCdpParams
QueryCdpDeposits = types.QueryCdpDeposits
QueryCdpsByRatioParams = types.QueryCdpsByRatioParams
Keeper = keeper.Keeper
)

View File

@ -7,11 +7,26 @@ import (
)
// InitGenesis sets initial genesis state for cdp module
func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, gs GenesisState) {
func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, sk SupplyKeeper, gs GenesisState) {
if err := gs.Validate(); err != nil {
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
}
// check if the module accounts exists
cdpModuleAcc := sk.GetModuleAccount(ctx, ModuleName)
if cdpModuleAcc == nil {
panic(fmt.Sprintf("%s module account has not been set", ModuleName))
}
liqModuleAcc := sk.GetModuleAccount(ctx, LiquidatorMacc)
if liqModuleAcc == nil {
panic(fmt.Sprintf("%s module account has not been set", LiquidatorMacc))
}
savingsRateMacc := sk.GetModuleAccount(ctx, SavingsRateMacc)
if savingsRateMacc == nil {
panic(fmt.Sprintf("%s module account has not been set", SavingsRateMacc))
}
// validate denoms - check that any collaterals in the params are in the pricefeed,
// pricefeed MUST call InitGenesis before cdp
collateralMap := make(map[string]int)
@ -84,6 +99,10 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
if !found {
previousBlockTime = DefaultPreviousBlockTime
}
previousDistributionTime, found := k.GetPreviousSavingsDistribution(ctx)
if !found {
previousDistributionTime = DefaultPreviousDistributionTime
}
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousBlockTime)
return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousBlockTime, previousDistributionTime)
}

View File

@ -39,9 +39,10 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
cdpGenesis := cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
CollateralParams: cdp.CollateralParams{
{
Denom: asset,
@ -61,14 +62,16 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
}
@ -101,9 +104,10 @@ func NewPricefeedGenStateMulti() app.GenesisState {
func NewCDPGenStateMulti() app.GenesisState {
cdpGenesis := cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
CollateralParams: cdp.CollateralParams{
{
Denom: "xrp",
@ -134,20 +138,23 @@ func NewCDPGenStateMulti() app.GenesisState {
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
}
@ -207,6 +214,9 @@ func badGenStates() []badGenState {
g13 := baseGenState()
g13.GovDenom = ""
g14 := baseGenState()
g14.Params.DebtParams[0].SavingsRate = d("4.0")
return []badGenState{
badGenState{Genesis: g1, Reason: "duplicate collateral denom"},
badGenState{Genesis: g2, Reason: "duplicate collateral prefix"},
@ -220,15 +230,17 @@ func badGenStates() []badGenState {
badGenState{Genesis: g11, Reason: "negative auction size"},
badGenState{Genesis: g12, Reason: "invalid liquidation penalty"},
badGenState{Genesis: g13, Reason: "gov denom not set"},
badGenState{Genesis: g14, Reason: "invalid savings rate"},
}
}
func baseGenState() cdp.GenesisState {
return cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
CollateralParams: cdp.CollateralParams{
{
Denom: "xrp",
@ -264,10 +276,11 @@ func baseGenState() cdp.GenesisState {
},
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
}

View File

@ -220,11 +220,12 @@ func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) sdk.Error {
return err
}
}
remainingSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc)
if remainingSurplus.GTE(params.SurplusAuctionThreshold) {
for _, dp := range params.DebtParams {
surplusLot := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, sdk.NewCoin(dp.Denom, surplusLot), k.GetGovDenom(ctx))
for _, dp := range params.DebtParams {
surplus := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(dp.Denom)
if surplus.GTE(params.SurplusAuctionThreshold) {
surplusLot := sdk.NewCoin(dp.Denom, surplus)
_, err := k.auctionKeeper.StartSurplusAuction(ctx, types.LiquidatorMacc, surplusLot, k.GetGovDenom(ctx))
if err != nil {
return err
}

View File

@ -114,6 +114,7 @@ func (suite *CdpTestSuite) TestGetSetCdp() {
_, addrs := app.GeneratePrivKeyAddressPairs(1)
cdp := types.NewCDP(types.DefaultCdpStartingID, addrs[0], cs(c("xrp", 1)), cs(c("usdx", 1)), tmtime.Canonical(time.Now()))
suite.keeper.SetCDP(suite.ctx, cdp)
t, found := suite.keeper.GetCDP(suite.ctx, "xrp", types.DefaultCdpStartingID)
suite.True(found)
suite.Equal(cdp, t)

View File

@ -27,6 +27,46 @@ func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coins, periods sdk.
return newFees
}
// UpdateFeesForRiskyCdps calculates fees for risky CDPs
// The overall logic is first select the CDPs with 10% of the liquidation ratio
// Then we call calculate fees on each of those CDPs
// Next we store the result of the fees in the cdp.AccumulatedFees field
// Finally we set the cdp.FeesUpdated time to the current block time (ctx.BlockTime()) since that
// is when we made the update
func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, marketID string) sdk.Error {
price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID)
if err != nil {
return err
}
liquidationRatio := k.getLiquidationRatio(ctx, collateralDenom)
// NOTE - we have a fixed cutoff at 110% - this may or may not be changed in the future
normalizedRatio := sdk.OneDec().Quo(price.Price.Quo(liquidationRatio)).Mul(sdk.MustNewDecFromStr("1.1"))
// now iterate over all the cdps based on collateral ratio
k.IterateCdpsByCollateralRatio(ctx, collateralDenom, normalizedRatio, func(cdp types.CDP) bool {
// get the number of periods
periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix()))
// now calculate and store additional fees
additionalFees := k.CalculateFees(ctx, cdp.Principal, periods, collateralDenom)
// now add the additional fees to the accumulated fees for the cdp
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(additionalFees)
// and set the fees updated time to the current block time since we just updated it
cdp.FeesUpdated = ctx.BlockTime()
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees))
k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio)
return false // this returns true when you want to stop iterating. Since we want to iterate through all we return false
})
return nil
}
// IncrementTotalPrincipal increments the total amount of debt that has been drawn with that collateral type
func (k Keeper) IncrementTotalPrincipal(ctx sdk.Context, collateralDenom string, principal sdk.Coins) {
for _, pc := range principal {

View File

@ -3,6 +3,7 @@ package keeper_test
import (
"math/rand"
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
@ -76,6 +77,64 @@ func (suite *FeeTestSuite) TestCalculateFeesPrecisionLoss() {
}
// createCdps is a helper function to create two CDPs each with zero fees
func (suite *FeeTestSuite) createCdps() {
// create 2 accounts in the state and give them some coins
// create two private key pair addresses
_, addrs := app.GeneratePrivKeyAddressPairs(2)
ak := suite.app.GetAccountKeeper()
// setup the first account
acc := ak.NewAccountWithAddress(suite.ctx, addrs[0])
acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
ak.SetAccount(suite.ctx, acc)
// now setup the second account
acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1])
acc2.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000)))
ak.SetAccount(suite.ctx, acc2)
// now create two cdps with the addresses we just created
// use the created account to create a cdp that SHOULD have fees updated
// to get a ratio between 100 - 110% of liquidation ratio we can use 200xrp ($50) and 24 usdx (208% collateralization with liquidation ratio of 200%)
// create CDP for the first address
err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("usdx", 24000000)))
suite.NoError(err) // check that no error was thrown
// use the other account to create a cdp that SHOULD NOT have fees updated - 500% collateralization
// create CDP for the second address
err = suite.keeper.AddCdp(suite.ctx, addrs[1], cs(c("xrp", 200000000)), cs(c("usdx", 10000000)))
suite.NoError(err) // check that no error was thrown
}
// UpdateFeesForRiskyCdpsTest tests the functionality for updating the fees for risky CDPs
func (suite *FeeTestSuite) TestUpdateFeesForRiskyCdps() {
// this helper function creates two CDPs with id 1 and 2 respectively, each with zero fees
suite.createCdps()
// move the context forward in time so that cdps will have fees accumulate if CalculateFees is called
// note - time must be moved forward by a sufficient amount in order for additional
// fees to accumulate, in this example 60 seconds
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 60))
err := suite.keeper.UpdateFeesForRiskyCdps(suite.ctx, "xrp", "xrp:usd")
suite.NoError(err) // check that we don't have any error
// cdp we expect fees to accumulate for
cdp1, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 1)
// check fees are not zero
// check that the fees have been updated
suite.False(cdp1.AccumulatedFees.Empty())
// now check that we have the correct amount of fees overall (2 USDX for this scenario)
suite.Equal(sdk.NewInt(2), cdp1.AccumulatedFees.AmountOf("usdx"))
// cdp we expect fees to not accumulate for
cdp2, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 2)
// check fees are zero
suite.True(cdp2.AccumulatedFees.Empty())
}
func (suite *FeeTestSuite) TestGetSetPreviousBlockTime() {
now := tmtime.Now()

View File

@ -39,9 +39,10 @@ func NewPricefeedGenState(asset string, price sdk.Dec) app.GenesisState {
func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
cdpGenesis := cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
CollateralParams: cdp.CollateralParams{
{
Denom: asset,
@ -61,14 +62,16 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState {
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.9"),
},
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
}
@ -101,9 +104,10 @@ func NewPricefeedGenStateMulti() app.GenesisState {
func NewCDPGenStateMulti() app.GenesisState {
cdpGenesis := cdp.GenesisState{
Params: cdp.Params{
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000), sdk.NewInt64Coin("susd", 1000000000000)),
SurplusAuctionThreshold: cdp.DefaultSurplusThreshold,
DebtAuctionThreshold: cdp.DefaultDebtThreshold,
SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency,
CollateralParams: cdp.CollateralParams{
{
Denom: "xrp",
@ -134,20 +138,23 @@ func NewCDPGenStateMulti() app.GenesisState {
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
{
Denom: "susd",
ReferenceAsset: "usd",
ConversionFactor: i(6),
DebtFloor: i(10000000),
SavingsRate: d("0.95"),
},
},
},
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
StartingCdpID: cdp.DefaultCdpStartingID,
DebtDenom: cdp.DefaultDebtDenom,
GovDenom: cdp.DefaultGovDenom,
CDPs: cdp.CDPs{},
PreviousBlockTime: cdp.DefaultPreviousBlockTime,
PreviousDistributionTime: cdp.DefaultPreviousDistributionTime,
}
return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)}
}

View File

@ -18,21 +18,12 @@ type Keeper struct {
pricefeedKeeper types.PricefeedKeeper
supplyKeeper types.SupplyKeeper
auctionKeeper types.AuctionKeeper
accountKeeper types.AccountKeeper
codespace sdk.CodespaceType
}
// NewKeeper creates a new keeper
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, ak types.AuctionKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType) Keeper {
// ensure cdp module account is set
if addr := sk.GetModuleAddress(types.ModuleName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
}
// ensure liquidator module account is set
if addr := sk.GetModuleAddress(types.LiquidatorMacc); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", types.LiquidatorMacc))
}
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, ak types.AuctionKeeper, sk types.SupplyKeeper, ack types.AccountKeeper, codespace sdk.CodespaceType) Keeper {
return Keeper{
key: key,
@ -41,6 +32,7 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace,
pricefeedKeeper: pfk,
auctionKeeper: ak,
supplyKeeper: sk,
accountKeeper: ack,
codespace: codespace,
}
}

95
x/cdp/keeper/savings.go Normal file
View File

@ -0,0 +1,95 @@
package keeper
import (
"time"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
auctiontypes "github.com/kava-labs/kava/x/auction/types"
"github.com/kava-labs/kava/x/cdp/types"
)
// DistributeSavingsRate distributes surplus that has accumulated in the liquidator account to address holding stable coins according the the savings rate
func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) sdk.Error {
dp, found := k.GetDebtParam(ctx, debtDenom)
if !found {
return types.ErrDebtNotSupported(k.codespace, debtDenom)
}
savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc)
surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom)
if surplusToDistribute.IsZero() {
return nil
}
modAccountCoins := k.getModuleAccountCoins(ctx, dp.Denom)
totalSupplyLessModAccounts := k.supplyKeeper.GetSupply(ctx).GetTotal().Sub(modAccountCoins)
surplusDistributed := sdk.ZeroInt()
var iterationErr sdk.Error
k.accountKeeper.IterateAccounts(ctx, func(acc authexported.Account) (stop bool) {
_, ok := acc.(supplyexported.ModuleAccountI)
if ok {
// don't distribute savings rate to module accounts
return false
}
debtAmount := acc.GetCoins().AmountOf(debtDenom)
if !debtAmount.IsPositive() {
return false
}
// (balance * rewardToDisribute) / totalSupply
// interest is the ratable fraction of savings rate owed to that account, rounded using bankers rounding
interest := (sdk.NewDecFromInt(debtAmount).Mul(sdk.NewDecFromInt(surplusToDistribute))).Quo(sdk.NewDecFromInt(totalSupplyLessModAccounts.AmountOf(debtDenom))).RoundInt()
// sanity check, if we are going to over-distribute due to rounding, distribute only the remaining savings rate that hasn't been distributed.
if interest.GT(surplusToDistribute.Sub(surplusDistributed)) {
interest = surplusToDistribute.Sub(surplusDistributed)
}
// sanity check - don't send saving rate if the rounded amount is zero
if !interest.IsPositive() {
return false
}
interestCoins := sdk.NewCoins(sdk.NewCoin(debtDenom, interest))
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.SavingsRateMacc, acc.GetAddress(), interestCoins)
if err != nil {
iterationErr = err
return true
}
surplusDistributed = surplusDistributed.Add(interest)
return false
})
if iterationErr != nil {
return iterationErr
}
return nil
}
// GetPreviousSavingsDistribution get the time of the previous savings rate distribution
func (k Keeper) GetPreviousSavingsDistribution(ctx sdk.Context) (distTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDistributionTimeKey)
b := store.Get([]byte{})
if b == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &distTime)
return distTime, true
}
// SetPreviousSavingsDistribution set the time of the previous block
func (k Keeper) SetPreviousSavingsDistribution(ctx sdk.Context, distTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousDistributionTimeKey)
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(distTime))
}
func (k Keeper) getModuleAccountCoins(ctx sdk.Context, denom string) sdk.Coins {
// NOTE: these are the module accounts that could end up holding stable denoms at some point.
// Since there are currently no api methods to 'GetAllModuleAccounts', this function will need to be updated if a
// new module account is added which can hold stable denoms.
savingsRateMaccCoinAmount := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc).GetCoins().AmountOf(denom)
cdpMaccCoinAmount := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName).GetCoins().AmountOf(denom)
auctionMaccCoinAmount := k.supplyKeeper.GetModuleAccount(ctx, auctiontypes.ModuleName).GetCoins().AmountOf(denom)
liquidatorMaccCoinAmount := k.supplyKeeper.GetModuleAccount(ctx, types.LiquidatorMacc).GetCoins().AmountOf(denom)
feeMaccCoinAmount := k.supplyKeeper.GetModuleAccount(ctx, authtypes.FeeCollectorName).GetCoins().AmountOf(denom)
totalModAccountAmount := savingsRateMaccCoinAmount.Add(cdpMaccCoinAmount).Add(auctionMaccCoinAmount).Add(liquidatorMaccCoinAmount).Add(feeMaccCoinAmount)
return sdk.NewCoins(sdk.NewCoin(denom, totalModAccountAmount))
}

View File

@ -0,0 +1,81 @@
package keeper_test
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/cdp/keeper"
"github.com/kava-labs/kava/x/cdp/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
type SavingsTestSuite struct {
suite.Suite
keeper keeper.Keeper
app app.TestApp
ctx sdk.Context
addrs []sdk.AccAddress
}
func (suite *SavingsTestSuite) SetupTest() {
tApp := app.NewTestApp()
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
_, addrs := app.GeneratePrivKeyAddressPairs(3)
authGS := app.NewAuthGenState(
addrs,
[]sdk.Coins{
cs(c("usdx", 100000)), cs(c("usdx", 50000)), cs(c("usdx", 50000)),
},
)
tApp.InitializeFromGenesisStates(
authGS,
NewPricefeedGenStateMulti(),
NewCDPGenStateMulti(),
)
sk := tApp.GetSupplyKeeper()
macc := sk.GetModuleAccount(ctx, types.SavingsRateMacc)
err := sk.MintCoins(ctx, macc.GetName(), cs(c("usdx", 10000)))
suite.NoError(err)
keeper := tApp.GetCDPKeeper()
suite.app = tApp
suite.keeper = keeper
suite.ctx = ctx
suite.addrs = addrs
}
func (suite *SavingsTestSuite) TestApplySavingsRate() {
err := suite.keeper.DistributeSavingsRate(suite.ctx, "usdx")
suite.NoError(err)
ak := suite.app.GetAccountKeeper()
acc0 := ak.GetAccount(suite.ctx, suite.addrs[0])
suite.Equal(cs(c("usdx", 105000)), acc0.GetCoins())
acc1 := ak.GetAccount(suite.ctx, suite.addrs[1])
suite.Equal(cs(c("usdx", 52500)), acc1.GetCoins())
acc2 := ak.GetAccount(suite.ctx, suite.addrs[2])
suite.Equal(cs(c("usdx", 52500)), acc2.GetCoins())
sk := suite.app.GetSupplyKeeper()
macc := sk.GetModuleAccount(suite.ctx, types.SavingsRateMacc)
suite.True(macc.GetCoins().AmountOf("usdx").IsZero())
}
func (suite *SavingsTestSuite) TestGetSetPreviousDistributionTime() {
now := tmtime.Now()
_, f := suite.keeper.GetPreviousSavingsDistribution(suite.ctx)
suite.False(f)
suite.NotPanics(func() { suite.keeper.SetPreviousSavingsDistribution(suite.ctx, now) })
pdt, f := suite.keeper.GetPreviousSavingsDistribution(suite.ctx)
suite.True(f)
suite.Equal(now, pdt)
}
func TestSavingsTestSuite(t *testing.T) {
suite.Run(t, new(SavingsTestSuite))
}

View File

@ -82,15 +82,24 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) sdk.Error {
// HandleNewDebt compounds the accumulated fees for the input collateral and principal coins.
// the following operations are performed:
// 1. mints the fee coins in the liquidator module account,
// 2. mints the same amount of debt coins in the cdp module account
// 1. The fees accumulated since the last block are calculated
// 2. The fees are minted, split between the liquidator module account (surplus) and the savings rate module account (savings rate) according to the savings rate parameter.
// 2. An equal amount of debt coins are minted in the cdp module account
// 3. updates the total amount of principal for the input collateral type in the store,
func (k Keeper) HandleNewDebt(ctx sdk.Context, collateralDenom string, principalDenom string, periods sdk.Int) {
dp, _ := k.GetDebtParam(ctx, principalDenom)
savingsRate := dp.SavingsRate
previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, principalDenom)
feeCoins := sdk.NewCoins(sdk.NewCoin(principalDenom, previousDebt))
newFees := k.CalculateFees(ctx, feeCoins, periods, collateralDenom)
if newFees.IsZero() {
return
}
newFeesSavings := sdk.NewDecFromInt(newFees.AmountOf(principalDenom)).Mul(savingsRate).RoundInt()
newFeesSurplus := newFees.AmountOf(principalDenom).Sub(newFeesSavings)
k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees)
k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, newFees)
k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(principalDenom, newFeesSurplus)))
k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(principalDenom, newFeesSavings)))
k.SetTotalPrincipal(ctx, collateralDenom, principalDenom, feeCoins.Add(newFees).AmountOf(principalDenom))
}
@ -100,6 +109,9 @@ func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, li
if err != nil {
return err
}
// price = $0.5
// liquidation ratio = 1.5
// normalizedRatio = (1/(0.5/1.5)) = 3
normalizedRatio := sdk.OneDec().Quo(price.Price.Quo(liquidationRatio))
cdpsToLiquidate := k.GetAllCdpsByDenomAndRatio(ctx, denom, normalizedRatio)
for _, c := range cdpsToLiquidate {

View File

@ -97,14 +97,16 @@ type AppModule struct {
keeper Keeper
pricefeedKeeper PricefeedKeeper
supplyKeeper SupplyKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(keeper Keeper, pricefeedKeeper PricefeedKeeper) AppModule {
func NewAppModule(keeper Keeper, pricefeedKeeper PricefeedKeeper, supplyKeeper SupplyKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
keeper: keeper,
pricefeedKeeper: pricefeedKeeper,
supplyKeeper: supplyKeeper,
}
}
@ -140,7 +142,7 @@ func (am AppModule) NewQuerierHandler() sdk.Querier {
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
var genesisState GenesisState
ModuleCdc.MustUnmarshalJSON(data, &genesisState)
InitGenesis(ctx, am.keeper, am.pricefeedKeeper, genesisState)
InitGenesis(ctx, am.keeper, am.pricefeedKeeper, am.supplyKeeper, genesisState)
return []abci.ValidatorUpdate{}
}

View File

@ -3,6 +3,7 @@
At the start of every block the BeginBlocker of the cdp module:
- updates total CDP fees
- update fees for individual "risky" CDPs
- liquidates CDPs under the collateral ratio
- nets out system debt and, if necessary, starts auctions to re-balance it
- records the last block time
@ -13,6 +14,13 @@ At the start of every block the BeginBlocker of the cdp module:
- An equal amount of debt coins are minted and sent to the system's CDP module account.
- An equal amount of stable asset coins are minted and sent to the system's liquidator module account
## Update risky cdps
- UpdateFeesForRiskyCdps calculates fees for risky CDPs
- Select the CDPs with 10% of the liquidation ratio - the risky CDPs
- Calculate additional accumulated fees on each of those CDPs
- Update the fees updated time for the CDP to the current block time
## Liquidate CDP
- Get every cdp that is under the liquidation ratio for its collateral type.
@ -26,9 +34,15 @@ At the start of every block the BeginBlocker of the cdp module:
- Burn the maximum possible equal amount of debt and stable asset from the liquidator module account.
- If there is enough debt remaining for an auction, start one.
- If there is enough surplus stable asset remaining for an auction, start one.
- If there is enough surplus stable asset, minus surplus reserved for the savings rate, remaining for an auction, start one.
- Otherwise do nothing, leave debt/surplus to accumulate over subsequent blocks.
## Distribute Surplus Stable Asset According to the Savings Rate
- If `SavingsDistributionFrequency` seconds have elapsed since the previous distribution, the savings rate is applied to all accounts that hold stable asset.
- Each account that holds stable asset is distributed a ratable portion of the surplus that is apportioned to the savings rate.
- If distribution occurred, the time of the distribution is recorded.
## Update Previous Block Time
The current block time is recorded.

View File

@ -2,12 +2,13 @@
The cdp module contains the following parameters:
| Key | Type | Example | Description |
|------------------|-------------------------|------------------------------------|------------------------------------------------------------------|
| CollateralParams | array (CollateralParam) | [{see below}] | array of params for each enabled collateral type |
| DebtParams | array (DebtParam) | [{see below}] | array of params for each enabled pegged asset |
| GlobalDebtLimit | array (coin) | [{"denom":"usdx","amount":"1000"}] | maximum pegged assets that can be minted across the whole system |
| CircuitBreaker | bool | false | flag to disable user interactions with the system |
| Key | Type | Example | Description |
|------------------ |-------------------------|------------------------------------|------------------------------------------------------------------|
| CollateralParams | array (CollateralParam) | [{see below}] | array of params for each enabled collateral type |
| DebtParams | array (DebtParam) | [{see below}] | array of params for each enabled pegged asset |
| GlobalDebtLimit | array (coin) | [{"denom":"usdx","amount":"1000"}] | maximum pegged assets that can be minted across the whole system |
| SavingsDistributionFrequency | string (int) | "84600" | number of seconds between distribution of the savings rate|
| CircuitBreaker | bool | false | flag to disable user interactions with the system |
Each CollateralParam has the following parameters:

View File

@ -39,7 +39,7 @@ func (cdp CDP) String() string {
Collateral Type: %s
Collateral: %s
Principal: %s
Fees: %s
AccumulatedFees: %s
Fees Last Updated: %s`,
cdp.Owner,
cdp.ID,

View File

@ -4,6 +4,7 @@ import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
pftypes "github.com/kava-labs/kava/x/pricefeed/types"
)
@ -21,6 +22,7 @@ type SupplyKeeper interface {
SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) sdk.Error
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
GetSupply(ctx sdk.Context) (supply supplyexported.SupplyI)
}
// PricefeedKeeper defines the expected interface for the pricefeed
@ -39,3 +41,8 @@ type AuctionKeeper interface {
StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, sdk.Error)
StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, sdk.Error)
}
// AccountKeeper expected interface for the account keeper (noalias)
type AccountKeeper interface {
IterateAccounts(ctx sdk.Context, cb func(account authexported.Account) (stop bool))
}

View File

@ -8,38 +8,41 @@ import (
// GenesisState is the state that must be provided at genesis.
type GenesisState struct {
Params Params `json:"params" yaml:"params"`
CDPs CDPs `json:"cdps" yaml:"cdps"`
Deposits Deposits `json:"deposits" yaml:"deposits"`
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
Params Params `json:"params" yaml:"params"`
CDPs CDPs `json:"cdps" yaml:"cdps"`
Deposits Deposits `json:"deposits" yaml:"deposits"`
StartingCdpID uint64 `json:"starting_cdp_id" yaml:"starting_cdp_id"`
DebtDenom string `json:"debt_denom" yaml:"debt_denom"`
GovDenom string `json:"gov_denom" yaml:"gov_denom"`
PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"`
PreviousDistributionTime time.Time `json:"previous_distribution_time" yaml"previous_distribution_time"`
}
// NewGenesisState returns a new genesis state
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64, debtDenom, govDenom string, previousBlockTime time.Time) GenesisState {
func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64, debtDenom, govDenom string, previousBlockTime time.Time, previousDistTime time.Time) GenesisState {
return GenesisState{
Params: params,
CDPs: cdps,
Deposits: deposits,
StartingCdpID: startingCdpID,
DebtDenom: debtDenom,
GovDenom: govDenom,
PreviousBlockTime: previousBlockTime,
Params: params,
CDPs: cdps,
Deposits: deposits,
StartingCdpID: startingCdpID,
DebtDenom: debtDenom,
GovDenom: govDenom,
PreviousBlockTime: previousBlockTime,
PreviousDistributionTime: previousDistTime,
}
}
// DefaultGenesisState returns a default genesis state
func DefaultGenesisState() GenesisState {
return GenesisState{
Params: DefaultParams(),
CDPs: CDPs{},
Deposits: Deposits{},
StartingCdpID: DefaultCdpStartingID,
DebtDenom: DefaultDebtDenom,
GovDenom: DefaultGovDenom,
PreviousBlockTime: DefaultPreviousBlockTime,
Params: DefaultParams(),
CDPs: CDPs{},
Deposits: Deposits{},
StartingCdpID: DefaultCdpStartingID,
DebtDenom: DefaultDebtDenom,
GovDenom: DefaultGovDenom,
PreviousBlockTime: DefaultPreviousBlockTime,
PreviousDistributionTime: DefaultPreviousDistributionTime,
}
}
@ -55,6 +58,10 @@ func (gs GenesisState) Validate() error {
return fmt.Errorf("previous block time not set")
}
if gs.PreviousDistributionTime.Equal(time.Time{}) {
return fmt.Errorf("previous distribution time not set")
}
if gs.DebtDenom == "" {
return fmt.Errorf("debt denom not set")

View File

@ -25,6 +25,9 @@ const (
// LiquidatorMacc module account for liquidator
LiquidatorMacc = "liquidator"
// SavingsRateMacc module account for savings rate
SavingsRateMacc = "savings"
)
var sep = []byte(":")
@ -43,18 +46,20 @@ var sep = []byte(":")
// - 0x06<denom>:totalPrincipal
// - 0x07<denom>:feeRate
// - 0x08:previousBlockTime
// - 0x09:previousDistributionTime
// KVStore key prefixes
var (
CdpIDKeyPrefix = []byte{0x00}
CdpKeyPrefix = []byte{0x01}
CollateralRatioIndexPrefix = []byte{0x02}
CdpIDKey = []byte{0x03}
DebtDenomKey = []byte{0x04}
GovDenomKey = []byte{0x05}
DepositKeyPrefix = []byte{0x06}
PrincipalKeyPrefix = []byte{0x07}
PreviousBlockTimeKey = []byte{0x08}
CdpIDKeyPrefix = []byte{0x00}
CdpKeyPrefix = []byte{0x01}
CollateralRatioIndexPrefix = []byte{0x02}
CdpIDKey = []byte{0x03}
DebtDenomKey = []byte{0x04}
GovDenomKey = []byte{0x05}
DepositKeyPrefix = []byte{0x06}
PrincipalKeyPrefix = []byte{0x07}
PreviousBlockTimeKey = []byte{0x08}
PreviousDistributionTimeKey = []byte{0x09}
)
var lenPositiveDec = len(SortableDecBytes(sdk.OneDec()))

View File

@ -11,34 +11,38 @@ import (
// Parameter keys
var (
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
KeyCollateralParams = []byte("CollateralParams")
KeyDebtParams = []byte("DebtParams")
KeyCircuitBreaker = []byte("CircuitBreaker")
KeyDebtThreshold = []byte("DebtThreshold")
KeySurplusThreshold = []byte("SurplusThreshold")
DefaultGlobalDebt = sdk.Coins{}
DefaultCircuitBreaker = false
DefaultCollateralParams = CollateralParams{}
DefaultDebtParams = DebtParams{}
DefaultCdpStartingID = uint64(1)
DefaultDebtDenom = "debt"
DefaultGovDenom = "ukava"
DefaultSurplusThreshold = sdk.NewInt(1000000000)
DefaultDebtThreshold = sdk.NewInt(1000000000)
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
minCollateralPrefix = 0
maxCollateralPrefix = 255
KeyGlobalDebtLimit = []byte("GlobalDebtLimit")
KeyCollateralParams = []byte("CollateralParams")
KeyDebtParams = []byte("DebtParams")
KeyDistributionFrequency = []byte("DistributionFrequency")
KeyCircuitBreaker = []byte("CircuitBreaker")
KeyDebtThreshold = []byte("DebtThreshold")
KeySurplusThreshold = []byte("SurplusThreshold")
DefaultGlobalDebt = sdk.Coins{}
DefaultCircuitBreaker = false
DefaultCollateralParams = CollateralParams{}
DefaultDebtParams = DebtParams{}
DefaultCdpStartingID = uint64(1)
DefaultDebtDenom = "debt"
DefaultGovDenom = "ukava"
DefaultSurplusThreshold = sdk.NewInt(1000000000)
DefaultDebtThreshold = sdk.NewInt(1000000000)
DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0))
DefaultPreviousDistributionTime = tmtime.Canonical(time.Unix(0, 0))
DefaultSavingsDistributionFrequency = time.Hour * 24 * 2
minCollateralPrefix = 0
maxCollateralPrefix = 255
)
// Params governance parameters for cdp module
type Params struct {
CollateralParams CollateralParams `json:"collateral_params" yaml:"collateral_params"`
DebtParams DebtParams `json:"debt_params" yaml:"debt_params"`
GlobalDebtLimit sdk.Coins `json:"global_debt_limit" yaml:"global_debt_limit"`
SurplusAuctionThreshold sdk.Int `json:"surplus_auction_threshold" yaml:"surplus_auction_threshold"`
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
CollateralParams CollateralParams `json:"collateral_params" yaml:"collateral_params"`
DebtParams DebtParams `json:"debt_params" yaml:"debt_params"`
GlobalDebtLimit sdk.Coins `json:"global_debt_limit" yaml:"global_debt_limit"`
SurplusAuctionThreshold sdk.Int `json:"surplus_auction_threshold" yaml:"surplus_auction_threshold"`
DebtAuctionThreshold sdk.Int `json:"debt_auction_threshold" yaml:"debt_auction_threshold"`
SavingsDistributionFrequency time.Duration `json:"savings_distribution_frequency" yaml:"savings_distribution_frequency"`
CircuitBreaker bool `json:"circuit_breaker" yaml:"circuit_breaker"`
}
// String implements fmt.Stringer
@ -49,26 +53,28 @@ func (p Params) String() string {
Debt Params: %s
Surplus Auction Threshold: %s
Debt Auction Threshold: %s
Savings Distribution Frequency: %s
Circuit Breaker: %t`,
p.GlobalDebtLimit, p.CollateralParams, p.DebtParams, p.SurplusAuctionThreshold, p.DebtAuctionThreshold, p.CircuitBreaker,
p.GlobalDebtLimit, p.CollateralParams, p.DebtParams, p.SurplusAuctionThreshold, p.DebtAuctionThreshold, p.SavingsDistributionFrequency, p.CircuitBreaker,
)
}
// NewParams returns a new params object
func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParams DebtParams, surplusThreshold sdk.Int, debtThreshold sdk.Int, breaker bool) Params {
func NewParams(debtLimit sdk.Coins, collateralParams CollateralParams, debtParams DebtParams, surplusThreshold sdk.Int, debtThreshold sdk.Int, distributionFreq time.Duration, breaker bool) Params {
return Params{
GlobalDebtLimit: debtLimit,
CollateralParams: collateralParams,
DebtParams: debtParams,
DebtAuctionThreshold: debtThreshold,
SurplusAuctionThreshold: surplusThreshold,
CircuitBreaker: breaker,
GlobalDebtLimit: debtLimit,
CollateralParams: collateralParams,
DebtParams: debtParams,
DebtAuctionThreshold: debtThreshold,
SurplusAuctionThreshold: surplusThreshold,
SavingsDistributionFrequency: distributionFreq,
CircuitBreaker: breaker,
}
}
// DefaultParams returns default params for cdp module
func DefaultParams() Params {
return NewParams(DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParams, DefaultSurplusThreshold, DefaultDebtThreshold, DefaultCircuitBreaker)
return NewParams(DefaultGlobalDebt, DefaultCollateralParams, DefaultDebtParams, DefaultSurplusThreshold, DefaultDebtThreshold, DefaultSavingsDistributionFrequency, DefaultCircuitBreaker)
}
// CollateralParam governance parameters for each collateral type within the cdp module
@ -116,7 +122,8 @@ type DebtParam struct {
Denom string `json:"denom" yaml:"denom"`
ReferenceAsset string `json:"reference_asset" yaml:"reference_asset"`
ConversionFactor sdk.Int `json:"conversion_factor" yaml:"conversion_factor"`
DebtFloor sdk.Int `json:"debt_floor" yaml:"debt_floor"` // minimum active loan size, used to prevent dust
DebtFloor sdk.Int `json:"debt_floor" yaml:"debt_floor"` // minimum active loan size, used to prevent dust
SavingsRate sdk.Dec `json:"savings_rate" yaml:"savings_rate"` // the percentage of stability fees that are redirected to savings rate
}
func (dp DebtParam) String() string {
@ -155,6 +162,7 @@ func (p *Params) ParamSetPairs() params.ParamSetPairs {
{Key: KeyCircuitBreaker, Value: &p.CircuitBreaker},
{Key: KeySurplusThreshold, Value: &p.SurplusAuctionThreshold},
{Key: KeyDebtThreshold, Value: &p.DebtAuctionThreshold},
{Key: KeyDistributionFrequency, Value: &p.SavingsDistributionFrequency},
}
}
@ -167,6 +175,9 @@ func (p Params) Validate() error {
if found {
return fmt.Errorf("duplicate debt denom: %s", dp.Denom)
}
if dp.SavingsRate.LT(sdk.ZeroDec()) || dp.SavingsRate.GT(sdk.OneDec()) {
return fmt.Errorf("savings rate should be between 0 and 1, is %s for %s", dp.SavingsRate, dp.Denom)
}
debtDenoms[dp.Denom] = 1
}
@ -233,5 +244,9 @@ func (p Params) Validate() error {
if !p.DebtAuctionThreshold.IsPositive() {
return fmt.Errorf("debt auction threshold should be positive, is %s", p.DebtAuctionThreshold)
}
if p.SavingsDistributionFrequency.Seconds() <= float64(0) {
return fmt.Errorf("savings distribution frequency should be positive, is %s", p.SavingsDistributionFrequency)
}
return nil
}

12
x/kavadist/abci.go Normal file
View File

@ -0,0 +1,12 @@
package kavadist
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
func BeginBlocker(ctx sdk.Context, k Keeper) {
err := k.MintPeriodInflation(ctx)
if err != nil {
panic(err)
}
}

54
x/kavadist/alias.go Normal file
View File

@ -0,0 +1,54 @@
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/kavadist/keeper
// ALIASGEN: github.com/kava-labs/kava/x/kavadist/types
package kavadist
import (
"github.com/kava-labs/kava/x/kavadist/keeper"
"github.com/kava-labs/kava/x/kavadist/types"
)
const (
DefaultCodespace = types.DefaultCodespace
EventTypeKavaDist = types.EventTypeKavaDist
AttributeKeyInflation = types.AttributeKeyInflation
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
QuerierRoute = types.QuerierRoute
DefaultParamspace = types.DefaultParamspace
KavaDistMacc = types.KavaDistMacc
QueryGetParams = types.QueryGetParams
)
var (
// functions aliases
NewKeeper = keeper.NewKeeper
RegisterCodec = types.RegisterCodec
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
NewParams = types.NewParams
DefaultParams = types.DefaultParams
ParamKeyTable = types.ParamKeyTable
// variable aliases
ModuleCdc = types.ModuleCdc
CurrentDistPeriodKey = types.CurrentDistPeriodKey
PreviousBlockTimeKey = types.PreviousBlockTimeKey
KeyActive = types.KeyActive
KeyPeriods = types.KeyPeriods
DefaultActive = types.DefaultActive
DefaultPeriods = types.DefaultPeriods
DefaultPreviousBlockTime = types.DefaultPreviousBlockTime
GovDenom = types.GovDenom
)
type (
Keeper = keeper.Keeper
GenesisState = types.GenesisState
Params = types.Params
Period = types.Period
Periods = types.Periods
)

1
x/kavadist/doc.go Normal file
View File

@ -0,0 +1 @@
package kavadist

39
x/kavadist/genesis.go Normal file
View File

@ -0,0 +1,39 @@
package kavadist
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/kavadist/types"
)
// InitGenesis initializes the store state from a genesis state.
func InitGenesis(ctx sdk.Context, k Keeper, supplyKeeper types.SupplyKeeper, gs GenesisState) {
if err := gs.Validate(); err != nil {
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
}
k.SetParams(ctx, gs.Params)
// only set the previous block time if it's different than default
if !gs.PreviousBlockTime.Equal(DefaultPreviousBlockTime) {
k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime)
}
// check if the module account exists
moduleAcc := supplyKeeper.GetModuleAccount(ctx, KavaDistMacc)
if moduleAcc == nil {
panic(fmt.Sprintf("%s module account has not been set", KavaDistMacc))
}
}
// ExportGenesis export genesis state for cdp module
func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState {
params := k.GetParams(ctx)
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found {
previousBlockTime = DefaultPreviousBlockTime
}
return NewGenesisState(params,previousBlockTime)
}

18
x/kavadist/handler.go Normal file
View File

@ -0,0 +1,18 @@
package kavadist
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// NewHandler creates an sdk.Handler for kavadist messages
func NewHandler(k Keeper) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
switch msg := msg.(type) {
default:
errMsg := fmt.Sprintf("unrecognized cdp msg type: %T", msg)
return sdk.ErrUnknownRequest(errMsg).Result()
}
}
}

View File

@ -0,0 +1,49 @@
package keeper
import (
"time"
"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/kavadist/types"
)
// Keeper keeper for the cdp module
type Keeper struct {
key sdk.StoreKey
cdc *codec.Codec
paramSubspace subspace.Subspace
supplyKeeper types.SupplyKeeper
codespace sdk.CodespaceType
}
// NewKeeper creates a new keeper
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper, codespace sdk.CodespaceType) Keeper {
return Keeper{
key: key,
cdc: cdc,
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
supplyKeeper: sk,
codespace: codespace,
}
}
// GetPreviousBlockTime get the blocktime for the previous block
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time, found bool) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
b := store.Get([]byte{})
if b == nil {
return time.Time{}, false
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &blockTime)
return blockTime, true
}
// SetPreviousBlockTime set the time of the previous block
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
store := prefix.NewStore(ctx.KVStore(k.key), types.PreviousBlockTimeKey)
store.Set([]byte{}, k.cdc.MustMarshalBinaryLengthPrefixed(blockTime))
}

87
x/kavadist/keeper/mint.go Normal file
View File

@ -0,0 +1,87 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
cdptypes "github.com/kava-labs/kava/x/cdp/types"
"github.com/kava-labs/kava/x/kavadist/types"
)
// MintPeriodInflation mints new tokens according to the inflation schedule specified in the paramters
func (k Keeper) MintPeriodInflation(ctx sdk.Context) sdk.Error {
params := k.GetParams(ctx)
if !params.Active {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeKavaDist,
sdk.NewAttribute(types.AttributeKeyStatus, types.AttributeValueInactive),
),
)
return nil
}
previousBlockTime, found := k.GetPreviousBlockTime(ctx)
if !found {
previousBlockTime = ctx.BlockTime()
k.SetPreviousBlockTime(ctx, previousBlockTime)
return nil
}
for _, period := range params.Periods {
// Case 1 - period is fully expired
if period.End.Before(previousBlockTime) {
continue
}
// Case 2 - period has ended since the previous block time
if period.End.After(previousBlockTime) && period.End.Before(ctx.BlockTime()) {
// calculate time elapsed relative to the periods end time
timeElapsed := sdk.NewInt(period.End.Unix() - previousBlockTime.Unix())
err := k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom)
if err != nil {
return err
}
// update the value of previousBlockTime so that the next period starts from the end of the last
// period and not the original value of previousBlockTime
previousBlockTime = period.End
}
// Case 3 - period is ongoing
if (period.Start.Before(previousBlockTime) || period.Start.Equal(previousBlockTime)) && period.End.After(ctx.BlockTime()) {
// calculate time elapsed relative to the current block time
timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix())
err := k.mintInflationaryCoins(ctx, period.Inflation, timeElapsed, types.GovDenom)
if err != nil {
return err
}
}
// Case 4 - period hasn't started
if period.Start.After(ctx.BlockTime()) || period.Start.Equal(ctx.BlockTime()) {
continue
}
}
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
return nil
}
func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, timePeriods sdk.Int, denom string) sdk.Error {
totalSupply := k.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(denom)
// used to scale accumulator calculations by 10^18
scalar := sdk.NewInt(1000000000000000000)
// convert inflation rate to integer
inflationInt := inflationRate.Mul(sdk.NewDecFromInt(scalar)).TruncateInt()
// calculate the multiplier (amount to multiply the total supply by to achieve the desired inflation)
// multiply the result by 10^-18 because RelativePow returns the result scaled by 10^18
accumulator := sdk.NewDecFromInt(cdptypes.RelativePow(inflationInt, timePeriods, scalar)).Mul(sdk.SmallestDec())
// calculate the number of coins to mint
amountToMint := (sdk.NewDecFromInt(totalSupply).Mul(accumulator)).Sub(sdk.NewDecFromInt(totalSupply)).TruncateInt()
if amountToMint.IsZero() {
return nil
}
err := k.supplyKeeper.MintCoins(ctx, types.KavaDistMacc, sdk.NewCoins(sdk.NewCoin(denom, amountToMint)))
if err != nil {
return err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeKavaDist,
sdk.NewAttribute(types.AttributeKeyInflation, amountToMint.String()),
),
)
return nil
}

View File

@ -0,0 +1,144 @@
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/kavadist/keeper"
"github.com/kava-labs/kava/x/kavadist/types"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
)
type MintTestSuite struct {
suite.Suite
keeper keeper.Keeper
supplyKeeper types.SupplyKeeper
app app.TestApp
ctx sdk.Context
}
var (
p = types.Periods{
types.Period{
Start: time.Date(2020, time.March, 1, 1, 0, 0, 0, time.UTC),
End: time.Date(2021, time.March, 1, 1, 0, 0, 0, time.UTC),
Inflation: sdk.MustNewDecFromStr("1.000000003022265980"),
},
}
)
func (suite *MintTestSuite) SetupTest() {
config := sdk.GetConfig()
app.SetBech32AddressPrefixes(config)
tApp := app.NewTestApp()
_, addrs := app.GeneratePrivKeyAddressPairs(1)
coins := []sdk.Coins{sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1000000000000)))}
authGS := app.NewAuthGenState(
addrs, coins)
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
params := types.NewParams(true, p)
gs := app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(types.NewGenesisState(params, types.DefaultPreviousBlockTime))}
tApp.InitializeFromGenesisStates(
authGS,
gs,
)
keeper := tApp.GetKavadistKeeper()
sk := tApp.GetSupplyKeeper()
suite.app = tApp
suite.ctx = ctx
suite.keeper = keeper
suite.supplyKeeper = sk
}
func (suite *MintTestSuite) TestMintExpiredPeriod() {
initialSupply := suite.supplyKeeper.GetSupply(suite.ctx).GetTotal().AmountOf(types.GovDenom)
suite.NotPanics(func() { suite.keeper.SetPreviousBlockTime(suite.ctx, time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)) })
ctx := suite.ctx.WithBlockTime(time.Date(2022, 1, 1, 0, 7, 0, 0, time.UTC))
err := suite.keeper.MintPeriodInflation(ctx)
suite.NoError(err)
finalSupply := suite.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(types.GovDenom)
suite.Equal(initialSupply, finalSupply)
}
func (suite *MintTestSuite) TestMintPeriodNotStarted() {
initialSupply := suite.supplyKeeper.GetSupply(suite.ctx).GetTotal().AmountOf(types.GovDenom)
suite.NotPanics(func() { suite.keeper.SetPreviousBlockTime(suite.ctx, time.Date(2019, 1, 1, 0, 0, 0, 0, time.UTC)) })
ctx := suite.ctx.WithBlockTime(time.Date(2019, 1, 1, 0, 7, 0, 0, time.UTC))
err := suite.keeper.MintPeriodInflation(ctx)
suite.NoError(err)
finalSupply := suite.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(types.GovDenom)
suite.Equal(initialSupply, finalSupply)
}
func (suite *MintTestSuite) TestMintOngoingPeriod() {
initialSupply := suite.supplyKeeper.GetSupply(suite.ctx).GetTotal().AmountOf(types.GovDenom)
suite.NotPanics(func() {
suite.keeper.SetPreviousBlockTime(suite.ctx, time.Date(2020, time.March, 1, 1, 0, 1, 0, time.UTC))
})
ctx := suite.ctx.WithBlockTime(time.Date(2021, 2, 28, 23, 59, 59, 0, time.UTC))
err := suite.keeper.MintPeriodInflation(ctx)
suite.NoError(err)
finalSupply := suite.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(types.GovDenom)
suite.True(finalSupply.GT(initialSupply))
mAcc := suite.supplyKeeper.GetModuleAccount(ctx, types.ModuleName)
mAccSupply := mAcc.GetCoins().AmountOf(types.GovDenom)
suite.True(mAccSupply.Equal(finalSupply.Sub(initialSupply)))
// expect that inflation is ~10%
expectedSupply := sdk.NewDecFromInt(initialSupply).Mul(sdk.MustNewDecFromStr("1.1"))
supplyError := sdk.OneDec().Sub((sdk.NewDecFromInt(finalSupply).Quo(expectedSupply))).Abs()
suite.True(supplyError.LTE(sdk.MustNewDecFromStr("0.001")))
}
func (suite *MintTestSuite) TestMintPeriodTransition() {
initialSupply := suite.supplyKeeper.GetSupply(suite.ctx).GetTotal().AmountOf(types.GovDenom)
params := suite.keeper.GetParams(suite.ctx)
periods := types.Periods{
p[0],
types.Period{
Start: time.Date(2021, time.March, 1, 1, 0, 0, 0, time.UTC),
End: time.Date(2022, time.March, 1, 1, 0, 0, 0, time.UTC),
Inflation: sdk.MustNewDecFromStr("1.000000003022265980"),
},
}
params.Periods = periods
suite.NotPanics(func() {
suite.keeper.SetParams(suite.ctx, params)
})
suite.NotPanics(func() {
suite.keeper.SetPreviousBlockTime(suite.ctx, time.Date(2020, time.March, 1, 1, 0, 1, 0, time.UTC))
})
ctx := suite.ctx.WithBlockTime(time.Date(2021, 3, 10, 0, 0, 0, 0, time.UTC))
err := suite.keeper.MintPeriodInflation(ctx)
suite.NoError(err)
finalSupply := suite.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(types.GovDenom)
suite.True(finalSupply.GT(initialSupply))
}
func (suite *MintTestSuite) TestMintNotActive() {
initialSupply := suite.supplyKeeper.GetSupply(suite.ctx).GetTotal().AmountOf(types.GovDenom)
params := suite.keeper.GetParams(suite.ctx)
params.Active = false
suite.NotPanics(func() {
suite.keeper.SetParams(suite.ctx, params)
})
suite.NotPanics(func() {
suite.keeper.SetPreviousBlockTime(suite.ctx, time.Date(2020, time.March, 1, 1, 0, 1, 0, time.UTC))
})
ctx := suite.ctx.WithBlockTime(time.Date(2021, 2, 28, 23, 59, 59, 0, time.UTC))
err := suite.keeper.MintPeriodInflation(ctx)
suite.NoError(err)
finalSupply := suite.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(types.GovDenom)
suite.Equal(initialSupply, finalSupply)
}
func TestMintTestSuite(t *testing.T) {
suite.Run(t, new(MintTestSuite))
}

View File

@ -0,0 +1,18 @@
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/kavadist/types"
)
// GetParams returns the params from the store
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
var p types.Params
k.paramSubspace.GetParamSet(ctx, &p)
return p
}
// SetParams sets params on the store
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
k.paramSubspace.SetParamSet(ctx, &params)
}

153
x/kavadist/module.go Normal file
View File

@ -0,0 +1,153 @@
package kavadist
import (
"encoding/json"
"math/rand"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"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"
sim "github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/kava-labs/kava/x/kavadist/simulation"
"github.com/kava-labs/kava/x/kavadist/types"
abci "github.com/tendermint/tendermint/abci/types"
)
var (
_ module.AppModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
_ module.AppModuleSimulation = AppModuleSimulation{}
)
// AppModuleBasic app module basics object
type AppModuleBasic struct{}
// Name get module name
func (AppModuleBasic) Name() string {
return ModuleName
}
// RegisterCodec register module codec
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
RegisterCodec(cdc)
}
// DefaultGenesis default genesis state
func (AppModuleBasic) DefaultGenesis() json.RawMessage {
return ModuleCdc.MustMarshalJSON(DefaultGenesisState())
}
// ValidateGenesis module validate genesis
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 no REST routes for the crisis module.
func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {}
// GetTxCmd returns the root tx command for the crisis module.
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { return nil }
// GetQueryCmd returns no root query command for the crisis module.
func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil }
//____________________________________________________________________________
// AppModuleSimulation defines the module simulation functions used by the cdp module.
type AppModuleSimulation struct{}
// RegisterStoreDecoder registers a decoder for cdp module's types
func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
sdr[StoreKey] = simulation.DecodeStore
}
// GenerateGenesisState creates a randomized GenState of the cdp module
func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
// RandomizedParams creates randomized cdp param changes for the simulator.
func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange {
return simulation.ParamChanges(r)
}
//____________________________________________________________________________
// AppModule app module type
type AppModule struct {
AppModuleBasic
AppModuleSimulation
keeper Keeper
supplyKeeper types.SupplyKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
AppModuleSimulation: AppModuleSimulation{},
keeper: keeper,
supplyKeeper: supplyKeeper,
}
}
// Name module name
func (AppModule) Name() string {
return ModuleName
}
// RegisterInvariants register module invariants
func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
// Route module message route name
func (AppModule) Route() string {
return ModuleName
}
// NewHandler module handler
func (am AppModule) NewHandler() sdk.Handler {
return NewHandler(am.keeper)
}
// QuerierRoute module querier route name
func (AppModule) QuerierRoute() string {
return ModuleName
}
// NewQuerierHandler returns no sdk.Querier.
func (AppModule) NewQuerierHandler() sdk.Querier { return nil }
// InitGenesis module init-genesis
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 module export genesis
func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
gs := ExportGenesis(ctx, am.keeper)
return ModuleCdc.MustMarshalJSON(gs)
}
// BeginBlock module begin-block
func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {
BeginBlocker(ctx, am.keeper)
}
// EndBlock module end-block
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}

View File

@ -0,0 +1,12 @@
package simulation
import (
"github.com/cosmos/cosmos-sdk/codec"
cmn "github.com/tendermint/tendermint/libs/common"
)
// DecodeStore unmarshals the KVPair's Value to the corresponding cdp type
func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string {
// TODO implement this
return ""
}

View File

@ -0,0 +1,14 @@
package simulation
import (
"math/rand"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// ParamChanges defines the parameters that can be modified by param change proposals
// on the simulation
func ParamChanges(r *rand.Rand) []simulation.ParamChange {
// TODO implement this
return []simulation.ParamChange{}
}

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