From 82f637649c75b317db63ef1486f4cf635b1fe739 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 27 Mar 2020 22:54:00 -0400 Subject: [PATCH 01/45] [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 * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update go.sum Co-Authored-By: Kevin Davis * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update x/cdp/abci.go Co-Authored-By: Kevin Davis * 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 * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * 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 * Update x/cdp/spec/04_begin_block.md Fix typo as suggested Co-Authored-By: Kevin Davis Co-authored-by: Kevin Davis * 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 * Update swagger-ui/startchain.sh Send output to dev null not to console Co-Authored-By: Kevin Davis * 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 Co-authored-by: Kevin Davis * 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 Co-authored-by: John Maheswaran Co-authored-by: John Maheswaran --- Makefile | 3 + app/app.go | 41 +- app/test_common.go | 4 + .../genesis_savings_rate.json | 352 ++++++++ contrib/testnet-5000/README.md | 125 +++ .../genesis_examples/bep3_genesis.json | 244 ++++++ .../internal_proposed_genesis.json | 244 ++++++ .../rest_examples/broadcast-claim-swap.json | 29 + .../rest_examples/broadcast-create-swap.json | 41 + .../rest_examples/broadcast-refund-swap.json | 28 + .../rest_examples/claim-swap-unsigned.json | 21 + .../rest_examples/claim-swap.json | 15 + .../rest_examples/create-swap-unsigned.json | 33 + .../rest_examples/create-swap.json | 27 + .../rest_examples/refund-swap-unsigned.json | 20 + .../rest_examples/refund-swap.json | 14 + go.mod | 6 +- go.sum | 66 +- rest_test/how_to_run_dredd_tests.md | 25 + rest_test/run_all_tests_from_make.sh | 133 +++ rest_test/setup/setuptest.go | 349 ++++++++ rest_test/stopchain.sh | 7 + swagger-ui/swagger.yaml | 766 +++++++++++------- x/bep3/abci.go | 18 + x/bep3/abci_test.go | 242 ++++++ x/bep3/alias.go | 160 ++++ x/bep3/client/cli/query.go | 225 +++++ x/bep3/client/cli/tx.go | 181 +++++ x/bep3/client/rest/query.go | 164 ++++ x/bep3/client/rest/rest.go | 46 ++ x/bep3/client/rest/tx.go | 110 +++ x/bep3/genesis.go | 122 +++ x/bep3/genesis_test.go | 298 +++++++ x/bep3/handler.go | 89 ++ x/bep3/handler_test.go | 132 +++ x/bep3/integration_test.go | 87 ++ x/bep3/keeper/asset.go | 113 +++ x/bep3/keeper/asset_test.go | 415 ++++++++++ x/bep3/keeper/integration_test.go | 94 +++ x/bep3/keeper/keeper.go | 223 +++++ x/bep3/keeper/keeper_test.go | 370 +++++++++ x/bep3/keeper/params.go | 75 ++ x/bep3/keeper/params_test.go | 141 ++++ x/bep3/keeper/querier.go | 100 +++ x/bep3/keeper/querier_test.go | 167 ++++ x/bep3/keeper/swap.go | 266 ++++++ x/bep3/keeper/swap_test.go | 696 ++++++++++++++++ x/bep3/module.go | 136 ++++ x/bep3/spec/README.md | 17 + x/bep3/types/asset.go | 50 ++ x/bep3/types/codec.go | 20 + x/bep3/types/common_test.go | 35 + x/bep3/types/errors.go | 118 +++ x/bep3/types/events.go | 30 + x/bep3/types/expected_keepers.go | 19 + x/bep3/types/genesis.go | 66 ++ x/bep3/types/genesis_test.go | 104 +++ x/bep3/types/hash.go | 48 ++ x/bep3/types/hash_test.go | 61 ++ x/bep3/types/keys.go | 67 ++ x/bep3/types/msg.go | 260 ++++++ x/bep3/types/msg_test.go | 122 +++ x/bep3/types/params.go | 146 ++++ x/bep3/types/params_test.go | 217 +++++ x/bep3/types/querier.go | 54 ++ x/bep3/types/swap.go | 219 +++++ x/bep3/types/swap_test.go | 140 ++++ x/cdp/abci.go | 40 +- x/cdp/alias.go | 74 +- x/cdp/genesis.go | 23 +- x/cdp/integration_test.go | 61 +- x/cdp/keeper/auctions.go | 11 +- x/cdp/keeper/cdp_test.go | 1 + x/cdp/keeper/fees.go | 40 + x/cdp/keeper/fees_test.go | 59 ++ x/cdp/keeper/integration_test.go | 39 +- x/cdp/keeper/keeper.go | 14 +- x/cdp/keeper/savings.go | 95 +++ x/cdp/keeper/savings_test.go | 81 ++ x/cdp/keeper/seize.go | 18 +- x/cdp/module.go | 6 +- x/cdp/spec/04_begin_block.md | 16 +- x/cdp/spec/06_params.md | 13 +- x/cdp/types/cdp.go | 2 +- x/cdp/types/expected_keepers.go | 7 + x/cdp/types/genesis.go | 51 +- x/cdp/types/keys.go | 23 +- x/cdp/types/params.go | 83 +- x/kavadist/abci.go | 12 + x/kavadist/alias.go | 54 ++ x/kavadist/doc.go | 1 + x/kavadist/genesis.go | 39 + x/kavadist/handler.go | 18 + x/kavadist/keeper/keeper.go | 49 ++ x/kavadist/keeper/mint.go | 87 ++ x/kavadist/keeper/mint_test.go | 144 ++++ x/kavadist/keeper/params.go | 18 + x/kavadist/module.go | 153 ++++ x/kavadist/simulation/decoder.go | 12 + x/kavadist/simulation/params.go | 14 + x/kavadist/simulation/simulation.go | 22 + x/kavadist/types/codec.go | 17 + x/kavadist/types/errors.go | 11 + x/kavadist/types/events.go | 9 + x/kavadist/types/expected_keepers.go | 15 + x/kavadist/types/genesis.go | 54 ++ x/kavadist/types/keys.go | 26 + x/kavadist/types/params.go | 102 +++ x/kavadist/types/params_test.go | 99 +++ x/kavadist/types/querier.go | 6 + x/pricefeed/client/rest/tx.go | 2 +- 111 files changed, 10262 insertions(+), 515 deletions(-) create mode 100644 contrib/genesis_examples/genesis_savings_rate.json create mode 100644 contrib/testnet-5000/README.md create mode 100644 contrib/testnet-5000/genesis_examples/bep3_genesis.json create mode 100644 contrib/testnet-5000/genesis_examples/internal_proposed_genesis.json create mode 100644 contrib/testnet-5000/rest_examples/broadcast-claim-swap.json create mode 100644 contrib/testnet-5000/rest_examples/broadcast-create-swap.json create mode 100644 contrib/testnet-5000/rest_examples/broadcast-refund-swap.json create mode 100644 contrib/testnet-5000/rest_examples/claim-swap-unsigned.json create mode 100644 contrib/testnet-5000/rest_examples/claim-swap.json create mode 100644 contrib/testnet-5000/rest_examples/create-swap-unsigned.json create mode 100644 contrib/testnet-5000/rest_examples/create-swap.json create mode 100644 contrib/testnet-5000/rest_examples/refund-swap-unsigned.json create mode 100644 contrib/testnet-5000/rest_examples/refund-swap.json create mode 100644 rest_test/how_to_run_dredd_tests.md create mode 100755 rest_test/run_all_tests_from_make.sh create mode 100644 rest_test/setup/setuptest.go create mode 100755 rest_test/stopchain.sh create mode 100644 x/bep3/abci.go create mode 100644 x/bep3/abci_test.go create mode 100644 x/bep3/alias.go create mode 100644 x/bep3/client/cli/query.go create mode 100644 x/bep3/client/cli/tx.go create mode 100644 x/bep3/client/rest/query.go create mode 100644 x/bep3/client/rest/rest.go create mode 100644 x/bep3/client/rest/tx.go create mode 100644 x/bep3/genesis.go create mode 100644 x/bep3/genesis_test.go create mode 100644 x/bep3/handler.go create mode 100644 x/bep3/handler_test.go create mode 100644 x/bep3/integration_test.go create mode 100644 x/bep3/keeper/asset.go create mode 100644 x/bep3/keeper/asset_test.go create mode 100644 x/bep3/keeper/integration_test.go create mode 100644 x/bep3/keeper/keeper.go create mode 100644 x/bep3/keeper/keeper_test.go create mode 100644 x/bep3/keeper/params.go create mode 100644 x/bep3/keeper/params_test.go create mode 100644 x/bep3/keeper/querier.go create mode 100644 x/bep3/keeper/querier_test.go create mode 100644 x/bep3/keeper/swap.go create mode 100644 x/bep3/keeper/swap_test.go create mode 100644 x/bep3/module.go create mode 100644 x/bep3/spec/README.md create mode 100644 x/bep3/types/asset.go create mode 100644 x/bep3/types/codec.go create mode 100644 x/bep3/types/common_test.go create mode 100644 x/bep3/types/errors.go create mode 100644 x/bep3/types/events.go create mode 100644 x/bep3/types/expected_keepers.go create mode 100644 x/bep3/types/genesis.go create mode 100644 x/bep3/types/genesis_test.go create mode 100644 x/bep3/types/hash.go create mode 100644 x/bep3/types/hash_test.go create mode 100644 x/bep3/types/keys.go create mode 100644 x/bep3/types/msg.go create mode 100644 x/bep3/types/msg_test.go create mode 100644 x/bep3/types/params.go create mode 100644 x/bep3/types/params_test.go create mode 100644 x/bep3/types/querier.go create mode 100644 x/bep3/types/swap.go create mode 100644 x/bep3/types/swap_test.go create mode 100644 x/cdp/keeper/savings.go create mode 100644 x/cdp/keeper/savings_test.go create mode 100644 x/kavadist/abci.go create mode 100644 x/kavadist/alias.go create mode 100644 x/kavadist/doc.go create mode 100644 x/kavadist/genesis.go create mode 100644 x/kavadist/handler.go create mode 100644 x/kavadist/keeper/keeper.go create mode 100644 x/kavadist/keeper/mint.go create mode 100644 x/kavadist/keeper/mint_test.go create mode 100644 x/kavadist/keeper/params.go create mode 100644 x/kavadist/module.go create mode 100644 x/kavadist/simulation/decoder.go create mode 100644 x/kavadist/simulation/params.go create mode 100644 x/kavadist/simulation/simulation.go create mode 100644 x/kavadist/types/codec.go create mode 100644 x/kavadist/types/errors.go create mode 100644 x/kavadist/types/events.go create mode 100644 x/kavadist/types/expected_keepers.go create mode 100644 x/kavadist/types/genesis.go create mode 100644 x/kavadist/types/keys.go create mode 100644 x/kavadist/types/params.go create mode 100644 x/kavadist/types/params_test.go create mode 100644 x/kavadist/types/querier.go diff --git a/Makefile b/Makefile index d2712d6f..11c1c996 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/app/app.go b/app/app.go index 3d4daa72..0bce4540 100644 --- a/app/app.go +++ b/app/app.go @@ -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() diff --git a/app/test_common.go b/app/test_common.go index 7bb0da05..be6032dd 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -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 { diff --git a/contrib/genesis_examples/genesis_savings_rate.json b/contrib/genesis_examples/genesis_savings_rate.json new file mode 100644 index 00000000..538d5b72 --- /dev/null +++ b/contrib/genesis_examples/genesis_savings_rate.json @@ -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": {} + } + } +} \ No newline at end of file diff --git a/contrib/testnet-5000/README.md b/contrib/testnet-5000/README.md new file mode 100644 index 00000000..f85311ad --- /dev/null +++ b/contrib/testnet-5000/README.md @@ -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 +``` diff --git a/contrib/testnet-5000/genesis_examples/bep3_genesis.json b/contrib/testnet-5000/genesis_examples/bep3_genesis.json new file mode 100644 index 00000000..4853c045 --- /dev/null +++ b/contrib/testnet-5000/genesis_examples/bep3_genesis.json @@ -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 + } +} diff --git a/contrib/testnet-5000/genesis_examples/internal_proposed_genesis.json b/contrib/testnet-5000/genesis_examples/internal_proposed_genesis.json new file mode 100644 index 00000000..4853c045 --- /dev/null +++ b/contrib/testnet-5000/genesis_examples/internal_proposed_genesis.json @@ -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 + } +} diff --git a/contrib/testnet-5000/rest_examples/broadcast-claim-swap.json b/contrib/testnet-5000/rest_examples/broadcast-claim-swap.json new file mode 100644 index 00000000..e0ca95bf --- /dev/null +++ b/contrib/testnet-5000/rest_examples/broadcast-claim-swap.json @@ -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": "" + } +} diff --git a/contrib/testnet-5000/rest_examples/broadcast-create-swap.json b/contrib/testnet-5000/rest_examples/broadcast-create-swap.json new file mode 100644 index 00000000..b37d8457 --- /dev/null +++ b/contrib/testnet-5000/rest_examples/broadcast-create-swap.json @@ -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": "" + } +} diff --git a/contrib/testnet-5000/rest_examples/broadcast-refund-swap.json b/contrib/testnet-5000/rest_examples/broadcast-refund-swap.json new file mode 100644 index 00000000..d3fc0070 --- /dev/null +++ b/contrib/testnet-5000/rest_examples/broadcast-refund-swap.json @@ -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": "" + } +} diff --git a/contrib/testnet-5000/rest_examples/claim-swap-unsigned.json b/contrib/testnet-5000/rest_examples/claim-swap-unsigned.json new file mode 100644 index 00000000..eeed7deb --- /dev/null +++ b/contrib/testnet-5000/rest_examples/claim-swap-unsigned.json @@ -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": "" + } +} diff --git a/contrib/testnet-5000/rest_examples/claim-swap.json b/contrib/testnet-5000/rest_examples/claim-swap.json new file mode 100644 index 00000000..54db1637 --- /dev/null +++ b/contrib/testnet-5000/rest_examples/claim-swap.json @@ -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" +} diff --git a/contrib/testnet-5000/rest_examples/create-swap-unsigned.json b/contrib/testnet-5000/rest_examples/create-swap-unsigned.json new file mode 100644 index 00000000..c610123b --- /dev/null +++ b/contrib/testnet-5000/rest_examples/create-swap-unsigned.json @@ -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": "" + } +} diff --git a/contrib/testnet-5000/rest_examples/create-swap.json b/contrib/testnet-5000/rest_examples/create-swap.json new file mode 100644 index 00000000..c891d1e8 --- /dev/null +++ b/contrib/testnet-5000/rest_examples/create-swap.json @@ -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 +} diff --git a/contrib/testnet-5000/rest_examples/refund-swap-unsigned.json b/contrib/testnet-5000/rest_examples/refund-swap-unsigned.json new file mode 100644 index 00000000..faa176c2 --- /dev/null +++ b/contrib/testnet-5000/rest_examples/refund-swap-unsigned.json @@ -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": "" + } +} diff --git a/contrib/testnet-5000/rest_examples/refund-swap.json b/contrib/testnet-5000/rest_examples/refund-swap.json new file mode 100644 index 00000000..4cbf7ff1 --- /dev/null +++ b/contrib/testnet-5000/rest_examples/refund-swap.json @@ -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" +} diff --git a/go.mod b/go.mod index ffba0067..10583537 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index cb578ad0..4a1f0603 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/rest_test/how_to_run_dredd_tests.md b/rest_test/how_to_run_dredd_tests.md new file mode 100644 index 00000000..96c3ebe9 --- /dev/null +++ b/rest_test/how_to_run_dredd_tests.md @@ -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`) + + diff --git a/rest_test/run_all_tests_from_make.sh b/rest_test/run_all_tests_from_make.sh new file mode 100755 index 00000000..1b0ca862 --- /dev/null +++ b/rest_test/run_all_tests_from_make.sh @@ -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 diff --git a/rest_test/setup/setuptest.go b/rest_test/setup/setuptest.go new file mode 100644 index 00000000..9d66f390 --- /dev/null +++ b/rest_test/setup/setuptest.go @@ -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() + +} diff --git a/rest_test/stopchain.sh b/rest_test/stopchain.sh new file mode 100755 index 00000000..d392bbbd --- /dev/null +++ b/rest_test/stopchain.sh @@ -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" \ No newline at end of file diff --git a/swagger-ui/swagger.yaml b/swagger-ui/swagger.yaml index e3b526d1..9d564f22 100644 --- a/swagger-ui/swagger.yaml +++ b/swagger-ui/swagger.yaml @@ -152,7 +152,7 @@ description: Block height required: true type: number - x-example: 1 + x-example: 2 responses: 200: description: The block at a specific height @@ -231,7 +231,7 @@ description: Tx hash required: true type: string - x-example: BCBE20E8D46758B96AE5883B792858296AC06E51435490FBDCAE25A72B3CC76B + x-example: B901DB8552BC5F5F82E5709EC37AAD8C0F8005CDB3608E5580055CC4D0D9B275 responses: 200: description: Tx with the provided hash @@ -256,8 +256,8 @@ - in: query name: message.sender type: string - description: "transaction tags with sender: 'GET /txs?message.action=send&message.sender=kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv'" - x-example: "kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv" + description: "transaction tags with sender: 'GET /txs?message.action=send&message.sender=kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9'" + x-example: "kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9" - in: query name: page description: Page number @@ -268,6 +268,11 @@ description: Maximum number of items per page type: integer x-example: 1 + - in: query + name: txheight # this should actually be tx.height but dredd doesn't handle periods in get parameter names so no period allows the test to pass + description: Transaction height + type: integer + x-example: 1 responses: 200: description: All txs matching the provided events @@ -295,10 +300,10 @@ type: object properties: tx: - $ref: "#/definitions/StdTx" + $ref: "#/definitions/PostStdTx" mode: type: string - example: sync + example: block responses: 200: description: Tx broadcasting result @@ -317,15 +322,13 @@ produces: - application/json parameters: - - in: body - name: tx - description: The tx to encode + - description: "" + name: EncodeBody + in: body required: true schema: type: object - properties: - tx: - $ref: "#/definitions/StdTx" + $ref: "#/definitions/EncodeTx" responses: 200: description: The tx was successfully decoded and re-encoded @@ -336,7 +339,7 @@ type: string example: The base64-encoded Amino-serialized bytes for the tx 400: - description: The tx was malformated + description: The tx was malformatted 500: description: Server internal error /txs/decode: @@ -359,7 +362,7 @@ properties: tx: type: string - example: SvBiXe4KPqijYZoKFFHEzJ8c2HPAfv2EFUcIhx0yPagwEhTy0vPA+GGhCEslKXa4Af0uB+mfShoMCgVzdGFrZRIDMTAwEgQQwJoM + example: ogEoKBapCjyoo2GaChRKWendsRagTF1ACC1nxzjVxW3xJBIU/A6hCReBn5NYI6K2dl10kCEeTfgaCgoFc3Rha2USATESEAoKCgVzdGFrZRIBMRDAmgwaQhJAdlC1HbNw+ux6lRrK3mNdmaH62NE3ThD8SswlDcnhFex7pKSNhaxE4m6TgDhosoK6EyU0LnOZKutXKECNSvO+WCIIdGVzdG1lbW8= responses: 200: description: The tx was successfully decoded @@ -382,14 +385,18 @@ description: Account address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c responses: 200: description: Account balances schema: - type: array - items: - $ref: "#/definitions/Coin" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Coin" 500: description: Server internal error /bank/accounts/{address}/transfers: @@ -407,7 +414,7 @@ description: Account address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 - in: body name: account description: The sender and tx information @@ -422,8 +429,8 @@ items: $ref: "#/definitions/Coin" responses: - 202: - description: Tx was succesfully generated + 200: + description: Tx was successfully generated schema: $ref: "#/definitions/StdTx" 400: @@ -443,7 +450,7 @@ description: Account address required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 responses: 200: description: Account information on the blockchain @@ -476,7 +483,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Get all delegations from a delegator tags: @@ -487,47 +494,55 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Delegation" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Delegation" 400: description: Invalid delegator address 500: description: Internal Server Error - post: - summary: Submit delegation - parameters: - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - delegator_address: - $ref: "#/definitions/Address" - validator_address: - $ref: "#/definitions/ValidatorAddress" - delegation: - $ref: "#/definitions/Coin" - tags: - - Staking - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: OK - schema: - $ref: "#/definitions/BroadcastTxCommitResult" - 400: - description: Invalid delegator address or delegation request body - 401: - description: Key password is wrong - 500: - description: Internal Server Error + + # THE BELOW ENDPOINT IS NOT IMPLEMENTED IN COSMOS, SEE: + # https://github.com/cosmos/cosmos-sdk/blob/18de630d0ae1887113e266982b51c2bf1f662edb/x/staking/client/rest/query.go + + # post: + # summary: Submit delegation + # parameters: + # - in: body + # name: delegation + # description: The password of the account to remove from the KMS + # schema: + # type: object + # properties: + # base_req: + # $ref: "#/definitions/BaseReq" + # delegator_address: + # $ref: "#/definitions/Address" + # validator_address: + # $ref: "#/definitions/ValidatorAddress" + # delegation: + # $ref: "#/definitions/Coin" + # tags: + # - Staking + # consumes: + # - application/json + # produces: + # - application/json + # responses: + # 200: + # description: OK + # schema: + # $ref: "#/definitions/BroadcastTxCommitResult" + # 400: + # description: Invalid delegator address or delegation request body + # 401: + # description: Key password is wrong + # 500: + # description: Internal Server Error /staking/delegators/{delegatorAddr}/delegations/{validatorAddr}: parameters: - in: path @@ -535,13 +550,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query the current delegation between a delegator and a validator tags: @@ -564,7 +579,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Get all unbonding delegations from a delegator tags: @@ -575,48 +590,57 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/UnbondingDelegation" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/UnbondingDelegation" + 400: description: Invalid delegator address 500: description: Internal Server Error - post: - summary: Submit an unbonding delegation - parameters: - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: "#/definitions/BaseReq" - delegator_address: - $ref: "#/definitions/Address" - validator_address: - $ref: "#/definitions/ValidatorAddress" - shares: - type: string - example: "100" - tags: - - Staking - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: OK - schema: - $ref: "#/definitions/BroadcastTxCommitResult" - 400: - description: Invalid delegator address or unbonding delegation request body - 401: - description: Key password is wrong - 500: - description: Internal Server Error + + # THE BELOW ENDPOINT IS NOT IMPLEMENTED IN COSMOS, SEE: + # https://github.com/cosmos/cosmos-sdk/blob/18de630d0ae1887113e266982b51c2bf1f662edb/x/staking/client/rest/query.go + + # post: + # summary: Submit an unbonding delegation + # parameters: + # - in: body + # name: delegation + # description: The password of the account to remove from the KMS + # schema: + # type: object + # properties: + # base_req: + # $ref: "#/definitions/BaseReq" + # delegator_address: + # $ref: "#/definitions/Address" + # validator_address: + # $ref: "#/definitions/ValidatorAddress" + # shares: + # type: string + # example: "100" + # tags: + # - Staking + # consumes: + # - application/json + # produces: + # - application/json + # responses: + # 200: + # description: OK + # schema: + # $ref: "#/definitions/BroadcastTxCommitResult" + # 400: + # description: Invalid delegator address or unbonding delegation request body + # 401: + # description: Key password is wrong + # 500: + # description: Internal Server Error /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: parameters: - in: path @@ -624,13 +648,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query all unbonding delegations between a delegator and a validator tags: @@ -672,55 +696,63 @@ responses: 200: description: OK - schema: - type: array - items: - $ref: "#/definitions/Redelegation" - 500: - description: Internal Server Error - /staking/delegators/{delegatorAddr}/redelegations: - parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv - post: - summary: Submit a redelegation - parameters: - - in: body - name: delegation - description: The sender and tx information schema: type: object properties: - base_req: - $ref: "#/definitions/BaseReq" - delegator_address: - $ref: "#/definitions/Address" - validator_src_addressess: - $ref: "#/definitions/ValidatorAddress" - validator_dst_address: - $ref: "#/definitions/ValidatorAddress" - shares: + height: type: string - example: "100" - tags: - - Staking - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: Tx was succesfully generated - schema: - $ref: "#/definitions/StdTx" - 400: - description: Invalid delegator address or redelegation request body + result: + items: + $ref: "#/definitions/Redelegation" 500: description: Internal Server Error + + # THE BELOW ENDPOINT IS NOT IMPLEMENTED IN COSMOS, SEE: + # https://github.com/cosmos/cosmos-sdk/blob/18de630d0ae1887113e266982b51c2bf1f662edb/x/staking/client/rest/query.go + + # /staking/delegators/{delegatorAddr}/redelegations: + # parameters: + # - in: path + # name: delegatorAddr + # description: Bech32 AccAddress of Delegator + # required: true + # type: string + # x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c + # post: + # summary: Submit a redelegation + # parameters: + # - in: body + # name: delegation + # description: The sender and tx information + # schema: + # type: object + # properties: + # base_req: + # $ref: "#/definitions/BaseReq" + # delegator_address: + # $ref: "#/definitions/Address" + # validator_src_addresses: + # $ref: "#/definitions/ValidatorAddress" + # validator_dst_address: + # $ref: "#/definitions/ValidatorAddress" + # shares: + # type: string + # example: "100" + # tags: + # - Staking + # consumes: + # - application/json + # produces: + # - application/json + # responses: + # 200: + # description: Tx was successfully generated + # schema: + # $ref: "#/definitions/StdTx" + # 400: + # description: Invalid delegator address or redelegation request body + # 500: + # description: Internal Server Error /staking/delegators/{delegatorAddr}/validators: parameters: - in: path @@ -728,7 +760,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Query all validators that a delegator is bonded to tags: @@ -739,9 +771,13 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Validator" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Validator" 400: description: Invalid delegator address 500: @@ -753,13 +789,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 ValAddress of Delegator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query a validator that a delegator is bonded to tags: @@ -802,9 +838,13 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Validator" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Validator" 500: description: Internal Server Error /staking/validators/{validatorAddr}: @@ -814,7 +854,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query the information from a single validator tags: @@ -825,7 +865,14 @@ 200: description: OK schema: - $ref: "#/definitions/Validator" + type: object + properties: + height: + type: string + result: + # type: array // TODO what if there are multiple validators? should it be an array? + items: + $ref: "#/definitions/Validator" 400: description: Invalid validator address 500: @@ -837,7 +884,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1q53rwutgpzx7szcrgzqguxyccjpzt9j44jzrtj get: summary: Get all delegations from a validator tags: @@ -848,9 +895,13 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Delegation" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Delegation" 400: description: Invalid validator address 500: @@ -862,7 +913,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1q53rwutgpzx7szcrgzqguxyccjpzt9j44jzrtj get: summary: Get all unbonding delegations from a validator tags: @@ -873,9 +924,13 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/UnbondingDelegation" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/UnbondingDelegation" 400: description: Invalid validator address 500: @@ -986,9 +1041,14 @@ 200: description: OK schema: - type: array items: - $ref: "#/definitions/SigningInfo" + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/SigningInfo" 400: description: Invalid validator public key for one of the validators 500: @@ -1009,7 +1069,7 @@ name: validatorAddr required: true in: path - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1q53rwutgpzx7szcrgzqguxyccjpzt9j44jzrtj - description: "" name: UnjailBody in: body @@ -1018,10 +1078,10 @@ type: object properties: base_req: - $ref: "#/definitions/StdTx" + $ref: "#/definitions/BaseReq" responses: 200: - description: Tx was succesfully generated + description: Tx was successfully generated schema: $ref: "#/definitions/BroadcastTxCommitResult" 400: @@ -1079,8 +1139,10 @@ $ref: "#/definitions/BaseReq" title: type: string + example: Test_title description: type: string + example: my_test_description proposal_type: type: string example: "text" @@ -1092,7 +1154,7 @@ $ref: "#/definitions/Coin" responses: 200: - description: Tx was succesfully generated + description: Tx was successfully generated schema: $ref: "#/definitions/StdTx" 400: @@ -1126,9 +1188,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/TextProposal" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/TextProposal" 400: description: Invalid query parameters 500: @@ -1155,10 +1222,10 @@ $ref: "#/definitions/BaseReq" title: type: string - x-example: "Param Change" + example: Param_Change description: type: string - x-example: "Update max validators" + example: Update_max_validators proposer: $ref: "#/definitions/Address" deposit: @@ -1171,7 +1238,7 @@ $ref: "#/definitions/ParamChange" responses: 200: - description: The transaction was succesfully generated + description: The transaction was successfully generated schema: $ref: "#/definitions/StdTx" 400: @@ -1242,9 +1309,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Deposit" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Deposit" 400: description: Invalid proposal id 500: @@ -1311,7 +1383,7 @@ name: depositor required: true in: path - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c responses: 200: description: OK @@ -1342,9 +1414,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Vote" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Vote" 400: description: Invalid proposal id 500: @@ -1410,7 +1487,7 @@ name: voter required: true in: path - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c responses: 200: description: OK @@ -1530,7 +1607,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava167w96tdvmazakdwkw2u57227eduula2cy572lf + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c get: summary: Get the total rewards balance from all delegations description: Get the sum of all the rewards earned by delegations by a single delegator @@ -1581,13 +1658,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query a delegation reward description: Query a single delegation reward by a delegator @@ -1599,9 +1676,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Coin" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Coin" 400: description: Invalid delegator address 500: @@ -1640,7 +1722,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava167w96tdvmazakdwkw2u57227eduula2cy572lf + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Get the rewards withdrawal address description: Get the delegations' rewards withdrawal address. This is the address in which the user will receive the reward funds @@ -1652,7 +1734,13 @@ 200: description: OK schema: - $ref: "#/definitions/Address" + type: object + properties: + height: + type: string + result: + $ref: "#/definitions/Address" + 400: description: Invalid delegator address 500: @@ -1693,7 +1781,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Validator distribution information description: Query the distribution information of a single validator @@ -1717,7 +1805,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Fee distribution outstanding rewards of a single validator tags: @@ -1728,9 +1816,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Coin" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Coin" 500: description: Internal Server Error /distribution/validators/{validatorAddr}/rewards: @@ -1740,7 +1833,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Commission and self-delegation rewards of a single validator description: Query the commission and self-delegation rewards of validator. @@ -1752,9 +1845,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Coin" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Coin" 400: description: Invalid validator address 500: @@ -1797,9 +1895,14 @@ 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Coin" + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Coin" 500: description: Internal Server Error /distribution/parameters: @@ -1859,7 +1962,7 @@ 200: description: OK schema: - type: string + $ref: "#/definitions/StandardResponse" 500: description: Internal Server Error /minting/annual-provisions: @@ -1873,7 +1976,7 @@ 200: description: OK schema: - type: string + $ref: "#/definitions/StandardResponse" 500: description: Internal Server Error /supply/total: @@ -1908,7 +2011,7 @@ 200: description: OK schema: - type: string + $ref: "#/definitions/StandardResponse" 400: description: Invalid coin denomination 500: @@ -1982,7 +2085,7 @@ hash: $ref: "#/definitions/Hash" height: - type: integer + type: string KVPair: type: object properties: @@ -1992,8 +2095,6 @@ type: string Msg: type: object - required: - - from properties: type: type: string @@ -2001,26 +2102,72 @@ value: type: object properties: - from: + from_address: type: string - example: kava1mzqz3jfs9nzfp6v7qp647rv0afxlu2csl0txmq + example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c + to_address: + type: string + example: kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz + amount: + type: array + items: + $ref: "#/definitions/Coin" + PostMsg: + type: object + properties: + type: + type: string + example: "cosmos-sdk/MsgSend" + value: + type: object + properties: + from_address: + type: string + example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c + to_address: + type: string + example: kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz + amount: + type: array + items: + $ref: "#/definitions/PostCoin1" + + Address: type: string description: bech32 encoded address - example: kava1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27 + example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c ValidatorAddress: type: string description: bech32 encoded address - example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + example: kavavaloper1q53rwutgpzx7szcrgzqguxyccjpzt9j44jzrtj Coin: type: object properties: denom: type: string - example: ukava + example: stake amount: type: string - example: "50" + example: "1" + PostCoin1: + type: object + properties: + denom: + type: string + example: stake + amount: + type: string + example: "2000000" + PostCoin2: + type: object + properties: + denom: + type: string + example: stake + amount: + type: string + example: "2000" Hash: type: string example: EE5F3404034C524501629B56E0DDC38FAD651F04 @@ -2029,7 +2176,7 @@ properties: signature: type: string - example: "W1HfKcc4F0rCSoxheZ7fsrB5nGK58U4gKysuzsmUwhloVnCxmbCx289uVMMvQN6tOcQsz7hMVTJrXSA1xzevvw==" + example: "dlC1HbNw+ux6lRrK3mNdmaH62NE3ThD8SswlDcnhFex7pKSNhaxE4m6TgDhosoK6EyU0LnOZKutXKECNSvO+WA==" pubkey: type: object properties: @@ -2038,7 +2185,24 @@ example: "tendermint/PubKeySecp256k1" value: type: string - example: "Agey31h/NYpcy0sYm4liHMrXJMzbQUrgV4uHd/w09CXN" + example: "AmWAim83Qp+kIcj3RT7i327b3l0EHwzCrGVGXusb70B7" + + PostSignature: + type: object + properties: + signature: + type: string + example: "KzBeQp/2+47oiqc16BImnOYidSAesZ3kgR3S8Fy+baFlvDlDv7goU/+rm4c7+woudNte3uZAG0CuUeHsF+Ld8Q==" + pubkey: + type: object + properties: + type: + type: string + example: "tendermint/PubKeySecp256k1" + value: + type: string + example: "AmWAim83Qp+kIcj3RT7i327b3l0EHwzCrGVGXusb70B7" + TxQuery: type: object properties: @@ -2046,8 +2210,8 @@ type: string example: "D085138D913993919295FF4B0A9107F1F2CDE0D37A87CE0644E217CBF3B49656" height: - type: number - example: 368 + type: string + example: "368" tx: $ref: "#/definitions/StdTx" result: @@ -2069,20 +2233,20 @@ type: object properties: total_count: - type: number - example: 1 + type: string + example: "1" count: - type: number - example: 1 + type: string + example: "1" page_number: - type: number - example: 1 + type: string + example: "1" page_total: - type: number - example: 1 + type: string + example: "1" limit: - type: number - example: 30 + type: string + example: "30" txs: type: array items: @@ -2099,16 +2263,53 @@ properties: gas: type: string + example: "200000" amount: type: array items: $ref: "#/definitions/Coin" memo: type: string + example: "testmemo" signatures: type: array items: $ref: "#/definitions/Signature" + + PostStdTx: + type: object + properties: + msg: + type: array + items: + $ref: "#/definitions/PostMsg" + fee: + type: object + properties: + gas: + type: string + example: "200000" + amount: + type: array + items: + $ref: "#/definitions/PostCoin2" + memo: + type: string + example: "" + signatures: + type: array + items: + $ref: "#/definitions/PostSignature" + + EncodeTx: + type: object + properties: + type: + type: string + example: "cosmos-sdk/StdTx" + value: + type: object + $ref: "#/definitions/StdTx" BlockID: type: object properties: @@ -2118,8 +2319,8 @@ type: object properties: total: - type: number - example: 0 + type: string + example: "0" hash: $ref: "#/definitions/Hash" BlockHeader: @@ -2129,19 +2330,19 @@ type: string example: kavahub-2 height: - type: number - example: 1 + type: string + example: "1" time: type: string example: "2017-12-30T05:53:09.287+01:00" num_txs: - type: number - example: 0 + type: string + example: "0" last_block_id: $ref: "#/definitions/BlockID" total_txs: - type: number - example: 35 + type: string + example: "35" last_commit_hash: $ref: "#/definitions/Hash" data_hash: @@ -2174,46 +2375,54 @@ properties: header: $ref: "#/definitions/BlockHeader" - txs: - type: array - items: - type: string + data: + type: object + properties: + txs: + type: array + x-nullable: true evidence: - type: array - items: - type: string + type: object + properties: + evidence: + type: object + x-nullable: true last_commit: type: object properties: block_id: $ref: "#/definitions/BlockID" precommits: - type: array - items: - type: object - properties: - validator_address: - type: string - validator_index: - type: string - example: "0" - height: - type: string - example: "0" - round: - type: string - example: "0" - timestamp: - type: string - example: "2017-12-30T05:53:09.287+01:00" - type: - type: number - example: 2 - block_id: - $ref: "#/definitions/BlockID" - signature: - type: string - example: "7uTC74QlknqYWEwg7Vn6M8Om7FuZ0EO4bjvuj6rwH1mTUJrRuMMZvAAqT9VjNgP0RA/TDp6u/92AqrZfXJSpBQ==" + # have to use an 'object' type for precommits as it is nullable + $ref: '#/definitions/Precommits' + Precommits: + type: array + items: + type: object + properties: + validator_address: + type: string + validator_index: + type: string + example: "0" + height: + type: string + example: "2" + round: + type: string + example: "0" + timestamp: + type: string + example: "2017-12-30T05:53:09.287+01:00" + type: + type: number + example: 2 + block_id: + $ref: "#/definitions/BlockID" + signature: + type: string + example: "7uTC74QlknqYWEwg7Vn6M8Om7FuZ0EO4bjvuj6rwH1mTUJrRuMMZvAAqT9VjNgP0RA/TDp6u/92AqrZfXJSpBQ==" + BlockQuery: type: object properties: @@ -2251,11 +2460,11 @@ properties: from: type: string - example: "kava1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc" + example: "kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9" description: Sender address or Keybase name to generate a transaction memo: type: string - example: "Sent via Cosmos Voyager 🚀" + example: Sent_via_Kava chain_id: type: string example: "Cosmos-Hub" @@ -2539,4 +2748,11 @@ total: type: array items: - $ref: "#/definitions/Coin" \ No newline at end of file + $ref: "#/definitions/Coin" + StandardResponse: + type: object + properties: + height: + type: string + result: + type: string \ No newline at end of file diff --git a/x/bep3/abci.go b/x/bep3/abci.go new file mode 100644 index 00000000..b5c2d424 --- /dev/null +++ b/x/bep3/abci.go @@ -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) + } +} diff --git a/x/bep3/abci_test.go b/x/bep3/abci_test.go new file mode 100644 index 00000000..37ca6eb2 --- /dev/null +++ b/x/bep3/abci_test.go @@ -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)) +} diff --git a/x/bep3/alias.go b/x/bep3/alias.go new file mode 100644 index 00000000..c020c3af --- /dev/null +++ b/x/bep3/alias.go @@ -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 +) diff --git a/x/bep3/client/cli/query.go b/x/bep3/client/cli/query.go new file mode 100644 index 00000000..4523970d --- /dev/null +++ b/x/bep3/client/cli/query.go @@ -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) + }, + } +} diff --git a/x/bep3/client/cli/tx.go b/x/bep3/client/cli/tx.go new file mode 100644 index 00000000..957b0e8a --- /dev/null +++ b/x/bep3/client/cli/tx.go @@ -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}) + }, + } +} diff --git a/x/bep3/client/rest/query.go b/x/bep3/client/rest/query.go new file mode 100644 index 00000000..7170cf03 --- /dev/null +++ b/x/bep3/client/rest/query.go @@ -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) + } +} diff --git a/x/bep3/client/rest/rest.go b/x/bep3/client/rest/rest.go new file mode 100644 index 00000000..02777077 --- /dev/null +++ b/x/bep3/client/rest/rest.go @@ -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"` +} diff --git a/x/bep3/client/rest/tx.go b/x/bep3/client/rest/tx.go new file mode 100644 index 00000000..022993e3 --- /dev/null +++ b/x/bep3/client/rest/tx.go @@ -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}) + } +} diff --git a/x/bep3/genesis.go b/x/bep3/genesis.go new file mode 100644 index 00000000..37f08878 --- /dev/null +++ b/x/bep3/genesis.go @@ -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) +} diff --git a/x/bep3/genesis_test.go b/x/bep3/genesis_test.go new file mode 100644 index 00000000..7b76af77 --- /dev/null +++ b/x/bep3/genesis_test.go @@ -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)) +} diff --git a/x/bep3/handler.go b/x/bep3/handler.go new file mode 100644 index 00000000..27accffe --- /dev/null +++ b/x/bep3/handler.go @@ -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(), + } +} diff --git a/x/bep3/handler_test.go b/x/bep3/handler_test.go new file mode 100644 index 00000000..b905b23d --- /dev/null +++ b/x/bep3/handler_test.go @@ -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)) +} diff --git a/x/bep3/integration_test.go b/x/bep3/integration_test.go new file mode 100644 index 00000000..bb9bc7c2 --- /dev/null +++ b/x/bep3/integration_test.go @@ -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 +} diff --git a/x/bep3/keeper/asset.go b/x/bep3/keeper/asset.go new file mode 100644 index 00000000..df586cee --- /dev/null +++ b/x/bep3/keeper/asset.go @@ -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 +} diff --git a/x/bep3/keeper/asset_test.go b/x/bep3/keeper/asset_test.go new file mode 100644 index 00000000..9c167289 --- /dev/null +++ b/x/bep3/keeper/asset_test.go @@ -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)) +} diff --git a/x/bep3/keeper/integration_test.go b/x/bep3/keeper/integration_test.go new file mode 100644 index 00000000..5e4d4a37 --- /dev/null +++ b/x/bep3/keeper/integration_test.go @@ -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)) +} diff --git a/x/bep3/keeper/keeper.go b/x/bep3/keeper/keeper.go new file mode 100644 index 00000000..0a0641af --- /dev/null +++ b/x/bep3/keeper/keeper.go @@ -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 +} diff --git a/x/bep3/keeper/keeper_test.go b/x/bep3/keeper/keeper_test.go new file mode 100644 index 00000000..9837a747 --- /dev/null +++ b/x/bep3/keeper/keeper_test.go @@ -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)) +} diff --git a/x/bep3/keeper/params.go b/x/bep3/keeper/params.go new file mode 100644 index 00000000..7b8cc88c --- /dev/null +++ b/x/bep3/keeper/params.go @@ -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, ¶ms) + 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, ¶ms) +} + +// 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 +} diff --git a/x/bep3/keeper/params_test.go b/x/bep3/keeper/params_test.go new file mode 100644 index 00000000..2e3fd4b7 --- /dev/null +++ b/x/bep3/keeper/params_test.go @@ -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)) +} diff --git a/x/bep3/keeper/querier.go b/x/bep3/keeper/querier.go new file mode 100644 index 00000000..a64db015 --- /dev/null +++ b/x/bep3/keeper/querier.go @@ -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 +} diff --git a/x/bep3/keeper/querier_test.go b/x/bep3/keeper/querier_test.go new file mode 100644 index 00000000..c7355e4d --- /dev/null +++ b/x/bep3/keeper/querier_test.go @@ -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)) +} diff --git a/x/bep3/keeper/swap.go b/x/bep3/keeper/swap.go new file mode 100644 index 00000000..cbc9a26c --- /dev/null +++ b/x/bep3/keeper/swap.go @@ -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 +} diff --git a/x/bep3/keeper/swap_test.go b/x/bep3/keeper/swap_test.go new file mode 100644 index 00000000..7cfb1447 --- /dev/null +++ b/x/bep3/keeper/swap_test.go @@ -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)) +} diff --git a/x/bep3/module.go b/x/bep3/module.go new file mode 100644 index 00000000..65db1b6a --- /dev/null +++ b/x/bep3/module.go @@ -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{} +} diff --git a/x/bep3/spec/README.md b/x/bep3/spec/README.md new file mode 100644 index 00000000..86472c50 --- /dev/null +++ b/x/bep3/spec/README.md @@ -0,0 +1,17 @@ +# bep3 module specification + +## Abstract + + + +## Contents + +// TODO: Create the below files if they are needed. + diff --git a/x/bep3/types/asset.go b/x/bep3/types/asset.go new file mode 100644 index 00000000..51ef07e5 --- /dev/null +++ b/x/bep3/types/asset.go @@ -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 +} diff --git a/x/bep3/types/codec.go b/x/bep3/types/codec.go new file mode 100644 index 00000000..ec9f2229 --- /dev/null +++ b/x/bep3/types/codec.go @@ -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) +} diff --git a/x/bep3/types/common_test.go b/x/bep3/types/common_test.go new file mode 100644 index 00000000..11fd749e --- /dev/null +++ b/x/bep3/types/common_test.go @@ -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 +} diff --git a/x/bep3/types/errors.go b/x/bep3/types/errors.go new file mode 100644 index 00000000..06eb951f --- /dev/null +++ b/x/bep3/types/errors.go @@ -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")) +} diff --git a/x/bep3/types/events.go b/x/bep3/types/events.go new file mode 100644 index 00000000..cb320386 --- /dev/null +++ b/x/bep3/types/events.go @@ -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 +) diff --git a/x/bep3/types/expected_keepers.go b/x/bep3/types/expected_keepers.go new file mode 100644 index 00000000..83141c9e --- /dev/null +++ b/x/bep3/types/expected_keepers.go @@ -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 +} diff --git a/x/bep3/types/genesis.go b/x/bep3/types/genesis.go new file mode 100644 index 00000000..a2159e13 --- /dev/null +++ b/x/bep3/types/genesis.go @@ -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 +} diff --git a/x/bep3/types/genesis_test.go b/x/bep3/types/genesis_test.go new file mode 100644 index 00000000..b72f9be8 --- /dev/null +++ b/x/bep3/types/genesis_test.go @@ -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)) +} diff --git a/x/bep3/types/hash.go b/x/bep3/types/hash.go new file mode 100644 index 00000000..bcb23fcf --- /dev/null +++ b/x/bep3/types/hash.go @@ -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) +} diff --git a/x/bep3/types/hash_test.go b/x/bep3/types/hash_test.go new file mode 100644 index 00000000..0161a059 --- /dev/null +++ b/x/bep3/types/hash_test.go @@ -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)) +} \ No newline at end of file diff --git a/x/bep3/types/keys.go b/x/bep3/types/keys.go new file mode 100644 index 00000000..fa5316dc --- /dev/null +++ b/x/bep3/types/keys.go @@ -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 +} diff --git a/x/bep3/types/msg.go b/x/bep3/types/msg.go new file mode 100644 index 00000000..15d84fe4 --- /dev/null +++ b/x/bep3/types/msg.go @@ -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 +} diff --git a/x/bep3/types/msg_test.go b/x/bep3/types/msg_test.go new file mode 100644 index 00000000..30cdd2ef --- /dev/null +++ b/x/bep3/types/msg_test.go @@ -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) + } + } +} diff --git a/x/bep3/types/params.go b/x/bep3/types/params.go new file mode 100644 index 00000000..cc8cc18f --- /dev/null +++ b/x/bep3/types/params.go @@ -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 +} diff --git a/x/bep3/types/params_test.go b/x/bep3/types/params_test.go new file mode 100644 index 00000000..26a2e7e8 --- /dev/null +++ b/x/bep3/types/params_test.go @@ -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)) +} diff --git a/x/bep3/types/querier.go b/x/bep3/types/querier.go new file mode 100644 index 00000000..8a88456a --- /dev/null +++ b/x/bep3/types/querier.go @@ -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, + } +} diff --git a/x/bep3/types/swap.go b/x/bep3/types/swap.go new file mode 100644 index 00000000..1a7dbacb --- /dev/null +++ b/x/bep3/types/swap.go @@ -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 +} diff --git a/x/bep3/types/swap_test.go b/x/bep3/types/swap_test.go new file mode 100644 index 00000000..db22dab0 --- /dev/null +++ b/x/bep3/types/swap_test.go @@ -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)) +} diff --git a/x/cdp/abci.go b/x/cdp/abci.go index cc36bd74..f31e81ee 100644 --- a/x/cdp/abci.go +++ b/x/cdp/abci.go @@ -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 } diff --git a/x/cdp/alias.go b/x/cdp/alias.go index d542be32..7cae19d8 100644 --- a/x/cdp/alias.go +++ b/x/cdp/alias.go @@ -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 ) diff --git a/x/cdp/genesis.go b/x/cdp/genesis.go index 45e34b46..0a4e6880 100644 --- a/x/cdp/genesis.go +++ b/x/cdp/genesis.go @@ -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) } diff --git a/x/cdp/integration_test.go b/x/cdp/integration_test.go index 8ea36252..68c644ee 100644 --- a/x/cdp/integration_test.go +++ b/x/cdp/integration_test.go @@ -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, } } diff --git a/x/cdp/keeper/auctions.go b/x/cdp/keeper/auctions.go index 6e39bbbb..1d74f6ba 100644 --- a/x/cdp/keeper/auctions.go +++ b/x/cdp/keeper/auctions.go @@ -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 } diff --git a/x/cdp/keeper/cdp_test.go b/x/cdp/keeper/cdp_test.go index 89e35136..a76dd8a2 100644 --- a/x/cdp/keeper/cdp_test.go +++ b/x/cdp/keeper/cdp_test.go @@ -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) diff --git a/x/cdp/keeper/fees.go b/x/cdp/keeper/fees.go index 0c5671f6..53af1138 100644 --- a/x/cdp/keeper/fees.go +++ b/x/cdp/keeper/fees.go @@ -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 { diff --git a/x/cdp/keeper/fees_test.go b/x/cdp/keeper/fees_test.go index 48a5fd2d..4367d368 100644 --- a/x/cdp/keeper/fees_test.go +++ b/x/cdp/keeper/fees_test.go @@ -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() diff --git a/x/cdp/keeper/integration_test.go b/x/cdp/keeper/integration_test.go index 6905882a..0714b69e 100644 --- a/x/cdp/keeper/integration_test.go +++ b/x/cdp/keeper/integration_test.go @@ -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)} } diff --git a/x/cdp/keeper/keeper.go b/x/cdp/keeper/keeper.go index 94570d2b..98d416a1 100644 --- a/x/cdp/keeper/keeper.go +++ b/x/cdp/keeper/keeper.go @@ -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, } } diff --git a/x/cdp/keeper/savings.go b/x/cdp/keeper/savings.go new file mode 100644 index 00000000..68678e24 --- /dev/null +++ b/x/cdp/keeper/savings.go @@ -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)) +} diff --git a/x/cdp/keeper/savings_test.go b/x/cdp/keeper/savings_test.go new file mode 100644 index 00000000..5e5a910e --- /dev/null +++ b/x/cdp/keeper/savings_test.go @@ -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)) +} diff --git a/x/cdp/keeper/seize.go b/x/cdp/keeper/seize.go index 08645a2b..2a63b14f 100644 --- a/x/cdp/keeper/seize.go +++ b/x/cdp/keeper/seize.go @@ -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 { diff --git a/x/cdp/module.go b/x/cdp/module.go index 108189de..5c2febb1 100644 --- a/x/cdp/module.go +++ b/x/cdp/module.go @@ -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{} } diff --git a/x/cdp/spec/04_begin_block.md b/x/cdp/spec/04_begin_block.md index e8257c0c..77cd41e4 100644 --- a/x/cdp/spec/04_begin_block.md +++ b/x/cdp/spec/04_begin_block.md @@ -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. diff --git a/x/cdp/spec/06_params.md b/x/cdp/spec/06_params.md index fbde95d7..4297619c 100644 --- a/x/cdp/spec/06_params.md +++ b/x/cdp/spec/06_params.md @@ -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: diff --git a/x/cdp/types/cdp.go b/x/cdp/types/cdp.go index 8c5365be..da06c836 100644 --- a/x/cdp/types/cdp.go +++ b/x/cdp/types/cdp.go @@ -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, diff --git a/x/cdp/types/expected_keepers.go b/x/cdp/types/expected_keepers.go index 402458dc..dcbd8314 100644 --- a/x/cdp/types/expected_keepers.go +++ b/x/cdp/types/expected_keepers.go @@ -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)) +} diff --git a/x/cdp/types/genesis.go b/x/cdp/types/genesis.go index 4097e6d4..1d1da37b 100644 --- a/x/cdp/types/genesis.go +++ b/x/cdp/types/genesis.go @@ -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") diff --git a/x/cdp/types/keys.go b/x/cdp/types/keys.go index 6f34524a..de0de209 100644 --- a/x/cdp/types/keys.go +++ b/x/cdp/types/keys.go @@ -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:totalPrincipal // - 0x07: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())) diff --git a/x/cdp/types/params.go b/x/cdp/types/params.go index 2d8c78d4..db54cbf5 100644 --- a/x/cdp/types/params.go +++ b/x/cdp/types/params.go @@ -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 } diff --git a/x/kavadist/abci.go b/x/kavadist/abci.go new file mode 100644 index 00000000..de7b80ce --- /dev/null +++ b/x/kavadist/abci.go @@ -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) + } +} diff --git a/x/kavadist/alias.go b/x/kavadist/alias.go new file mode 100644 index 00000000..e9295e14 --- /dev/null +++ b/x/kavadist/alias.go @@ -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 +) diff --git a/x/kavadist/doc.go b/x/kavadist/doc.go new file mode 100644 index 00000000..5926f7e4 --- /dev/null +++ b/x/kavadist/doc.go @@ -0,0 +1 @@ +package kavadist diff --git a/x/kavadist/genesis.go b/x/kavadist/genesis.go new file mode 100644 index 00000000..c4219a02 --- /dev/null +++ b/x/kavadist/genesis.go @@ -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) +} diff --git a/x/kavadist/handler.go b/x/kavadist/handler.go new file mode 100644 index 00000000..585efbbb --- /dev/null +++ b/x/kavadist/handler.go @@ -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() + } + } +} \ No newline at end of file diff --git a/x/kavadist/keeper/keeper.go b/x/kavadist/keeper/keeper.go new file mode 100644 index 00000000..cf8729c6 --- /dev/null +++ b/x/kavadist/keeper/keeper.go @@ -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)) +} diff --git a/x/kavadist/keeper/mint.go b/x/kavadist/keeper/mint.go new file mode 100644 index 00000000..4a1da2c3 --- /dev/null +++ b/x/kavadist/keeper/mint.go @@ -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 +} diff --git a/x/kavadist/keeper/mint_test.go b/x/kavadist/keeper/mint_test.go new file mode 100644 index 00000000..c9b77753 --- /dev/null +++ b/x/kavadist/keeper/mint_test.go @@ -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)) +} diff --git a/x/kavadist/keeper/params.go b/x/kavadist/keeper/params.go new file mode 100644 index 00000000..526cb255 --- /dev/null +++ b/x/kavadist/keeper/params.go @@ -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, ¶ms) +} diff --git a/x/kavadist/module.go b/x/kavadist/module.go new file mode 100644 index 00000000..218655c6 --- /dev/null +++ b/x/kavadist/module.go @@ -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{} +} diff --git a/x/kavadist/simulation/decoder.go b/x/kavadist/simulation/decoder.go new file mode 100644 index 00000000..edef9051 --- /dev/null +++ b/x/kavadist/simulation/decoder.go @@ -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 "" +} diff --git a/x/kavadist/simulation/params.go b/x/kavadist/simulation/params.go new file mode 100644 index 00000000..8c1f7aff --- /dev/null +++ b/x/kavadist/simulation/params.go @@ -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{} +} diff --git a/x/kavadist/simulation/simulation.go b/x/kavadist/simulation/simulation.go new file mode 100644 index 00000000..a1fbd964 --- /dev/null +++ b/x/kavadist/simulation/simulation.go @@ -0,0 +1,22 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/kava-labs/kava/x/kavadist/types" +) + +// RandomizedGenState generates a random GenesisState for cdp +func RandomizedGenState(simState *module.SimulationState) { + + // TODO implement this fully + // - randomly generating the genesis params + // - overwriting with genesis provided to simulation + genesis := types.DefaultGenesisState() + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/kavadist/types/codec.go b/x/kavadist/types/codec.go new file mode 100644 index 00000000..16afc367 --- /dev/null +++ b/x/kavadist/types/codec.go @@ -0,0 +1,17 @@ +package types + +import "github.com/cosmos/cosmos-sdk/codec" + +// ModuleCdc generic sealed codec to be used throughout module +var ModuleCdc *codec.Codec + +func init() { + cdc := codec.New() + RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + ModuleCdc = cdc.Seal() +} + +// RegisterCodec registers the necessary types for cdp module +func RegisterCodec(cdc *codec.Codec) { +} diff --git a/x/kavadist/types/errors.go b/x/kavadist/types/errors.go new file mode 100644 index 00000000..173f7a49 --- /dev/null +++ b/x/kavadist/types/errors.go @@ -0,0 +1,11 @@ +// DONTCOVER +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// Error codes specific to kavadist module +const ( + DefaultCodespace sdk.CodespaceType = ModuleName +) diff --git a/x/kavadist/types/events.go b/x/kavadist/types/events.go new file mode 100644 index 00000000..9bdb0fed --- /dev/null +++ b/x/kavadist/types/events.go @@ -0,0 +1,9 @@ +package types + +// Event types for cdp module +const ( + EventTypeKavaDist = ModuleName + AttributeKeyInflation = "kava_dist_inflation" + AttributeKeyStatus = "kava_dist_status" + AttributeValueInactive = "inactive" +) diff --git a/x/kavadist/types/expected_keepers.go b/x/kavadist/types/expected_keepers.go new file mode 100644 index 00000000..a2e54262 --- /dev/null +++ b/x/kavadist/types/expected_keepers.go @@ -0,0 +1,15 @@ +package types // noalias + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "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, name string) exported.ModuleAccountI + GetSupply(ctx sdk.Context) (supply exported.SupplyI) + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) sdk.Error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error +} diff --git a/x/kavadist/types/genesis.go b/x/kavadist/types/genesis.go new file mode 100644 index 00000000..e857ab0a --- /dev/null +++ b/x/kavadist/types/genesis.go @@ -0,0 +1,54 @@ +package types + +import ( + "bytes" + "fmt" + "time" +) + +// GenesisState is the state that must be provided at genesis. +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` +} + +// NewGenesisState returns a new genesis state +func NewGenesisState(params Params, previousBlockTime time.Time) GenesisState { + return GenesisState{ + Params: params, + PreviousBlockTime: previousBlockTime, + } +} + +// DefaultGenesisState returns a default genesis state +func DefaultGenesisState() GenesisState { + return GenesisState{ + Params: DefaultParams(), + PreviousBlockTime: DefaultPreviousBlockTime, + } +} + +// Validate performs basic validation of genesis data returning an +// error for any failed validation criteria. +func (gs GenesisState) Validate() error { + + if err := gs.Params.Validate(); err != nil { + return err + } + if gs.PreviousBlockTime.Equal(time.Time{}) { + return fmt.Errorf("previous block time not set") + } + return nil +} + +// Equal checks whether two gov 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{}) +} diff --git a/x/kavadist/types/keys.go b/x/kavadist/types/keys.go new file mode 100644 index 00000000..82ed8361 --- /dev/null +++ b/x/kavadist/types/keys.go @@ -0,0 +1,26 @@ +package types + +const ( + // ModuleName name that will be used throughout the module + ModuleName = "kavadist" + + // StoreKey Top level store key where all module items will be stored + StoreKey = ModuleName + + // RouterKey Top level router key + RouterKey = ModuleName + + // QuerierRoute Top level query string + QuerierRoute = ModuleName + + // DefaultParamspace default name for parameter store + DefaultParamspace = ModuleName + + // KavaDistMacc module account for kavadist + KavaDistMacc = ModuleName +) + +var ( + CurrentDistPeriodKey = []byte{0x00} + PreviousBlockTimeKey = []byte{0x01} +) diff --git a/x/kavadist/types/params.go b/x/kavadist/types/params.go new file mode 100644 index 00000000..6cb955b0 --- /dev/null +++ b/x/kavadist/types/params.go @@ -0,0 +1,102 @@ +package types + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + cdptypes "github.com/kava-labs/kava/x/cdp/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// Parameter keys and default values +var ( + KeyActive = []byte("Active") + KeyPeriods = []byte("Periods") + DefaultActive = false + DefaultPeriods = Periods{} + DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) + GovDenom = cdptypes.DefaultGovDenom +) + +// Params governance parameters for kavadist module +type Params struct { + Active bool `json:"active" yaml:"active"` + Periods Periods `json:"periods" yaml:"periods"` +} + +// Period stores the specified start and end dates, and the inflation, expressed as a decimal representing the yearly APR of KAVA tokens that will be minted during that period +type Period struct { + Start time.Time `json:"start" yaml:"start"` // example "2020-03-01T15:20:00Z" + End time.Time `json:"end" yaml:"end"` // example "2020-06-01T15:20:00Z" + Inflation sdk.Dec `json:"inflation" yaml:"inflation"` // example "1.000000003022265980" - 10% inflation +} + +// String implements fmt.Stringer +func (pr Period) String() string { + return fmt.Sprintf(`Period: + Start: %s + End: %s + Inflation: %s`, pr.Start, pr.End, pr.Inflation) +} + +// Periods array of Period +type Periods []Period + +// String implements fmt.Stringer +func (prs Periods) String() string { + out := "Periods\n" + for _, pr := range prs { + out += fmt.Sprintf("%s\n", pr) + } + return out +} + +// NewParams returns a new params object +func NewParams(active bool, periods Periods) Params { + return Params{ + Active: active, + Periods: periods, + } +} + +// DefaultParams returns default params for kavadist module +func DefaultParams() Params { + return NewParams(DefaultActive, DefaultPeriods) +} + +// String implements fmt.Stringer +func (p Params) String() string { + return fmt.Sprintf(`Params: + Active: %t + Periods %s`, p.Active, p.Periods) +} + +// 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 +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{ + {Key: KeyActive, Value: &p.Active}, + {Key: KeyPeriods, Value: &p.Periods}, + } +} + +// Validate checks that the parameters have valid values. +func (p Params) Validate() error { + prevEnd := tmtime.Canonical(time.Unix(0, 0)) + for _, pr := range p.Periods { + if pr.End.Before(pr.Start) { + return fmt.Errorf("end time for period is before start time: %s", pr) + } + if pr.Start.Before(prevEnd) { + return fmt.Errorf("periods must be in chronological order: %s", p.Periods) + } + prevEnd = pr.End + } + return nil +} diff --git a/x/kavadist/types/params_test.go b/x/kavadist/types/params_test.go new file mode 100644 index 00000000..f2e02b90 --- /dev/null +++ b/x/kavadist/types/params_test.go @@ -0,0 +1,99 @@ +package types_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/kavadist/types" + "github.com/stretchr/testify/suite" +) + +type paramTest struct { + params types.Params + expectPass bool +} + +type ParamTestSuite struct { + suite.Suite + + tests []paramTest +} + +func (suite *ParamTestSuite) SetupTest() { + p1 := types.Params{ + Active: true, + Periods: 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"), + }, + 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"), + }, + }, + } + p2 := types.Params{ + Active: true, + Periods: types.Periods{ + types.Period{ + Start: time.Date(2022, 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"), + }, + types.Period{ + Start: time.Date(2023, time.March, 1, 1, 0, 0, 0, time.UTC), + End: time.Date(2024, time.March, 1, 1, 0, 0, 0, time.UTC), + Inflation: sdk.MustNewDecFromStr("1.000000003022265980"), + }, + }, + } + p3 := types.Params{ + Active: true, + Periods: 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"), + }, + types.Period{ + Start: time.Date(2020, 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"), + }, + }, + } + + suite.tests = []paramTest{ + paramTest{ + params: p1, + expectPass: true, + }, + paramTest{ + params: p2, + expectPass: false, + }, + paramTest{ + params: p3, + expectPass: false, + }, + } +} + +func (suite *ParamTestSuite) TestParamValidation() { + for _, t := range suite.tests { + err := t.params.Validate() + if t.expectPass { + suite.NoError(err) + } else { + suite.Error(err) + } + } +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(ParamTestSuite)) +} diff --git a/x/kavadist/types/querier.go b/x/kavadist/types/querier.go new file mode 100644 index 00000000..104ab89a --- /dev/null +++ b/x/kavadist/types/querier.go @@ -0,0 +1,6 @@ +package types + +// Querier routes for the kavadist module +const ( + QueryGetParams = "params" +) diff --git a/x/pricefeed/client/rest/tx.go b/x/pricefeed/client/rest/tx.go index 5ba7b8dc..6e0b87e6 100644 --- a/x/pricefeed/client/rest/tx.go +++ b/x/pricefeed/client/rest/tx.go @@ -15,7 +15,7 @@ import ( ) func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { - r.HandleFunc(fmt.Sprintf("/%s/postprice", types.ModuleName), postPriceHandlerFn(cliCtx)).Methods("PUT") + r.HandleFunc(fmt.Sprintf("/%s/postprice", types.ModuleName), postPriceHandlerFn(cliCtx)).Methods("POST") } From 2721ba3889fa45b56d03158573a9d92349a13422 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Sun, 29 Mar 2020 11:37:28 -0700 Subject: [PATCH 02/45] R4R: bep3 msgs [HOTFIX] (#406) * implement hotfix * add interface compliance check --- x/bep3/types/msg.go | 58 ++++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/x/bep3/types/msg.go b/x/bep3/types/msg.go index 15d84fe4..309fa2af 100644 --- a/x/bep3/types/msg.go +++ b/x/bep3/types/msg.go @@ -1,7 +1,6 @@ package types import ( - "encoding/json" "fmt" sdk "github.com/cosmos/cosmos-sdk/types" @@ -25,24 +24,28 @@ const ( MaxExpectedIncomeLength = 64 ) +// ensure Msg interface compliance at compile time var ( + _ sdk.Msg = &MsgCreateAtomicSwap{} + _ sdk.Msg = &MsgClaimAtomicSwap{} + _ sdk.Msg = &MsgRefundAtomicSwap{} + AtomicSwapCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("KavaAtomicSwapCoins"))) // 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"` + 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"` } // NewMsgCreateAtomicSwap initializes a new MsgCreateAtomicSwap @@ -137,18 +140,15 @@ func (msg MsgCreateAtomicSwap) ValidateBasic() sdk.Error { // 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 + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // 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"` + 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"` } // NewMsgClaimAtomicSwap initializes a new MsgClaimAtomicSwap @@ -197,17 +197,14 @@ func (msg MsgClaimAtomicSwap) ValidateBasic() sdk.Error { // 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 + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } // MsgRefundAtomicSwap defines a refund msg type MsgRefundAtomicSwap struct { - From sdk.AccAddress `json:"from"` - SwapID cmn.HexBytes `json:"swap_id"` + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID cmn.HexBytes `json:"swap_id" yaml:"swap_id"` } // NewMsgRefundAtomicSwap initializes a new MsgRefundAtomicSwap @@ -252,9 +249,6 @@ func (msg MsgRefundAtomicSwap) ValidateBasic() sdk.Error { // 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 + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) } From f3e2e7e58507ce23ed42b09849cf7e1782499d95 Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Mon, 30 Mar 2020 16:02:43 +0100 Subject: [PATCH 03/45] add simulation stubs to make tests pass (#408) --- app/app.go | 1 + x/bep3/module.go | 31 +++++++++++++++++++++++++++++-- x/bep3/simulation/decoder.go | 12 ++++++++++++ x/bep3/simulation/genesis.go | 22 ++++++++++++++++++++++ x/bep3/simulation/params.go | 14 ++++++++++++++ 5 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 x/bep3/simulation/decoder.go create mode 100644 x/bep3/simulation/genesis.go create mode 100644 x/bep3/simulation/params.go diff --git a/app/app.go b/app/app.go index 0bce4540..c22a97ae 100644 --- a/app/app.go +++ b/app/app.go @@ -334,6 +334,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, 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), + bep3.NewAppModule(app.bep3Keeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), ) diff --git a/x/bep3/module.go b/x/bep3/module.go index 65db1b6a..c11054c3 100644 --- a/x/bep3/module.go +++ b/x/bep3/module.go @@ -2,22 +2,26 @@ package bep3 import ( "encoding/json" + "math/rand" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + sim "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/gorilla/mux" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" "github.com/kava-labs/kava/x/bep3/client/cli" "github.com/kava-labs/kava/x/bep3/client/rest" + "github.com/kava-labs/kava/x/bep3/simulation" ) var ( - _ module.AppModule = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModuleSimulation{} ) // AppModuleBasic defines the basic application module used by the bep3 module. @@ -64,9 +68,32 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { return cli.GetQueryCmd(StoreKey, cdc) } +//____________________________________________________________________________ + +// AppModuleSimulation defines the module simulation functions used by the auction module. +type AppModuleSimulation struct{} + +// RegisterStoreDecoder registers a decoder for auction module's types +func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// GenerateGenesisState creates a randomized GenState of the auction module +func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RandomizedParams creates randomized auction param changes for the simulator. +func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +//____________________________________________________________________________ + // AppModule implements the sdk.AppModule interface. type AppModule struct { AppModuleBasic + AppModuleSimulation keeper Keeper supplyKeeper SupplyKeeper diff --git a/x/bep3/simulation/decoder.go b/x/bep3/simulation/decoder.go new file mode 100644 index 00000000..6c35c157 --- /dev/null +++ b/x/bep3/simulation/decoder.go @@ -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 module's corresponding type +func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { + // TODO implement this + return "" +} diff --git a/x/bep3/simulation/genesis.go b/x/bep3/simulation/genesis.go new file mode 100644 index 00000000..5daaab0e --- /dev/null +++ b/x/bep3/simulation/genesis.go @@ -0,0 +1,22 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/kava-labs/kava/x/bep3/types" +) + +// RandomizedGenState generates a random GenesisState +func RandomizedGenState(simState *module.SimulationState) { + + // TODO implement this fully + // - randomly generating the genesis params + // - overwriting with genesis provided to simulation + genesisState := types.DefaultGenesisState() + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState) +} diff --git a/x/bep3/simulation/params.go b/x/bep3/simulation/params.go new file mode 100644 index 00000000..8c1f7aff --- /dev/null +++ b/x/bep3/simulation/params.go @@ -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{} +} From 882d12c63bbf3b088982f86f27021584b19ed8b6 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 30 Mar 2020 11:06:55 -0400 Subject: [PATCH 04/45] testnet-5k release candidate (#407) * 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 * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update go.sum Co-Authored-By: Kevin Davis * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * Update x/cdp/abci.go Co-Authored-By: Kevin Davis * 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 * Update x/cdp/keeper/fees.go Co-Authored-By: Kevin Davis * 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 * Update x/cdp/spec/04_begin_block.md Fix typo as suggested Co-Authored-By: Kevin Davis Co-authored-by: Kevin Davis * 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 * Update swagger-ui/startchain.sh Send output to dev null not to console Co-Authored-By: Kevin Davis * 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 Co-authored-by: Kevin Davis * 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) * update contrib structure (#403) Co-authored-by: Denali Marsh Co-authored-by: John Maheswaran Co-authored-by: John Maheswaran --- contrib/README.md | 19 +- .../genesis_savings_rate.json | 352 ++++++++++++++++++ contrib/testnet-4000/README.md | 22 +- .../cdp}/broadcast-create-cdp.json | 0 .../cdp}/broadcast-deposit-cdp.json | 0 .../cdp}/create-cdp-unsigned.json | 0 .../cdp}/create-cdp.json | 0 .../cdp}/deposit-cdp.json | 0 .../cdp}/example-create-cdp.json | 0 .../pricefeed}/broadcast-post-price.json | 0 .../pricefeed}/example-post-price.json | 0 .../pricefeed}/post-price-unsigned.json | 0 .../pricefeed}/post-price.json | 0 13 files changed, 380 insertions(+), 13 deletions(-) create mode 100644 contrib/dev/genesis_examples/genesis_savings_rate.json rename contrib/testnet-4000/{requests => rest_examples/cdp}/broadcast-create-cdp.json (100%) rename contrib/testnet-4000/{requests => rest_examples/cdp}/broadcast-deposit-cdp.json (100%) rename contrib/testnet-4000/{requests => rest_examples/cdp}/create-cdp-unsigned.json (100%) rename contrib/testnet-4000/{requests => rest_examples/cdp}/create-cdp.json (100%) rename contrib/testnet-4000/{requests => rest_examples/cdp}/deposit-cdp.json (100%) rename contrib/testnet-4000/{requests => rest_examples/cdp}/example-create-cdp.json (100%) rename contrib/testnet-4000/{requests => rest_examples/pricefeed}/broadcast-post-price.json (100%) rename contrib/testnet-4000/{requests => rest_examples/pricefeed}/example-post-price.json (100%) rename contrib/testnet-4000/{requests => rest_examples/pricefeed}/post-price-unsigned.json (100%) rename contrib/testnet-4000/{requests => rest_examples/pricefeed}/post-price.json (100%) diff --git a/contrib/README.md b/contrib/README.md index 30a4e0db..cf15df14 100644 --- a/contrib/README.md +++ b/contrib/README.md @@ -1,4 +1,19 @@ - # Contrib -Resources and examples for running and interacting with the kava blockchain. +Resources and examples for running and interacting with the kava blockchain. `contrib` contains sample genesis files, example governance proposals, and rest-server request examples for different versions of kava. + +## testnet-4000 + +kava-testnet-4000 introduces the cdp and auction modules, allowing users to create CDPs and mint usdx as well as participate in auctions. + +Configuration and rest-server interaction files are available in the [testnet-4000](./testnet-4000/README.md) directory. + +## testnet-5000 + +kava-testnet-5000 introduces the bep3 modules, allowing users to transfer BNB between the bnbchain testnet and kava. + +Configuration and rest-server interaction files are available in the [testnet-5000](./testnet-5000/README.md) directory. + +## dev + +During development new features offer require modifications to the genesis file. The dev directory houses these genesis files. diff --git a/contrib/dev/genesis_examples/genesis_savings_rate.json b/contrib/dev/genesis_examples/genesis_savings_rate.json new file mode 100644 index 00000000..538d5b72 --- /dev/null +++ b/contrib/dev/genesis_examples/genesis_savings_rate.json @@ -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": {} + } + } +} \ No newline at end of file diff --git a/contrib/testnet-4000/README.md b/contrib/testnet-4000/README.md index c579bb26..ce377e67 100644 --- a/contrib/testnet-4000/README.md +++ b/contrib/testnet-4000/README.md @@ -6,7 +6,7 @@ Resources and examples for running and interacting with kava-testnet-4000 ### Setup - Before making a request, query account information for the signing account. Note the 'accountnumber' and 'sequence' fields, we'll need them later in order to send our request: +Before making a request, query account information for the signing account. Note the 'accountnumber' and 'sequence' fields, we'll need them later in order to send our request: ```bash kvcli q auth account $(kvcli keys show accB -a) @@ -22,41 +22,41 @@ Now we'll create an unsigned request, sign it, and broadcast it to the Kava bloc ### Create CDP example request - Format the base request in create-cdp.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the CDP creation request's params 'owner', 'collateral', and 'principal'. An example formatted request can be found in `example-create-cdp.json`. +Format the base request in create-cdp.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the CDP creation request's params 'owner', 'collateral', and 'principal'. An example formatted request can be found in `example-create-cdp.json`. ```bash # Create an unsigned request - curl -H "Content-Type: application/json" -X PUT -d @./contrib/requests/create-cdp.json http://127.0.0.1:1317/cdp | jq > ./contrib/requests/create-cdp-unsigned.json + curl -H "Content-Type: application/json" -X PUT -d @./contrib/testnet-4000/rest_examples/cdp/create-cdp.json http://127.0.0.1:1317/cdp | jq > ./contrib/testnet-4000/rest_examples/cdp/create-cdp-unsigned.json # Sign the request - kvcli tx sign ./contrib/requests/create-cdp-unsigned.json --from accB --offline --chain-id testing --sequence 1 --account-number 2 | jq > ./contrib/requests/broadcast-create-cdp.json + kvcli tx sign ./contrib/testnet-4000/rest_examples/cdp/create-cdp-unsigned.json --from accB --offline --chain-id testing --sequence 1 --account-number 2 | jq > ./contrib/testnet-4000/rest_examples/cdp/broadcast-create-cdp.json # Broadcast the request - kvcli tx broadcast ./contrib/requests/broadcast-create-cdp.json + kvcli tx broadcast ./contrib/testnet-4000/rest_examples/cdp/broadcast-create-cdp.json ``` Congratulations, you've just created a CDP on Kava using the rest server! ### Post market price example request - Note that only market oracles can post prices, other senders will have their transactions rejected by Kava. +Note that only market oracles can post prices, other senders will have their transactions rejected by Kava. - Format the base request in post-price.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the post price request's params 'from', 'market_id', 'price', and 'expiry'. An example formatted request can be found in `example-post-price.json`. +Format the base request in post-price.json. You'll need to update the 'from', 'chain-id', 'account_number', 'sequence', and 'gas' as appropriate. Then, populate the post price request's params 'from', 'market_id', 'price', and 'expiry'. An example formatted request can be found in `example-post-price.json`. ```bash # Create an unsigned request - curl -H "Content-Type: application/json" -X PUT -d @./contrib/requests/post-price.json http://127.0.0.1:1317/pricefeed/postprice | jq > ./contrib/requests/post-price-unsigned.json + curl -H "Content-Type: application/json" -X PUT -d @./contrib/testnet-4000/rest_examples/pricefeed/post-price.json http://127.0.0.1:1317/pricefeed/postprice | jq > ./contrib/testnet-4000/rest_examples/pricefeed/post-price-unsigned.json # Sign the request - kvcli tx sign ./contrib/requests/post-price-unsigned.json --from validator --offline --chain-id testing --sequence 96 --account-number 0 | jq > ./contrib/requests/broadcast-post-price.json + kvcli tx sign ./contrib/testnet-4000/rest_examples/pricefeed/post-price-unsigned.json --from validator --offline --chain-id testing --sequence 96 --account-number 0 | jq > ./contrib/testnet-4000/rest_examples/pricefeed/broadcast-post-price.json # Broadcast the request - kvcli tx broadcast ./contrib/requests/broadcast-post-price.json + kvcli tx broadcast ./contrib/testnet-4000/rest_examples/pricefeed/broadcast-post-price.json ``` Congratulations, you've just posted a current market price on Kava using the rest server! ## Governance proposals -Example governance proposals are located in `/proposal_examples`. \ No newline at end of file +Example governance proposals are located in `/proposal_examples`. diff --git a/contrib/testnet-4000/requests/broadcast-create-cdp.json b/contrib/testnet-4000/rest_examples/cdp/broadcast-create-cdp.json similarity index 100% rename from contrib/testnet-4000/requests/broadcast-create-cdp.json rename to contrib/testnet-4000/rest_examples/cdp/broadcast-create-cdp.json diff --git a/contrib/testnet-4000/requests/broadcast-deposit-cdp.json b/contrib/testnet-4000/rest_examples/cdp/broadcast-deposit-cdp.json similarity index 100% rename from contrib/testnet-4000/requests/broadcast-deposit-cdp.json rename to contrib/testnet-4000/rest_examples/cdp/broadcast-deposit-cdp.json diff --git a/contrib/testnet-4000/requests/create-cdp-unsigned.json b/contrib/testnet-4000/rest_examples/cdp/create-cdp-unsigned.json similarity index 100% rename from contrib/testnet-4000/requests/create-cdp-unsigned.json rename to contrib/testnet-4000/rest_examples/cdp/create-cdp-unsigned.json diff --git a/contrib/testnet-4000/requests/create-cdp.json b/contrib/testnet-4000/rest_examples/cdp/create-cdp.json similarity index 100% rename from contrib/testnet-4000/requests/create-cdp.json rename to contrib/testnet-4000/rest_examples/cdp/create-cdp.json diff --git a/contrib/testnet-4000/requests/deposit-cdp.json b/contrib/testnet-4000/rest_examples/cdp/deposit-cdp.json similarity index 100% rename from contrib/testnet-4000/requests/deposit-cdp.json rename to contrib/testnet-4000/rest_examples/cdp/deposit-cdp.json diff --git a/contrib/testnet-4000/requests/example-create-cdp.json b/contrib/testnet-4000/rest_examples/cdp/example-create-cdp.json similarity index 100% rename from contrib/testnet-4000/requests/example-create-cdp.json rename to contrib/testnet-4000/rest_examples/cdp/example-create-cdp.json diff --git a/contrib/testnet-4000/requests/broadcast-post-price.json b/contrib/testnet-4000/rest_examples/pricefeed/broadcast-post-price.json similarity index 100% rename from contrib/testnet-4000/requests/broadcast-post-price.json rename to contrib/testnet-4000/rest_examples/pricefeed/broadcast-post-price.json diff --git a/contrib/testnet-4000/requests/example-post-price.json b/contrib/testnet-4000/rest_examples/pricefeed/example-post-price.json similarity index 100% rename from contrib/testnet-4000/requests/example-post-price.json rename to contrib/testnet-4000/rest_examples/pricefeed/example-post-price.json diff --git a/contrib/testnet-4000/requests/post-price-unsigned.json b/contrib/testnet-4000/rest_examples/pricefeed/post-price-unsigned.json similarity index 100% rename from contrib/testnet-4000/requests/post-price-unsigned.json rename to contrib/testnet-4000/rest_examples/pricefeed/post-price-unsigned.json diff --git a/contrib/testnet-4000/requests/post-price.json b/contrib/testnet-4000/rest_examples/pricefeed/post-price.json similarity index 100% rename from contrib/testnet-4000/requests/post-price.json rename to contrib/testnet-4000/rest_examples/pricefeed/post-price.json From e58d2dc32094f399b98164e8e4be2973349cacfa Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Tue, 31 Mar 2020 08:54:31 -0400 Subject: [PATCH 05/45] fix: remove old index when updating fees (#409) --- x/cdp/keeper/fees.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x/cdp/keeper/fees.go b/x/cdp/keeper/fees.go index 53af1138..6f6693ed 100644 --- a/x/cdp/keeper/fees.go +++ b/x/cdp/keeper/fees.go @@ -35,7 +35,6 @@ func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coins, periods sdk. // 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 @@ -48,7 +47,7 @@ func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, // now iterate over all the cdps based on collateral ratio k.IterateCdpsByCollateralRatio(ctx, collateralDenom, normalizedRatio, func(cdp types.CDP) bool { - + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) // get the number of periods periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) @@ -61,6 +60,7 @@ func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, // 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.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) 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 }) From 40237876830d68a03f985210ca4ffb0c8e55eb77 Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Tue, 31 Mar 2020 16:20:31 +0100 Subject: [PATCH 06/45] Update README to new logo (#410) * update logo --- README.md | 2 -- kava-logo.svg | 65 +++++++++++++++++---------------------------------- 2 files changed, 21 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 0952e808..4fc13f33 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,6 @@ ### [Telegram](https://t.me/kavalabs) | [Medium](https://medium.com/kava-labs) | [Validator Chat](https://riot.im/app/#/room/#kava-validators:matrix.org) -### Participate in Kava testnets and [snag a founder badge](./docs/REWARDS.md)! - Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [comsos-sdk](https://github.com/cosmos/cosmos-sdk). diff --git a/kava-logo.svg b/kava-logo.svg index 01a6c6f5..4012cec7 100644 --- a/kava-logo.svg +++ b/kava-logo.svg @@ -1,44 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + From 1a2137761e420dcc6f4e8d4b7bdbf02a9384955b Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Tue, 31 Mar 2020 18:57:28 -0400 Subject: [PATCH 07/45] fix: add auction begin blocker (#411) --- app/app.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/app.go b/app/app.go index c22a97ae..4650a26b 100644 --- a/app/app.go +++ b/app/app.go @@ -298,7 +298,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, // 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. - app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, bep3.ModuleName) + app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName) app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) From 11ed343deac8cf1d1ba9c47dc0f2117fd3294fa3 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Wed, 1 Apr 2020 12:27:38 -0300 Subject: [PATCH 08/45] simulation decoders for kava modules --- go.mod | 5 +++ go.sum | 11 +++++++ x/bep3/simulation/decoder.go | 29 +++++++++++++++-- x/cdp/simulation/decoder.go | 52 +++++++++++++++++++++++++++++-- x/kavadist/simulation/decoder.go | 18 +++++++++-- x/pricefeed/simulation/decoder.go | 23 ++++++++++++-- 6 files changed, 130 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 10583537..d763e103 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,14 @@ require ( github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.4.0 + github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9 // indirect github.com/tendermint/go-amino v0.15.0 + github.com/tendermint/go-crypto v0.9.0 github.com/tendermint/tendermint v0.32.7 github.com/tendermint/tm-db v0.2.0 + github.com/tendermint/tmlibs v0.9.0 // indirect + github.com/zondax/ledger-go v0.11.0 // indirect + github.com/zondax/ledger-goclient v0.9.9 // indirect golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 // indirect gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index 4a1f0603..70d5658a 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 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.38.2 h1:IeDZxxTgTMGJRZsV4s482O2gxjhLzToX5Zy1uwyasDs= 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= @@ -234,9 +235,13 @@ github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae h1:AOXNM7c2Vvo45SjAgeWF8Wy+NS7/NCqzRNpUc+HPAec= github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= +github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9 h1:zccWau0P8FELSb4HTDJ88hRo+WVNMbIbg27rFqDrhCE= +github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9/go.mod h1:nt45hbhDkWVdMBkr2TOgOzCrpBccXdN09WOiOYTHVEk= github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk= github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/go-crypto v0.9.0 h1:knZL34Ccy6BDjsPXwBslVTtyUpvOVAGbZMHcQriXulM= +github.com/tendermint/go-crypto v0.9.0/go.mod h1:bL+jG0FvO892QRYHtA/lEIQSMMq7anlRPTGbsWzQntU= 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= @@ -246,6 +251,8 @@ github.com/tendermint/tendermint v0.32.7/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8 github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ= github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= +github.com/tendermint/tmlibs v0.9.0 h1:3aU/D2v3aecqpODOuBXCfi950bHTefD5Pps5X3XuJDc= +github.com/tendermint/tmlibs v0.9.0/go.mod h1:4L0tAKpLTioy14VnmbXYTLIJN0pCMiehxDMdN6zZfM8= 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= @@ -253,6 +260,10 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q 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= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= +github.com/zondax/ledger-go v0.11.0 h1:EEqUh6eaZucWAaGo87G7sJiqRNJpzBZr+I9PpGgjjPg= +github.com/zondax/ledger-go v0.11.0/go.mod h1:NI6JDs8VWwgh+9Bf1vPZMm9Xufp2Q7Iwm2IzxJWzmus= +github.com/zondax/ledger-goclient v0.9.9 h1:XBvqkjluVda8dTYwPh2DPnlSGVEeoH1a1OprEAn8C98= +github.com/zondax/ledger-goclient v0.9.9/go.mod h1:ILyu7qO5zsod0bzyxY9NCMlFTb8AXZzJAJf0T85b2jA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/x/bep3/simulation/decoder.go b/x/bep3/simulation/decoder.go index 6c35c157..b5ff6001 100644 --- a/x/bep3/simulation/decoder.go +++ b/x/bep3/simulation/decoder.go @@ -1,12 +1,37 @@ package simulation import ( + "bytes" + "fmt" + "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/kava-labs/kava/x/bep3/types" ) // DecodeStore unmarshals the KVPair's Value to the module's corresponding type func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { - // TODO implement this - return "" + switch { + case bytes.Equal(kvA.Key[:1], types.AtomicSwapKeyPrefix): + var swapA, swapB *types.AtomicSwap + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &swapA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &swapB) + return fmt.Sprintf("%v\n%v", swapA, swapB) + + case bytes.Equal(kvA.Key[:1], types.AssetSupplyKeyPrefix): + var supplyA, supplyB types.AssetSupply + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &supplyA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &supplyB) + return fmt.Sprintf("%s\n%s", supplyA, supplyB) + + case bytes.Equal(kvA.Key[:1], types.AtomicSwapByBlockPrefix), + bytes.Equal(kvA.Key[:1], types.AtomicSwapLongtermStoragePrefix): + var bytesA cmn.HexBytes = kvA.Value + var bytesB cmn.HexBytes = kvA.Value + return fmt.Sprintf("%s\n%s", bytesA.String(), bytesB.String()) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } diff --git a/x/cdp/simulation/decoder.go b/x/cdp/simulation/decoder.go index edef9051..202838a8 100644 --- a/x/cdp/simulation/decoder.go +++ b/x/cdp/simulation/decoder.go @@ -1,12 +1,60 @@ package simulation import ( + "bytes" + "encoding/binary" + "fmt" + "time" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/kava-labs/kava/x/cdp/types" ) // 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 "" + switch { + case bytes.Equal(kvA.Key[:1], types.CdpIDKeyPrefix): + var cdpIDsA, cdpIDsB []uint64 + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &cdpIDsA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &cdpIDsB) + return fmt.Sprintf("%v\n%v", cdpIDsA, cdpIDsB) + + case bytes.Equal(kvA.Key[:1], types.CdpIDKey), + bytes.Equal(kvA.Key[:1], types.CollateralRatioIndexPrefix): + idA := binary.BigEndian.Uint64(kvA.Value) + idB := binary.BigEndian.Uint64(kvB.Value) + return fmt.Sprintf("%d\n%d", idA, idB) + + case bytes.Equal(kvA.Key[:1], types.DebtDenomKey), + bytes.Equal(kvA.Key[:1], types.GovDenomKey): + var denomA, denomB string + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &denomA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &denomB) + return fmt.Sprintf("%s\n%s", denomA, denomB) + + case bytes.Equal(kvA.Key[:1], types.DepositKeyPrefix): + var depositA, depositB types.Deposit + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &depositA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &depositB) + return fmt.Sprintf("%s\n%s", depositA, depositB) + + case bytes.Equal(kvA.Key[:1], types.PrincipalKeyPrefix): + var totalA, totalB sdk.Int + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &totalA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &totalB) + return fmt.Sprintf("%s\n%s", totalA, totalB) + + case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey), + bytes.Equal(kvA.Key[:1], types.PreviousDistributionTimeKey): + var timeA, timeB time.Time + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB) + return fmt.Sprintf("%s\n%s", timeA, timeB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } diff --git a/x/kavadist/simulation/decoder.go b/x/kavadist/simulation/decoder.go index edef9051..d28aed07 100644 --- a/x/kavadist/simulation/decoder.go +++ b/x/kavadist/simulation/decoder.go @@ -1,12 +1,26 @@ package simulation import ( + "bytes" + "fmt" + "time" + "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/kava-labs/kava/x/kavadist/types" ) // 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 "" + switch { + case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey): + var timeA, timeB time.Time + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB) + return fmt.Sprintf("%s\n%s", timeA, timeB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } diff --git a/x/pricefeed/simulation/decoder.go b/x/pricefeed/simulation/decoder.go index 115565b3..f0c27dee 100644 --- a/x/pricefeed/simulation/decoder.go +++ b/x/pricefeed/simulation/decoder.go @@ -1,12 +1,31 @@ package simulation import ( + "bytes" + "fmt" + "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/kava-labs/kava/x/pricefeed/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding pricefeed type func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { - // TODO implement this - return "" + switch { + case bytes.Contains(kvA.Key[:1], []byte(types.CurrentPricePrefix)): + var priceA, priceB types.CurrentPrice + cdc.MustUnmarshalBinaryBare(kvA.Value, &priceA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &priceB) + return fmt.Sprintf("%s\n%s", priceA, priceB) + + case bytes.Contains(kvA.Key[:1], []byte(types.RawPriceFeedPrefix)): + var postedPriceA, postedPriceB types.PostedPrice + cdc.MustUnmarshalBinaryBare(kvA.Value, &postedPriceA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &postedPriceB) + return fmt.Sprintf("%s\n%s", postedPriceA, postedPriceB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } From b473625b098b2361ac64ad6d7f250fc008e3d3c5 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Wed, 1 Apr 2020 13:26:05 -0300 Subject: [PATCH 09/45] go mod verify and tidy --- go.mod | 7 ------- go.sum | 11 ----------- 2 files changed, 18 deletions(-) diff --git a/go.mod b/go.mod index d763e103..aa784d25 100644 --- a/go.mod +++ b/go.mod @@ -6,20 +6,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/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/ed25519 v0.0.0-20171027050219-d8387025d2b9 // indirect github.com/tendermint/go-amino v0.15.0 - github.com/tendermint/go-crypto v0.9.0 github.com/tendermint/tendermint v0.32.7 github.com/tendermint/tm-db v0.2.0 - github.com/tendermint/tmlibs v0.9.0 // indirect - github.com/zondax/ledger-go v0.11.0 // indirect - github.com/zondax/ledger-goclient v0.9.9 // indirect golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 // indirect gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index 70d5658a..4a1f0603 100644 --- a/go.sum +++ b/go.sum @@ -40,7 +40,6 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 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.38.2 h1:IeDZxxTgTMGJRZsV4s482O2gxjhLzToX5Zy1uwyasDs= 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= @@ -235,13 +234,9 @@ github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae h1:AOXNM7c2Vvo45SjAgeWF8Wy+NS7/NCqzRNpUc+HPAec= github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= -github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9 h1:zccWau0P8FELSb4HTDJ88hRo+WVNMbIbg27rFqDrhCE= -github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9/go.mod h1:nt45hbhDkWVdMBkr2TOgOzCrpBccXdN09WOiOYTHVEk= github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk= github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/go-crypto v0.9.0 h1:knZL34Ccy6BDjsPXwBslVTtyUpvOVAGbZMHcQriXulM= -github.com/tendermint/go-crypto v0.9.0/go.mod h1:bL+jG0FvO892QRYHtA/lEIQSMMq7anlRPTGbsWzQntU= 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= @@ -251,8 +246,6 @@ github.com/tendermint/tendermint v0.32.7/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8 github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ= github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= -github.com/tendermint/tmlibs v0.9.0 h1:3aU/D2v3aecqpODOuBXCfi950bHTefD5Pps5X3XuJDc= -github.com/tendermint/tmlibs v0.9.0/go.mod h1:4L0tAKpLTioy14VnmbXYTLIJN0pCMiehxDMdN6zZfM8= 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= @@ -260,10 +253,6 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q 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= github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWpEM= -github.com/zondax/ledger-go v0.11.0 h1:EEqUh6eaZucWAaGo87G7sJiqRNJpzBZr+I9PpGgjjPg= -github.com/zondax/ledger-go v0.11.0/go.mod h1:NI6JDs8VWwgh+9Bf1vPZMm9Xufp2Q7Iwm2IzxJWzmus= -github.com/zondax/ledger-goclient v0.9.9 h1:XBvqkjluVda8dTYwPh2DPnlSGVEeoH1a1OprEAn8C98= -github.com/zondax/ledger-goclient v0.9.9/go.mod h1:ILyu7qO5zsod0bzyxY9NCMlFTb8AXZzJAJf0T85b2jA= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= From e02766bff36211fcf7f73735bfbfd8aa4c322617 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Wed, 1 Apr 2020 13:33:18 -0300 Subject: [PATCH 10/45] x/auction: simulation decoder --- x/auction/simulation/decoder.go | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/x/auction/simulation/decoder.go b/x/auction/simulation/decoder.go index a64f8744..232fc005 100644 --- a/x/auction/simulation/decoder.go +++ b/x/auction/simulation/decoder.go @@ -1,12 +1,32 @@ package simulation import ( + "bytes" + "encoding/binary" + "fmt" + "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/kava-labs/kava/x/auction/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding auction type func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { - // TODO implement this - return "" + switch { + case bytes.Equal(kvA.Key[:1], types.AuctionKeyPrefix): + var auctionA, auctionB types.Auction + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &auctionA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &auctionB) + return fmt.Sprintf("%v\n%v", auctionA, auctionB) + + case bytes.Equal(kvA.Key[:1], types.AuctionByTimeKeyPrefix), + bytes.Equal(kvA.Key[:1], types.NextAuctionIDKey): + auctionIDA := binary.BigEndian.Uint64(kvA.Value) + auctionIDB := binary.BigEndian.Uint64(kvB.Value) + return fmt.Sprintf("%d\n%d", auctionIDA, auctionIDB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } From fd39cea7a5509c5c79cb92b088392db9f1ab6a93 Mon Sep 17 00:00:00 2001 From: JM Date: Wed, 1 Apr 2020 13:17:02 -0400 Subject: [PATCH 11/45] CDP rest api automating testing (#401) * adding script to start the chain * fixed val rewards test * fix outstanding rewards test * fixed rewards * hooks skeleton * adding test file and hooks * 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() * 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 * 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 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 * fix: update genesis for guide (#394) * fixed mislabeled variable * managed to fix unjail test * fixed bank acct transfers test * add kava_dist to sample genesis file (#396) * 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 * 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 * 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 * Update swagger-ui/startchain.sh Send output to dev null not to console Co-Authored-By: Kevin Davis * 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 * do not shut down the blockchain, use new swagger file * initial fixes to get yaml to pass the dredd validator * first test results - 19 passing, 66 failing * 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 * Update swagger-ui/startchain.sh Send output to dev null not to console Co-Authored-By: Kevin Davis * 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 Co-authored-by: Kevin Davis * remove test results * fix merge conflict * fix more merge conflicts * keeping track of number of passing tests, currently 19 passing, 66 failing * fix /supply/total test * fix /minting/annual-provisions test * fix inflation test * fix community pool test * fix distribution test * fix another validator operator test * fix two more tests * fix a rewards test * fix outstanding rewards test * fixed ~7 more tests * fixed another test * fixed another test * fixed another test * fixed bech32 encoding issue and two more tests * fixed another address issue test * fixed another test * fixed two more tests * another test fix * fix param change test * fix gov proposal test * fixed another gov issue * fixed unjail test * fixed slashing test * fixed another few tests * another fix * another fix * another fix * another fix * several more fixes * another test fixed... * more fixes * another fix, 61 now passing, 21 failing * another fix * multiple more fixes * another fix... * 70 tests now passing, 12 failing. Failing tests are CDPs, auction, and pricefeed tests * 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 * use the cdp and pricefeed genesis file * changes to several addresses and denominations in several tests * adding script to start without rebuilding the genesis file * comment out file copy that was causing script to fail * fix a pricefeed test * adding a new test genesis file for use in the dredd tests * fixed another pricefeed test * fixed oracle test * fixed pricefeed markets * fixed oracles issue * fixed two cdp tests * feat: update genesis examples * fix: use post instead of put (#405) * finally fixed a timestamp format issue for put pricefeed test * update contrib structure (#403) * used for testing * update chain name * adding xrp to vlad in genesis * updaing to set xrp and pricefeed and cdp params in genesis file * changes to create cdp method * some minor changes * finally successfully creating the cdp * all the pure cdp tests are now passing, so overall 79 passing, 3 failing (3 auction tests) * fix to kill the blockchain when done and shutting down * uncomment to run all functions * minor changes * fix on tx test * adding method to update price of xrp to hopefully trigger auction * adding btc to the vlad wallet, keep blockchain running * create btc cdp and update price to trigger auction * fixed an auction test - 80 tests now passing, 2 failing * fix auction bid test, 81 now passing only 1 failing * fixes to the script to setup chain and run all tests * adding code review note * setup and send all messages to blockchain * add note to readme how to run the dredd tests * kill the kava if it is already running * ALL 82 TESTS NOW PASSINGga swagger-ui/testnet-4000/swagger-testnet-4000.yamlga swagger-ui/testnet-4000/swagger-testnet-4000.yaml 0 FAILING * remove unused genesis file as we now create it on the fly from the run all tests script * remove old file * fixed jq issue - 81 tests now passing. one is failing for some reason - taking a look * fixed pricefeed - all 82 tests now passing zero failing * Update README.md Co-Authored-By: Federico Kunze <31522760+fedekunze@users.noreply.github.com> * remove old file * Update rest_test/run_all_tests_from_make.sh Co-Authored-By: Kevin Davis * fix setup test Co-authored-by: John Maheswaran Co-authored-by: Kevin Davis Co-authored-by: Denali Marsh Co-authored-by: Kevin Davis Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> --- README.md | 2 +- contrib/testnet-4000/README.md | 8 + rest_test/run_all_tests_from_make.sh | 45 +- rest_test/setup/setuptest.go | 135 ++- .../testnet-4000/swagger-testnet-4000.yaml | 884 ++++++++++++------ 5 files changed, 736 insertions(+), 338 deletions(-) diff --git a/README.md b/README.md index 4fc13f33..d481e32b 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ -Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [comsos-sdk](https://github.com/cosmos/cosmos-sdk). +Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [cosmos-sdk](https://github.com/cosmos/cosmos-sdk). ## Mainnet diff --git a/contrib/testnet-4000/README.md b/contrib/testnet-4000/README.md index ce377e67..b1e45544 100644 --- a/contrib/testnet-4000/README.md +++ b/contrib/testnet-4000/README.md @@ -60,3 +60,11 @@ Congratulations, you've just posted a current market price on Kava using the res ## Governance proposals Example governance proposals are located in `/proposal_examples`. + +## Dredd automated REST API testing + +To run the dredd tests to hit the endpoints and test the api run the following command from the `kava` folder: + +```bash + make test_dredd +``` \ No newline at end of file diff --git a/rest_test/run_all_tests_from_make.sh b/rest_test/run_all_tests_from_make.sh index 1b0ca862..d1e01f50 100755 --- a/rest_test/run_all_tests_from_make.sh +++ b/rest_test/run_all_tests_from_make.sh @@ -1,40 +1,37 @@ #! /bin/bash + +# kill kava if it is already running +pgrep kvd | xargs kill +pgrep kvcli | xargs kill + # TODO import from development environment in envkey password="password" validatorMnemonic="equip town gesture square tomorrow volume nephew minute witness beef rich gadget actress egg sing secret pole winter alarm law today check violin uncover" - # address: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c # address: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 - faucet="chief access utility giant burger winner jar false naive mobile often perfect advice village love enroll embark bacon under flock harbor render father since" - # address: kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz # address: kavavaloper1ls82zzghsx0exkpr52m8vht5jqs3un0c5j2c04 - # variables for home directories for kvd and kvcli kvdHome=/tmp/kvdHome kvcliHome=/tmp/kvcliHome - +genesis=$kvdHome/config/genesis.json +swaggerFile=swagger-ui/testnet-4000/swagger-testnet-4000.yaml # Remove any existing data directory rm -rf $kvdHome rm -rf $kvcliHome - # make the directories mkdir /tmp/kvdHome mkdir /tmp/kvcliHome - # create validator key printf "$password\n$validatorMnemonic\n" | kvcli keys add vlad --recover --home $kvcliHome # create faucet key printf "$password\n$faucet\n" | kvcli --home $kvcliHome keys add faucet --recover --home $kvcliHome - # function used to show that it is still loading showLoading() { mypid=$! loadingText=$1 - echo -ne "$loadingText\r" - while kill -0 $mypid 2>/dev/null; do echo -ne "$loadingText.\r" sleep 0.5 @@ -46,32 +43,33 @@ showLoading() { 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 +kvd --home $kvdHome add-genesis-account $(kvcli --home $kvcliHome keys show vlad -a) 10000000000000stake,1000000000000xrp,100000000000btc # add faucet account to genesis kvd --home $kvdHome add-genesis-account $(kvcli --home $kvcliHome keys show faucet -a) 10000000000000stake,1000000000000xrp,100000000000btc - # Create a delegation tx for the validator and add to genesis printf "$password\n" | kvd --home $kvdHome gentx --name vlad --home-client $kvcliHome { kvd --home $kvdHome collect-gentxs } > /dev/null 2>&1 - +# set blocktime to ~1s +jq '.app_state.mint.params.blocks_per_year = "31540000"' $genesis > $genesis.tmp && mv $genesis.tmp $genesis +# update pricefeed information +jq '.app_state.pricefeed.params.markets += [{"active": true, "base_asset": "xrp", "market_id": "xrp:usd", "oracles": ["kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c"], "quote_asset": "usd"}, {"active": true, "base_asset": "btc", "market_id": "btc:usd", "oracles": ["kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c"], "quote_asset": "usd"}]' $genesis > $genesis.tmp && mv $genesis.tmp $genesis +jq '.app_state.pricefeed.posted_prices += [{"expiry": "2050-01-01T00:00:00Z", "market_id": "btc:usd", "oracle_address": "kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c", "price": "8700.0"}, {"expiry": "2050-01-01T00:00:00Z", "market_id": "xrp:usd", "oracle_address": "kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c", "price": "0.25"}]' $genesis > $genesis.tmp && mv $genesis.tmp $genesis +# now update cdp params +jq '.app_state.cdp.params = { "circuit_breaker": false, "collateral_params": [ { "auction_size": "10000000000", "conversion_factor": "8", "debt_limit": [ { "amount": "1000000000", "denom": "usdx" } ], "denom": "btc", "liquidation_penalty": "0.05", "liquidation_ratio": "1.5", "market_id": "btc:usd", "prefix": 0, "stability_fee": "1.0000000007829977" }, { "auction_size": "100000000", "conversion_factor": "6", "debt_limit": [ { "amount": "10000000", "denom": "usdx" } ], "denom": "xrp", "liquidation_penalty": "0.1", "liquidation_ratio": "2.0", "market_id": "xrp:usd", "prefix": 1, "stability_fee": "1.0000000007829977"} ], "debt_auction_threshold": "9000000", "debt_params": [ { "conversion_factor": "6", "debt_floor": "10000000", "debt_limit": [ { "amount": "2000000000000", "denom": "usdx" } ], "denom": "usdx", "reference_asset": "usd", "savings_rate": "0.95" } ], "global_debt_limit": [ { "amount": "2000000000000", "denom": "usdx" } ], "surplus_auction_threshold": "9000000", "savings_distribution_frequency": "120000000000" }' $genesis > $genesis.tmp && mv $genesis.tmp $genesis # start the blockchain in the background, wait until it starts making blocks { kvd start --home $kvdHome & kvdPid="$!" } > /dev/null 2>&1 - printf "\n" sleep 10 & showLoading "Starting rest server, please wait" # start the rest server. Use ./stopchain.sh to stop both rest server and the blockchain @@ -81,30 +79,24 @@ kvcli rest-server --laddr tcp://127.0.0.1:1317 --chain-id=testing --home $kvcliH 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" - +dredd $swaggerFile localhost:1317 2>&1 | tee output & showLoading "Running dredd tests" ######################################################## -# Now run the check the return code from the dredd command. +# 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 ] +if [ $? -eq 0 ] then # check that all the tests passed (ie zero failing) if [[ $(cat output | grep "0 failing") ]] @@ -122,7 +114,6 @@ then 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" diff --git a/rest_test/setup/setuptest.go b/rest_test/setup/setuptest.go index 9d66f390..dc52be9f 100644 --- a/rest_test/setup/setuptest.go +++ b/rest_test/setup/setuptest.go @@ -6,6 +6,7 @@ import ( "io/ioutil" "net/http" "os" + "time" "github.com/cosmos/cosmos-sdk/client/keys" crkeys "github.com/cosmos/cosmos-sdk/crypto/keys" @@ -20,7 +21,10 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov/types" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/pricefeed" "github.com/tendermint/go-amino" + tmtime "github.com/tendermint/tendermint/types/time" ) func init() { @@ -31,6 +35,14 @@ func init() { } func main() { + if len(os.Args) < 2 { + fmt.Printf("Please include the kvcli home directory as a command line argument\n") + fmt.Printf("For example: ./setuptest /tmp/kvcliHome\n") + fmt.Printf("Exiting...goodbye!\n") + return + } + + // setup messages send to blockchain so it is in the correct state for testing sendProposal() sendDeposit() sendVote() @@ -46,6 +58,122 @@ func main() { sendCoins() + // create an XRP cdp and send to blockchain + sendXrpCdp() + + // create a BTC cdp and send to blockchain + sendBtcCdp() + + // reduce the price of BTC to trigger an auction + sendMsgPostPrice() +} + +// lower the price of xrp to trigger an auction +func sendMsgPostPrice() { + // get the address + address := getTestAddress() + // get the keyname and password + keyname, password := getKeynameAndPassword() + + addr, err := sdk.AccAddressFromBech32(address) // validator address + if err != nil { + panic(err) + } + + price, err := sdk.NewDecFromStr("1") + if err != nil { + panic(err) + } + // set the expiry time + expiry := tmtime.Now().Add(time.Second * 100000) + + // create a cdp message to send to the blockchain + // from, assetcode, price, expiry + msg := pricefeed.NewMsgPostPrice( + addr, + "btc:usd", + price, + expiry, + ) + + // helper methods for transactions + cdc := app.MakeCodec() // make codec for the app + + // get the keybase + keybase := getKeybase() + + // cast to the generic msg type + msgToSend := []sdk.Msg{msg} + + // send the message to the blockchain + sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase) + +} + +func sendBtcCdp() { + // get the address + address := getTestAddress() + // get the keyname and password + keyname, password := getKeynameAndPassword() + + addr, err := sdk.AccAddressFromBech32(address) // validator address + if err != nil { + panic(err) + } + + // create a cdp message to send to the blockchain + // sender, collateral, principal + msg := cdp.NewMsgCreateCDP( + addr, + sdk.NewCoins(sdk.NewInt64Coin("btc", 200000000)), + sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000)), + ) + + // helper methods for transactions + cdc := app.MakeCodec() // make codec for the app + + // get the keybase + keybase := getKeybase() + + // cast to the generic msg type + msgToSend := []sdk.Msg{msg} + + // send the message to the blockchain + sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase) + +} + +func sendXrpCdp() { + // get the address + address := getTestAddress() + // get the keyname and password + keyname, password := getKeynameAndPassword() + + addr, err := sdk.AccAddressFromBech32(address) // validator address + if err != nil { + panic(err) + } + + // create a cdp message to send to the blockchain + // sender, collateral, principal + msg := cdp.NewMsgCreateCDP( + addr, + sdk.NewCoins(sdk.NewInt64Coin("xrp", 200000000)), + sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000)), + ) + + // helper methods for transactions + cdc := app.MakeCodec() // make codec for the app + + // get the keybase + keybase := getKeybase() + + // cast to the generic msg type + msgToSend := []sdk.Msg{msg} + + // send the message to the blockchain + sendMsgToBlockchain(cdc, address, keyname, password, msgToSend, keybase) + } func sendProposal() { @@ -274,9 +402,9 @@ func sendMsgToBlockchain(cdc *amino.Codec, address string, keyname string, txBldr := auth.NewTxBuilderFromCLI(). WithTxEncoder(authclient.GetTxEncoder(cdc)).WithChainID("testing"). WithKeybase(keybase).WithAccountNumber(accountNumber). - WithSequence(sequenceNumber) + WithSequence(sequenceNumber).WithGas(500000) - // build and sign the transaction + // 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) @@ -296,6 +424,9 @@ func sendMsgToBlockchain(cdc *amino.Codec, address string, keyname string, Mode: "block", }, ) + + fmt.Printf("%s", bytes.NewBuffer(jsonBytes)) + if err != nil { panic(err) } diff --git a/swagger-ui/testnet-4000/swagger-testnet-4000.yaml b/swagger-ui/testnet-4000/swagger-testnet-4000.yaml index 78954f36..bf369a13 100644 --- a/swagger-ui/testnet-4000/swagger-testnet-4000.yaml +++ b/swagger-ui/testnet-4000/swagger-testnet-4000.yaml @@ -237,12 +237,12 @@ description: Tx hash required: true type: string - x-example: BCBE20E8D46758B96AE5883B792858296AC06E51435490FBDCAE25A72B3CC76B + x-example: 4B2C3449FF2647BD51B54C32761FE4EBE7AE024BBE2A9898972A69BEB82D97C2 responses: 200: description: Tx with the provided hash schema: - $ref: '#/definitions/TxQuery' + $ref: "#/definitions/TxQuery" 500: description: Internal Server Error /txs: @@ -258,12 +258,12 @@ name: message.action type: string description: "transaction events such as 'message.action=send' which results in the following endpoint: 'GET /txs?message.action=send'. note that each module documents its own events. look for xx_events.md in the corresponding cosmos-sdk/docs/spec directory" - x-example: 'send' + x-example: "send" - in: query name: message.sender type: string - description: "transaction tags with sender: 'GET /txs?message.action=send&message.sender=kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv'" - x-example: 'kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv' + description: "transaction tags with sender: 'GET /txs?message.action=send&message.sender=kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9'" + x-example: "kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9" - in: query name: page description: Page number @@ -274,11 +274,16 @@ description: Maximum number of items per page type: integer x-example: 1 + - in: query + name: txheight # this should actually be tx.height but dredd doesn't handle periods in get parameter names so no period allows the test to pass + description: Transaction height + type: integer + x-example: 1 responses: 200: description: All txs matching the provided events schema: - $ref: '#/definitions/PaginatedQueryTxs' + $ref: "#/definitions/PaginatedQueryTxs" 400: description: Invalid search events 500: @@ -301,15 +306,15 @@ type: object properties: tx: - $ref: '#/definitions/StdTx' + $ref: "#/definitions/PostStdTx" mode: type: string - example: sync + example: block responses: 200: description: Tx broadcasting result schema: - $ref: '#/definitions/BroadcastTxCommitResult' + $ref: "#/definitions/BroadcastTxCommitResult" 500: description: Internal Server Error /txs/encode: @@ -323,15 +328,13 @@ produces: - application/json parameters: - - in: body - name: tx - description: The tx to encode + - description: "" + name: EncodeBody + in: body required: true schema: type: object - properties: - tx: - $ref: '#/definitions/StdTx' + $ref: "#/definitions/EncodeTx" responses: 200: description: The tx was successfully decoded and re-encoded @@ -342,7 +345,7 @@ type: string example: The base64-encoded Amino-serialized bytes for the tx 400: - description: The tx was malformated + description: The tx was malformatted 500: description: Server internal error /txs/decode: @@ -365,12 +368,12 @@ properties: tx: type: string - example: SvBiXe4KPqijYZoKFFHEzJ8c2HPAfv2EFUcIhx0yPagwEhTy0vPA+GGhCEslKXa4Af0uB+mfShoMCgVzdGFrZRIDMTAwEgQQwJoM + example: ogEoKBapCjyoo2GaChRKWendsRagTF1ACC1nxzjVxW3xJBIU/A6hCReBn5NYI6K2dl10kCEeTfgaCgoFc3Rha2USATESEAoKCgVzdGFrZRIBMRDAmgwaQhJAdlC1HbNw+ux6lRrK3mNdmaH62NE3ThD8SswlDcnhFex7pKSNhaxE4m6TgDhosoK6EyU0LnOZKutXKECNSvO+WCIIdGVzdG1lbW8= responses: 200: description: The tx was successfully decoded schema: - $ref: '#/definitions/StdTx' + $ref: "#/definitions/StdTx" 400: description: The tx was malformated 500: @@ -426,7 +429,7 @@ description: Owner address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: denom description: Collateral denom @@ -472,7 +475,7 @@ description: Owner address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: denom description: Collateral denom @@ -518,7 +521,7 @@ description: Owner address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: denom description: Collateral denom @@ -562,7 +565,7 @@ description: Owner address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: denom description: Collateral denom @@ -642,7 +645,7 @@ description: Owner address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: denom description: Collateral denom @@ -674,9 +677,15 @@ 200: description: All CDPs with the input collateral denom schema: - type: array - items: - $ref: '#/definitions/CdpResponse' + type: object + properties: + height: + type: string + result: + type: array + x-nullable: true + items: + $ref: '#/definitions/CdpResponse' 500: description: Server internal error /cdp/cdps/ratio/{denom}/{ratio}: @@ -698,14 +707,20 @@ description: Collateralization ratio required: true type: string - x-example: 2.0 + x-example: "2.0" responses: 200: description: All CDPs with the input collateral denom and collateralization ratio less than the input ratio schema: - type: array - items: - $ref: '#/definitions/CdpResponse' + type: object + properties: + height: + type: string + result: + type: array + x-nullable: true + items: + $ref: '#/definitions/CdpResponse' 500: description: Server internal error /cdp/cdps/cdp/deposits/{owner}/{denom}: @@ -721,7 +736,7 @@ description: Owner address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: denom description: Collateral denom @@ -732,9 +747,14 @@ 200: description: Deposits associated with the cdp schema: - type: array - items: - $ref: '#/definitions/CdpDepositResponse' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/CdpDepositResponse' 500: description: Server internal error /auction/auctions/{id}/bids: @@ -752,7 +772,16 @@ description: Auction id required: true type: string - x-example: 1 + x-example: "1" + - in: body + name: Auction bid request body + schema: + properties: + base_req: + $ref: '#/definitions/BaseReq' + amount: + $ref: '#/definitions/CoinBid' + responses: 200: description: The transaction was successfully generated @@ -788,9 +817,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/AuctionResponse' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/AuctionResponse' 500: description: Internal Server Error /auction/auctions/{id}: @@ -807,7 +841,7 @@ description: Auction id required: true type: string - x-example: 2 + x-example: "1" responses: 200: description: OK @@ -818,7 +852,7 @@ 500: description: Internal Server Error /pricefeed/postprice: - put: + post: summary: Generate post price transaction consumes: - application/json @@ -844,7 +878,7 @@ example: '0.298464000000000007' expiry: type: string - example: '2020-02-12T23:20:00Z' + example: '158551630291' responses: 200: description: The transaction was successfully generated @@ -879,9 +913,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Market' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/Market' 500: description: Internal Server Error /pricefeed/oracles/{market_id}: @@ -902,9 +941,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Address' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/Address' 500: description: Internal Server Error /pricefeed/rawprices/{market_id}: @@ -925,9 +969,15 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/PostedPrice' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/PostedPrice' + 500: description: Internal Server Error /pricefeed/price/{market_id}: @@ -948,7 +998,7 @@ 200: description: OK schema: - $ref: '#/definitions/Prices' + $ref: '#/definitions/Price' 500: description: Internal Server Error /bank/balances/{address}: @@ -964,16 +1014,21 @@ description: Account address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c responses: 200: description: Account balances schema: - type: array - items: - $ref: '#/definitions/Coin' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Coin" 500: description: Server internal error + /bank/accounts/{address}/transfers: post: summary: Send coins from one account to another @@ -989,7 +1044,7 @@ description: Account address in bech32 format required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 - in: body name: account description: The sender and tx information @@ -998,16 +1053,16 @@ type: object properties: base_req: - $ref: '#/definitions/BaseReq' + $ref: "#/definitions/BaseReq" amount: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" responses: - 202: + 200: description: Tx was successfully generated schema: - $ref: '#/definitions/StdTx' + $ref: "#/definitions/StdTx" 400: description: Invalid request 500: @@ -1025,7 +1080,7 @@ description: Account address required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 responses: 200: description: Account information on the blockchain @@ -1044,13 +1099,13 @@ coins: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" public_key: - $ref: '#/definitions/PublicKey' + $ref: "#/definitions/PublicKey" sequence: type: string 500: - description: Server internel error + description: Server internal error /staking/delegators/{delegatorAddr}/delegations: parameters: - in: path @@ -1058,7 +1113,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Get all delegations from a delegator tags: @@ -1069,47 +1124,55 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Delegation' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Delegation" 400: description: Invalid delegator address 500: description: Internal Server Error - post: - summary: Submit delegation - parameters: - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: '#/definitions/BaseReq' - delegator_address: - $ref: '#/definitions/Address' - validator_address: - $ref: '#/definitions/ValidatorAddress' - delegation: - $ref: '#/definitions/Coin' - tags: - - Staking - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: OK - schema: - $ref: '#/definitions/BroadcastTxCommitResult' - 400: - description: Invalid delegator address or delegation request body - 401: - description: Key password is wrong - 500: - description: Internal Server Error + + # THE BELOW ENDPOINT IS NOT IMPLEMENTED IN COSMOS, SEE: + # https://github.com/cosmos/cosmos-sdk/blob/18de630d0ae1887113e266982b51c2bf1f662edb/x/staking/client/rest/query.go + + # post: + # summary: Submit delegation + # parameters: + # - in: body + # name: delegation + # description: The password of the account to remove from the KMS + # schema: + # type: object + # properties: + # base_req: + # $ref: "#/definitions/BaseReq" + # delegator_address: + # $ref: "#/definitions/Address" + # validator_address: + # $ref: "#/definitions/ValidatorAddress" + # delegation: + # $ref: "#/definitions/Coin" + # tags: + # - Staking + # consumes: + # - application/json + # produces: + # - application/json + # responses: + # 200: + # description: OK + # schema: + # $ref: "#/definitions/BroadcastTxCommitResult" + # 400: + # description: Invalid delegator address or delegation request body + # 401: + # description: Key password is wrong + # 500: + # description: Internal Server Error /staking/delegators/{delegatorAddr}/delegations/{validatorAddr}: parameters: - in: path @@ -1117,13 +1180,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query the current delegation between a delegator and a validator tags: @@ -1134,7 +1197,7 @@ 200: description: OK schema: - $ref: '#/definitions/Delegation' + $ref: "#/definitions/Delegation" 400: description: Invalid delegator address or validator address 500: @@ -1146,7 +1209,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Get all unbonding delegations from a delegator tags: @@ -1157,48 +1220,57 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/UnbondingDelegation' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/UnbondingDelegation" + 400: description: Invalid delegator address 500: description: Internal Server Error - post: - summary: Submit an unbonding delegation - parameters: - - in: body - name: delegation - description: The password of the account to remove from the KMS - schema: - type: object - properties: - base_req: - $ref: '#/definitions/BaseReq' - delegator_address: - $ref: '#/definitions/Address' - validator_address: - $ref: '#/definitions/ValidatorAddress' - shares: - type: string - example: '100' - tags: - - Staking - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: OK - schema: - $ref: '#/definitions/BroadcastTxCommitResult' - 400: - description: Invalid delegator address or unbonding delegation request body - 401: - description: Key password is wrong - 500: - description: Internal Server Error + + # THE BELOW ENDPOINT IS NOT IMPLEMENTED IN COSMOS, SEE: + # https://github.com/cosmos/cosmos-sdk/blob/18de630d0ae1887113e266982b51c2bf1f662edb/x/staking/client/rest/query.go + + # post: + # summary: Submit an unbonding delegation + # parameters: + # - in: body + # name: delegation + # description: The password of the account to remove from the KMS + # schema: + # type: object + # properties: + # base_req: + # $ref: "#/definitions/BaseReq" + # delegator_address: + # $ref: "#/definitions/Address" + # validator_address: + # $ref: "#/definitions/ValidatorAddress" + # shares: + # type: string + # example: "100" + # tags: + # - Staking + # consumes: + # - application/json + # produces: + # - application/json + # responses: + # 200: + # description: OK + # schema: + # $ref: "#/definitions/BroadcastTxCommitResult" + # 400: + # description: Invalid delegator address or unbonding delegation request body + # 401: + # description: Key password is wrong + # 500: + # description: Internal Server Error /staking/delegators/{delegatorAddr}/unbonding_delegations/{validatorAddr}: parameters: - in: path @@ -1206,13 +1278,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query all unbonding delegations between a delegator and a validator tags: @@ -1223,7 +1295,7 @@ 200: description: OK schema: - $ref: '#/definitions/UnbondingDelegationPair' + $ref: "#/definitions/UnbondingDelegationPair" 400: description: Invalid delegator address or validator address 500: @@ -1254,55 +1326,63 @@ responses: 200: description: OK - schema: - type: array - items: - $ref: '#/definitions/Redelegation' - 500: - description: Internal Server Error - /staking/delegators/{delegatorAddr}/redelegations: - parameters: - - in: path - name: delegatorAddr - description: Bech32 AccAddress of Delegator - required: true - type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv - post: - summary: Submit a redelegation - parameters: - - in: body - name: delegation - description: The sender and tx information schema: type: object properties: - base_req: - $ref: '#/definitions/BaseReq' - delegator_address: - $ref: '#/definitions/Address' - validator_src_addressess: - $ref: '#/definitions/ValidatorAddress' - validator_dst_address: - $ref: '#/definitions/ValidatorAddress' - shares: + height: type: string - example: '100' - tags: - - Staking - consumes: - - application/json - produces: - - application/json - responses: - 200: - description: Tx was successfully generated - schema: - $ref: '#/definitions/StdTx' - 400: - description: Invalid delegator address or redelegation request body + result: + items: + $ref: "#/definitions/Redelegation" 500: description: Internal Server Error + + # THE BELOW ENDPOINT IS NOT IMPLEMENTED IN COSMOS, SEE: + # https://github.com/cosmos/cosmos-sdk/blob/18de630d0ae1887113e266982b51c2bf1f662edb/x/staking/client/rest/query.go + + # /staking/delegators/{delegatorAddr}/redelegations: + # parameters: + # - in: path + # name: delegatorAddr + # description: Bech32 AccAddress of Delegator + # required: true + # type: string + # x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c + # post: + # summary: Submit a redelegation + # parameters: + # - in: body + # name: delegation + # description: The sender and tx information + # schema: + # type: object + # properties: + # base_req: + # $ref: "#/definitions/BaseReq" + # delegator_address: + # $ref: "#/definitions/Address" + # validator_src_addresses: + # $ref: "#/definitions/ValidatorAddress" + # validator_dst_address: + # $ref: "#/definitions/ValidatorAddress" + # shares: + # type: string + # example: "100" + # tags: + # - Staking + # consumes: + # - application/json + # produces: + # - application/json + # responses: + # 200: + # description: Tx was successfully generated + # schema: + # $ref: "#/definitions/StdTx" + # 400: + # description: Invalid delegator address or redelegation request body + # 500: + # description: Internal Server Error /staking/delegators/{delegatorAddr}/validators: parameters: - in: path @@ -1310,7 +1390,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Query all validators that a delegator is bonded to tags: @@ -1321,9 +1401,13 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Validator' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Validator" 400: description: Invalid delegator address 500: @@ -1335,13 +1419,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 ValAddress of Delegator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query a validator that a delegator is bonded to tags: @@ -1352,7 +1436,7 @@ 200: description: OK schema: - $ref: '#/definitions/Validator' + $ref: "#/definitions/Validator" 400: description: Invalid delegator address or validator address 500: @@ -1384,9 +1468,13 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Validator' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Validator" 500: description: Internal Server Error /staking/validators/{validatorAddr}: @@ -1396,7 +1484,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query the information from a single validator tags: @@ -1419,7 +1507,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1q53rwutgpzx7szcrgzqguxyccjpzt9j44jzrtj get: summary: Get all delegations from a validator tags: @@ -1430,9 +1518,13 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Delegation' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/Delegation" 400: description: Invalid validator address 500: @@ -1444,7 +1536,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1q53rwutgpzx7szcrgzqguxyccjpzt9j44jzrtj get: summary: Get all unbonding delegations from a validator tags: @@ -1455,9 +1547,13 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/UnbondingDelegation' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/UnbondingDelegation" 400: description: Invalid validator address 500: @@ -1568,9 +1664,14 @@ 200: description: OK schema: - type: array items: - $ref: '#/definitions/SigningInfo' + type: object + properties: + height: + type: string + result: + items: + $ref: "#/definitions/SigningInfo" 400: description: Invalid validator public key for one of the validators 500: @@ -1591,8 +1692,8 @@ name: validatorAddr required: true in: path - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l - - description: '' + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 + - description: "" name: UnjailBody in: body required: true @@ -1600,12 +1701,12 @@ type: object properties: base_req: - $ref: '#/definitions/StdTx' + $ref: "#/definitions/BaseReq" responses: 200: description: Tx was successfully generated schema: - $ref: '#/definitions/BroadcastTxCommitResult' + $ref: "#/definitions/BroadcastTxCommitResult" 400: description: Invalid validator address or base_req 500: @@ -1658,25 +1759,34 @@ type: object properties: base_req: - $ref: '#/definitions/BaseReq' + $ref: "#/definitions/BaseReq" title: type: string + example: Test_title description: type: string + example: my_test_description proposal_type: type: string - example: 'text' + example: "text" proposer: - $ref: '#/definitions/Address' + $ref: "#/definitions/Address" initial_deposit: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" responses: 200: description: Tx was successfully generated schema: - $ref: '#/definitions/StdTx' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/TextProposal" 400: description: Invalid proposal body 500: @@ -1706,11 +1816,16 @@ type: string responses: 200: - description: OK + description: Tx was successfully generated schema: - type: array - items: - $ref: '#/definitions/TextProposal' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/TextProposal" 400: description: Invalid query parameters 500: @@ -1734,23 +1849,23 @@ type: object properties: base_req: - $ref: '#/definitions/BaseReq' + $ref: "#/definitions/BaseReq" title: type: string - x-example: 'Param Change' + example: Param_Change description: type: string - x-example: 'Update max validators' + example: Update_max_validators proposer: - $ref: '#/definitions/Address' + $ref: "#/definitions/Address" deposit: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" changes: type: array items: - $ref: '#/definitions/ParamChange' + $ref: "#/definitions/ParamChange" responses: 200: description: The transaction was successfully generated @@ -1824,9 +1939,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Deposit' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/Deposit' 400: description: Invalid proposal id 500: @@ -1887,13 +2007,13 @@ name: proposalId required: true in: path - x-example: '2' + x-example: "2" - type: string description: Bech32 depositor address name: depositor required: true in: path - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c responses: 200: description: OK @@ -1924,9 +2044,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Vote' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/Vote' 400: description: Invalid proposal id 500: @@ -1992,7 +2117,7 @@ name: voter required: true in: path - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c responses: 200: description: OK @@ -2112,7 +2237,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava167w96tdvmazakdwkw2u57227eduula2cy572lf + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c get: summary: Get the total rewards balance from all delegations description: Get the sum of all the rewards earned by delegations by a single delegator @@ -2124,7 +2249,12 @@ 200: description: OK schema: - $ref: '#/definitions/DelegatorTotalRewards' + type: object + properties: + height: + type: string + result: + $ref: '#/definitions/DelegatorTotalRewards' 400: description: Invalid delegator address 500: @@ -2163,13 +2293,13 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv + x-example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c - in: path name: validatorAddr description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Query a delegation reward description: Query a single delegation reward by a delegator @@ -2181,9 +2311,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Coin' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Coin" 400: description: Invalid delegator address 500: @@ -2222,7 +2357,7 @@ description: Bech32 AccAddress of Delegator required: true type: string - x-example: kava167w96tdvmazakdwkw2u57227eduula2cy572lf + x-example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 get: summary: Get the rewards withdrawal address description: Get the delegations' rewards withdrawal address. This is the address in which the user will receive the reward funds @@ -2234,7 +2369,12 @@ 200: description: OK schema: - $ref: '#/definitions/Address' + type: object + properties: + height: + type: string + result: + $ref: "#/definitions/Address" 400: description: Invalid delegator address 500: @@ -2275,7 +2415,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Validator distribution information description: Query the distribution information of a single validator @@ -2299,7 +2439,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Fee distribution outstanding rewards of a single validator tags: @@ -2310,9 +2450,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Coin' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/Coin' 500: description: Internal Server Error /distribution/validators/{validatorAddr}/rewards: @@ -2322,7 +2467,7 @@ description: Bech32 OperatorAddress of validator required: true type: string - x-example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + x-example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 get: summary: Commission and self-delegation rewards of a single validator description: Query the commission and self-delegation rewards of validator. @@ -2332,11 +2477,16 @@ - application/json responses: 200: - description: OK + description: OK schema: - type: array - items: - $ref: '#/definitions/Coin' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: '#/definitions/Coin' 400: description: Invalid validator address 500: @@ -2379,9 +2529,14 @@ 200: description: OK schema: - type: array - items: - $ref: '#/definitions/Coin' + type: object + properties: + height: + type: string + result: + type: array + items: + $ref: "#/definitions/Coin" 500: description: Internal Server Error /distribution/parameters: @@ -2441,7 +2596,7 @@ 200: description: OK schema: - type: string + $ref: "#/definitions/StandardResponse" 500: description: Internal Server Error /minting/annual-provisions: @@ -2455,7 +2610,7 @@ 200: description: OK schema: - type: string + $ref: "#/definitions/StandardResponse" 500: description: Internal Server Error /supply/total: @@ -2490,7 +2645,7 @@ 200: description: OK schema: - type: string + $ref: "#/definitions/StandardResponse" 400: description: Invalid coin denomination 500: @@ -2564,7 +2719,7 @@ hash: $ref: '#/definitions/Hash' height: - type: integer + type: string KVPair: type: object properties: @@ -2574,26 +2729,31 @@ type: string Msg: type: object - required: - - from properties: type: type: string - example: 'string' + example: "cosmos-sdk/MsgSend" value: type: object properties: - from: + from_address: type: string - example: kava1mzqz3jfs9nzfp6v7qp647rv0afxlu2csl0txmq + example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c + to_address: + type: string + example: kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz + amount: + type: array + items: + $ref: "#/definitions/Coin" Address: type: string description: bech32 encoded address - example: kava1depk54cuajgkzea6zpgkq36tnjwdzv4afc3d27 + example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c ValidatorAddress: type: string description: bech32 encoded address - example: kavavaloper16xyempempp92x9hyzz9wrgf94r6j9h5f2w4n2l + example: kavavaloper1ffv7nhd3z6sych2qpqkk03ec6hzkmufyz4scd0 Coin: type: object properties: @@ -2608,7 +2768,7 @@ properties: denom: type: string - example: bnb + example: "xrp" amount: type: string example: '10000000000' @@ -2617,10 +2777,19 @@ properties: denom: type: string - example: usdx + example: "usdx" amount: type: string example: '10000000' + CoinBid: + type: object + properties: + denom: + type: string + example: "usdx" + amount: + type: string + example: '100000000' Hash: type: string example: EE5F3404034C524501629B56E0DDC38FAD651F04 @@ -2646,8 +2815,8 @@ type: string example: 'D085138D913993919295FF4B0A9107F1F2CDE0D37A87CE0644E217CBF3B49656' height: - type: number - example: 368 + type: string + example: "368" tx: $ref: '#/definitions/StdTx' result: @@ -2669,20 +2838,20 @@ type: object properties: total_count: - type: number - example: 1 + type: string + example: "1" count: - type: number - example: 1 + type: string + example: "1" page_number: - type: number - example: 1 + type: string + example: "1" page_total: - type: number - example: 1 + type: string + example: "1" limit: - type: number - example: 30 + type: string + example: "30" txs: type: array items: @@ -2693,16 +2862,17 @@ msg: type: array items: - $ref: '#/definitions/Msg' + $ref: "#/definitions/Msg" fee: type: object properties: gas: type: string + example: "200000" amount: type: array items: - $ref: '#/definitions/Coin' + $ref: "#/definitions/Coin" memo: type: string signatures: @@ -2718,8 +2888,8 @@ type: object properties: total: - type: number - example: 0 + type: string + example: "0" hash: $ref: '#/definitions/Hash' BlockHeader: @@ -2727,21 +2897,21 @@ properties: chain_id: type: string - example: kavahub-2 + example: testing height: - type: number - example: 1 + type: string + example: "1" time: type: string example: '2017-12-30T05:53:09.287+01:00' num_txs: - type: number - example: 0 + type: string + example: "0" last_block_id: $ref: '#/definitions/BlockID' total_txs: - type: number - example: 35 + type: string + example: "35" last_commit_hash: $ref: '#/definitions/Hash' data_hash: @@ -2779,9 +2949,10 @@ items: type: string evidence: - type: array - items: - type: string + type: object + # type: array + # items: + # type: string last_commit: type: object properties: @@ -2789,6 +2960,7 @@ $ref: '#/definitions/BlockID' precommits: type: array + x-nullable: true items: type: object properties: @@ -2851,20 +3023,20 @@ properties: from: type: string - example: 'kava1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc' + example: 'kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c' description: Sender address or Keybase name to generate a transaction memo: type: string example: 'Sent via Cosmos Voyager 🚀' chain_id: type: string - example: 'kava-1' + example: 'testing' account_number: type: string example: '0' sequence: type: string - example: '1' + example: '5' gas: type: string example: '200000' @@ -2877,7 +3049,7 @@ $ref: '#/definitions/Coin' simulate: type: boolean - example: false + example: true description: Estimate gas for a transaction (cannot be used in conjunction with generate_only) TendermintValidator: type: object @@ -2901,7 +3073,7 @@ example: 1 owner: type: string - example: kava1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc + example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 collateral: $ref: '#/definitions/CoinCollateral' principal: @@ -2924,9 +3096,11 @@ example: 1 depositor: type: string - example: kava1g9ahr6xhht5rmqven628nklxluzyv8z9jqjcmc + example: kava1q53rwutgpzx7szcrgzqguxyccjpzt9j4cyctn9 amount: - $ref: '#/definitions/CoinCollateral' + type: array + items: + $ref: '#/definitions/CoinCollateral' PricefeedParameters: type: object properties: @@ -2957,7 +3131,7 @@ example: '0.298758999999999997' expiry: type: string - example: '2020-02-12T23:10:00Z' + example: '2021-02-12T23:10:00Z' Market: type: object properties: @@ -2972,6 +3146,7 @@ example: 'usd' oracles: type: array + x-nullable: true items: $ref: '#/definitions/Address' active: @@ -3307,3 +3482,96 @@ type: array items: $ref: '#/definitions/Coin' + StandardResponse: + type: object + properties: + height: + type: string + result: + type: string + PostStdTx: + type: object + properties: + msg: + type: array + items: + $ref: "#/definitions/PostMsg" + fee: + type: object + properties: + gas: + type: string + example: "200000" + amount: + type: array + items: + $ref: "#/definitions/PostCoin2" + memo: + type: string + example: "" + signatures: + type: array + items: + $ref: "#/definitions/PostSignature" + + PostMsg: + type: object + properties: + type: + type: string + example: "cosmos-sdk/MsgSend" + value: + type: object + properties: + from_address: + type: string + example: kava1ffv7nhd3z6sych2qpqkk03ec6hzkmufy0r2s4c + to_address: + type: string + example: kava1ls82zzghsx0exkpr52m8vht5jqs3un0ceysshz + amount: + type: array + items: + $ref: "#/definitions/PostCoin1" + PostCoin1: + type: object + properties: + denom: + type: string + example: stake + amount: + type: string + example: "2000000" + PostCoin2: + type: object + properties: + denom: + type: string + example: stake + amount: + type: string + example: "2000" + PostSignature: + type: object + properties: + signature: + type: string + example: "KzBeQp/2+47oiqc16BImnOYidSAesZ3kgR3S8Fy+baFlvDlDv7goU/+rm4c7+woudNte3uZAG0CuUeHsF+Ld8Q==" + pubkey: + type: object + properties: + type: + type: string + example: "tendermint/PubKeySecp256k1" + value: + type: string + example: "AmWAim83Qp+kIcj3RT7i327b3l0EHwzCrGVGXusb70B7" + EncodeTx: + type: object + properties: + type: + type: string + example: "cosmos-sdk/StdTx" + value: + type: object + $ref: "#/definitions/StdTx" From 9817a10ca6ecf14f001345db62fcfa5c4afbb26a Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Wed, 1 Apr 2020 15:36:46 -0300 Subject: [PATCH 12/45] validator-vesting: decoder_test --- x/validator-vesting/simulation/decoder.go | 2 +- .../simulation/decoder_test.go | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 x/validator-vesting/simulation/decoder_test.go diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go index f6c052f5..a09bbd81 100644 --- a/x/validator-vesting/simulation/decoder.go +++ b/x/validator-vesting/simulation/decoder.go @@ -26,6 +26,6 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &btB) return fmt.Sprintf("%v\n%v", btA, btB) default: - panic(fmt.Sprintf("invalid account key %X", kvA.Key)) + panic(fmt.Sprintf("invalid %s key %X", types.ModuleName, kvA.Key)) } } diff --git a/x/validator-vesting/simulation/decoder_test.go b/x/validator-vesting/simulation/decoder_test.go new file mode 100644 index 00000000..fee10265 --- /dev/null +++ b/x/validator-vesting/simulation/decoder_test.go @@ -0,0 +1,60 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + + "github.com/kava-labs/kava/x/validator-vesting/internal/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + types.RegisterCodec(cdc) + codec.RegisterEvidences(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + acc := types.ValidatorVestingAccount{SigningThreshold: 1} + now := time.Now().UTC() + + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: types.ValidatorVestingAccountPrefix, Value: cdc.MustMarshalBinaryBare(acc)}, + cmn.KVPair{Key: types.BlocktimeKey, Value: cdc.MustMarshalBinaryLengthPrefixed(now)}, + cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"ValidatorVestingAccount", fmt.Sprintf("%v\n%v", acc, acc)}, + {"BlockTime", fmt.Sprintf("%s\n%s", now, now)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} From 3f4aba1c7f16274497a621d01da8a5ef61ecdc7f Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Sat, 4 Apr 2020 19:42:35 -0300 Subject: [PATCH 13/45] decoder tests --- x/auction/simulation/decoder_test.go | 58 +++++++++++++++++++++ x/bep3/simulation/decoder_test.go | 61 ++++++++++++++++++++++ x/cdp/simulation/decoder_test.go | 72 ++++++++++++++++++++++++++ x/kavadist/simulation/decoder_test.go | 54 +++++++++++++++++++ x/pricefeed/simulation/decoder.go | 4 +- x/pricefeed/simulation/decoder_test.go | 57 ++++++++++++++++++++ 6 files changed, 304 insertions(+), 2 deletions(-) create mode 100644 x/auction/simulation/decoder_test.go create mode 100644 x/bep3/simulation/decoder_test.go create mode 100644 x/cdp/simulation/decoder_test.go create mode 100644 x/kavadist/simulation/decoder_test.go create mode 100644 x/pricefeed/simulation/decoder_test.go diff --git a/x/auction/simulation/decoder_test.go b/x/auction/simulation/decoder_test.go new file mode 100644 index 00000000..1aa5f749 --- /dev/null +++ b/x/auction/simulation/decoder_test.go @@ -0,0 +1,58 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/auction/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + oneCoin := sdk.NewCoin("coin", sdk.OneInt()) + auction := types.NewSurplusAuction("me", oneCoin, "coin", time.Now().UTC()) + + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: types.AuctionKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&auction)}, + cmn.KVPair{Key: types.AuctionByTimeKeyPrefix, Value: sdk.Uint64ToBigEndian(2)}, + cmn.KVPair{Key: types.NextAuctionIDKey, Value: sdk.Uint64ToBigEndian(10)}, + cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Auction", fmt.Sprintf("%v\n%v", auction, auction)}, + {"AuctionByTime", "2\n2"}, + {"NextAuctionI", "10\n10"}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/bep3/simulation/decoder_test.go b/x/bep3/simulation/decoder_test.go new file mode 100644 index 00000000..e9fc0a75 --- /dev/null +++ b/x/bep3/simulation/decoder_test.go @@ -0,0 +1,61 @@ +package simulation + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/bep3/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + oneCoin := sdk.NewCoin("coin", sdk.OneInt()) + swap := types.NewAtomicSwap(sdk.Coins{oneCoin}, nil, 10, 100, nil, nil, "otherChainSender", "otherChainRec", 200, types.Completed, true, types.Outgoing) + supply := types.AssetSupply{Denom: "coin", IncomingSupply: oneCoin, OutgoingSupply: oneCoin, CurrentSupply: oneCoin, Limit: oneCoin} + bz := cmn.HexBytes([]byte{1, 2}) + + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: types.AtomicSwapKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(swap)}, + cmn.KVPair{Key: types.AssetSupplyKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(supply)}, + cmn.KVPair{Key: types.AtomicSwapByBlockPrefix, Value: bz}, + cmn.KVPair{Key: types.AtomicSwapByBlockPrefix, Value: bz}, + cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"AtomicSwap", fmt.Sprintf("%v\n%v", swap, swap)}, + {"AssetSupply", fmt.Sprintf("%v\n%v", supply, supply)}, + {"AtomicSwapByBlock", fmt.Sprintf("%s\n%s", bz, bz)}, + {"AtomicSwapLongtermStorage", fmt.Sprintf("%s\n%s", bz, bz)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/cdp/simulation/decoder_test.go b/x/cdp/simulation/decoder_test.go new file mode 100644 index 00000000..5790ea6d --- /dev/null +++ b/x/cdp/simulation/decoder_test.go @@ -0,0 +1,72 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/cdp/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + cdpIds := []uint64{1, 2, 3, 4, 5} + denom := "denom" + deposit := types.Deposit{CdpID: 1, Amount: sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt()))} + principal := sdk.OneInt() + prevDistTime := time.Now().UTC() + + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)}, + cmn.KVPair{Key: types.CdpIDKey, Value: sdk.Uint64ToBigEndian(2)}, + cmn.KVPair{Key: types.CollateralRatioIndexPrefix, Value: sdk.Uint64ToBigEndian(10)}, + cmn.KVPair{Key: []byte(types.DebtDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, + cmn.KVPair{Key: []byte(types.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, + cmn.KVPair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)}, + cmn.KVPair{Key: []byte(types.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)}, + cmn.KVPair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)}, + cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CdpIDs", fmt.Sprintf("%v\n%v", cdpIds, cdpIds)}, + {"CdpID", "2\n2"}, + {"CollateralRatioIndex", "10\n10"}, + {"DebtDenom", fmt.Sprintf("%s\n%s", denom, denom)}, + {"GovDenom", fmt.Sprintf("%s\n%s", denom, denom)}, + {"DepositKeyPrefix", fmt.Sprintf("%v\n%v", deposit, deposit)}, + {"Principal", fmt.Sprintf("%v\n%v", principal, principal)}, + {"PreviousDistributionTime", fmt.Sprintf("%s\n%s", prevDistTime, prevDistTime)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/kavadist/simulation/decoder_test.go b/x/kavadist/simulation/decoder_test.go new file mode 100644 index 00000000..235b22ff --- /dev/null +++ b/x/kavadist/simulation/decoder_test.go @@ -0,0 +1,54 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/kavadist/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + prevBlockTime := time.Now().UTC() + + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)}, + cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"PreviousBlockTime", fmt.Sprintf("%s\n%s", prevBlockTime, prevBlockTime)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/pricefeed/simulation/decoder.go b/x/pricefeed/simulation/decoder.go index f0c27dee..aaa01e9e 100644 --- a/x/pricefeed/simulation/decoder.go +++ b/x/pricefeed/simulation/decoder.go @@ -13,13 +13,13 @@ import ( // DecodeStore unmarshals the KVPair's Value to the corresponding pricefeed type func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { switch { - case bytes.Contains(kvA.Key[:1], []byte(types.CurrentPricePrefix)): + case bytes.Contains(kvA.Key, []byte(types.CurrentPricePrefix)): var priceA, priceB types.CurrentPrice cdc.MustUnmarshalBinaryBare(kvA.Value, &priceA) cdc.MustUnmarshalBinaryBare(kvB.Value, &priceB) return fmt.Sprintf("%s\n%s", priceA, priceB) - case bytes.Contains(kvA.Key[:1], []byte(types.RawPriceFeedPrefix)): + case bytes.Contains(kvA.Key, []byte(types.RawPriceFeedPrefix)): var postedPriceA, postedPriceB types.PostedPrice cdc.MustUnmarshalBinaryBare(kvA.Value, &postedPriceA) cdc.MustUnmarshalBinaryBare(kvB.Value, &postedPriceB) diff --git a/x/pricefeed/simulation/decoder_test.go b/x/pricefeed/simulation/decoder_test.go new file mode 100644 index 00000000..2cc846ec --- /dev/null +++ b/x/pricefeed/simulation/decoder_test.go @@ -0,0 +1,57 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/require" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/pricefeed/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + currentPrice := types.CurrentPrice{MarketID: "current", Price: sdk.OneDec()} + postedPrice := types.PostedPrice{MarketID: "posted", Price: sdk.OneDec(), Expiry: time.Now().UTC()} + + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: []byte(types.CurrentPricePrefix), Value: cdc.MustMarshalBinaryBare(currentPrice)}, + cmn.KVPair{Key: []byte(types.RawPriceFeedPrefix), Value: cdc.MustMarshalBinaryBare(postedPrice)}, + cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"CurrentPrice", fmt.Sprintf("%v\n%v", currentPrice, currentPrice)}, + {"PostedPrice", fmt.Sprintf("%s\n%s", postedPrice, postedPrice)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} From 4e3dfdf70736fb8fb395466ab0eb3371c6b3c963 Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Sat, 4 Apr 2020 20:26:15 -0300 Subject: [PATCH 14/45] x/validator-vesting: remove internal pkg --- x/validator-vesting/abci.go | 2 +- x/validator-vesting/abci_test.go | 4 ++-- x/validator-vesting/alias.go | 8 ++++---- x/validator-vesting/client/cli/query.go | 2 +- x/validator-vesting/client/rest/query.go | 2 +- x/validator-vesting/genesis.go | 2 +- x/validator-vesting/{internal => }/keeper/keeper.go | 2 +- x/validator-vesting/{internal => }/keeper/keeper_test.go | 2 +- x/validator-vesting/{internal => }/keeper/querier.go | 2 +- x/validator-vesting/{internal => }/keeper/test_common.go | 2 +- x/validator-vesting/module.go | 2 +- x/validator-vesting/simulation/decoder.go | 2 +- x/validator-vesting/simulation/genesis.go | 2 +- x/validator-vesting/test_common.go | 4 ++-- x/validator-vesting/{internal => }/types/codec.go | 0 .../{internal => }/types/expected_keepers.go | 0 x/validator-vesting/{internal => }/types/genesis.go | 0 x/validator-vesting/{internal => }/types/key.go | 0 x/validator-vesting/{internal => }/types/querier.go | 0 x/validator-vesting/{internal => }/types/test_common.go | 0 .../{internal => }/types/validator_vesting_account.go | 0 .../types/validator_vesting_account_test.go | 0 22 files changed, 19 insertions(+), 19 deletions(-) rename x/validator-vesting/{internal => }/keeper/keeper.go (99%) rename x/validator-vesting/{internal => }/keeper/keeper_test.go (99%) rename x/validator-vesting/{internal => }/keeper/querier.go (97%) rename x/validator-vesting/{internal => }/keeper/test_common.go (99%) rename x/validator-vesting/{internal => }/types/codec.go (100%) rename x/validator-vesting/{internal => }/types/expected_keepers.go (100%) rename x/validator-vesting/{internal => }/types/genesis.go (100%) rename x/validator-vesting/{internal => }/types/key.go (100%) rename x/validator-vesting/{internal => }/types/querier.go (100%) rename x/validator-vesting/{internal => }/types/test_common.go (100%) rename x/validator-vesting/{internal => }/types/validator_vesting_account.go (100%) rename x/validator-vesting/{internal => }/types/validator_vesting_account_test.go (100%) diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index 43a4f0b8..bd896d56 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -7,7 +7,7 @@ import ( tmtime "github.com/tendermint/tendermint/types/time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" + "github.com/kava-labs/kava/x/validator-vesting/keeper" abci "github.com/tendermint/tendermint/abci/types" ) diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index b2e65667..dcac8266 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -11,8 +11,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/keeper" + "github.com/kava-labs/kava/x/validator-vesting/types" ) func TestBeginBlockerZeroHeight(t *testing.T) { diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 9246d614..5ddc76e1 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -1,13 +1,13 @@ // nolint // autogenerated code using github.com/rigelrozanski/multitool // aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/keeper -// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/types +// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/keeper +// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/types package validatorvesting import ( - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/keeper" + "github.com/kava-labs/kava/x/validator-vesting/types" ) const ( diff --git a/x/validator-vesting/client/cli/query.go b/x/validator-vesting/client/cli/query.go index 5b13bff7..ce00168f 100644 --- a/x/validator-vesting/client/cli/query.go +++ b/x/validator-vesting/client/cli/query.go @@ -3,7 +3,7 @@ package cli import ( "fmt" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" diff --git a/x/validator-vesting/client/rest/query.go b/x/validator-vesting/client/rest/query.go index 3dbf8a3e..ebe1de2e 100644 --- a/x/validator-vesting/client/rest/query.go +++ b/x/validator-vesting/client/rest/query.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/types/rest" "github.com/gorilla/mux" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // define routes that get registered by the main application diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index d3e463c6..e987b00c 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -2,7 +2,7 @@ package validatorvesting import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // InitGenesis stores the account address of each ValidatorVestingAccount in the validator vesting keeper, for faster lookup. diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/keeper/keeper.go similarity index 99% rename from x/validator-vesting/internal/keeper/keeper.go rename to x/validator-vesting/keeper/keeper.go index bff59ee4..54662879 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/keeper/keeper.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" "github.com/tendermint/tendermint/libs/log" ) diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/keeper/keeper_test.go similarity index 99% rename from x/validator-vesting/internal/keeper/keeper_test.go rename to x/validator-vesting/keeper/keeper_test.go index cedf77d3..51d940ed 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/keeper/keeper_test.go @@ -11,7 +11,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/staking" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) func TestGetSetValidatorVestingAccounts(t *testing.T) { diff --git a/x/validator-vesting/internal/keeper/querier.go b/x/validator-vesting/keeper/querier.go similarity index 97% rename from x/validator-vesting/internal/keeper/querier.go rename to x/validator-vesting/keeper/querier.go index dafc5f5b..32c7a11e 100644 --- a/x/validator-vesting/internal/keeper/querier.go +++ b/x/validator-vesting/keeper/querier.go @@ -4,7 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/auth/vesting" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" abci "github.com/tendermint/tendermint/abci/types" ) diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/keeper/test_common.go similarity index 99% rename from x/validator-vesting/internal/keeper/test_common.go rename to x/validator-vesting/keeper/test_common.go index 8c630329..36ba430e 100644 --- a/x/validator-vesting/internal/keeper/test_common.go +++ b/x/validator-vesting/keeper/test_common.go @@ -25,7 +25,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) //nolint: deadcode unused diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go index 16a081e4..903e8b8a 100644 --- a/x/validator-vesting/module.go +++ b/x/validator-vesting/module.go @@ -15,8 +15,8 @@ import ( sim "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/kava-labs/kava/x/validator-vesting/client/cli" "github.com/kava-labs/kava/x/validator-vesting/client/rest" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" "github.com/kava-labs/kava/x/validator-vesting/simulation" + "github.com/kava-labs/kava/x/validator-vesting/types" ) var ( diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go index f6c052f5..c1428358 100644 --- a/x/validator-vesting/simulation/decoder.go +++ b/x/validator-vesting/simulation/decoder.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding auth type diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index cccdc358..c77bb9a3 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -11,7 +11,7 @@ import ( vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) // RandomizedGenState generates a random GenesisState for validator-vesting diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go index 909ee0bb..0bc8c141 100644 --- a/x/validator-vesting/test_common.go +++ b/x/validator-vesting/test_common.go @@ -18,8 +18,8 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" - "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/keeper" + "github.com/kava-labs/kava/x/validator-vesting/types" ) var ( diff --git a/x/validator-vesting/internal/types/codec.go b/x/validator-vesting/types/codec.go similarity index 100% rename from x/validator-vesting/internal/types/codec.go rename to x/validator-vesting/types/codec.go diff --git a/x/validator-vesting/internal/types/expected_keepers.go b/x/validator-vesting/types/expected_keepers.go similarity index 100% rename from x/validator-vesting/internal/types/expected_keepers.go rename to x/validator-vesting/types/expected_keepers.go diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/types/genesis.go similarity index 100% rename from x/validator-vesting/internal/types/genesis.go rename to x/validator-vesting/types/genesis.go diff --git a/x/validator-vesting/internal/types/key.go b/x/validator-vesting/types/key.go similarity index 100% rename from x/validator-vesting/internal/types/key.go rename to x/validator-vesting/types/key.go diff --git a/x/validator-vesting/internal/types/querier.go b/x/validator-vesting/types/querier.go similarity index 100% rename from x/validator-vesting/internal/types/querier.go rename to x/validator-vesting/types/querier.go diff --git a/x/validator-vesting/internal/types/test_common.go b/x/validator-vesting/types/test_common.go similarity index 100% rename from x/validator-vesting/internal/types/test_common.go rename to x/validator-vesting/types/test_common.go diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/types/validator_vesting_account.go similarity index 100% rename from x/validator-vesting/internal/types/validator_vesting_account.go rename to x/validator-vesting/types/validator_vesting_account.go diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/types/validator_vesting_account_test.go similarity index 100% rename from x/validator-vesting/internal/types/validator_vesting_account_test.go rename to x/validator-vesting/types/validator_vesting_account_test.go From a6031172a1edf80d999beb18656b998a662d020a Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 6 Apr 2020 09:56:59 -0400 Subject: [PATCH 15/45] feat: clean up pricefeed code --- x/pricefeed/alias.go | 58 +++++++++++++----------- x/pricefeed/keeper/keeper.go | 38 ++++++---------- x/pricefeed/simulation/decoder.go | 23 +++++++++- x/pricefeed/simulation/decoder_test.go | 63 ++++++++++++++++++++++++++ x/pricefeed/types/key.go | 26 +++++++---- x/pricefeed/types/market.go | 30 ++++++++++++ 6 files changed, 176 insertions(+), 62 deletions(-) create mode 100644 x/pricefeed/simulation/decoder_test.go diff --git a/x/pricefeed/alias.go b/x/pricefeed/alias.go index be764b2c..1e5aa2be 100644 --- a/x/pricefeed/alias.go +++ b/x/pricefeed/alias.go @@ -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/pricefeed/types/ -// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper/ +// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper +// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types package pricefeed import ( @@ -31,48 +31,54 @@ const ( RouterKey = types.RouterKey QuerierRoute = types.QuerierRoute DefaultParamspace = types.DefaultParamspace - RawPriceFeedPrefix = types.RawPriceFeedPrefix - CurrentPricePrefix = types.CurrentPricePrefix - MarketPrefix = types.MarketPrefix - OraclePrefix = types.OraclePrefix TypeMsgPostPrice = types.TypeMsgPostPrice - QueryPrice = types.QueryPrice - QueryRawPrices = types.QueryRawPrices + QueryGetParams = types.QueryGetParams QueryMarkets = types.QueryMarkets + QueryOracles = types.QueryOracles + QueryRawPrices = types.QueryRawPrices + QueryPrice = types.QueryPrice ) var ( // functions aliases - RegisterCodec = types.RegisterCodec - ErrEmptyInput = types.ErrEmptyInput - ErrExpired = types.ErrExpired - ErrNoValidPrice = types.ErrNoValidPrice - ErrInvalidMarket = types.ErrInvalidMarket - ErrInvalidOracle = types.ErrInvalidOracle - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - NewMsgPostPrice = types.NewMsgPostPrice - NewParams = types.NewParams - DefaultParams = types.DefaultParams - ParamKeyTable = types.ParamKeyTable - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + RegisterCodec = types.RegisterCodec + ErrEmptyInput = types.ErrEmptyInput + ErrExpired = types.ErrExpired + ErrNoValidPrice = types.ErrNoValidPrice + ErrInvalidMarket = types.ErrInvalidMarket + ErrInvalidOracle = types.ErrInvalidOracle + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + CurrentPriceKey = types.CurrentPriceKey + RawPriceKey = types.RawPriceKey + NewCurrentPrice = types.NewCurrentPrice + NewPostedPrice = types.NewPostedPrice + NewMsgPostPrice = types.NewMsgPostPrice + NewParams = types.NewParams + DefaultParams = types.DefaultParams + ParamKeyTable = types.ParamKeyTable + NewQueryWithMarketIDParams = types.NewQueryWithMarketIDParams // variable aliases - ModuleCdc = types.ModuleCdc - KeyMarkets = types.KeyMarkets - DefaultMarkets = types.DefaultMarkets + ModuleCdc = types.ModuleCdc + CurrentPricePrefix = types.CurrentPricePrefix + RawPriceFeedPrefix = types.RawPriceFeedPrefix + KeyMarkets = types.KeyMarkets + DefaultMarkets = types.DefaultMarkets ) type ( + Keeper = keeper.Keeper GenesisState = types.GenesisState Market = types.Market Markets = types.Markets CurrentPrice = types.CurrentPrice PostedPrice = types.PostedPrice + PostedPrices = types.PostedPrices SortDecs = types.SortDecs MsgPostPrice = types.MsgPostPrice Params = types.Params QueryWithMarketIDParams = types.QueryWithMarketIDParams - Keeper = keeper.Keeper ) diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index 0e4fc8e9..bd769662 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -58,13 +58,9 @@ func (k Keeper) SetPrice( } // set the price for that particular oracle if found { - prices[index] = types.PostedPrice{ - MarketID: marketID, OracleAddress: oracle, - Price: price, Expiry: expiry} + prices[index] = types.NewPostedPrice(marketID, oracle, price, expiry) } else { - prices = append(prices, types.PostedPrice{ - MarketID: marketID, OracleAddress: oracle, - Price: price, Expiry: expiry}) + prices = append(prices, types.NewPostedPrice(marketID, oracle, price, expiry)) index = len(prices) - 1 } @@ -79,7 +75,7 @@ func (k Keeper) SetPrice( ), ) store.Set( - []byte(types.RawPriceFeedPrefix+marketID), k.cdc.MustMarshalBinaryBare(prices), + types.RawPriceKey(marketID), k.cdc.MustMarshalBinaryBare(prices), ) return prices[index], nil } @@ -101,20 +97,17 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { } prices := k.GetRawPrices(ctx, marketID) - var notExpiredPrices []types.CurrentPrice + var notExpiredPrices types.CurrentPrices // filter out expired prices for _, v := range prices { if v.Expiry.After(ctx.BlockTime()) { - notExpiredPrices = append(notExpiredPrices, types.CurrentPrice{ - MarketID: v.MarketID, - Price: v.Price, - }) + notExpiredPrices = append(notExpiredPrices, types.NewCurrentPrice(v.MarketID, v.Price)) } } if len(notExpiredPrices) == 0 { store := ctx.KVStore(k.key) store.Set( - []byte(types.CurrentPricePrefix+marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), + types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), ) return types.ErrNoValidPrice(k.codespace) } @@ -135,20 +128,17 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { } store := ctx.KVStore(k.key) - currentPrice := types.CurrentPrice{ - MarketID: marketID, - Price: medianPrice, - } + currentPrice := types.NewCurrentPrice(marketID, medianPrice) store.Set( - []byte(types.CurrentPricePrefix+marketID), k.cdc.MustMarshalBinaryBare(currentPrice), + types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(currentPrice), ) return nil } // CalculateMedianPrice calculates the median prices for the input prices. -func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices []types.CurrentPrice) sdk.Dec { +func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec { l := len(prices) if l == 1 { @@ -169,7 +159,7 @@ func (k Keeper) CalculateMedianPrice(ctx sdk.Context, prices []types.CurrentPric } -func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice) sdk.Dec { +func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices types.CurrentPrices) sdk.Dec { sum := prices[0].Price.Add(prices[1].Price) mean := sum.Quo(sdk.NewDec(2)) return mean @@ -178,7 +168,7 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice) // GetCurrentPrice fetches the current median price of all oracles for a specific market func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, sdk.Error) { store := ctx.KVStore(k.key) - bz := store.Get([]byte(types.CurrentPricePrefix + marketID)) + bz := store.Get(types.CurrentPriceKey(marketID)) if bz == nil { return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) @@ -192,10 +182,10 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current } // GetRawPrices fetches the set of all prices posted by oracles for an asset -func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) []types.PostedPrice { +func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) types.PostedPrices { store := ctx.KVStore(k.key) - bz := store.Get([]byte(types.RawPriceFeedPrefix + marketID)) - var prices []types.PostedPrice + bz := store.Get(types.RawPriceKey(marketID)) + var prices types.PostedPrices k.cdc.MustUnmarshalBinaryBare(bz, &prices) return prices } diff --git a/x/pricefeed/simulation/decoder.go b/x/pricefeed/simulation/decoder.go index 115565b3..3d4a07c4 100644 --- a/x/pricefeed/simulation/decoder.go +++ b/x/pricefeed/simulation/decoder.go @@ -1,12 +1,31 @@ package simulation import ( + "bytes" + "fmt" + "github.com/cosmos/cosmos-sdk/codec" cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/kava-labs/kava/x/pricefeed/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding pricefeed type func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { - // TODO implement this - return "" + switch { + case bytes.Contains(kvA.Key[:1], []byte(types.CurrentPricePrefix)): + var priceA, priceB types.CurrentPrice + cdc.MustUnmarshalBinaryBare(kvA.Value, &priceA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &priceB) + return fmt.Sprintf("%s\n%s", priceA, priceB) + + case bytes.Contains(kvA.Key[:1], []byte(types.RawPriceFeedPrefix)): + var postedPriceA, postedPriceB types.PostedPrices + cdc.MustUnmarshalBinaryBare(kvA.Value, &postedPriceA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &postedPriceB) + return fmt.Sprintf("%s\n%s", postedPriceA, postedPriceB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } diff --git a/x/pricefeed/simulation/decoder_test.go b/x/pricefeed/simulation/decoder_test.go new file mode 100644 index 00000000..635ffa27 --- /dev/null +++ b/x/pricefeed/simulation/decoder_test.go @@ -0,0 +1,63 @@ +package simulation_test + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/pricefeed/simulation" + "github.com/kava-labs/kava/x/pricefeed/types" + "github.com/stretchr/testify/suite" + cmn "github.com/tendermint/tendermint/libs/common" + tmtime "github.com/tendermint/tendermint/types/time" +) + +type decoderTest struct { + name string + expectedLog string +} + +type DecoderTestSuite struct { + suite.Suite + + tests []decoderTest +} + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + types.RegisterCodec(cdc) + return +} + +func (suite *DecoderTestSuite) TestDecodeStore() { + cdc := makeTestCodec() + price := types.NewCurrentPrice("bnb:usd", sdk.MustNewDecFromStr("12.0")) + _, addrs := app.GeneratePrivKeyAddressPairs(1) + rawPrices := types.PostedPrices{ + types.NewPostedPrice("bnb:usd", addrs[0], sdk.MustNewDecFromStr("12.0"), tmtime.Now().Add(time.Hour*2)), + } + kvPairs := cmn.KVPairs{ + cmn.KVPair{Key: types.CurrentPriceKey("bnb:usd"), Value: cdc.MustMarshalBinaryBare(price)}, + cmn.KVPair{Key: types.RawPriceKey("bnb:usd"), Value: cdc.MustMarshalBinaryBare(rawPrices)}, + } + + decoderTests := []decoderTest{ + decoderTest{"current price", fmt.Sprintf("%s\n%s", price, price)}, + decoderTest{"raw prices", fmt.Sprintf("%s\n%s", rawPrices, rawPrices)}, + } + + for i, t := range decoderTests { + suite.Run(t.name, func() { + suite.Equal(t.expectedLog, simulation.DecodeStore(cdc, kvPairs[i], kvPairs[i])) + }) + } +} + +func TestDecoderTestSuite(t *testing.T) { + suite.Run(t, new(DecoderTestSuite)) +} diff --git a/x/pricefeed/types/key.go b/x/pricefeed/types/key.go index a1107954..74a8a81b 100644 --- a/x/pricefeed/types/key.go +++ b/x/pricefeed/types/key.go @@ -15,16 +15,22 @@ const ( // DefaultParamspace default namestore DefaultParamspace = ModuleName +) + +var ( + // CurrentPricePrefix prefix for the current price of an asset + CurrentPricePrefix = []byte{0x00} // RawPriceFeedPrefix prefix for the raw pricefeed of an asset - RawPriceFeedPrefix = StoreKey + ":raw:" - - // CurrentPricePrefix prefix for the current price of an asset - CurrentPricePrefix = StoreKey + ":currentprice:" - - // MarketPrefix Prefix for the assets in the pricefeed system - MarketPrefix = StoreKey + ":markets" - - // OraclePrefix store prefix for the oracle accounts - OraclePrefix = StoreKey + ":oracles" + RawPriceFeedPrefix = []byte{0x01} ) + +// CurrentPriceKey returns the prefix for the current price +func CurrentPriceKey(marketID string) []byte { + return append(CurrentPricePrefix, []byte(marketID)...) +} + +// RawPriceKey returns the prefix for the raw price +func RawPriceKey(marketID string) []byte { + return append(RawPriceFeedPrefix, []byte(marketID)...) +} diff --git a/x/pricefeed/types/market.go b/x/pricefeed/types/market.go index a357db73..16231be8 100644 --- a/x/pricefeed/types/market.go +++ b/x/pricefeed/types/market.go @@ -46,6 +46,14 @@ type CurrentPrice struct { Price sdk.Dec `json:"price" yaml:"price"` } +// NewCurrentPrice returns an instance of CurrentPrice +func NewCurrentPrice(marketID string, price sdk.Dec) CurrentPrice { + return CurrentPrice{MarketID: marketID, Price: price} +} + +// CurrentPrices type for an array of CurrentPrice +type CurrentPrices []CurrentPrice + // PostedPrice price for market posted by a specific oracle type PostedPrice struct { MarketID string `json:"market_id" yaml:"market_id"` @@ -54,6 +62,19 @@ type PostedPrice struct { Expiry time.Time `json:"expiry" yaml:"expiry"` } +// NewPostedPrice returns a new PostedPrice +func NewPostedPrice(marketID string, oracle sdk.AccAddress, price sdk.Dec, expiry time.Time) PostedPrice { + return PostedPrice{ + MarketID: marketID, + OracleAddress: oracle, + Price: price, + Expiry: expiry, + } +} + +// PostedPrices type for an array of PostedPrice +type PostedPrices []PostedPrice + // implement fmt.Stringer func (cp CurrentPrice) String() string { return strings.TrimSpace(fmt.Sprintf(`Market ID: %s @@ -68,6 +89,15 @@ Price: %s Expiry: %s`, pp.MarketID, pp.OracleAddress, pp.Price, pp.Expiry)) } +// String implements fmt.Stringer +func (ps PostedPrices) String() string { + out := "Posted Prices:\n" + for _, p := range ps { + out += fmt.Sprintf("%s\n", p.String()) + } + return strings.TrimSpace(out) +} + // SortDecs provides the interface needed to sort sdk.Dec slices type SortDecs []sdk.Dec From 66c73362c84b80781147536aae0bd62316cbf99e Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 6 Apr 2020 18:43:43 -0400 Subject: [PATCH 16/45] address comments from review --- x/cdp/simulation/decoder.go | 6 ++++++ x/cdp/simulation/decoder_test.go | 6 +++++- x/pricefeed/simulation/decoder.go | 2 +- x/pricefeed/simulation/decoder_test.go | 2 +- x/validator-vesting/simulation/decoder_test.go | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/x/cdp/simulation/decoder.go b/x/cdp/simulation/decoder.go index 202838a8..b2bc1fd0 100644 --- a/x/cdp/simulation/decoder.go +++ b/x/cdp/simulation/decoder.go @@ -22,6 +22,12 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &cdpIDsB) return fmt.Sprintf("%v\n%v", cdpIDsA, cdpIDsB) + case bytes.Equal(kvA.Key[:1], types.CdpKeyPrefix): + var cdpA, cdpB types.CDP + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &cdpA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &cdpB) + return fmt.Sprintf("%v\n%v", cdpA, cdpB) + case bytes.Equal(kvA.Key[:1], types.CdpIDKey), bytes.Equal(kvA.Key[:1], types.CollateralRatioIndexPrefix): idA := binary.BigEndian.Uint64(kvA.Value) diff --git a/x/cdp/simulation/decoder_test.go b/x/cdp/simulation/decoder_test.go index 5790ea6d..52927624 100644 --- a/x/cdp/simulation/decoder_test.go +++ b/x/cdp/simulation/decoder_test.go @@ -28,12 +28,15 @@ func TestDecodeDistributionStore(t *testing.T) { cdpIds := []uint64{1, 2, 3, 4, 5} denom := "denom" - deposit := types.Deposit{CdpID: 1, Amount: sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt()))} + oneCoins := sdk.NewCoins(sdk.NewCoin(denom, sdk.OneInt())) + deposit := types.Deposit{CdpID: 1, Amount: oneCoins} principal := sdk.OneInt() prevDistTime := time.Now().UTC() + cdp := types.CDP{ID: 1, FeesUpdated: prevDistTime, Collateral: oneCoins, Principal: oneCoins, AccumulatedFees: oneCoins} kvPairs := cmn.KVPairs{ cmn.KVPair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)}, + cmn.KVPair{Key: types.CdpKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdp)}, cmn.KVPair{Key: types.CdpIDKey, Value: sdk.Uint64ToBigEndian(2)}, cmn.KVPair{Key: types.CollateralRatioIndexPrefix, Value: sdk.Uint64ToBigEndian(10)}, cmn.KVPair{Key: []byte(types.DebtDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, @@ -49,6 +52,7 @@ func TestDecodeDistributionStore(t *testing.T) { expectedLog string }{ {"CdpIDs", fmt.Sprintf("%v\n%v", cdpIds, cdpIds)}, + {"CDP", fmt.Sprintf("%v\n%v", cdp, cdp)}, {"CdpID", "2\n2"}, {"CollateralRatioIndex", "10\n10"}, {"DebtDenom", fmt.Sprintf("%s\n%s", denom, denom)}, diff --git a/x/pricefeed/simulation/decoder.go b/x/pricefeed/simulation/decoder.go index aaa01e9e..9693b6e3 100644 --- a/x/pricefeed/simulation/decoder.go +++ b/x/pricefeed/simulation/decoder.go @@ -20,7 +20,7 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { return fmt.Sprintf("%s\n%s", priceA, priceB) case bytes.Contains(kvA.Key, []byte(types.RawPriceFeedPrefix)): - var postedPriceA, postedPriceB types.PostedPrice + var postedPriceA, postedPriceB []types.PostedPrice cdc.MustUnmarshalBinaryBare(kvA.Value, &postedPriceA) cdc.MustUnmarshalBinaryBare(kvB.Value, &postedPriceB) return fmt.Sprintf("%s\n%s", postedPriceA, postedPriceB) diff --git a/x/pricefeed/simulation/decoder_test.go b/x/pricefeed/simulation/decoder_test.go index 2cc846ec..bc886819 100644 --- a/x/pricefeed/simulation/decoder_test.go +++ b/x/pricefeed/simulation/decoder_test.go @@ -27,7 +27,7 @@ func TestDecodeDistributionStore(t *testing.T) { cdc := makeTestCodec() currentPrice := types.CurrentPrice{MarketID: "current", Price: sdk.OneDec()} - postedPrice := types.PostedPrice{MarketID: "posted", Price: sdk.OneDec(), Expiry: time.Now().UTC()} + postedPrice := []types.PostedPrice{{MarketID: "posted", Price: sdk.OneDec(), Expiry: time.Now().UTC()}} kvPairs := cmn.KVPairs{ cmn.KVPair{Key: []byte(types.CurrentPricePrefix), Value: cdc.MustMarshalBinaryBare(currentPrice)}, diff --git a/x/validator-vesting/simulation/decoder_test.go b/x/validator-vesting/simulation/decoder_test.go index fee10265..7cb532ca 100644 --- a/x/validator-vesting/simulation/decoder_test.go +++ b/x/validator-vesting/simulation/decoder_test.go @@ -13,7 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/types" ) func makeTestCodec() (cdc *codec.Codec) { From 71d862300d86d6b9d9dcb6401fcda4e7cf119bbd Mon Sep 17 00:00:00 2001 From: Federico Kunze Date: Mon, 6 Apr 2020 18:45:46 -0400 Subject: [PATCH 17/45] remove pointer from AtomicSwap --- x/bep3/simulation/decoder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/bep3/simulation/decoder.go b/x/bep3/simulation/decoder.go index b5ff6001..351b0b00 100644 --- a/x/bep3/simulation/decoder.go +++ b/x/bep3/simulation/decoder.go @@ -14,7 +14,7 @@ import ( func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { switch { case bytes.Equal(kvA.Key[:1], types.AtomicSwapKeyPrefix): - var swapA, swapB *types.AtomicSwap + var swapA, swapB types.AtomicSwap cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &swapA) cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &swapB) return fmt.Sprintf("%v\n%v", swapA, swapB) From 3da4657102abc0ff7b610bcdbef752f9afe9b852 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Sat, 11 Apr 2020 20:54:45 -0700 Subject: [PATCH 18/45] [R4R] BEP3 simulations (#423) * implement randomized genesis, params * implement operations: MsgCreateAtomicSwap * implement claim, refund future ops * remove dynamic block locks * refactor BondedAddresses * add consistent supported assets --- app/sim_test.go | 13 +++ x/bep3/simulation/genesis.go | 160 ++++++++++++++++++++++++++-- x/bep3/simulation/operations/msg.go | 153 ++++++++++++++++++++++++++ x/bep3/simulation/params.go | 38 ++++++- 4 files changed, 354 insertions(+), 10 deletions(-) create mode 100644 x/bep3/simulation/operations/msg.go diff --git a/app/sim_test.go b/app/sim_test.go index ede0aaf1..db6a797b 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -34,6 +34,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" "github.com/cosmos/cosmos-sdk/x/supply" + bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" ) // Simulation parameter constants @@ -56,6 +57,7 @@ const ( OpWeightMsgUndelegate = "op_weight_msg_undelegate" OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" OpWeightMsgUnjail = "op_weight_msg_unjail" + OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap" ) // TestMain runs setup and teardown code before all tests. @@ -264,6 +266,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper }(nil), slashingsimops.SimulateMsgUnjail(app.slashingKeeper), }, + { + func(_ *rand.Rand) int { + var v int + ap.GetOrGenerate(app.cdc, OpWeightMsgCreateAtomicSwap, &v, nil, + func(_ *rand.Rand) { + v = 100 + }) + return v + }(nil), + bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper), + }, } } diff --git a/x/bep3/simulation/genesis.go b/x/bep3/simulation/genesis.go index 5daaab0e..b5402736 100644 --- a/x/bep3/simulation/genesis.go +++ b/x/bep3/simulation/genesis.go @@ -2,21 +2,167 @@ package simulation import ( "fmt" + "math/rand" + "strings" + "time" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/kava-labs/kava/x/bep3/types" ) +// Simulation parameter constants +const ( + BnbDeputyAddress = "bnb_deputy_address" + MinBlockLock = "min_block_lock" + MaxBlockLock = "max_block_lock" + SupportedAssets = "supported_assets" +) + +var ( + MaxSupplyLimit = sdk.NewInt(10000000000000000) + Accs []simulation.Account + ConsistentDenoms = [3]string{"bnb", "xrp", "btc"} +) + +// GenBnbDeputyAddress randomized BnbDeputyAddress +func GenBnbDeputyAddress(r *rand.Rand) sdk.AccAddress { + return simulation.RandomAcc(r, Accs).Address +} + +// GenMinBlockLock randomized MinBlockLock +func GenMinBlockLock(r *rand.Rand) int64 { + min := int(types.AbsoluteMinimumBlockLock) + max := int(types.AbsoluteMaximumBlockLock) + return int64(r.Intn(max-min) + min) +} + +// GenMaxBlockLock randomized MaxBlockLock +func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 { + min := int(minBlockLock) + max := int(types.AbsoluteMaximumBlockLock) + return int64(r.Intn(max-min) + min) +} + +// GenSupportedAssets gets randomized SupportedAssets +func GenSupportedAssets(r *rand.Rand) types.AssetParams { + var assets types.AssetParams + for i := 0; i < (r.Intn(10) + 1); i++ { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3))) + asset := genSupportedAsset(r, denom) + assets = append(assets, asset) + } + // Add bnb, btc, or xrp as a supported asset for interactions with other modules + stableAsset := genSupportedAsset(r, ConsistentDenoms[r.Intn(3)]) + assets = append(assets, stableAsset) + + return assets +} + +func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam { + coinID, _ := simulation.RandPositiveInt(r, sdk.NewInt(100000)) + limit, _ := simulation.RandPositiveInt(r, MaxSupplyLimit) + return types.AssetParam{ + Denom: denom, + CoinID: int(coinID.Int64()), + Limit: limit, + Active: true, + } +} + // RandomizedGenState generates a random GenesisState func RandomizedGenState(simState *module.SimulationState) { + Accs = simState.Accounts - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - genesisState := types.DefaultGenesisState() + bep3Genesis := loadRandomBep3GenState(simState) + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, bep3Genesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(bep3Genesis) - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesisState)) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisState) + authGenesis, totalCoins := loadAuthGenState(simState, bep3Genesis) + simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) + + // Update supply to match amount of coins in auth + var supplyGenesis supply.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) + for _, deputyCoin := range totalCoins { + supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin) + } + simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) +} + +func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState { + bnbDeputyAddress := simulation.RandomAcc(simState.Rand, simState.Accounts).Address + + // min/max block lock are hardcoded to 50/100 for expected -NumBlocks=100 + minBlockLock := int64(types.AbsoluteMinimumBlockLock) + maxBlockLock := minBlockLock * 2 + + var supportedAssets types.AssetParams + simState.AppParams.GetOrGenerate( + simState.Cdc, SupportedAssets, &supportedAssets, simState.Rand, + func(r *rand.Rand) { supportedAssets = GenSupportedAssets(r) }, + ) + + bep3Genesis := types.GenesisState{ + Params: types.Params{ + BnbDeputyAddress: bnbDeputyAddress, + MinBlockLock: minBlockLock, + MaxBlockLock: maxBlockLock, + SupportedAssets: supportedAssets, + }, + } + + return bep3Genesis +} + +func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.GenesisState) ( + auth.GenesisState, []sdk.Coins) { + var authGenesis auth.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) + + deputy, found := getAccount(authGenesis.Accounts, bep3Genesis.Params.BnbDeputyAddress) + if !found { + panic("deputy address not found in available accounts") + } + + // Load total limit of each supported asset to deputy's account + var totalCoins []sdk.Coins + for _, asset := range bep3Genesis.Params.SupportedAssets { + assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.Limit)) + if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin)); err != nil { + panic(err) + } + totalCoins = append(totalCoins, assetCoin) + } + authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, deputy) + + return authGenesis, totalCoins +} + +// Return an account from a list of accounts that matches an address. +func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) { + for _, a := range accounts { + if a.GetAddress().Equals(addr) { + return a, true + } + } + return nil, false +} + +// In a list of accounts, replace the first account found with the same address. If not found, append the account. +func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount { + newAccounts := accounts + for i, a := range accounts { + if a.GetAddress().Equals(acc.GetAddress()) { + newAccounts[i] = acc + return newAccounts + } + } + return append(newAccounts, acc) } diff --git a/x/bep3/simulation/operations/msg.go b/x/bep3/simulation/operations/msg.go new file mode 100644 index 00000000..2f0fa6a0 --- /dev/null +++ b/x/bep3/simulation/operations/msg.go @@ -0,0 +1,153 @@ +package operations + +import ( + "fmt" + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/bep3" + "github.com/kava-labs/kava/x/bep3/keeper" + "github.com/kava-labs/kava/x/bep3/types" +) + +var ( + noOpMsg = simulation.NoOpMsg(bep3.ModuleName) +) + +// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values +func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation { + handler := bep3.NewHandler(k) + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + sender := k.GetBnbDeputyAddress(ctx) + recipient := simulation.RandomAcc(r, accs).Address + + recipientOtherChain := simulation.RandStringOfLength(r, 43) + senderOtherChain := simulation.RandStringOfLength(r, 43) + + // Generate cryptographically strong pseudo-random number + randomNumber, err := types.GenerateSecureRandomNumber() + if err != nil { + return noOpMsg, nil, err + } + // Must use current blocktime instead of 'now' since initial blocktime was randomly generated + timestamp := ctx.BlockTime().Unix() + randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp) + + // Randomly select an asset from supported assets + assets, found := k.GetAssets(ctx) + if !found { + return noOpMsg, nil, fmt.Errorf("no supported assets found") + } + asset := assets[r.Intn(len(assets))] + + // Check that the sender has coins of this type + availableAmount := ak.GetAccount(ctx, sender).GetCoins().AmountOf(asset.Denom) + if !availableAmount.IsPositive() { + return noOpMsg, nil, fmt.Errorf("available amount must be positive") + } + + // Get a random amount of the available coins + amount, err := simulation.RandPositiveInt(r, availableAmount) + if err != nil { + return noOpMsg, nil, err + } + + // If we don't adjust the conversion factor, we'll be out of funds soon + adjustedAmount := amount.Int64() / int64(math.Pow10(8)) + coin := sdk.NewInt64Coin(asset.Denom, adjustedAmount) + coins := sdk.NewCoins(coin) + expectedIncome := coin.String() + + // We're assuming that sims are run with -NumBlocks=100 + heightSpan := int64(55) + crossChain := true + + msg := types.NewMsgCreateAtomicSwap( + sender, recipient, recipientOtherChain, senderOtherChain, randomNumberHash, + timestamp, coins, expectedIncome, heightSpan, crossChain) + + if err := msg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected MsgCreateAtomicSwap to pass ValidateBasic: %s", err) + } + + // Submit msg + ok := submitMsg(ctx, handler, msg) + + // If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation + var futureOp simulation.FutureOperation + if ok { + swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain) + acc := simulation.RandomAcc(r, accs) + evenOdd := r.Intn(2) + 1 + if evenOdd%2 == 0 { + // Claim future operation + executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) + futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.Bytes(), executionBlock, handler) + } else { + // Refund future operation + executionBlock := ctx.BlockHeight() + msg.HeightSpan + futureOp = loadRefundFutureOp(acc.Address, swapID, executionBlock, handler) + } + } + + return simulation.NewOperationMsg(msg, ok, ""), []simulation.FutureOperation{futureOp}, nil + } +} + +func loadClaimFutureOp(sender sdk.AccAddress, swapID []byte, randomNumber []byte, height int64, handler sdk.Handler) simulation.FutureOperation { + claimOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + // Build the refund msg and validate basic + claimMsg := types.NewMsgClaimAtomicSwap(sender, swapID, randomNumber) + if err := claimMsg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected MsgClaimAtomicSwap to pass ValidateBasic: %s", err) + } + + // Test msg submission at target block height + ok := handler(ctx.WithBlockHeight(height), claimMsg).IsOK() + return simulation.NewOperationMsg(claimMsg, ok, ""), nil, nil + } + + return simulation.FutureOperation{ + BlockHeight: int(height), + Op: claimOp, + } +} + +func loadRefundFutureOp(sender sdk.AccAddress, swapID []byte, height int64, handler sdk.Handler) simulation.FutureOperation { + refundOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + // Build the refund msg and validate basic + refundMsg := types.NewMsgRefundAtomicSwap(sender, swapID) + if err := refundMsg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected MsgRefundAtomicSwap to pass ValidateBasic: %s", err) + } + + // Test msg submission at target block height + ok := handler(ctx.WithBlockHeight(height), refundMsg).IsOK() + return simulation.NewOperationMsg(refundMsg, ok, ""), nil, nil + } + + return simulation.FutureOperation{ + BlockHeight: int(height), + Op: refundOp, + } +} + +func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { + ctx, write := ctx.CacheContext() + ok = handler(ctx, msg).IsOK() + if ok { + write() + } + return ok +} diff --git a/x/bep3/simulation/params.go b/x/bep3/simulation/params.go index 8c1f7aff..41c3a346 100644 --- a/x/bep3/simulation/params.go +++ b/x/bep3/simulation/params.go @@ -1,14 +1,46 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/bep3/types" +) + +const ( + keyBnbDeputyAddress = "BnbDeputyAddress" + keyMinBlockLock = "MinBlockLock" + keyMaxBlockLock = "MaxBlockLock" + keySupportedAssets = "SupportedAssets" ) // ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation func ParamChanges(r *rand.Rand) []simulation.ParamChange { - // TODO implement this - return []simulation.ParamChange{} + // We generate MinBlockLock first because the result is required by GenMaxBlockLock() + minBlockLockVal := GenMinBlockLock(r) + + return []simulation.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%s\"", GenBnbDeputyAddress(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", minBlockLockVal) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenMaxBlockLock(r, minBlockLockVal)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keySupportedAssets, "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%v\"", GenSupportedAssets(r)) + }, + ), + } } From 8d199746cd5cd179315d41214a146319f879e26f Mon Sep 17 00:00:00 2001 From: jmahess <7819619+jmahess@users.noreply.github.com> Date: Sun, 12 Apr 2020 12:34:01 -0400 Subject: [PATCH 19/45] [R4R] Pricefeed simulations (#420) Co-authored-by: rhuairahrighairigh Co-authored-by: John Maheswaran Co-authored-by: Kevin Davis --- app/sim_test.go | 14 +++ x/pricefeed/keeper/keeper.go | 2 + x/pricefeed/simulation/genesis.go | 53 ++++++++- x/pricefeed/simulation/operations/msg.go | 136 +++++++++++++++++++++++ 4 files changed, 200 insertions(+), 5 deletions(-) create mode 100644 x/pricefeed/simulation/operations/msg.go diff --git a/app/sim_test.go b/app/sim_test.go index db6a797b..5a3b1b00 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -34,7 +34,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" "github.com/cosmos/cosmos-sdk/x/supply" + bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" + pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" ) // Simulation parameter constants @@ -57,6 +59,7 @@ const ( OpWeightMsgUndelegate = "op_weight_msg_undelegate" OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" OpWeightMsgUnjail = "op_weight_msg_unjail" + OpWeightMsgPricefeed = "op_weight_msg_pricefeed" OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap" ) @@ -277,6 +280,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper }(nil), bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper), }, + { + func(_ *rand.Rand) int { + var v int + ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil, + func(_ *rand.Rand) { + v = 10000 // TODO + }) + return v + }(nil), + pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper), + }, } } diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index 0e4fc8e9..159d1c87 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -178,6 +178,7 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices []types.CurrentPrice) // GetCurrentPrice fetches the current median price of all oracles for a specific market func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, sdk.Error) { store := ctx.KVStore(k.key) + bz := store.Get([]byte(types.CurrentPricePrefix + marketID)) if bz == nil { @@ -185,6 +186,7 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current } var price types.CurrentPrice k.cdc.MustUnmarshalBinaryBare(bz, &price) + if price.Price.Equal(sdk.ZeroDec()) { return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) } diff --git a/x/pricefeed/simulation/genesis.go b/x/pricefeed/simulation/genesis.go index 756fc988..9a84d291 100644 --- a/x/pricefeed/simulation/genesis.go +++ b/x/pricefeed/simulation/genesis.go @@ -2,21 +2,64 @@ package simulation import ( "fmt" + "time" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/kava-labs/kava/x/pricefeed/types" + pricefeed "github.com/kava-labs/kava/x/pricefeed/types" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // RandomizedGenState generates a random GenesisState for pricefeed func RandomizedGenState(simState *module.SimulationState) { - - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - pricefeedGenesis := types.DefaultGenesisState() + // get the params with xrp, btc and bnb to usd + // getPricefeedSimulationParams is defined to return params with xrp:usd, btc:usd, bnb:usd + params := getPricefeedSimulationParams() + markets := []types.Market{} + genPrices := []types.PostedPrice{} + // chose one account to be the oracle + oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))] + for _, market := range params.Markets { + updatedMarket := types.Market{market.MarketID, market.BaseAsset, market.QuoteAsset, []sdk.AccAddress{oracle.Address}, true} + markets = append(markets, updatedMarket) + genPrice := types.PostedPrice{market.MarketID, oracle.Address, getInitialPrice(market.MarketID), simState.GenTimestamp.Add(time.Hour * 24)} + genPrices = append(genPrices, genPrice) + } + params = types.NewParams(markets) + pricefeedGenesis := types.NewGenesisState(params, genPrices) fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, pricefeedGenesis)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis) } + +// getPricefeedSimulationParams returns the params with xrp:usd, btc:usd, bnb:usd +func getPricefeedSimulationParams() types.Params { + // SET UP THE PRICEFEED GENESIS STATE + pricefeedGenesis := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + pricefeed.Market{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + } + return pricefeedGenesis.Params +} + +// getInitialPrice gets the starting price for each of the base assets +func getInitialPrice(marketId string) (price sdk.Dec) { + switch marketId { + case "btc:usd": + return sdk.MustNewDecFromStr("7000") + case "bnb:usd": + return sdk.MustNewDecFromStr("14") + case "xrp:usd": + return sdk.MustNewDecFromStr("0.2") + } + panic(fmt.Sprintf("Invalid marketId in getInitialPrice: %s\n", marketId)) +} diff --git a/x/pricefeed/simulation/operations/msg.go b/x/pricefeed/simulation/operations/msg.go new file mode 100644 index 00000000..bcac646d --- /dev/null +++ b/x/pricefeed/simulation/operations/msg.go @@ -0,0 +1,136 @@ +package operations + +import ( + "fmt" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/pricefeed" + "github.com/kava-labs/kava/x/pricefeed/keeper" + "github.com/kava-labs/kava/x/pricefeed/types" +) + +var ( + noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName) +) + +// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price +func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation { + // get a pricefeed handler + handler := pricefeed.NewHandler(keeper) + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + // OVERALL LOGIC: + // (1) RANDOMLY PICK AN ASSET OUT OF BNB AN BTC [TODO QUESTION - USDX IS EXCLUDED AS IT IS A STABLE DENOM + // (2) GET THE CURRENT PRICE OF THAT ASSET IN USD + // (3) GENERATE A RANDOM NUMBER IN THE RANGE 0.8-1.2 (UNIFORM DISTRIBUTION) + // (4) MULTIPLY THE CURRENT PRICE BY THE RANDOM NUMBER + // (5) POST THE NEW PRICE TO THE KEEPER + + // pick a random asset out of BNB and BTC + randomMarket := pickRandomAsset(ctx, keeper, r) + + marketID := randomMarket.MarketID + + // Get the current price of the asset + currentPrice, err := keeper.GetCurrentPrice(ctx, marketID) // Note this is marketID AND **NOT** just the base asset + if err != nil { + return noOpMsg, nil, fmt.Errorf("Error getting current price") + } + + // get the address for the account + // this address needs to be an oracle and also exist. genesis should add all the accounts as oracles. + address := getRandomOracle(r, randomMarket) + + // generate a new random price based off the current price + price, err := pickNewRandomPrice(r, currentPrice.Price) + if err != nil { + return noOpMsg, nil, fmt.Errorf("Error picking random price") + } + + // get the expiry time based off the current time + expiry := getExpiryTime(ctx) + + // now create the msg to post price + msg := types.NewMsgPostPrice(address, marketID, price, expiry) + + // Perform basic validation of the msg - don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic + if err := msg.ValidateBasic(); err != nil { + return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + // now we submit the pricefeed update message + if ok := submitMsg(ctx, handler, msg); !ok { + return noOpMsg, nil, fmt.Errorf("could not submit pricefeed msg") + } + return simulation.NewOperationMsg(msg, true, "pricefeed update submitted"), nil, nil + } +} + +// getRandomOracle picks a random oracle from the list of oracles +func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress { + randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles)) + oracle := market.Oracles[randomIndex] + return oracle +} + +// pickRandomAsset picks a random asset out of the assets with equal probability +// it returns the Market which includes the base asset as one of its fields +func pickRandomAsset(ctx sdk.Context, keeper keeper.Keeper, r *rand.Rand) (market types.Market) { + // get the params + params := keeper.GetParams(ctx) + // now pick a random asset + randomIndex := simulation.RandIntBetween(r, 0, len(params.Markets)) + market = params.Markets[randomIndex] + return market +} + +// getExpiryTime gets a price expiry time by taking the current time and adding a delta to it +func getExpiryTime(ctx sdk.Context) (t time.Time) { + // need to use the blocktime from the context as the context generates random start time when running simulations + t = ctx.BlockTime().Add(time.Second * 1000000) + return t +} + +// pickNewRandomPrice picks a new random price given the current price +// It takes the current price then generates a random number to multiply it by to create variation while +// still being in the similar range. Random walk style. +func pickNewRandomPrice(r *rand.Rand, currentPrice sdk.Dec) (price sdk.Dec, err sdk.Error) { + // Pick random price + // this is in the range [0-0.4) because when added to 0.8 it gives a multiplier in the range 0.8-1.2 + got := sdk.MustNewDecFromStr("0.4") + + randomPriceMultiplier := simulation.RandomDecAmount(r, got) // get a random number + if err != nil { + fmt.Errorf("Error generating random price multiplier\n") + return sdk.ZeroDec(), err + } + // 0.8 offset corresponds to 80% of the the current price + offset := sdk.MustNewDecFromStr("0.8") + + // gives a result in range 0.8-1.2 inclusive, so the price can fluctuate from 80% to 120% of its current value + randomPriceMultiplier = randomPriceMultiplier.Add(offset) + + // multiply the current price by the price multiplier + price = randomPriceMultiplier.Mul(currentPrice) + // return the price + return price, nil +} + +// submitMsg submits a message to the current instance of the keeper and returns a boolean whether the operation completed successfully or not +func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { + ctx, write := ctx.CacheContext() + got := handler(ctx, msg) + + ok = got.IsOK() + if ok { + write() + } + return ok +} From 5bdffd5c1cb312385392a482b1b517c92dd8ff0a Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Mon, 13 Apr 2020 17:01:54 +0100 Subject: [PATCH 20/45] Add Auction Simulations (#419) * first pass * fix bid amount calculation * untested refactor of sim ops and genesis * refactor operations and fix auction bug * add param changes and genesis * address minor TODO * add first draft of invariants * improve param generation * complete invariants * fix genesis tests * log no-op better * small fixes * add missed comma Co-authored-by: John Maheswaran --- app/app.go | 13 +- app/sim_test.go | 15 ++- x/auction/alias.go | 1 + x/auction/genesis.go | 2 +- x/auction/genesis_test.go | 28 +++- x/auction/keeper/auctions.go | 12 +- x/auction/keeper/invariants.go | 143 ++++++++++++++++++++ x/auction/module.go | 6 +- x/auction/simulation/genesis.go | 157 +++++++++++++++++++++- x/auction/simulation/operations/msg.go | 177 +++++++++++++++++++++++++ x/auction/simulation/params.go | 36 ++++- x/auction/types/auctions.go | 54 ++++++-- 12 files changed, 609 insertions(+), 35 deletions(-) create mode 100644 x/auction/keeper/invariants.go create mode 100644 x/auction/simulation/operations/msg.go diff --git a/app/app.go b/app/app.go index 4650a26b..6b7bcc98 100644 --- a/app/app.go +++ b/app/app.go @@ -302,16 +302,15 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) - // Note: genutils must occur after staking so that pools are properly - // initialized with tokens from genesis accounts. - // - // Note: Changing the order of the auth module and modules that use module accounts - // results in subtle changes to the way accounts are loaded from genesis. app.mm.SetOrderInitGenesis( - auth.ModuleName, validatorvesting.ModuleName, distr.ModuleName, + auth.ModuleName, // loads all accounts - should run before any module with a module account + validatorvesting.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, - gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, + gov.ModuleName, mint.ModuleName, pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok? + supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis + crisis.ModuleName, // runs the invariants at genesis - should run after other modules + genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts. ) app.mm.RegisterInvariants(&app.crisisKeeper) diff --git a/app/sim_test.go b/app/sim_test.go index 5a3b1b00..3881d398 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -35,6 +35,7 @@ import ( stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" "github.com/cosmos/cosmos-sdk/x/supply" + auctionsimops "github.com/kava-labs/kava/x/auction/simulation/operations" bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" ) @@ -59,6 +60,7 @@ const ( OpWeightMsgUndelegate = "op_weight_msg_undelegate" OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" OpWeightMsgUnjail = "op_weight_msg_unjail" + OpWeightMsgPlaceBid = "op_weight_msg_place_bid" OpWeightMsgPricefeed = "op_weight_msg_pricefeed" OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap" ) @@ -269,6 +271,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper }(nil), slashingsimops.SimulateMsgUnjail(app.slashingKeeper), }, + { + func(_ *rand.Rand) int { + var v int + ap.GetOrGenerate(app.cdc, OpWeightMsgPlaceBid, &v, nil, + func(_ *rand.Rand) { + v = 100 + }) + return v + }(nil), + auctionsimops.SimulateMsgPlaceBid(app.accountKeeper, app.auctionKeeper), + }, { func(_ *rand.Rand) int { var v int @@ -285,7 +298,7 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper var v int ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil, func(_ *rand.Rand) { - v = 10000 // TODO + v = 100 }) return v }(nil), diff --git a/x/auction/alias.go b/x/auction/alias.go index 1aed46a7..3a9537ca 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -55,6 +55,7 @@ var ( // functions aliases NewKeeper = keeper.NewKeeper NewQuerier = keeper.NewQuerier + RegisterInvariants = keeper.RegisterInvariants NewSurplusAuction = types.NewSurplusAuction NewDebtAuction = types.NewDebtAuction NewCollateralAuction = types.NewCollateralAuction diff --git a/x/auction/genesis.go b/x/auction/genesis.go index eedcdf1e..ac6544b9 100644 --- a/x/auction/genesis.go +++ b/x/auction/genesis.go @@ -22,7 +22,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper for _, a := range gs.Auctions { keeper.SetAuction(ctx, a) // find the total coins that should be present in the module account - totalAuctionCoins.Add(a.GetModuleAccountCoins()) + totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) } // check if the module account exists diff --git a/x/auction/genesis_test.go b/x/auction/genesis_test.go index a7352c52..78ecf1f9 100644 --- a/x/auction/genesis_test.go +++ b/x/auction/genesis_test.go @@ -29,6 +29,12 @@ func TestInitGenesis(t *testing.T) { tApp := app.NewTestApp() keeper := tApp.GetAuctionKeeper() ctx := tApp.NewContext(true, abci.Header{}) + // setup module account + supplyKeeper := tApp.GetSupplyKeeper() + moduleAcc := supplyKeeper.GetModuleAccount(ctx, auction.ModuleName) + require.NoError(t, moduleAcc.SetCoins(testAuction.GetModuleAccountCoins())) + supplyKeeper.SetModuleAccount(ctx, moduleAcc) + // create genesis gs := auction.NewGenesisState( 10, @@ -38,7 +44,7 @@ func TestInitGenesis(t *testing.T) { // run init require.NotPanics(t, func() { - auction.InitGenesis(ctx, keeper, tApp.GetSupplyKeeper(), gs) + auction.InitGenesis(ctx, keeper, supplyKeeper, gs) }) // check state is as expected @@ -59,7 +65,7 @@ func TestInitGenesis(t *testing.T) { return false }) }) - t.Run("invalid", func(t *testing.T) { + t.Run("invalid (invalid nextAuctionID)", func(t *testing.T) { // setup keepers tApp := app.NewTestApp() ctx := tApp.NewContext(true, abci.Header{}) @@ -71,6 +77,24 @@ func TestInitGenesis(t *testing.T) { auction.GenesisAuctions{testAuction}, ) + // check init fails + require.Panics(t, func() { + auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs) + }) + }) + t.Run("invalid (missing mod account coins)", func(t *testing.T) { + // setup keepers + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{}) + + // create invalid genesis + gs := auction.NewGenesisState( + 10, + auction.DefaultParams(), + auction.GenesisAuctions{testAuction}, + ) + // invalid as there is no module account setup + // check init fails require.Panics(t, func() { auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs) diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index 025c3df1..da257d35 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -175,7 +175,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder ), ) if bid.Amount.LT(minNewBidAmt) { - return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt)) + return a, types.ErrBidTooSmall(k.codespace, bid, sdk.Coin{Denom: a.Bid.Denom, Amount: minNewBidAmt}) // not using NewCoin as it can panic } // New bidder pays back old bidder @@ -239,7 +239,7 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc ) minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment % if bid.Amount.LT(minNewBidAmt) { - return a, types.ErrBidTooSmall(k.codespace, bid, sdk.NewCoin(a.Bid.Denom, minNewBidAmt)) + return a, types.ErrBidTooSmall(k.codespace, bid, sdk.Coin{Denom: a.Bid.Denom, Amount: minNewBidAmt}) // not using NewCoin as it can panic } if a.MaxBid.IsLT(bid) { return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid) @@ -314,10 +314,10 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc ), ) if lot.Amount.GT(maxNewLotAmt) { - return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt)) + return a, types.ErrLotTooLarge(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: maxNewLotAmt}) // not using NewCoin as it can panic } if lot.IsNegative() { - return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt())) + return a, types.ErrLotTooSmall(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: sdk.ZeroInt()}) } // New bidder pays back old bidder @@ -380,10 +380,10 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac ), ) if lot.Amount.GT(maxNewLotAmt) { - return a, types.ErrLotTooLarge(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, maxNewLotAmt)) + return a, types.ErrLotTooLarge(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: maxNewLotAmt}) // not using NewCoin as it can panic } if lot.IsNegative() { - return a, types.ErrLotTooSmall(k.codespace, lot, sdk.NewCoin(a.Lot.Denom, sdk.ZeroInt())) + return a, types.ErrLotTooSmall(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: sdk.ZeroInt()}) } // New bidder pays back old bidder diff --git a/x/auction/keeper/invariants.go b/x/auction/keeper/invariants.go new file mode 100644 index 00000000..769b9904 --- /dev/null +++ b/x/auction/keeper/invariants.go @@ -0,0 +1,143 @@ +package keeper + +import ( + "fmt" + "time" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/auction/types" +) + +// RegisterInvariants registers all staking invariants +func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { + + ir.RegisterRoute(types.ModuleName, "module-account", + ModuleAccountInvariants(k)) + ir.RegisterRoute(types.ModuleName, "valid-auctions", + ValidAuctionInvariant(k)) + ir.RegisterRoute(types.ModuleName, "valid-index", + ValidIndexInvariant(k)) +} + +// ModuleAccountInvariant checks that the module account's coins matches those stored in auctions +func ModuleAccountInvariants(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + + totalAuctionCoins := sdk.NewCoins() + k.IterateAuctions(ctx, func(auction types.Auction) bool { + a, ok := auction.(types.GenesisAuction) + if !ok { + panic("stored auction type does not fulfill GenesisAuction interface") + } + totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) + return false + }) + + moduleAccCoins := k.supplyKeeper.GetModuleAccount(ctx, types.ModuleName).GetCoins() + broken := !moduleAccCoins.IsEqual(totalAuctionCoins) + + invariantMessage := sdk.FormatInvariant( + types.ModuleName, + "module account", + fmt.Sprintf( + "\texpected ModuleAccount coins: %s\n"+ + "\tactual ModuleAccount coins: %s\n", + totalAuctionCoins, moduleAccCoins), + ) + return invariantMessage, broken + } +} + +// ValidAuctionInvariant verifies that all auctions in the store are independently valid +func ValidAuctionInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + var validationErr error + var invalidAuction types.Auction + k.IterateAuctions(ctx, func(auction types.Auction) bool { + a, ok := auction.(types.GenesisAuction) + if !ok { + panic("stored auction type does not fulfill GenesisAuction interface") + } + + currentTime := ctx.BlockTime() + if !currentTime.Equal(time.Time{}) { // this avoids a simulator bug where app.InitGenesis is called with blockTime=0 instead of the correct time + if a.GetEndTime().Before(currentTime) { + validationErr = fmt.Errorf("endTime after current block time (%s)", currentTime) + invalidAuction = a + return true + } + } + + if err := a.Validate(); err != nil { + validationErr = err + invalidAuction = a + return true + } + return false + }) + + broken := validationErr != nil + invariantMessage := sdk.FormatInvariant( + types.ModuleName, + "valid auctions", + fmt.Sprintf( + "\tfound invalid auction, reason: %s\n"+ + "\tauction:\n\t%s\n", + validationErr, invalidAuction), + ) + return invariantMessage, broken + } +} + +// ValidIndexInvariant checks that all auctions in the store are also in the index and vice versa. +func ValidIndexInvariant(k Keeper) sdk.Invariant { + return func(ctx sdk.Context) (string, bool) { + /* Method: + - check all the auction IDs in the index have a corresponding auction in the store + - index is now valid but there could be extra auction in the store + - check for these extra auctions by checking num items in the store equals that of index (store keys are always unique) + - doesn't check the IDs in the auction structs match the IDs in the keys + */ + + // Check all auction IDs in the index are in the auction store + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) + + indexIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix) + defer indexIterator.Close() + + var indexLength int + for ; indexIterator.Valid(); indexIterator.Next() { + indexLength++ + + idBytes := indexIterator.Value() + auctionBytes := store.Get(idBytes) + if auctionBytes == nil { + invariantMessage := sdk.FormatInvariant( + types.ModuleName, + "valid index", + fmt.Sprintf("\tauction with ID '%d' found in index but not in store", types.Uint64FromBytes(idBytes))) + return invariantMessage, true + } + } + + // Check length of auction store matches the length of the index + storeIterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix) + defer storeIterator.Close() + var storeLength int + for ; storeIterator.Valid(); storeIterator.Next() { + storeLength++ + } + + if storeLength != indexLength { + invariantMessage := sdk.FormatInvariant( + types.ModuleName, + "valid index", + fmt.Sprintf("\tmismatched number of items in auction store (%d) and index (%d)", storeLength, indexLength)) + return invariantMessage, true + } + + return "", false + } +} diff --git a/x/auction/module.go b/x/auction/module.go index f34b19f5..5e25bff6 100644 --- a/x/auction/module.go +++ b/x/auction/module.go @@ -110,8 +110,10 @@ func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule { } } -// RegisterInvariants performs a no-op. -func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} +// RegisterInvariants registers the module invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) { + RegisterInvariants(ir, am.keeper) +} // Route module message route name func (AppModule) Route() string { diff --git a/x/auction/simulation/genesis.go b/x/auction/simulation/genesis.go index 20bac731..51b14d04 100644 --- a/x/auction/simulation/genesis.go +++ b/x/auction/simulation/genesis.go @@ -2,21 +2,170 @@ package simulation import ( "fmt" + "math/rand" + "time" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/supply" "github.com/kava-labs/kava/x/auction/types" + cdptypes "github.com/kava-labs/kava/x/cdp/types" ) +const ( + // Block time params are un-exported constants in cosmos-sdk/x/simulation. + // Copy them here in lieu of importing them. + minTimePerBlock time.Duration = (10000 / 2) * time.Second + maxTimePerBlock time.Duration = 10000 * time.Second + + // Calculate the average block time + AverageBlockTime time.Duration = (maxTimePerBlock - minTimePerBlock) / 2 + // MaxBidDuration is a crude way of ensuring that BidDuration ≤ MaxAuctionDuration for all generated params + MaxBidDuration time.Duration = AverageBlockTime * 50 +) + +func GenBidDuration(r *rand.Rand) time.Duration { + d, err := RandomPositiveDuration(r, 0, MaxBidDuration) + if err != nil { + panic(err) + } + return d +} +func GenMaxAuctionDuration(r *rand.Rand) time.Duration { + d, err := RandomPositiveDuration(r, MaxBidDuration, AverageBlockTime*200) + if err != nil { + panic(err) + } + return d +} + +func GenIncrementCollateral(r *rand.Rand) sdk.Dec { + return simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("1")) +} + +var GenIncrementDebt = GenIncrementCollateral +var GenIncrementSurplus = GenIncrementCollateral + // RandomizedGenState generates a random GenesisState for auction func RandomizedGenState(simState *module.SimulationState) { - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - auctionGenesis := types.DefaultGenesisState() + p := types.NewParams( + GenMaxAuctionDuration(simState.Rand), + GenBidDuration(simState.Rand), + GenIncrementSurplus(simState.Rand), + GenIncrementDebt(simState.Rand), + GenIncrementCollateral(simState.Rand), + ) + if err := p.Validate(); err != nil { + panic(err) + } + auctionGenesis := types.NewGenesisState( + types.DefaultNextAuctionID, + p, + nil, + ) + // Add auctions + auctions := types.GenesisAuctions{ + types.NewDebtAuction( + cdptypes.LiquidatorMacc, // using cdp account rather than generic test one to avoid having to set permissions on the supply keeper + sdk.NewInt64Coin("usdx", 100), + sdk.NewInt64Coin("ukava", 1000000000000), + simState.GenTimestamp.Add(time.Hour*5), + sdk.NewInt64Coin("debt", 100), // same as usdx + ), + } + var startingID = auctionGenesis.NextAuctionID + var ok bool + var totalAuctionCoins sdk.Coins + for i, a := range auctions { + auctions[i], ok = a.WithID(uint64(i) + startingID).(types.GenesisAuction) + if !ok { + panic("can't convert Auction to GenesisAuction") + } + totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) + } + auctionGenesis.NextAuctionID = startingID + uint64(len(auctions)) + auctionGenesis.Auctions = append(auctionGenesis.Auctions, auctions...) + + // Also need to update the auction module account (to reflect the coins held in the auctions) + var authGenesis auth.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) + + auctionModAcc, found := getAccount(authGenesis.Accounts, supply.NewModuleAddress(types.ModuleName)) + if !found { + auctionModAcc = supply.NewEmptyModuleAccount(types.ModuleName) + } + if err := auctionModAcc.SetCoins(totalAuctionCoins); err != nil { + panic(err) + } + authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, auctionModAcc) + + // TODO adding bidder coins as well - this should be moved elsewhere + bidder, found := getAccount(authGenesis.Accounts, simState.Accounts[0].Address) // 0 is the bidder // FIXME + if !found { + panic("bidder not found") + } + bidderCoins := sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000000)) + if err := bidder.SetCoins(bidder.GetCoins().Add(bidderCoins)); err != nil { + panic(err) + } + authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, bidder) + + simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) + + // Update the supply genesis state to reflect the new coins + // TODO find some way for this to happen automatically / move it elsewhere + var supplyGenesis supply.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) + supplyGenesis.Supply = supplyGenesis.Supply.Add(totalAuctionCoins).Add(bidderCoins) + simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) + + // TODO liquidator mod account doesn't need to be initialized for this example + // - it just mints kava, doesn't need a starting balance + // - and supply.GetModuleAccount creates one if it doesn't exist + + // Note: this line prints out the auction genesis state, not just the auction parameters. Some sdk modules print out just the parameters. fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, auctionGenesis)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(auctionGenesis) } + +// Return an account from a list of accounts that matches an address. +func getAccount(accounts []authexported.GenesisAccount, addr sdk.AccAddress) (authexported.GenesisAccount, bool) { + for _, a := range accounts { + if a.GetAddress().Equals(addr) { + return a, true + } + } + return nil, false +} + +// In a list of accounts, replace the first account found with the same address. If not found, append the account. +func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount { + newAccounts := accounts + for i, a := range accounts { + if a.GetAddress().Equals(acc.GetAddress()) { + newAccounts[i] = acc + return newAccounts + } + } + return append(newAccounts, acc) +} + +func RandomPositiveDuration(r *rand.Rand, inclusiveMin, exclusiveMax time.Duration) (time.Duration, error) { + min := int64(inclusiveMin) + max := int64(exclusiveMax) + if min < 0 || max < 0 { + return 0, fmt.Errorf("min and max must be positive") + } + if min >= max { + return 0, fmt.Errorf("max must be < min") + } + randPositiveInt64 := r.Int63n(max-min) + min + return time.Duration(randPositiveInt64), nil +} diff --git a/x/auction/simulation/operations/msg.go b/x/auction/simulation/operations/msg.go new file mode 100644 index 00000000..57dae24c --- /dev/null +++ b/x/auction/simulation/operations/msg.go @@ -0,0 +1,177 @@ +package operations + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/auction" +) + +var ( + noOpMsg = simulation.NoOpMsg(auction.ModuleName) + ErrorNotEnoughCoins = errors.New("account doesn't have enough coins") +) + +// Return a function that runs a random state change on the module keeper. +// There's two error paths +// - return a OpMessage, but nil error - this will log a message but keep running the simulation +// - return an error - this will stop the simulation +func SimulateMsgPlaceBid(authKeeper auth.AccountKeeper, keeper auction.Keeper) simulation.Operation { + handler := auction.NewHandler(keeper) + + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + simulation.OperationMsg, []simulation.FutureOperation, error) { + + // get open auctions + openAuctions := auction.Auctions{} + keeper.IterateAuctions(ctx, func(a auction.Auction) bool { + openAuctions = append(openAuctions, a) + return false + }) + + // shuffle auctions slice so that bids are evenly distributed across auctions + rand.Shuffle(len(openAuctions), func(i, j int) { + openAuctions[i], openAuctions[j] = openAuctions[j], openAuctions[i] + }) + // TODO do the same for accounts? + var accounts []authexported.Account + for _, acc := range accs { + accounts = append(accounts, authKeeper.GetAccount(ctx, acc.Address)) + } + + // search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction) + blockTime := ctx.BlockHeader().Time + bidder, openAuction, found := findValidAccountAuctionPair(accounts, openAuctions, func(acc authexported.Account, auc auction.Auction) bool { + _, err := generateBidAmount(r, auc, acc, blockTime) + if err == ErrorNotEnoughCoins { + return false // keep searching + } else if err != nil { + panic(err) // raise errors + } + return true // found valid pair + }) + if !found { + return simulation.NewOperationMsgBasic(auction.ModuleName, "no-operation (no valid auction and bidder)", "", false, nil), nil, nil + } + + // pick a bid amount for the chosen auction and bidder + amount, _ := generateBidAmount(r, openAuction, bidder, blockTime) + + // create a msg + msg := auction.NewMsgPlaceBid(openAuction.GetID(), bidder.GetAddress(), amount) + if err := msg.ValidateBasic(); err != nil { // don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic + return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + } + + // submit the msg + result := submitMsg(ctx, handler, msg) + // Return an operationMsg indicating whether the msg was submitted successfully + // Using result.Log as the comment field as it contains any error message emitted by the keeper + return simulation.NewOperationMsg(msg, result.IsOK(), result.Log), nil, nil + } +} + +func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) sdk.Result { + ctx, write := ctx.CacheContext() + result := handler(ctx, msg) + if result.IsOK() { + write() + } + return result +} + +func generateBidAmount(r *rand.Rand, auc auction.Auction, bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { + bidderBalance := bidder.SpendableCoins(blockTime) + + switch a := auc.(type) { + + case auction.DebtAuction: + if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin + return sdk.Coin{}, ErrorNotEnoughCoins + } + amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount // TODO min bid increments + if err != nil { + panic(err) + } + return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin + + case auction.SurplusAuction: + if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // gov coin // TODO account for bid increments + return sdk.Coin{}, ErrorNotEnoughCoins + } + amt, err := RandIntInclusive(r, a.Bid.Amount, bidderBalance.AmountOf(a.Bid.Denom)) + if err != nil { + panic(err) + } + return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin + + case auction.CollateralAuction: + if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin // TODO account for bid increments (in forward phase) + return sdk.Coin{}, ErrorNotEnoughCoins + } + if a.IsReversePhase() { + amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount + if err != nil { + panic(err) + } + return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin + } else { + amt, err := RandIntInclusive(r, a.Bid.Amount, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) + if err != nil { + panic(err) + } + // pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase + if r.Intn(10) == 0 { // 10% + amt = a.MaxBid.Amount + } + return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin + } + + default: + return sdk.Coin{}, fmt.Errorf("unknown auction type") + } +} + +// findValidAccountAuctionPair finds an auction and account for which the callback func returns true +func findValidAccountAuctionPair(accounts []authexported.Account, auctions auction.Auctions, cb func(authexported.Account, auction.Auction) bool) (authexported.Account, auction.Auction, bool) { + for _, auc := range auctions { + for _, acc := range accounts { + if isValid := cb(acc, auc); isValid { + return acc, auc, true + } + + } + } + return nil, nil, false +} + +// RandInt randomly generates an sdk.Int in the range [inclusiveMin, inclusiveMax]. It works for negative and positive integers. +func RandIntInclusive(r *rand.Rand, inclusiveMin, inclusiveMax sdk.Int) (sdk.Int, error) { + if inclusiveMin.GT(inclusiveMax) { + return sdk.Int{}, fmt.Errorf("min larger than max") + } + return RandInt(r, inclusiveMin, inclusiveMax.Add(sdk.OneInt())) +} + +// RandInt randomly generates an sdk.Int in the range [inclusiveMin, exclusiveMax). It works for negative and positive integers. +func RandInt(r *rand.Rand, inclusiveMin, exclusiveMax sdk.Int) (sdk.Int, error) { + // validate input + if inclusiveMin.GTE(exclusiveMax) { + return sdk.Int{}, fmt.Errorf("min larger or equal to max") + } + // shift the range to start at 0 + shiftedRange := exclusiveMax.Sub(inclusiveMin) // should always be positive given the check above + // randomly pick from the shifted range + shiftedRandInt := sdk.NewIntFromBigInt(new(big.Int).Rand(r, shiftedRange.BigInt())) + // shift back to the original range + return shiftedRandInt.Add(inclusiveMin), nil +} diff --git a/x/auction/simulation/params.go b/x/auction/simulation/params.go index 8c1f7aff..7ae658cd 100644 --- a/x/auction/simulation/params.go +++ b/x/auction/simulation/params.go @@ -1,14 +1,46 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/auction/types" ) // ParamChanges defines the parameters that can be modified by param change proposals // on the simulation func ParamChanges(r *rand.Rand) []simulation.ParamChange { - // TODO implement this - return []simulation.ParamChange{} + // Note: params are encoded to JSON before being stored in the param store. These param changes + // update the raw values in the store so values need to be JSON. This is why values that are represented + // as strings in JSON (such as time.Duration) have the escaped quotes. + // TODO should we encode the values properly with ModuleCdc.MustMarshalJSON()? + return []simulation.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, string(types.KeyBidDuration), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenBidDuration(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyMaxAuctionDuration), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenMaxAuctionDuration(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementCollateral), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenIncrementCollateral(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementDebt), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenIncrementDebt(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementSurplus), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%d\"", GenIncrementSurplus(r)) + }, + ), + } } diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index 6a84f4bb..e0205e65 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -69,6 +69,12 @@ func (a BaseAuction) Validate() error { if a.EndTime.After(a.MaxEndTime) { return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime) } + if !a.Lot.IsValid() { + return fmt.Errorf("invalid lot: %s", a.Lot) + } + if !a.Bid.IsValid() { + return fmt.Errorf("invalid bid: %s", a.Bid) + } return nil } @@ -148,6 +154,13 @@ func (a DebtAuction) GetModuleAccountCoins() sdk.Coins { // GetPhase returns the direction of a debt auction, which never changes. func (a DebtAuction) GetPhase() string { return "reverse" } +func (a DebtAuction) Validate() error { + if !a.CorrespondingDebt.IsValid() { + return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt) + } + return a.BaseAuction.Validate() +} + // NewDebtAuction returns a new debt auction. func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction { // Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator) @@ -208,6 +221,19 @@ func (a CollateralAuction) GetPhase() string { return "forward" } +func (a CollateralAuction) Validate() error { + if !a.CorrespondingDebt.IsValid() { + return fmt.Errorf("invalid corresponding debt: %s", a.CorrespondingDebt) + } + if !a.MaxBid.IsValid() { + return fmt.Errorf("invalid max bid: %s", a.MaxBid) + } + if err := a.LotReturns.Validate(); err != nil { + return fmt.Errorf("invalid lot returns: %w", err) + } + return a.BaseAuction.Validate() +} + func (a CollateralAuction) String() string { return fmt.Sprintf(`Auction %d: Initiator: %s @@ -251,16 +277,24 @@ type WeightedAddresses struct { // NewWeightedAddresses returns a new list addresses with weights. func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) { - if len(addrs) != len(weights) { - return WeightedAddresses{}, sdk.ErrInternal("number of addresses doesn't match number of weights") - } - for _, w := range weights { - if w.IsNegative() { - return WeightedAddresses{}, sdk.ErrInternal("weights contain a negative amount") - } - } - return WeightedAddresses{ + wa := WeightedAddresses{ Addresses: addrs, Weights: weights, - }, nil + } + if err := wa.Validate(); err != nil { + return WeightedAddresses{}, sdk.ErrInternal(err.Error()) + } + return wa, nil +} + +func (wa WeightedAddresses) Validate() error { + if len(wa.Addresses) != len(wa.Weights) { + return fmt.Errorf("number of addresses doesn't match number of weights") + } + for _, w := range wa.Weights { + if w.IsNegative() { + return fmt.Errorf("weights contain a negative amount") + } + } + return nil } From 004837d7fc297a06be57ade054ab35e1b736092c Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 13 Apr 2020 13:06:59 -0400 Subject: [PATCH 21/45] Remove non-determinism from bep3 sims (#427) * feat: remove non-determism from bep3 sims --- x/bep3/simulation/genesis.go | 5 ++--- x/bep3/simulation/operations/msg.go | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/x/bep3/simulation/genesis.go b/x/bep3/simulation/genesis.go index b5402736..e4af0003 100644 --- a/x/bep3/simulation/genesis.go +++ b/x/bep3/simulation/genesis.go @@ -4,7 +4,6 @@ import ( "fmt" "math/rand" "strings" - "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -52,8 +51,8 @@ func GenMaxBlockLock(r *rand.Rand, minBlockLock int64) int64 { // GenSupportedAssets gets randomized SupportedAssets func GenSupportedAssets(r *rand.Rand) types.AssetParams { var assets types.AssetParams - for i := 0; i < (r.Intn(10) + 1); i++ { - r := rand.New(rand.NewSource(time.Now().UnixNano())) + numAssets := (r.Intn(10) + 1) + for i := 0; i < numAssets; i++ { denom := strings.ToLower(simulation.RandStringOfLength(r, (r.Intn(3) + 3))) asset := genSupportedAsset(r, denom) assets = append(assets, asset) diff --git a/x/bep3/simulation/operations/msg.go b/x/bep3/simulation/operations/msg.go index 2f0fa6a0..c04eb218 100644 --- a/x/bep3/simulation/operations/msg.go +++ b/x/bep3/simulation/operations/msg.go @@ -33,13 +33,13 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat senderOtherChain := simulation.RandStringOfLength(r, 43) // Generate cryptographically strong pseudo-random number - randomNumber, err := types.GenerateSecureRandomNumber() + randomNumber, err := simulation.RandPositiveInt(r, sdk.NewInt(math.MaxInt64)) if err != nil { return noOpMsg, nil, err } // Must use current blocktime instead of 'now' since initial blocktime was randomly generated timestamp := ctx.BlockTime().Unix() - randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp) + randomNumberHash := types.CalculateRandomHash(randomNumber.BigInt().Bytes(), timestamp) // Randomly select an asset from supported assets assets, found := k.GetAssets(ctx) @@ -90,7 +90,7 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat if evenOdd%2 == 0 { // Claim future operation executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) - futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.Bytes(), executionBlock, handler) + futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.BigInt().Bytes(), executionBlock, handler) } else { // Refund future operation executionBlock := ctx.BlockHeight() + msg.HeightSpan From 8f9aece875a9eed3771de92f156b0cd6ddf97409 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 13 Apr 2020 14:08:14 -0400 Subject: [PATCH 22/45] address review comments --- x/pricefeed/genesis.go | 10 ++++++++-- x/pricefeed/keeper/keeper.go | 28 +++++++++++++++++++++------- x/pricefeed/keeper/keeper_test.go | 9 ++++++--- x/pricefeed/keeper/querier.go | 14 ++++++++------ 4 files changed, 43 insertions(+), 18 deletions(-) diff --git a/x/pricefeed/genesis.go b/x/pricefeed/genesis.go index 9919f6a7..476d4c56 100644 --- a/x/pricefeed/genesis.go +++ b/x/pricefeed/genesis.go @@ -25,7 +25,10 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, gs GenesisState) { // Set the current price (if any) based on what's now in the store for _, market := range params.Markets { if market.Active { - rps := keeper.GetRawPrices(ctx, market.MarketID) + rps, err := keeper.GetRawPrices(ctx, market.MarketID) + if err != nil { + panic(err) + } if len(rps) > 0 { err := keeper.SetCurrentPrices(ctx, market.MarketID) if err != nil { @@ -44,7 +47,10 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { var postedPrices []PostedPrice for _, market := range keeper.GetMarkets(ctx) { - pp := keeper.GetRawPrices(ctx, market.MarketID) + pp, err := keeper.GetRawPrices(ctx, market.MarketID) + if err != nil { + panic(err) + } postedPrices = append(postedPrices, pp...) } diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index 393643a7..211e797a 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -46,7 +46,10 @@ func (k Keeper) SetPrice( // If the expiry is less than or equal to the current blockheight, we consider the price valid if expiry.After(ctx.BlockTime()) { store := ctx.KVStore(k.key) - prices := k.GetRawPrices(ctx, marketID) + prices, err := k.GetRawPrices(ctx, marketID) + if err != nil { + return types.PostedPrice{}, err + } var index int found := false for i := range prices { @@ -96,7 +99,10 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { validPrevPrice = false } - prices := k.GetRawPrices(ctx, marketID) + prices, err := k.GetRawPrices(ctx, marketID) + if err != nil { + return err + } var notExpiredPrices types.CurrentPrices // filter out expired prices for _, v := range prices { @@ -174,8 +180,10 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) } var price types.CurrentPrice - k.cdc.MustUnmarshalBinaryBare(bz, &price) - + err := k.cdc.UnmarshalBinaryBare(bz, &price) + if err != nil { + return types.CurrentPrice{}, sdk.ErrInternal(sdk.AppendMsgToErr("failed to unmarshal result", err.Error())) + } if price.Price.Equal(sdk.ZeroDec()) { return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) } @@ -183,12 +191,18 @@ func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.Current } // GetRawPrices fetches the set of all prices posted by oracles for an asset -func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) types.PostedPrices { +func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) (types.PostedPrices, sdk.Error) { store := ctx.KVStore(k.key) bz := store.Get(types.RawPriceKey(marketID)) + if bz == nil { + return types.PostedPrices{}, nil + } var prices types.PostedPrices - k.cdc.MustUnmarshalBinaryBare(bz, &prices) - return prices + err := k.cdc.UnmarshalBinaryBare(bz, &prices) + if err != nil { + return types.PostedPrices{}, sdk.ErrInternal(sdk.AppendMsgToErr("failed to unmarshal result", err.Error())) + } + return prices, nil } // Codespace return the codespace for the keeper diff --git a/x/pricefeed/keeper/keeper_test.go b/x/pricefeed/keeper/keeper_test.go index 0f79b026..2288fa87 100644 --- a/x/pricefeed/keeper/keeper_test.go +++ b/x/pricefeed/keeper/keeper_test.go @@ -67,7 +67,8 @@ func TestKeeper_GetSetPrice(t *testing.T) { time.Now().Add(1*time.Hour)) require.NoError(t, err) // Get raw prices - rawPrices := keeper.GetRawPrices(ctx, "tstusd") + rawPrices, err := keeper.GetRawPrices(ctx, "tstusd") + require.NoError(t, err) require.Equal(t, len(rawPrices), 1) require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.33")), true) // Set price by oracle 2 @@ -77,7 +78,8 @@ func TestKeeper_GetSetPrice(t *testing.T) { time.Now().Add(time.Hour*1)) require.NoError(t, err) - rawPrices = keeper.GetRawPrices(ctx, "tstusd") + rawPrices, err = keeper.GetRawPrices(ctx, "tstusd") + require.NoError(t, err) require.Equal(t, len(rawPrices), 2) require.Equal(t, rawPrices[1].Price.Equal(sdk.MustNewDecFromStr("0.35")), true) @@ -87,7 +89,8 @@ func TestKeeper_GetSetPrice(t *testing.T) { sdk.MustNewDecFromStr("0.37"), time.Now().Add(time.Hour*1)) require.NoError(t, err) - rawPrices = keeper.GetRawPrices(ctx, "tstusd") + rawPrices, err = keeper.GetRawPrices(ctx, "tstusd") + require.NoError(t, err) require.Equal(t, rawPrices[0].Price.Equal(sdk.MustNewDecFromStr("0.37")), true) } diff --git a/x/pricefeed/keeper/querier.go b/x/pricefeed/keeper/querier.go index 94015535..1a111fdf 100644 --- a/x/pricefeed/keeper/querier.go +++ b/x/pricefeed/keeper/querier.go @@ -56,18 +56,20 @@ func queryPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []by func queryRawPrices(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { var requestParams types.QueryWithMarketIDParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) - if err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + err2 := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + if err2 != nil { + return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err2)) } _, found := keeper.GetMarket(ctx, requestParams.MarketID) if !found { return []byte{}, sdk.ErrUnknownRequest("asset not found") } - rawPrices := keeper.GetRawPrices(ctx, requestParams.MarketID) - - bz, err := codec.MarshalJSONIndent(keeper.cdc, rawPrices) + rawPrices, err := keeper.GetRawPrices(ctx, requestParams.MarketID) if err != nil { + return nil, err + } + bz, err2 := codec.MarshalJSONIndent(keeper.cdc, rawPrices) + if err2 != nil { panic("could not marshal result to JSON") } From 1fab788fd50e792bc09c590f416db9fcf6b9511d Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 13 Apr 2020 21:29:46 -0400 Subject: [PATCH 23/45] [R4R] Add cdp simulations (#422) feat: cdp sims Co-authored-by: rhuairahrighairigh Co-authored-by: John Maheswaran --- app/app.go | 2 +- app/sim_test.go | 13 ++ simulations/README.md | 2 + x/cdp/simulation/genesis.go | 155 +++++++++++++++- x/cdp/simulation/operations/msgs.go | 215 ++++++++++++++++++++++ x/cdp/simulation/operations/utils.go | 26 +++ x/cdp/simulation/operations/utils_test.go | 28 +++ x/cdp/simulation/params.go | 1 - x/pricefeed/simulation/genesis.go | 5 +- 9 files changed, 436 insertions(+), 11 deletions(-) create mode 100644 x/cdp/simulation/operations/msgs.go create mode 100644 x/cdp/simulation/operations/utils.go create mode 100644 x/cdp/simulation/operations/utils_test.go diff --git a/app/app.go b/app/app.go index 6b7bcc98..7d1a4bec 100644 --- a/app/app.go +++ b/app/app.go @@ -330,8 +330,8 @@ 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, app.supplyKeeper), // TODO how is the order be decided here? Is this order correct? pricefeed.NewAppModule(app.pricefeedKeeper), + cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper, app.supplyKeeper), auction.NewAppModule(app.auctionKeeper, app.supplyKeeper), bep3.NewAppModule(app.bep3Keeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), diff --git a/app/sim_test.go b/app/sim_test.go index 3881d398..37ad51a3 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -37,6 +37,7 @@ import ( auctionsimops "github.com/kava-labs/kava/x/auction/simulation/operations" bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" + cdpsimops "github.com/kava-labs/kava/x/cdp/simulation/operations" pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" ) @@ -63,6 +64,7 @@ const ( OpWeightMsgPlaceBid = "op_weight_msg_place_bid" OpWeightMsgPricefeed = "op_weight_msg_pricefeed" OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap" + OpWeightMsgCdp = "op_weight_msg_cdp" ) // TestMain runs setup and teardown code before all tests. @@ -304,6 +306,17 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper }(nil), pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper), }, + { + func(_ *rand.Rand) int { + var v int + ap.GetOrGenerate(app.cdc, OpWeightMsgCdp, &v, nil, + func(_ *rand.Rand) { + v = 100 // TODO + }) + return v + }(nil), + cdpsimops.SimulateMsgCdp(app.accountKeeper, app.cdpKeeper, app.pricefeedKeeper), + }, } } diff --git a/simulations/README.md b/simulations/README.md index 69cbdab9..c4260146 100644 --- a/simulations/README.md +++ b/simulations/README.md @@ -41,3 +41,5 @@ AWS Batch allows for "array jobs" which are a way of specifying many duplicates - click on the compute environment name, to get details, then click the link ECS Cluster Name to get details on the actual machines running - for array jobs, click the job name to get details of the individual jobs + +## Sims - TODO \ No newline at end of file diff --git a/x/cdp/simulation/genesis.go b/x/cdp/simulation/genesis.go index 96bdfb21..a82104c9 100644 --- a/x/cdp/simulation/genesis.go +++ b/x/cdp/simulation/genesis.go @@ -4,19 +4,164 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" - + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/supply" + supplyExported "github.com/cosmos/cosmos-sdk/x/supply/exported" "github.com/kava-labs/kava/x/cdp/types" ) // RandomizedGenState generates a random GenesisState for cdp func RandomizedGenState(simState *module.SimulationState) { - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - cdpGenesis := types.DefaultGenesisState() + cdpGenesis := randomCdpGenState(simState.Rand.Intn(2)) + + // hacky way to give accounts coins so they can create cdps (coins includes usdx so it's possible to have sufficient balance to close a cdp) + var authGenesis auth.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) + totalCdpCoins := sdk.NewCoins() + for _, acc := range authGenesis.Accounts { + _, ok := acc.(supplyExported.ModuleAccountI) + if ok { + continue + } + coinsToAdd := sdk.NewCoins( + sdk.NewCoin("bnb", sdk.NewInt(int64(simState.Rand.Intn(100000000000)))), + sdk.NewCoin("xrp", sdk.NewInt(int64(simState.Rand.Intn(100000000000)))), + sdk.NewCoin("btc", sdk.NewInt(int64(simState.Rand.Intn(500000000)))), + sdk.NewCoin("usdx", sdk.NewInt(int64(simState.Rand.Intn(1000000000)))), + ) + err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd)) + if err != nil { + panic(err) + } + totalCdpCoins = totalCdpCoins.Add(coinsToAdd) + authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, acc) + } + simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) + + var supplyGenesis supply.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) + supplyGenesis.Supply = supplyGenesis.Supply.Add(totalCdpCoins) + simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, cdpGenesis)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(cdpGenesis) } + +// In a list of accounts, replace the first account found with the same address. If not found, append the account. +func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount { + newAccounts := accounts + for i, a := range accounts { + if a.GetAddress().Equals(acc.GetAddress()) { + newAccounts[i] = acc + return newAccounts + } + } + return append(newAccounts, acc) +} + +func randomCdpGenState(selection int) types.GenesisState { + switch selection { + case 0: + return types.GenesisState{ + Params: types.Params{ + GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000)), + SurplusAuctionThreshold: types.DefaultSurplusThreshold, + DebtAuctionThreshold: types.DefaultDebtThreshold, + SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency, + CollateralParams: types.CollateralParams{ + { + Denom: "xrp", + LiquidationRatio: sdk.MustNewDecFromStr("2.0"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 20000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000004431822130"), + LiquidationPenalty: sdk.MustNewDecFromStr("0.075"), + AuctionSize: sdk.NewInt(10000000000), + Prefix: 0x20, + MarketID: "xrp:usd", + ConversionFactor: sdk.NewInt(6), + }, + { + Denom: "btc", + LiquidationRatio: sdk.MustNewDecFromStr("1.25"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), + LiquidationPenalty: sdk.MustNewDecFromStr("0.05"), + AuctionSize: sdk.NewInt(50000000), + Prefix: 0x21, + MarketID: "btc:usd", + ConversionFactor: sdk.NewInt(8), + }, + { + Denom: "bnb", + LiquidationRatio: sdk.MustNewDecFromStr("1.5"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 30000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"), + LiquidationPenalty: sdk.MustNewDecFromStr("0.15"), + AuctionSize: sdk.NewInt(10000000000), + Prefix: 0x22, + MarketID: "bnb:usd", + ConversionFactor: sdk.NewInt(8), + }, + }, + DebtParams: types.DebtParams{ + { + Denom: "usdx", + ReferenceAsset: "usd", + ConversionFactor: sdk.NewInt(6), + DebtFloor: sdk.NewInt(10000000), + SavingsRate: sdk.MustNewDecFromStr("0.95"), + }, + }, + }, + StartingCdpID: types.DefaultCdpStartingID, + DebtDenom: types.DefaultDebtDenom, + GovDenom: types.DefaultGovDenom, + CDPs: types.CDPs{}, + PreviousBlockTime: types.DefaultPreviousBlockTime, + PreviousDistributionTime: types.DefaultPreviousDistributionTime, + } + case 1: + return types.GenesisState{ + Params: types.Params{ + GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000)), + SurplusAuctionThreshold: types.DefaultSurplusThreshold, + DebtAuctionThreshold: types.DefaultDebtThreshold, + SavingsDistributionFrequency: types.DefaultSavingsDistributionFrequency, + CollateralParams: types.CollateralParams{ + { + Denom: "bnb", + LiquidationRatio: sdk.MustNewDecFromStr("1.5"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"), + LiquidationPenalty: sdk.MustNewDecFromStr("0.075"), + AuctionSize: sdk.NewInt(10000000000), + Prefix: 0x20, + MarketID: "bnb:usd", + ConversionFactor: sdk.NewInt(8), + }, + }, + DebtParams: types.DebtParams{ + { + Denom: "usdx", + ReferenceAsset: "usd", + ConversionFactor: sdk.NewInt(6), + DebtFloor: sdk.NewInt(10000000), + SavingsRate: sdk.MustNewDecFromStr("0.95"), + }, + }, + }, + StartingCdpID: types.DefaultCdpStartingID, + DebtDenom: types.DefaultDebtDenom, + GovDenom: types.DefaultGovDenom, + CDPs: types.CDPs{}, + PreviousBlockTime: types.DefaultPreviousBlockTime, + PreviousDistributionTime: types.DefaultPreviousDistributionTime, + } + default: + panic("invalid genesis state selector") + } +} diff --git a/x/cdp/simulation/operations/msgs.go b/x/cdp/simulation/operations/msgs.go new file mode 100644 index 00000000..29c6a6bf --- /dev/null +++ b/x/cdp/simulation/operations/msgs.go @@ -0,0 +1,215 @@ +package operations + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/pricefeed" +) + +// SimulateMsgCdp generates a MsgCreateCdp or MsgDepositCdp with random values. +func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) simulation.Operation { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( + opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) { + + handler := cdp.NewHandler(k) + simacc := simulation.RandomAcc(r, accs) + acc := ak.GetAccount(ctx, simacc.Address) + if acc == nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, nil + } + coins := acc.GetCoins() + collateralParams := k.GetParams(ctx).CollateralParams + if len(collateralParams) == 0 { + return simulation.NoOpMsg(cdp.ModuleName), nil, nil + } + randCollateralParam := collateralParams[r.Intn(len(collateralParams))] + randDebtAsset := randCollateralParam.DebtLimit[r.Intn(len(randCollateralParam.DebtLimit))] + randDebtParam, _ := k.GetDebtParam(ctx, randDebtAsset.Denom) + if coins.AmountOf(randCollateralParam.Denom).IsZero() { + return simulation.NoOpMsg(cdp.ModuleName), nil, nil + } + + price, err := pfk.GetCurrentPrice(ctx, randCollateralParam.MarketID) + if err != nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, err + } + // convert the price to the same units as the debt param + priceShifted := ShiftDec(price.Price, randDebtParam.ConversionFactor) + + existingCDP, found := k.GetCdpByOwnerAndDenom(ctx, acc.GetAddress(), randCollateralParam.Denom) + if !found { + // calculate the minimum amount of collateral that is needed to create a cdp with the debt floor amount of debt and the minimum liquidation ratio + // (debtFloor * liquidationRatio)/priceShifted + minCollateralDeposit := (sdk.NewDecFromInt(randDebtParam.DebtFloor).Mul(randCollateralParam.LiquidationRatio)).Quo(priceShifted) + // convert to proper collateral units + minCollateralDeposit = ShiftDec(minCollateralDeposit, randCollateralParam.ConversionFactor) + // convert to integer and always round up + minCollateralDepositRounded := minCollateralDeposit.TruncateInt().Add(sdk.OneInt()) + // if the account has less than the min deposit, return + if coins.AmountOf(randCollateralParam.Denom).LT(minCollateralDepositRounded) { + return simulation.NoOpMsg(cdp.ModuleName), nil, nil + } + // set the max collateral deposit to the amount of coins in the account + maxCollateralDeposit := coins.AmountOf(randCollateralParam.Denom) + + // randomly select a collateral deposit amount + collateralDeposit := sdk.NewInt(int64(simulation.RandIntBetween(r, int(minCollateralDepositRounded.Int64()), int(maxCollateralDeposit.Int64())))) + // calculate how much the randomly selected deposit is worth + collateralDepositValue := ShiftDec(sdk.NewDecFromInt(collateralDeposit), randCollateralParam.ConversionFactor.Neg()).Mul(priceShifted) + // calculate the max amount of debt that could be drawn for the chosen deposit + maxDebtDraw := collateralDepositValue.Quo(randCollateralParam.LiquidationRatio).TruncateInt() + // randomly select a debt draw amount + debtDraw := sdk.NewInt(int64(simulation.RandIntBetween(r, int(randDebtParam.DebtFloor.Int64()), int(maxDebtDraw.Int64())))) + msg := cdp.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, collateralDeposit)), sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, debtDraw))) + err := msg.ValidateBasic() + if err != nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %v", err) + } + ok := submitMsg(msg, handler, ctx) + if !ok { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit create cdp msg") + } + return simulation.NewOperationMsg(msg, ok, "create cdp"), nil, nil + } + + // a cdp already exists, deposit to it, draw debt from it, or repay debt to it + // close 25% of the time + if canClose(acc, existingCDP, randDebtParam.Denom) && shouldClose(r) { + repaymentAmount := coins.AmountOf(randDebtParam.Denom) + msg := cdp.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, repaymentAmount))) + err := msg.ValidateBasic() + if err != nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected repay (close) msg to pass ValidateBasic: %v", err) + } + ok := submitMsg(msg, handler, ctx) + if !ok { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit repay (close) msg") + } + return simulation.NewOperationMsg(msg, ok, "repay debt (close) cdp"), nil, nil + } + + // deposit 25% of the time + if hasCoins(acc, randCollateralParam.Denom) && shouldDeposit(r) { + randDepositAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(acc.GetCoins().AmountOf(randCollateralParam.Denom).Int64())))) + msg := cdp.NewMsgDeposit(acc.GetAddress(), acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, randDepositAmount))) + err := msg.ValidateBasic() + if err != nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected deposit msg to pass ValidateBasic: %v", err) + } + ok := submitMsg(msg, handler, ctx) + if !ok { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit deposit msg") + } + return simulation.NewOperationMsg(msg, ok, "deposit to cdp"), nil, nil + } + + // draw debt 25% of the time + if shouldDraw(r) { + collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.AmountOf(randCollateralParam.Denom)), randCollateralParam.ConversionFactor.Neg()) + collateralValue := collateralShifted.Mul(priceShifted) + debt := (existingCDP.Principal.Add(existingCDP.AccumulatedFees)).AmountOf(randDebtParam.Denom) + maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio) + maxDebt := maxTotalDebt.Sub(sdk.NewDecFromInt(debt)).TruncateInt().Sub(sdk.OneInt()) + randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDebt.Int64())))) + msg := cdp.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randDrawAmount))) + err := msg.ValidateBasic() + if err != nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected draw msg to pass ValidateBasic: %v", err) + } + ok := submitMsg(msg, handler, ctx) + if !ok { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit draw msg") + } + return simulation.NewOperationMsg(msg, ok, "draw debt from cdp"), nil, nil + + } + + // repay debt 25% of the time + if hasCoins(acc, randDebtParam.Denom) { + debt := (existingCDP.Principal.Add(existingCDP.AccumulatedFees)).AmountOf(randDebtParam.Denom) + maxRepay := acc.GetCoins().AmountOf(randDebtParam.Denom) + payableDebt := debt.Sub(randDebtParam.DebtFloor) + if maxRepay.GT(payableDebt) { + maxRepay = payableDebt + } + randRepayAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxRepay.Int64())))) + if debt.Equal(randDebtParam.DebtFloor) { + if acc.GetCoins().AmountOf(randDebtParam.Denom).GTE(debt) { + randRepayAmount = debt + } + } + msg := cdp.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randRepayAmount))) + err := msg.ValidateBasic() + if err != nil { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected repay msg to pass ValidateBasic: %v", err) + } + ok := submitMsg(msg, handler, ctx) + if !ok { + return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit repay msg") + } + return simulation.NewOperationMsg(msg, ok, "repay debt cdp"), nil, nil + } + + return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation (no valid actions)", "", false, nil), nil, nil + } +} + +func submitMsg(msg sdk.Msg, handler sdk.Handler, ctx sdk.Context) (ok bool) { + ctx, write := ctx.CacheContext() + res := handler(ctx, msg) + if res.IsOK() { + write() + } else { + fmt.Println(res.Log) + } + return res.IsOK() +} + +func shouldDraw(r *rand.Rand) bool { + threshold := 50 + value := simulation.RandIntBetween(r, 1, 100) + if value > threshold { + return true + } + return false +} + +func shouldDeposit(r *rand.Rand) bool { + threshold := 66 + value := simulation.RandIntBetween(r, 1, 100) + if value > threshold { + return true + } + return false +} + +func hasCoins(acc authexported.Account, denom string) bool { + if acc.GetCoins().AmountOf(denom).IsZero() { + return false + } + return true +} + +func shouldClose(r *rand.Rand) bool { + threshold := 75 + value := simulation.RandIntBetween(r, 1, 100) + if value > threshold { + return true + } + return false +} + +func canClose(acc authexported.Account, c cdp.CDP, denom string) bool { + repaymentAmount := c.Principal.Add(c.AccumulatedFees).AmountOf(denom) + if acc.GetCoins().AmountOf(denom).GTE(repaymentAmount) { + return true + } + return false +} diff --git a/x/cdp/simulation/operations/utils.go b/x/cdp/simulation/operations/utils.go new file mode 100644 index 00000000..bf8a804e --- /dev/null +++ b/x/cdp/simulation/operations/utils.go @@ -0,0 +1,26 @@ +package operations + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func ShiftDec(x sdk.Dec, places sdk.Int) sdk.Dec { + neg := places.IsNegative() + for i := 0; i < int(abs(places.Int64())); i++ { + if neg { + x = x.Mul(sdk.MustNewDecFromStr("0.1")) + } else { + x = x.Mul(sdk.NewDecFromInt(sdk.NewInt(10))) + } + + } + return x +} + +// abs returns the absolute value of x. +func abs(x int64) int64 { + if x < 0 { + return -x + } + return x +} diff --git a/x/cdp/simulation/operations/utils_test.go b/x/cdp/simulation/operations/utils_test.go new file mode 100644 index 00000000..5fedbef0 --- /dev/null +++ b/x/cdp/simulation/operations/utils_test.go @@ -0,0 +1,28 @@ +package operations_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/cdp/simulation/operations" + "github.com/stretchr/testify/require" +) + +func TestShiftDec(t *testing.T) { + tests := []struct { + value sdk.Dec + shift sdk.Int + expected sdk.Dec + }{ + {sdk.MustNewDecFromStr("5.5"), sdk.NewInt(1), sdk.MustNewDecFromStr("55")}, + {sdk.MustNewDecFromStr("5.5"), sdk.NewInt(-1), sdk.MustNewDecFromStr("0.55")}, + {sdk.MustNewDecFromStr("5.5"), sdk.NewInt(2), sdk.MustNewDecFromStr("550")}, + {sdk.MustNewDecFromStr("5.5"), sdk.NewInt(-2), sdk.MustNewDecFromStr("0.055")}, + } + + for _, tt := range tests { + t.Run(tt.value.String(), func(t *testing.T) { + require.Equal(t, tt.expected, operations.ShiftDec(tt.value, tt.shift)) + }) + } +} diff --git a/x/cdp/simulation/params.go b/x/cdp/simulation/params.go index 8c1f7aff..36ff612a 100644 --- a/x/cdp/simulation/params.go +++ b/x/cdp/simulation/params.go @@ -9,6 +9,5 @@ import ( // 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{} } diff --git a/x/pricefeed/simulation/genesis.go b/x/pricefeed/simulation/genesis.go index 9a84d291..bcbbff01 100644 --- a/x/pricefeed/simulation/genesis.go +++ b/x/pricefeed/simulation/genesis.go @@ -5,13 +5,11 @@ import ( "time" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/kava-labs/kava/x/pricefeed/types" pricefeed "github.com/kava-labs/kava/x/pricefeed/types" - - sdk "github.com/cosmos/cosmos-sdk/types" ) // RandomizedGenState generates a random GenesisState for pricefeed @@ -31,7 +29,6 @@ func RandomizedGenState(simState *module.SimulationState) { } params = types.NewParams(markets) pricefeedGenesis := types.NewGenesisState(params, genPrices) - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, pricefeedGenesis)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis) } From acc96952a7df3d5f1004263d9dd9d548bc213e90 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Tue, 14 Apr 2020 13:49:31 -0400 Subject: [PATCH 24/45] fix: non-determinism in auction sims (#432) --- x/auction/simulation/operations/msg.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/auction/simulation/operations/msg.go b/x/auction/simulation/operations/msg.go index 57dae24c..d38f97bc 100644 --- a/x/auction/simulation/operations/msg.go +++ b/x/auction/simulation/operations/msg.go @@ -39,7 +39,7 @@ func SimulateMsgPlaceBid(authKeeper auth.AccountKeeper, keeper auction.Keeper) s }) // shuffle auctions slice so that bids are evenly distributed across auctions - rand.Shuffle(len(openAuctions), func(i, j int) { + r.Shuffle(len(openAuctions), func(i, j int) { openAuctions[i], openAuctions[j] = openAuctions[j], openAuctions[i] }) // TODO do the same for accounts? From 45e40fe35781a299f5689062b275fbfdd8d32672 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Tue, 14 Apr 2020 14:42:08 -0700 Subject: [PATCH 25/45] [WIP] Kavadist Simulations (#435) * feat: kavadist sims * refactor genesis, add validation * implement params * rename simulation to genesis Co-authored-by: Kevin Davis --- x/kavadist/simulation/genesis.go | 84 +++++++++++++++++++++++++++++ x/kavadist/simulation/params.go | 24 ++++++++- x/kavadist/simulation/simulation.go | 22 -------- x/kavadist/simulation/utils.go | 64 ++++++++++++++++++++++ x/kavadist/simulation/utils_test.go | 30 +++++++++++ x/kavadist/types/params.go | 9 ++++ 6 files changed, 209 insertions(+), 24 deletions(-) create mode 100644 x/kavadist/simulation/genesis.go delete mode 100644 x/kavadist/simulation/simulation.go create mode 100644 x/kavadist/simulation/utils.go create mode 100644 x/kavadist/simulation/utils_test.go diff --git a/x/kavadist/simulation/genesis.go b/x/kavadist/simulation/genesis.go new file mode 100644 index 00000000..d12a73d5 --- /dev/null +++ b/x/kavadist/simulation/genesis.go @@ -0,0 +1,84 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/kavadist/types" +) + +// SecondsPerYear is the number of seconds in a year +const SecondsPerYear = 31536000 + +// BaseAprPadding prevents the calculated SPR inflation rate from being 0.0 +const BaseAprPadding = "0.000000000100000000" + +// RandomizedGenState generates a random GenesisState for kavadist module +func RandomizedGenState(simState *module.SimulationState) { + params := genRandomParams(simState) + if err := params.Validate(); err != nil { + panic(err) + } + + kavadistGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime) + if err := kavadistGenesis.Validate(); err != nil { + panic(err) + } + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, kavadistGenesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(kavadistGenesis) +} + +func genRandomParams(simState *module.SimulationState) types.Params { + periods := genRandomPeriods(simState.Rand, simState.GenTimestamp) + params := types.NewParams(true, periods) + return params +} + +func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods { + var periods types.Periods + numPeriods := simulation.RandIntBetween(r, 1, 10) + periodStart := timestamp + for i := 0; i < numPeriods; i++ { + // set periods to be between 2 weeks and 2 years + durationMultiplier := simulation.RandIntBetween(r, 14, 104) + duration := time.Duration(int64(24*durationMultiplier)) * time.Hour + periodEnd := periodStart.Add(duration) + inflation := genRandomInflation(r) + period := types.NewPeriod(periodStart, periodEnd, inflation) + periods = append(periods, period) + periodStart = periodEnd + } + return periods +} + +func genRandomInflation(r *rand.Rand) sdk.Dec { + // If sim.RandomDecAmount returns 0 (happens frequently by design), add BaseAprPadding + extraAprInflation := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("0.25")) + for extraAprInflation.Equal(sdk.ZeroDec()) { + extraAprInflation = extraAprInflation.Add(sdk.MustNewDecFromStr(BaseAprPadding)) + } + + aprInflation := sdk.OneDec().Add(extraAprInflation) + // convert APR inflation to SPR (inflation per second) + inflationSpr, err := approxRoot(aprInflation, uint64(SecondsPerYear)) + if err != nil { + panic(fmt.Sprintf("error generating random inflation %v", err)) + } + return inflationSpr +} + +func genRandomActive(r *rand.Rand) bool { + threshold := 50 + value := simulation.RandIntBetween(r, 1, 100) + if value > threshold { + return true + } + return false +} diff --git a/x/kavadist/simulation/params.go b/x/kavadist/simulation/params.go index 8c1f7aff..909e3baf 100644 --- a/x/kavadist/simulation/params.go +++ b/x/kavadist/simulation/params.go @@ -1,14 +1,34 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/kavadist/types" ) // ParamChanges defines the parameters that can be modified by param change proposals // on the simulation func ParamChanges(r *rand.Rand) []simulation.ParamChange { - // TODO implement this - return []simulation.ParamChange{} + // Hacky way to validate periods since validation is wrapped in params + active := genRandomActive(r) + periods := genRandomPeriods(r, simulation.RandTimestamp(r)) + if err := types.NewParams(active, periods).Validate(); err != nil { + panic(err) + } + + return []simulation.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, string(types.KeyActive), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%t\"", active) + }, + ), + simulation.NewSimParamChange(types.ModuleName, string(types.KeyPeriods), "", + func(r *rand.Rand) string { + return fmt.Sprintf("\"%v\"", periods) + }, + ), + } } diff --git a/x/kavadist/simulation/simulation.go b/x/kavadist/simulation/simulation.go deleted file mode 100644 index a1fbd964..00000000 --- a/x/kavadist/simulation/simulation.go +++ /dev/null @@ -1,22 +0,0 @@ -package simulation - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/types/module" - - "github.com/kava-labs/kava/x/kavadist/types" -) - -// RandomizedGenState generates a random GenesisState for cdp -func RandomizedGenState(simState *module.SimulationState) { - - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - genesis := types.DefaultGenesisState() - - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis)) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) -} diff --git a/x/kavadist/simulation/utils.go b/x/kavadist/simulation/utils.go new file mode 100644 index 00000000..b6f771b7 --- /dev/null +++ b/x/kavadist/simulation/utils.go @@ -0,0 +1,64 @@ +package simulation + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TODO use the official version available in v0.38.2 of the sdk +// https://github.com/cosmos/cosmos-sdk/blob/e8d89a2fe26175b73545a3e79ae783032b4e975e/types/decimal.go#L328 +func approxRoot(d sdk.Dec, root uint64) (guess sdk.Dec, err error) { + defer func() { + if r := recover(); r != nil { + var ok bool + err, ok = r.(error) + if !ok { + err = errors.New("out of bounds") + } + } + }() + + if d.IsNegative() { + absRoot, err := approxRoot(d.MulInt64(-1), root) + return absRoot.MulInt64(-1), err + } + if root == 1 || d.IsZero() || d.Equal(sdk.OneDec()) { + return d, nil + } + if root == 0 { + return sdk.OneDec(), nil + } + rootInt := sdk.NewInt(int64(root)) + guess, delta := sdk.OneDec(), sdk.OneDec() + for delta.Abs().GT(sdk.SmallestDec()) { + prev := power(guess, (root - 1)) + if prev.IsZero() { + prev = sdk.SmallestDec() + } + delta = d.Quo(prev) + delta = delta.Sub(guess) + delta = delta.QuoInt(rootInt) + + guess = guess.Add(delta) + } + return guess, nil +} + +// Power returns a the result of raising to a positive integer power +func power(d sdk.Dec, power uint64) sdk.Dec { + if power == 0 { + return sdk.OneDec() + } + tmp := sdk.OneDec() + for i := power; i > 1; { + if i%2 == 0 { + i /= 2 + } else { + tmp = tmp.Mul(d) + i = (i - 1) / 2 + } + d = d.Mul(d) + } + return d.Mul(tmp) +} diff --git a/x/kavadist/simulation/utils_test.go b/x/kavadist/simulation/utils_test.go new file mode 100644 index 00000000..81a76c39 --- /dev/null +++ b/x/kavadist/simulation/utils_test.go @@ -0,0 +1,30 @@ +package simulation + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestApproxRoot(t *testing.T) { + testCases := []struct { + input sdk.Dec + root uint64 + expected sdk.Dec + }{ + {sdk.OneDec(), 10, sdk.OneDec()}, // 1.0 ^ (0.1) => 1.0 + {sdk.NewDecWithPrec(25, 2), 2, sdk.NewDecWithPrec(5, 1)}, // 0.25 ^ (0.5) => 0.5 + {sdk.NewDecWithPrec(4, 2), 2, sdk.NewDecWithPrec(2, 1)}, // 0.04 => 0.2 + {sdk.NewDecFromInt(sdk.NewInt(27)), 3, sdk.NewDecFromInt(sdk.NewInt(3))}, // 27 ^ (1/3) => 3 + {sdk.NewDecFromInt(sdk.NewInt(-81)), 4, sdk.NewDecFromInt(sdk.NewInt(-3))}, // -81 ^ (0.25) => -3 + {sdk.NewDecFromInt(sdk.NewInt(2)), 2, sdk.NewDecWithPrec(1414213562373095049, 18)}, // 2 ^ (0.5) => 1.414213562373095049 + {sdk.NewDecWithPrec(1005, 3), 31536000, sdk.MustNewDecFromStr("1.000000000158153904")}, // 1.005 ^ (1/31536000) = 1.000000000158153904 + } + + for i, tc := range testCases { + res, err := approxRoot(tc.input, tc.root) + require.NoError(t, err) + require.True(t, tc.expected.Sub(res).Abs().LTE(sdk.SmallestDec()), "unexpected result for test case %d, input: %v", i, tc.input) + } +} diff --git a/x/kavadist/types/params.go b/x/kavadist/types/params.go index 6cb955b0..d2ab89e0 100644 --- a/x/kavadist/types/params.go +++ b/x/kavadist/types/params.go @@ -33,6 +33,15 @@ type Period struct { Inflation sdk.Dec `json:"inflation" yaml:"inflation"` // example "1.000000003022265980" - 10% inflation } +// NewPeriod returns a new instance of Period +func NewPeriod(start time.Time, end time.Time, inflation sdk.Dec) Period { + return Period{ + Start: start, + End: end, + Inflation: inflation, + } +} + // String implements fmt.Stringer func (pr Period) String() string { return fmt.Sprintf(`Period: From 55b73e36eea1fe2bb396f4ee11df92bb2c4cbfb3 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 15 Apr 2020 13:50:14 -0400 Subject: [PATCH 26/45] Fix: Check debt limit when adding cdp (#433) * fix: check debt limit when opening cdp * fix: increase debt limit for querier tests --- x/cdp/keeper/cdp.go | 15 +++++++- x/cdp/keeper/cdp_test.go | 13 +++++-- x/cdp/keeper/integration_test.go | 58 +++++++++++++++++++++++++++++ x/cdp/keeper/querier_test.go | 10 +++-- x/cdp/simulation/genesis.go | 6 +-- x/cdp/simulation/operations/msgs.go | 29 +++++++++++++-- 6 files changed, 116 insertions(+), 15 deletions(-) diff --git a/x/cdp/keeper/cdp.go b/x/cdp/keeper/cdp.go index 5d729263..9e3611ee 100644 --- a/x/cdp/keeper/cdp.go +++ b/x/cdp/keeper/cdp.go @@ -27,6 +27,11 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi if err != nil { return err } + + err = k.ValidateDebtLimit(ctx, collateral[0].Denom, principal) + if err != nil { + return err + } err = k.ValidateCollateralizationRatio(ctx, collateral, principal, sdk.NewCoins()) if err != nil { return err @@ -371,10 +376,18 @@ func (k Keeper) ValidatePrincipalDraw(ctx sdk.Context, principal sdk.Coins) sdk. return nil } -// ValidateDebtLimit validates that the input debt amount does not exceed the global debt limit +// ValidateDebtLimit validates that the input debt amount does not exceed the global debt limit or the debt limit for that collateral func (k Keeper) ValidateDebtLimit(ctx sdk.Context, collateralDenom string, principal sdk.Coins) sdk.Error { + cp, found := k.GetCollateral(ctx, collateralDenom) + if !found { + return types.ErrCollateralNotSupported(k.codespace, collateralDenom) + } for _, dc := range principal { totalPrincipal := k.GetTotalPrincipal(ctx, collateralDenom, dc.Denom).Add(dc.Amount) + collateralLimit := cp.DebtLimit.AmountOf(dc.Denom) + if totalPrincipal.GT(collateralLimit) { + return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, collateralLimit))) + } globalLimit := k.GetParams(ctx).GlobalDebtLimit.AmountOf(dc.Denom) if totalPrincipal.GT(globalLimit) { return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, globalLimit))) diff --git a/x/cdp/keeper/cdp_test.go b/x/cdp/keeper/cdp_test.go index a76dd8a2..f7ca7982 100644 --- a/x/cdp/keeper/cdp_test.go +++ b/x/cdp/keeper/cdp_test.go @@ -38,7 +38,7 @@ func (suite *CdpTestSuite) SetupTest() { } func (suite *CdpTestSuite) TestAddCdp() { - _, addrs := app.GeneratePrivKeyAddressPairs(1) + _, addrs := app.GeneratePrivKeyAddressPairs(2) ak := suite.app.GetAccountKeeper() acc := ak.NewAccountWithAddress(suite.ctx, addrs[0]) acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000))) @@ -46,14 +46,21 @@ func (suite *CdpTestSuite) TestAddCdp() { err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("usdx", 26000000))) suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 500000000)), cs(c("usdx", 26000000))) - suite.Error(err) + suite.Error(err) // insufficient balance err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("xusd", 10000000))) suite.Equal(types.CodeDebtNotSupported, err.Result().Code) + + acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1]) + acc2.SetCoins(cs(c("btc", 500000000000))) + ak.SetAccount(suite.ctx, acc2) + err = suite.keeper.AddCdp(suite.ctx, addrs[1], cs(c("btc", 500000000000)), cs(c("usdx", 500000000001))) + suite.Equal(types.CodeExceedsDebtLimit, err.Result().Code) + ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2)) pk := suite.app.GetPriceFeedKeeper() _ = pk.SetCurrentPrices(ctx, "xrp:usd") err = suite.keeper.AddCdp(ctx, addrs[0], cs(c("xrp", 100000000)), cs(c("usdx", 10000000))) - suite.Error(err) + suite.Error(err) // no prices in pricefeed _ = pk.SetCurrentPrices(suite.ctx, "xrp:usd") err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 100000000)), cs(c("usdx", 10000000))) diff --git a/x/cdp/keeper/integration_test.go b/x/cdp/keeper/integration_test.go index 0714b69e..0cfbbf1f 100644 --- a/x/cdp/keeper/integration_test.go +++ b/x/cdp/keeper/integration_test.go @@ -159,6 +159,64 @@ func NewCDPGenStateMulti() app.GenesisState { return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} } +func NewCDPGenStateHighDebtLimit() app.GenesisState { + cdpGenesis := cdp.GenesisState{ + Params: cdp.Params{ + GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 100000000000000), sdk.NewInt64Coin("susd", 100000000000000)), + SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, + DebtAuctionThreshold: cdp.DefaultDebtThreshold, + SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, + CollateralParams: cdp.CollateralParams{ + { + Denom: "xrp", + LiquidationRatio: sdk.MustNewDecFromStr("2.0"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000), sdk.NewInt64Coin("susd", 50000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr + LiquidationPenalty: d("0.05"), + AuctionSize: i(7000000000), + Prefix: 0x20, + MarketID: "xrp:usd", + ConversionFactor: i(6), + }, + { + Denom: "btc", + LiquidationRatio: sdk.MustNewDecFromStr("1.5"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000), sdk.NewInt64Coin("susd", 50000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), // %2.5 apr + LiquidationPenalty: d("0.025"), + AuctionSize: i(10000000), + Prefix: 0x21, + MarketID: "btc:usd", + ConversionFactor: i(8), + }, + }, + DebtParams: cdp.DebtParams{ + { + Denom: "usdx", + 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, + PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, + } + return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} +} + func cdps() (cdps cdp.CDPs) { _, addrs := app.GeneratePrivKeyAddressPairs(3) c1 := cdp.NewCDP(uint64(1), addrs[0], sdk.NewCoins(sdk.NewCoin("xrp", sdk.NewInt(10000000))), sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(8000000))), tmtime.Canonical(time.Now())) diff --git a/x/cdp/keeper/querier_test.go b/x/cdp/keeper/querier_test.go index 41b65467..e68afb77 100644 --- a/x/cdp/keeper/querier_test.go +++ b/x/cdp/keeper/querier_test.go @@ -53,7 +53,7 @@ func (suite *QuerierTestSuite) SetupTest() { tApp.InitializeFromGenesisStates( authGS, NewPricefeedGenStateMulti(), - NewCDPGenStateMulti(), + NewCDPGenStateHighDebtLimit(), ) suite.ctx = ctx @@ -93,11 +93,13 @@ func (suite *QuerierTestSuite) SetupTest() { amount = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 500000000, 5000000000) debt = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 1000000000, 25000000000) } - suite.Nil(suite.keeper.AddCdp(suite.ctx, addrs[j], cs(c(collateral, int64(amount))), cs(c("usdx", int64(debt))))) + err = suite.keeper.AddCdp(suite.ctx, addrs[j], cs(c(collateral, int64(amount))), cs(c("usdx", int64(debt)))) + suite.NoError(err) c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1)) suite.True(f) cdps[j] = c - aCDP, _ := suite.keeper.LoadAugmentedCDP(suite.ctx, c) + aCDP, err := suite.keeper.LoadAugmentedCDP(suite.ctx, c) + suite.NoError(err) augmentedCDPs[j] = aCDP } @@ -250,7 +252,7 @@ func (suite *QuerierTestSuite) TestQueryParams() { var p types.Params suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &p)) - cdpGS := NewCDPGenStateMulti() + cdpGS := NewCDPGenStateHighDebtLimit() gs := types.GenesisState{} types.ModuleCdc.UnmarshalJSON(cdpGS["cdp"], &gs) suite.Equal(gs.Params, p) diff --git a/x/cdp/simulation/genesis.go b/x/cdp/simulation/genesis.go index a82104c9..8f817997 100644 --- a/x/cdp/simulation/genesis.go +++ b/x/cdp/simulation/genesis.go @@ -79,7 +79,7 @@ func randomCdpGenState(selection int) types.GenesisState { DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 20000000000000)), StabilityFee: sdk.MustNewDecFromStr("1.000000004431822130"), LiquidationPenalty: sdk.MustNewDecFromStr("0.075"), - AuctionSize: sdk.NewInt(10000000000), + AuctionSize: sdk.NewInt(100000000000), Prefix: 0x20, MarketID: "xrp:usd", ConversionFactor: sdk.NewInt(6), @@ -90,7 +90,7 @@ func randomCdpGenState(selection int) types.GenesisState { DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 50000000000000)), StabilityFee: sdk.MustNewDecFromStr("1.000000000782997609"), LiquidationPenalty: sdk.MustNewDecFromStr("0.05"), - AuctionSize: sdk.NewInt(50000000), + AuctionSize: sdk.NewInt(1000000000), Prefix: 0x21, MarketID: "btc:usd", ConversionFactor: sdk.NewInt(8), @@ -101,7 +101,7 @@ func randomCdpGenState(selection int) types.GenesisState { DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 30000000000000)), StabilityFee: sdk.MustNewDecFromStr("1.000000002293273137"), LiquidationPenalty: sdk.MustNewDecFromStr("0.15"), - AuctionSize: sdk.NewInt(10000000000), + AuctionSize: sdk.NewInt(1000000000000), Prefix: 0x22, MarketID: "bnb:usd", ConversionFactor: sdk.NewInt(8), diff --git a/x/cdp/simulation/operations/msgs.go b/x/cdp/simulation/operations/msgs.go index 29c6a6bf..7600d87b 100644 --- a/x/cdp/simulation/operations/msgs.go +++ b/x/cdp/simulation/operations/msgs.go @@ -52,9 +52,9 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s minCollateralDeposit = ShiftDec(minCollateralDeposit, randCollateralParam.ConversionFactor) // convert to integer and always round up minCollateralDepositRounded := minCollateralDeposit.TruncateInt().Add(sdk.OneInt()) - // if the account has less than the min deposit, return if coins.AmountOf(randCollateralParam.Denom).LT(minCollateralDepositRounded) { - return simulation.NoOpMsg(cdp.ModuleName), nil, nil + // account doesn't have enough funds to open a cdp for the min debt amount + return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "insufficient funds to open cdp", false, nil), nil, nil } // set the max collateral deposit to the amount of coins in the account maxCollateralDeposit := coins.AmountOf(randCollateralParam.Denom) @@ -65,6 +65,14 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s collateralDepositValue := ShiftDec(sdk.NewDecFromInt(collateralDeposit), randCollateralParam.ConversionFactor.Neg()).Mul(priceShifted) // calculate the max amount of debt that could be drawn for the chosen deposit maxDebtDraw := collateralDepositValue.Quo(randCollateralParam.LiquidationRatio).TruncateInt() + // check that the debt limit hasn't been reached + availableAssetDebt := randCollateralParam.DebtLimit.AmountOf(randDebtParam.Denom).Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, randDebtParam.Denom)) + if availableAssetDebt.LTE(randDebtParam.DebtFloor) { + // debt limit has been reached + return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "debt limit reached, cannot open cdp", false, nil), nil, nil + } + // ensure that the debt draw does not exceed the debt limit + maxDebtDraw = sdk.MinInt(maxDebtDraw, availableAssetDebt) // randomly select a debt draw amount debtDraw := sdk.NewInt(int64(simulation.RandIntBetween(r, int(randDebtParam.DebtFloor.Int64()), int(maxDebtDraw.Int64())))) msg := cdp.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, collateralDeposit)), sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, debtDraw))) @@ -114,10 +122,23 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s if shouldDraw(r) { collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.AmountOf(randCollateralParam.Denom)), randCollateralParam.ConversionFactor.Neg()) collateralValue := collateralShifted.Mul(priceShifted) + // given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio debt := (existingCDP.Principal.Add(existingCDP.AccumulatedFees)).AmountOf(randDebtParam.Denom) maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio) - maxDebt := maxTotalDebt.Sub(sdk.NewDecFromInt(debt)).TruncateInt().Sub(sdk.OneInt()) - randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDebt.Int64())))) + maxDebt := maxTotalDebt.Sub(sdk.NewDecFromInt(debt)).TruncateInt() + if maxDebt.LTE(sdk.OneInt()) { + // debt in cdp is maxed out + return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil + } + // check if the debt limit has been reached + availableAssetDebt := randCollateralParam.DebtLimit.AmountOf(randDebtParam.Denom).Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, randDebtParam.Denom)) + if availableAssetDebt.LTE(sdk.OneInt()) { + // debt limit has been reached + return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "debt limit reached, cannot draw more debt", false, nil), nil, nil + } + maxDraw := sdk.MinInt(maxDebt, availableAssetDebt) + + randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDraw.Int64())))) msg := cdp.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randDrawAmount))) err := msg.ValidateBasic() if err != nil { From 4cde3ba577f22a7a041afd5d078bfe4853ad80fa Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 15 Apr 2020 14:54:38 -0400 Subject: [PATCH 27/45] fix: prevent cdp sim from attempting to draw too much debt (#438) * fix: account for all fees when drawing more debt --- x/cdp/simulation/operations/msgs.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/x/cdp/simulation/operations/msgs.go b/x/cdp/simulation/operations/msgs.go index 7600d87b..dd1eb506 100644 --- a/x/cdp/simulation/operations/msgs.go +++ b/x/cdp/simulation/operations/msgs.go @@ -122,8 +122,10 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s if shouldDraw(r) { collateralShifted := ShiftDec(sdk.NewDecFromInt(existingCDP.Collateral.AmountOf(randCollateralParam.Denom)), randCollateralParam.ConversionFactor.Neg()) collateralValue := collateralShifted.Mul(priceShifted) + newFeesAccumulated := k.CalculateFees(ctx, existingCDP.Principal, sdk.NewInt(ctx.BlockTime().Unix()-existingCDP.FeesUpdated.Unix()), randCollateralParam.Denom).AmountOf(randDebtParam.Denom) + totalFees := existingCDP.AccumulatedFees.AmountOf(randCollateralParam.Denom).Add(newFeesAccumulated) // given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio - debt := (existingCDP.Principal.Add(existingCDP.AccumulatedFees)).AmountOf(randDebtParam.Denom) + debt := existingCDP.Principal.AmountOf(randDebtParam.Denom).Add(totalFees) maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio) maxDebt := maxTotalDebt.Sub(sdk.NewDecFromInt(debt)).TruncateInt() if maxDebt.LTE(sdk.OneInt()) { From 162a47343d3b50012294717e5a38af9d25965c4c Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 15 Apr 2020 22:37:22 -0400 Subject: [PATCH 28/45] feat: reduce determinism sim runs --- app/sim_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/sim_test.go b/app/sim_test.go index 37ad51a3..1d0bd12e 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -662,12 +662,12 @@ func TestAppStateDeterminism(t *testing.T) { config.OnOperation = false config.AllInvariants = false - numSeeds := 3 - numTimesToRunPerSeed := 5 + numSeeds := 1 + numTimesToRunPerSeed := 2 appHashList := make([]json.RawMessage, numTimesToRunPerSeed) for i := 0; i < numSeeds; i++ { - config.Seed = rand.Int63() + config.Seed = 8674665223082153551 for j := 0; j < numTimesToRunPerSeed; j++ { logger := log.NewNopLogger() From 3ab264b5b7064a0e8827010195c9f86c2ca743a6 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 15 Apr 2020 22:44:08 -0400 Subject: [PATCH 29/45] fix: remove hardcoded value --- app/sim_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sim_test.go b/app/sim_test.go index 1d0bd12e..3a00315b 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -667,7 +667,7 @@ func TestAppStateDeterminism(t *testing.T) { appHashList := make([]json.RawMessage, numTimesToRunPerSeed) for i := 0; i < numSeeds; i++ { - config.Seed = 8674665223082153551 + config.Seed = rand.Int63() for j := 0; j < numTimesToRunPerSeed; j++ { logger := log.NewNopLogger() From 783247851d0749ad3790c4b424774ff48fd5f6e4 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 16 Apr 2020 07:43:44 -0400 Subject: [PATCH 30/45] [R4R]: Avoid divide by zero when price is very small (#441) * fix: avoid divide by zero when price is very small * fix: typo --- x/cdp/keeper/fees.go | 7 +++++-- x/cdp/keeper/seize.go | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/x/cdp/keeper/fees.go b/x/cdp/keeper/fees.go index 6f6693ed..936cfa74 100644 --- a/x/cdp/keeper/fees.go +++ b/x/cdp/keeper/fees.go @@ -41,9 +41,12 @@ func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, } liquidationRatio := k.getLiquidationRatio(ctx, collateralDenom) - + priceDivLiqRatio := price.Price.Quo(liquidationRatio) + if priceDivLiqRatio.IsZero() { + priceDivLiqRatio = sdk.SmallestDec() + } // 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")) + normalizedRatio := sdk.OneDec().Quo(priceDivLiqRatio).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 { diff --git a/x/cdp/keeper/seize.go b/x/cdp/keeper/seize.go index 2a63b14f..6ef5d3e3 100644 --- a/x/cdp/keeper/seize.go +++ b/x/cdp/keeper/seize.go @@ -109,10 +109,14 @@ func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, li if err != nil { return err } + priceDivLiqRatio := price.Price.Quo(liquidationRatio) + if priceDivLiqRatio.IsZero() { + priceDivLiqRatio = sdk.SmallestDec() + } // price = $0.5 // liquidation ratio = 1.5 // normalizedRatio = (1/(0.5/1.5)) = 3 - normalizedRatio := sdk.OneDec().Quo(price.Price.Quo(liquidationRatio)) + normalizedRatio := sdk.OneDec().Quo(priceDivLiqRatio) cdpsToLiquidate := k.GetAllCdpsByDenomAndRatio(ctx, denom, normalizedRatio) for _, c := range cdpsToLiquidate { err := k.SeizeCollateral(ctx, c) From decbbd4c00e61f32c9472a23a2d9e6bd4baa7f6c Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 16 Apr 2020 16:58:00 -0400 Subject: [PATCH 31/45] feat: only run one seed in determinism tests --- app/sim_test.go | 47 +++++++++++++++++++++-------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/app/sim_test.go b/app/sim_test.go index 3a00315b..ee0a5395 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -662,38 +662,33 @@ func TestAppStateDeterminism(t *testing.T) { config.OnOperation = false config.AllInvariants = false - numSeeds := 1 numTimesToRunPerSeed := 2 appHashList := make([]json.RawMessage, numTimesToRunPerSeed) - for i := 0; i < numSeeds; i++ { - config.Seed = rand.Int63() + for j := 0; j < numTimesToRunPerSeed; j++ { + logger := log.NewNopLogger() + db := dbm.NewMemDB() + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) - for j := 0; j < numTimesToRunPerSeed; j++ { - logger := log.NewNopLogger() - db := dbm.NewMemDB() - app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) + fmt.Printf( + "running non-determinism simulation; seed %d: attempt: %d/%d\n", + config.Seed, j+1, numTimesToRunPerSeed, + ) - fmt.Printf( - "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", - config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, + _, _, err := simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), + testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + ) + require.NoError(t, err) + + appHash := app.LastCommitID().Hash + appHashList[j] = appHash + + if j != 0 { + require.Equal( + t, appHashList[0], appHashList[j], + "non-determinism in seed %d: attempt: %d/%d\n", config.Seed, j+1, numTimesToRunPerSeed, ) - - _, _, err := simulation.SimulateFromSeed( - t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, - ) - require.NoError(t, err) - - appHash := app.LastCommitID().Hash - appHashList[j] = appHash - - if j != 0 { - require.Equal( - t, appHashList[0], appHashList[j], - "non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, - ) - } } } } From be5b12d9d73919d83000567d33f506cc5965fa33 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 16 Apr 2020 16:58:26 -0400 Subject: [PATCH 32/45] add make method for faster testing and sims --- Makefile | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 11c1c996..6a4624e9 100644 --- a/Makefile +++ b/Makefile @@ -109,18 +109,26 @@ link-check: # TODO tidy up cli tests to use same -Enable flag as simulations, or the other way round # TODO -mod=readonly ? # build dependency needed for cli tests -test-all: build +test-all: build test test-cli # basic app tests @go test ./app -v - # cli tests - @go test ./cli_test -tags cli_test -v -p 4 # basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks) @go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h # other sim tests @go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h @go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h @# AppStateDeterminism does not use Seed flag - @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -v -timeout 24h + @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h + +# run module tests and short simulations +test-basic: test + # basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks) + @go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m + # other sim tests + @go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m + @go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m + @# AppStateDeterminism does not use Seed flag + @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m test: @go test ./... @@ -128,6 +136,9 @@ test: test_dredd: rest_test/./run_all_tests_from_make.sh +test-cli: + @go test ./cli_test -tags cli_test -v -p 4 + # Kick start lots of sims on an AWS cluster. # This submits an AWS Batch job to run a lot of sims, each within a docker image. Results are uploaded to S3 start-remote-sims: @@ -143,4 +154,4 @@ start-remote-sims: -—job-definition kava-sim-master \ -—container-override environment=[{SIM_NAME=master-$(VERSION)}] -.PHONY: all build-linux install clean build test test-all start-remote-sims +.PHONY: all build-linux install clean build test test-cli test-all test_dredd test-basic start-remote-sims From 5ac44ff86e2c54c387f7b65ba73177bf5f7936ab Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 16 Apr 2020 17:05:54 -0400 Subject: [PATCH 33/45] fix: run cli tests before module tests --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6a4624e9..ab532ba0 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ link-check: # TODO tidy up cli tests to use same -Enable flag as simulations, or the other way round # TODO -mod=readonly ? # build dependency needed for cli tests -test-all: build test test-cli +test-all: build test-cli test # basic app tests @go test ./app -v # basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks) From 5ae0b76e0c05cd886598aa7496c819c0d6bda0b7 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 17 Apr 2020 14:05:36 -0400 Subject: [PATCH 34/45] [R4R] Bep3 sim changes (#442) * fix: choose claim amount as percentage * fix: lower asset supply to avoid overwhelming auctions --- x/bep3/simulation/genesis.go | 2 +- x/bep3/simulation/operations/msg.go | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/x/bep3/simulation/genesis.go b/x/bep3/simulation/genesis.go index e4af0003..c05dad1d 100644 --- a/x/bep3/simulation/genesis.go +++ b/x/bep3/simulation/genesis.go @@ -24,7 +24,7 @@ const ( ) var ( - MaxSupplyLimit = sdk.NewInt(10000000000000000) + MaxSupplyLimit = sdk.NewInt(1000000000000) Accs []simulation.Account ConsistentDenoms = [3]string{"bnb", "xrp", "btc"} ) diff --git a/x/bep3/simulation/operations/msg.go b/x/bep3/simulation/operations/msg.go index c04eb218..1fdaff6b 100644 --- a/x/bep3/simulation/operations/msg.go +++ b/x/bep3/simulation/operations/msg.go @@ -50,19 +50,12 @@ func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulat // Check that the sender has coins of this type availableAmount := ak.GetAccount(ctx, sender).GetCoins().AmountOf(asset.Denom) - if !availableAmount.IsPositive() { - return noOpMsg, nil, fmt.Errorf("available amount must be positive") + // Get an amount of coins between 0.1 and 2% of total coins + amount := availableAmount.Quo(sdk.NewInt(int64(simulation.RandIntBetween(r, 50, 1000)))) + if amount.IsZero() { + return simulation.NewOperationMsgBasic(bep3.ModuleName, fmt.Sprintf("no-operation (all funds exhausted for asset %s)", asset.Denom), "", false, nil), nil, nil } - - // Get a random amount of the available coins - amount, err := simulation.RandPositiveInt(r, availableAmount) - if err != nil { - return noOpMsg, nil, err - } - - // If we don't adjust the conversion factor, we'll be out of funds soon - adjustedAmount := amount.Int64() / int64(math.Pow10(8)) - coin := sdk.NewInt64Coin(asset.Denom, adjustedAmount) + coin := sdk.NewCoin(asset.Denom, amount) coins := sdk.NewCoins(coin) expectedIncome := coin.String() From 55747ed0b86d27113758146b4e622a810cd60fd3 Mon Sep 17 00:00:00 2001 From: jmahess <7819619+jmahess@users.noreply.github.com> Date: Fri, 17 Apr 2020 18:29:54 -0400 Subject: [PATCH 35/45] [R4R] Fix pricefeed sims so does not always go to zero (#434) * Generate pricefeed prices using a random walk at the beginning of sims Co-authored-by: rhuairahrighairigh Co-authored-by: John Maheswaran Co-authored-by: Ruaridh Co-authored-by: John Maheswaran Co-authored-by: Kevin Davis Co-authored-by: Kevin Davis Co-authored-by: Denali Marsh --- app/sim_test.go | 2 +- x/cdp/simulation/operations/msgs.go | 2 +- x/pricefeed/simulation/genesis.go | 70 +++++++----- x/pricefeed/simulation/operations/msg.go | 133 ++++++++++++++--------- 4 files changed, 124 insertions(+), 83 deletions(-) diff --git a/app/sim_test.go b/app/sim_test.go index 37ad51a3..40a0c5e4 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -304,7 +304,7 @@ func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOper }) return v }(nil), - pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper), + pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper, config.NumBlocks), }, { func(_ *rand.Rand) int { diff --git a/x/cdp/simulation/operations/msgs.go b/x/cdp/simulation/operations/msgs.go index dd1eb506..c4bacec8 100644 --- a/x/cdp/simulation/operations/msgs.go +++ b/x/cdp/simulation/operations/msgs.go @@ -156,7 +156,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s // repay debt 25% of the time if hasCoins(acc, randDebtParam.Denom) { - debt := (existingCDP.Principal.Add(existingCDP.AccumulatedFees)).AmountOf(randDebtParam.Denom) + debt := existingCDP.Principal.AmountOf(randDebtParam.Denom) maxRepay := acc.GetCoins().AmountOf(randDebtParam.Denom) payableDebt := debt.Sub(randDebtParam.DebtFloor) if maxRepay.GT(payableDebt) { diff --git a/x/pricefeed/simulation/genesis.go b/x/pricefeed/simulation/genesis.go index bcbbff01..272695aa 100644 --- a/x/pricefeed/simulation/genesis.go +++ b/x/pricefeed/simulation/genesis.go @@ -12,51 +12,61 @@ import ( pricefeed "github.com/kava-labs/kava/x/pricefeed/types" ) +var ( + // BaseAssets is a list of collateral asset denoms + BaseAssets = [3]string{"bnb", "xrp", "btc"} + QuoteAsset = "usd" +) + // RandomizedGenState generates a random GenesisState for pricefeed func RandomizedGenState(simState *module.SimulationState) { - // get the params with xrp, btc and bnb to usd - // getPricefeedSimulationParams is defined to return params with xrp:usd, btc:usd, bnb:usd - params := getPricefeedSimulationParams() - markets := []types.Market{} - genPrices := []types.PostedPrice{} - // chose one account to be the oracle - oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))] - for _, market := range params.Markets { - updatedMarket := types.Market{market.MarketID, market.BaseAsset, market.QuoteAsset, []sdk.AccAddress{oracle.Address}, true} - markets = append(markets, updatedMarket) - genPrice := types.PostedPrice{market.MarketID, oracle.Address, getInitialPrice(market.MarketID), simState.GenTimestamp.Add(time.Hour * 24)} - genPrices = append(genPrices, genPrice) - } - params = types.NewParams(markets) - pricefeedGenesis := types.NewGenesisState(params, genPrices) + pricefeedGenesis := loadPricefeedGenState(simState) fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, pricefeedGenesis)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(pricefeedGenesis) } -// getPricefeedSimulationParams returns the params with xrp:usd, btc:usd, bnb:usd -func getPricefeedSimulationParams() types.Params { - // SET UP THE PRICEFEED GENESIS STATE - pricefeedGenesis := pricefeed.GenesisState{ - Params: pricefeed.Params{ - Markets: []pricefeed.Market{ - pricefeed.Market{MarketID: "btc:usd", BaseAsset: "btc", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, - pricefeed.Market{MarketID: "xrp:usd", BaseAsset: "xrp", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, - pricefeed.Market{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, - }, - }, +// loadPricefeedGenState loads a valid pricefeed gen state +func loadPricefeedGenState(simState *module.SimulationState) pricefeed.GenesisState { + var markets []pricefeed.Market + var postedPrices []pricefeed.PostedPrice + for _, denom := range BaseAssets { + // Select an account to be the oracle + oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))] + + marketID := fmt.Sprintf("%s:%s", denom, QuoteAsset) + // Construct market for asset + market := pricefeed.Market{ + MarketID: marketID, + BaseAsset: denom, + QuoteAsset: QuoteAsset, + Oracles: []sdk.AccAddress{oracle.Address}, + Active: true, + } + + // Construct posted price for asset + postedPrice := pricefeed.PostedPrice{ + MarketID: market.MarketID, + OracleAddress: oracle.Address, + Price: getInitialPrice(marketID), + Expiry: simState.GenTimestamp.Add(time.Hour * 24), + } + markets = append(markets, market) + postedPrices = append(postedPrices, postedPrice) } - return pricefeedGenesis.Params + params := pricefeed.NewParams(markets) + return pricefeed.NewGenesisState(params, postedPrices) } // getInitialPrice gets the starting price for each of the base assets -func getInitialPrice(marketId string) (price sdk.Dec) { - switch marketId { +func getInitialPrice(marketID string) (price sdk.Dec) { + switch marketID { case "btc:usd": return sdk.MustNewDecFromStr("7000") case "bnb:usd": return sdk.MustNewDecFromStr("14") case "xrp:usd": return sdk.MustNewDecFromStr("0.2") + default: + return sdk.MustNewDecFromStr("20") // Catch future additional assets } - panic(fmt.Sprintf("Invalid marketId in getInitialPrice: %s\n", marketId)) } diff --git a/x/pricefeed/simulation/operations/msg.go b/x/pricefeed/simulation/operations/msg.go index bcac646d..385168a8 100644 --- a/x/pricefeed/simulation/operations/msg.go +++ b/x/pricefeed/simulation/operations/msg.go @@ -3,6 +3,7 @@ package operations import ( "fmt" "math/rand" + "sync" "time" "github.com/cosmos/cosmos-sdk/baseapp" @@ -15,44 +16,54 @@ import ( ) var ( - noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName) + noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName) + btcPrices = []sdk.Dec{} + bnbPrices = []sdk.Dec{} + xrpPrices = []sdk.Dec{} + genPrices sync.Once ) // SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price -func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation { +func SimulateMsgUpdatePrices(keeper keeper.Keeper, blocks int) simulation.Operation { // get a pricefeed handler handler := pricefeed.NewHandler(keeper) return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( simulation.OperationMsg, []simulation.FutureOperation, error) { + genPrices.Do(func() { + // generate a random walk for each asset exactly once, with observations equal to the number of blocks in the sim + for _, m := range keeper.GetMarkets(ctx) { + startPrice := getStartPrice(m.MarketID) + // allow prices to fluctuate from 10x GAINZ to 100x REKT + maxPrice := sdk.MustNewDecFromStr("10.0").Mul(startPrice) + minPrice := sdk.MustNewDecFromStr("0.01").Mul(startPrice) + previousPrice := startPrice + for i := 0; i < blocks; i++ { + increment := getIncrement(m.MarketID) + // note calling r instead of rand here breaks determinism + upDown := rand.Intn(2) + if upDown == 0 { + if previousPrice.Add(increment).GT(maxPrice) { + previousPrice = maxPrice + } else { + previousPrice = previousPrice.Add(increment) + } + } else { + if previousPrice.Sub(increment).LT(minPrice) { + previousPrice = minPrice + } else { + previousPrice = previousPrice.Sub(increment) + } + } + setPrice(m.MarketID, previousPrice) + } + } + }) - // OVERALL LOGIC: - // (1) RANDOMLY PICK AN ASSET OUT OF BNB AN BTC [TODO QUESTION - USDX IS EXCLUDED AS IT IS A STABLE DENOM - // (2) GET THE CURRENT PRICE OF THAT ASSET IN USD - // (3) GENERATE A RANDOM NUMBER IN THE RANGE 0.8-1.2 (UNIFORM DISTRIBUTION) - // (4) MULTIPLY THE CURRENT PRICE BY THE RANDOM NUMBER - // (5) POST THE NEW PRICE TO THE KEEPER - - // pick a random asset out of BNB and BTC randomMarket := pickRandomAsset(ctx, keeper, r) - marketID := randomMarket.MarketID - - // Get the current price of the asset - currentPrice, err := keeper.GetCurrentPrice(ctx, marketID) // Note this is marketID AND **NOT** just the base asset - if err != nil { - return noOpMsg, nil, fmt.Errorf("Error getting current price") - } - - // get the address for the account - // this address needs to be an oracle and also exist. genesis should add all the accounts as oracles. address := getRandomOracle(r, randomMarket) - - // generate a new random price based off the current price - price, err := pickNewRandomPrice(r, currentPrice.Price) - if err != nil { - return noOpMsg, nil, fmt.Errorf("Error picking random price") - } + price := pickNewRandomPrice(marketID, int(ctx.BlockHeight())) // get the expiry time based off the current time expiry := getExpiryTime(ctx) @@ -73,6 +84,51 @@ func SimulateMsgUpdatePrices(keeper keeper.Keeper) simulation.Operation { } } +func getStartPrice(marketID string) (startPrice sdk.Dec) { + switch marketID { + case "btc:usd": + return sdk.MustNewDecFromStr("7000") + case "bnb:usd": + return sdk.MustNewDecFromStr("15") + case "xrp:usd": + return sdk.MustNewDecFromStr("0.25") + } + return sdk.MustNewDecFromStr("100") +} + +func getIncrement(marketID string) (increment sdk.Dec) { + startPrice := getStartPrice(marketID) + divisor := sdk.MustNewDecFromStr("20") + increment = startPrice.Quo(divisor) + return increment +} + +func setPrice(marketID string, price sdk.Dec) { + switch marketID { + case "btc:usd": + btcPrices = append(btcPrices, price) + return + case "bnb:usd": + bnbPrices = append(bnbPrices, price) + return + case "xrp:usd": + xrpPrices = append(xrpPrices, price) + } + return +} + +func pickNewRandomPrice(marketID string, blockHeight int) (newPrice sdk.Dec) { + switch marketID { + case "btc:usd": + return btcPrices[blockHeight-1] + case "bnb:usd": + return bnbPrices[blockHeight-1] + case "xrp:usd": + return xrpPrices[blockHeight-1] + } + panic("invalid price request") +} + // getRandomOracle picks a random oracle from the list of oracles func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress { randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles)) @@ -98,31 +154,6 @@ func getExpiryTime(ctx sdk.Context) (t time.Time) { return t } -// pickNewRandomPrice picks a new random price given the current price -// It takes the current price then generates a random number to multiply it by to create variation while -// still being in the similar range. Random walk style. -func pickNewRandomPrice(r *rand.Rand, currentPrice sdk.Dec) (price sdk.Dec, err sdk.Error) { - // Pick random price - // this is in the range [0-0.4) because when added to 0.8 it gives a multiplier in the range 0.8-1.2 - got := sdk.MustNewDecFromStr("0.4") - - randomPriceMultiplier := simulation.RandomDecAmount(r, got) // get a random number - if err != nil { - fmt.Errorf("Error generating random price multiplier\n") - return sdk.ZeroDec(), err - } - // 0.8 offset corresponds to 80% of the the current price - offset := sdk.MustNewDecFromStr("0.8") - - // gives a result in range 0.8-1.2 inclusive, so the price can fluctuate from 80% to 120% of its current value - randomPriceMultiplier = randomPriceMultiplier.Add(offset) - - // multiply the current price by the price multiplier - price = randomPriceMultiplier.Mul(currentPrice) - // return the price - return price, nil -} - // submitMsg submits a message to the current instance of the keeper and returns a boolean whether the operation completed successfully or not func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { ctx, write := ctx.CacheContext() From 89bad17d9613d8b166eabedf8a1b1326ee3d0ba3 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Sat, 18 Apr 2020 10:20:29 -0400 Subject: [PATCH 36/45] fix: ci failing due to memory consumption (#446) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ab532ba0..b0f77847 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ link-check: # TODO tidy up cli tests to use same -Enable flag as simulations, or the other way round # TODO -mod=readonly ? # build dependency needed for cli tests -test-all: build test-cli test +test-all: build # basic app tests @go test ./app -v # basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks) From 0949a912cfe0e24b6502760be68b9cb84e24dfb1 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Sat, 18 Apr 2020 11:34:25 -0400 Subject: [PATCH 37/45] Choose simulation seed that runs without unbonding (#447) * fix: ci failing due to memory consumption * feat: choose seed that doesn't immediately exit * fix: don't attempt to draw too much debt * fix: remove comment --- Makefile | 23 +++++++++++------------ x/cdp/simulation/operations/msgs.go | 2 +- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index b0f77847..ec0884fa 100644 --- a/Makefile +++ b/Makefile @@ -112,28 +112,27 @@ link-check: test-all: build # basic app tests @go test ./app -v - # basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks) - @go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h + # basic simulation (seed "4" happens to not unbond all validators before reaching 100 blocks) + @go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h # other sim tests - @go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h - @go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h + @go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h + @go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h @# AppStateDeterminism does not use Seed flag - @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 2 -v -timeout 24h + @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed 4 -v -timeout 24h # run module tests and short simulations test-basic: test - # basic simulation (seed "2" happens to not unbond all validators before reaching 100 blocks) - @go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m + @go test ./app -run TestFullAppSimulation -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m # other sim tests - @go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m - @go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m + @go test ./app -run TestAppImportExport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m + @go test ./app -run TestAppSimulationAfterImport -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m @# AppStateDeterminism does not use Seed flag - @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 2 -v -timeout 2m + @go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=5 -BlockSize=200 -Seed 4 -v -timeout 2m test: @go test ./... -test_dredd: +test-rest: rest_test/./run_all_tests_from_make.sh test-cli: @@ -154,4 +153,4 @@ start-remote-sims: -—job-definition kava-sim-master \ -—container-override environment=[{SIM_NAME=master-$(VERSION)}] -.PHONY: all build-linux install clean build test test-cli test-all test_dredd test-basic start-remote-sims +.PHONY: all build-linux install clean build test test-cli test-all test-rest test-basic start-remote-sims diff --git a/x/cdp/simulation/operations/msgs.go b/x/cdp/simulation/operations/msgs.go index c4bacec8..4afe59b4 100644 --- a/x/cdp/simulation/operations/msgs.go +++ b/x/cdp/simulation/operations/msgs.go @@ -127,7 +127,7 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s // given the current collateral value, calculate how much debt we could add while maintaining a valid liquidation ratio debt := existingCDP.Principal.AmountOf(randDebtParam.Denom).Add(totalFees) maxTotalDebt := collateralValue.Quo(randCollateralParam.LiquidationRatio) - maxDebt := maxTotalDebt.Sub(sdk.NewDecFromInt(debt)).TruncateInt() + maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt() if maxDebt.LTE(sdk.OneInt()) { // debt in cdp is maxed out return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil From a573625df8a7bba774f4e317205e5ee76ef83cec Mon Sep 17 00:00:00 2001 From: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:35:58 -0400 Subject: [PATCH 38/45] [R4R] bump SDK version to v0.38.3 (#421) * bump SDK version to v0.38.3 Co-authored-by: Denali Marsh Co-authored-by: Kevin Davis Co-authored-by: Kevin Davis Co-authored-by: denalimarsh Co-authored-by: rhuairahrighairigh --- app/app.go | 109 ++- app/export.go | 28 +- app/params/doc.go | 19 + app/params/params.go | 15 + app/sim_test.go | 651 +++--------------- app/test_common.go | 4 + cli_test/test_helpers.go | 35 +- cmd/kvcli/main.go | 23 +- cmd/kvd/main.go | 4 +- go.mod | 17 +- go.sum | 409 ++++++++--- rest_test/setup/setuptest.go | 5 +- x/auction/alias.go | 26 +- x/auction/client/cli/query.go | 4 +- x/auction/client/cli/tx.go | 8 +- x/auction/genesis.go | 2 +- x/auction/handler.go | 15 +- x/auction/keeper/auctions.go | 103 +-- x/auction/keeper/auctions_test.go | 35 +- x/auction/keeper/bidding_test.go | 123 ++-- x/auction/keeper/invariants.go | 4 +- x/auction/keeper/keeper.go | 16 +- x/auction/keeper/querier.go | 26 +- x/auction/module.go | 59 +- x/auction/simulation/decoder.go | 5 +- x/auction/simulation/decoder_test.go | 13 +- x/auction/simulation/genesis.go | 6 +- x/auction/simulation/operations.go | 251 +++++++ x/auction/simulation/operations/msg.go | 177 ----- x/auction/simulation/params.go | 20 +- x/auction/types/auctions.go | 11 +- x/auction/types/errors.go | 126 +--- x/auction/types/expected_keepers.go | 10 +- x/auction/types/msg.go | 17 +- x/auction/types/params.go | 116 +++- x/bep3/abci_test.go | 14 +- x/bep3/alias.go | 19 - x/bep3/client/cli/query.go | 4 +- x/bep3/client/cli/tx.go | 14 +- x/bep3/client/rest/rest.go | 39 +- x/bep3/genesis.go | 8 +- x/bep3/handler.go | 32 +- x/bep3/handler_test.go | 50 +- x/bep3/keeper/asset.go | 38 +- x/bep3/keeper/keeper.go | 11 +- x/bep3/keeper/params.go | 8 +- x/bep3/keeper/params_test.go | 15 +- x/bep3/keeper/querier.go | 45 +- x/bep3/keeper/querier_test.go | 17 +- x/bep3/keeper/swap.go | 117 ++-- x/bep3/keeper/swap_test.go | 21 +- x/bep3/module.go | 59 +- x/bep3/simulation/decoder.go | 10 +- x/bep3/simulation/decoder_test.go | 17 +- x/bep3/simulation/genesis.go | 19 +- x/bep3/simulation/operations.go | 210 ++++++ x/bep3/simulation/operations/msg.go | 146 ---- x/bep3/simulation/params.go | 10 +- x/bep3/types/errors.go | 144 +--- x/bep3/types/expected_keepers.go | 10 +- x/bep3/types/msg.go | 107 +-- x/bep3/types/msg_test.go | 17 +- x/bep3/types/params.go | 110 ++- x/bep3/types/params_test.go | 6 +- x/bep3/types/querier.go | 12 +- x/bep3/types/swap.go | 33 +- x/bep3/types/swap_test.go | 15 +- x/cdp/alias.go | 36 +- x/cdp/client/cli/query.go | 10 +- x/cdp/client/cli/tx.go | 20 +- x/cdp/genesis.go | 2 +- x/cdp/handler.go | 40 +- x/cdp/handler_test.go | 13 +- x/cdp/keeper/auctions.go | 10 +- x/cdp/keeper/cdp.go | 55 +- x/cdp/keeper/cdp_test.go | 32 +- x/cdp/keeper/deposit.go | 37 +- x/cdp/keeper/deposit_test.go | 15 +- x/cdp/keeper/draw.go | 55 +- x/cdp/keeper/draw_test.go | 21 +- x/cdp/keeper/fees.go | 10 +- x/cdp/keeper/fees_test.go | 8 +- x/cdp/keeper/keeper.go | 9 +- x/cdp/keeper/keeper_bench_test.go | 2 +- x/cdp/keeper/querier.go | 70 +- x/cdp/keeper/savings.go | 7 +- x/cdp/keeper/seize.go | 14 +- x/cdp/keeper/seize_test.go | 5 +- x/cdp/module.go | 55 +- x/cdp/simulation/decoder.go | 5 +- x/cdp/simulation/decoder_test.go | 25 +- x/cdp/simulation/genesis.go | 10 +- .../{operations/msgs.go => operations.go} | 259 ++++--- x/cdp/simulation/{operations => }/utils.go | 2 +- .../simulation/{operations => }/utils_test.go | 6 +- x/cdp/types/errors.go | 145 +--- x/cdp/types/expected_keepers.go | 22 +- x/cdp/types/msg.go | 83 +-- x/cdp/types/msg_test.go | 30 +- x/cdp/types/params.go | 219 ++++-- x/kavadist/alias.go | 14 +- x/kavadist/handler.go | 10 +- x/kavadist/keeper/keeper.go | 9 +- x/kavadist/keeper/mint.go | 4 +- x/kavadist/module.go | 57 +- x/kavadist/simulation/decoder.go | 5 +- x/kavadist/simulation/decoder_test.go | 8 +- x/kavadist/simulation/params.go | 8 +- x/kavadist/types/errors.go | 11 - x/kavadist/types/expected_keepers.go | 4 +- x/kavadist/types/params.go | 38 +- x/pricefeed/alias.go | 19 +- x/pricefeed/client/cli/query.go | 3 +- x/pricefeed/client/cli/tx.go | 7 +- x/pricefeed/handler.go | 16 +- x/pricefeed/keeper/keeper.go | 121 ++-- x/pricefeed/keeper/params.go | 12 +- x/pricefeed/keeper/querier.go | 63 +- x/pricefeed/module.go | 59 +- x/pricefeed/simulation/decoder.go | 4 +- x/pricefeed/simulation/decoder_test.go | 11 +- x/pricefeed/simulation/genesis.go | 3 +- .../{operations/msg.go => operations.go} | 108 ++- x/pricefeed/simulation/params.go | 1 - x/pricefeed/types/errors.go | 56 +- x/pricefeed/types/msgs.go | 20 +- x/pricefeed/types/params.go | 19 +- x/validator-vesting/client/cli/query.go | 7 +- x/validator-vesting/keeper/keeper.go | 2 +- x/validator-vesting/keeper/keeper_test.go | 2 +- x/validator-vesting/keeper/querier.go | 19 +- x/validator-vesting/keeper/test_common.go | 8 +- x/validator-vesting/module.go | 58 +- x/validator-vesting/simulation/decoder.go | 5 +- .../simulation/decoder_test.go | 11 +- x/validator-vesting/simulation/genesis.go | 6 +- x/validator-vesting/test_common.go | 4 +- x/validator-vesting/types/expected_keepers.go | 8 +- .../types/validator_vesting_account.go | 99 ++- .../types/validator_vesting_account_test.go | 2 +- 140 files changed, 3247 insertions(+), 2875 deletions(-) create mode 100644 app/params/doc.go create mode 100644 app/params/params.go create mode 100644 x/auction/simulation/operations.go delete mode 100644 x/auction/simulation/operations/msg.go create mode 100644 x/bep3/simulation/operations.go delete mode 100644 x/bep3/simulation/operations/msg.go rename x/cdp/simulation/{operations/msgs.go => operations.go} (50%) rename x/cdp/simulation/{operations => }/utils.go (95%) rename x/cdp/simulation/{operations => }/utils_test.go (81%) delete mode 100644 x/kavadist/types/errors.go rename x/pricefeed/simulation/{operations/msg.go => operations.go} (61%) diff --git a/app/app.go b/app/app.go index 7d1a4bec..151deeaa 100644 --- a/app/app.go +++ b/app/app.go @@ -12,12 +12,13 @@ import ( validatorvesting "github.com/kava-labs/kava/x/validator-vesting" abci "github.com/tendermint/tendermint/abci/types" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/libs/log" + tmos "github.com/tendermint/tendermint/libs/os" dbm "github.com/tendermint/tm-db" bam "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" @@ -26,6 +27,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/crisis" distr "github.com/cosmos/cosmos-sdk/x/distribution" + "github.com/cosmos/cosmos-sdk/x/evidence" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" @@ -61,6 +63,7 @@ var ( crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, supply.AppModuleBasic{}, + evidence.AppModuleBasic{}, auction.AppModuleBasic{}, cdp.AppModuleBasic{}, pricefeed.AppModuleBasic{}, @@ -86,7 +89,10 @@ var ( } ) -// Extended ABCI application +// Verify app interface at compile time +var _ simapp.App = (*App)(nil) + +// App represents an extended ABCI application type App struct { *bam.BaseApp cdc *codec.Codec @@ -108,6 +114,7 @@ type App struct { govKeeper gov.Keeper crisisKeeper crisis.Keeper paramsKeeper params.Keeper + evidenceKeeper evidence.Keeper vvKeeper validatorvesting.Keeper auctionKeeper auction.Keeper cdpKeeper cdp.Keeper @@ -136,7 +143,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, keys := sdk.NewKVStoreKeys( bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, - gov.StoreKey, params.StoreKey, validatorvesting.StoreKey, + gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey, auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey, kavadist.StoreKey, ) @@ -151,7 +158,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, } // init params keeper and subspaces - app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey], params.DefaultCodespace) + app.paramsKeeper = params.NewKeeper(app.cdc, keys[params.StoreKey], tkeys[params.TStoreKey]) authSubspace := app.paramsKeeper.Subspace(auth.DefaultParamspace) bankSubspace := app.paramsKeeper.Subspace(bank.DefaultParamspace) stakingSubspace := app.paramsKeeper.Subspace(staking.DefaultParamspace) @@ -159,6 +166,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, distrSubspace := app.paramsKeeper.Subspace(distr.DefaultParamspace) slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace) govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable()) + evidenceSubspace := app.paramsKeeper.Subspace(evidence.DefaultParamspace) crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace) cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace) @@ -171,51 +179,68 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, app.cdc, keys[auth.StoreKey], authSubspace, - auth.ProtoBaseAccount) + auth.ProtoBaseAccount, + ) app.bankKeeper = bank.NewBaseKeeper( app.accountKeeper, bankSubspace, - bank.DefaultCodespace, - app.ModuleAccountAddrs()) + app.ModuleAccountAddrs(), + ) app.supplyKeeper = supply.NewKeeper( app.cdc, keys[supply.StoreKey], app.accountKeeper, app.bankKeeper, - mAccPerms) + mAccPerms, + ) stakingKeeper := staking.NewKeeper( app.cdc, keys[staking.StoreKey], app.supplyKeeper, stakingSubspace, - staking.DefaultCodespace) + ) app.mintKeeper = mint.NewKeeper( app.cdc, keys[mint.StoreKey], mintSubspace, &stakingKeeper, app.supplyKeeper, - auth.FeeCollectorName) + auth.FeeCollectorName, + ) app.distrKeeper = distr.NewKeeper( app.cdc, keys[distr.StoreKey], distrSubspace, &stakingKeeper, app.supplyKeeper, - distr.DefaultCodespace, auth.FeeCollectorName, - app.ModuleAccountAddrs()) + app.ModuleAccountAddrs(), + ) app.slashingKeeper = slashing.NewKeeper( app.cdc, keys[slashing.StoreKey], &stakingKeeper, slashingSubspace, - slashing.DefaultCodespace) + ) app.crisisKeeper = crisis.NewKeeper( crisisSubspace, invCheckPeriod, app.supplyKeeper, - auth.FeeCollectorName) + auth.FeeCollectorName, + ) + + // create evidence keeper with router + evidenceKeeper := evidence.NewKeeper( + app.cdc, + keys[evidence.StoreKey], + evidenceSubspace, + &app.stakingKeeper, + app.slashingKeeper, + ) + evidenceRouter := evidence.NewRouter() + evidenceKeeper.SetRouter(evidenceRouter) + app.evidenceKeeper = *evidenceKeeper + govRouter := gov.NewRouter() govRouter. AddRoute(gov.RouterKey, gov.ProposalHandler). @@ -227,25 +252,28 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, govSubspace, app.supplyKeeper, &stakingKeeper, - gov.DefaultCodespace, - govRouter) + govRouter, + ) + app.vvKeeper = validatorvesting.NewKeeper( app.cdc, keys[validatorvesting.StoreKey], app.accountKeeper, app.bankKeeper, app.supplyKeeper, - &stakingKeeper) + &stakingKeeper, + ) app.pricefeedKeeper = pricefeed.NewKeeper( app.cdc, keys[pricefeed.StoreKey], pricefeedSubspace, - pricefeed.DefaultCodespace) + ) app.auctionKeeper = auction.NewKeeper( app.cdc, keys[auction.StoreKey], app.supplyKeeper, - auctionSubspace) + auctionSubspace, + ) app.cdpKeeper = cdp.NewKeeper( app.cdc, keys[cdp.StoreKey], @@ -254,19 +282,18 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, 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 @@ -282,16 +309,17 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, bank.NewAppModule(app.bankKeeper, app.accountKeeper), crisis.NewAppModule(&app.crisisKeeper), supply.NewAppModule(app.supplyKeeper, app.accountKeeper), - distr.NewAppModule(app.distrKeeper, app.supplyKeeper), - gov.NewAppModule(app.govKeeper, app.supplyKeeper), + gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), mint.NewAppModule(app.mintKeeper), - slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), + slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper), + distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper), staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), + evidence.NewAppModule(app.evidenceKeeper), validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper), - auction.NewAppModule(app.auctionKeeper, app.supplyKeeper), - cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper, app.supplyKeeper), - pricefeed.NewAppModule(app.pricefeedKeeper), - bep3.NewAppModule(app.bep3Keeper, app.supplyKeeper), + auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper), + cdp.NewAppModule(app.cdpKeeper, app.accountKeeper, app.pricefeedKeeper, app.supplyKeeper), + pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper), + bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), ) @@ -306,7 +334,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, auth.ModuleName, // loads all accounts - should run before any module with a module account validatorvesting.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, - gov.ModuleName, mint.ModuleName, + gov.ModuleName, mint.ModuleName, evidence.ModuleName, pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok? supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis crisis.ModuleName, // runs the invariants at genesis - should run after other modules @@ -325,15 +353,15 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper), bank.NewAppModule(app.bankKeeper, app.accountKeeper), supply.NewAppModule(app.supplyKeeper, app.accountKeeper), - gov.NewAppModule(app.govKeeper, app.supplyKeeper), + gov.NewAppModule(app.govKeeper, app.accountKeeper, app.supplyKeeper), mint.NewAppModule(app.mintKeeper), - distr.NewAppModule(app.distrKeeper, app.supplyKeeper), + distr.NewAppModule(app.distrKeeper, app.accountKeeper, app.supplyKeeper, app.stakingKeeper), staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper), - slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), - pricefeed.NewAppModule(app.pricefeedKeeper), - cdp.NewAppModule(app.cdpKeeper, app.pricefeedKeeper, app.supplyKeeper), - auction.NewAppModule(app.auctionKeeper, app.supplyKeeper), - bep3.NewAppModule(app.bep3Keeper, app.supplyKeeper), + slashing.NewAppModule(app.slashingKeeper, app.accountKeeper, app.stakingKeeper), + pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper), + cdp.NewAppModule(app.cdpKeeper, app.accountKeeper, app.pricefeedKeeper, app.supplyKeeper), + auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper), + bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), ) @@ -353,7 +381,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, if loadLatest { err := app.LoadLatestVersion(app.keys[bam.MainStoreKey]) if err != nil { - cmn.Exit(err.Error()) + tmos.Exit(err.Error()) } } @@ -422,6 +450,11 @@ func (app *App) Codec() *codec.Codec { return app.cdc } +// SimulationManager implements the SimulationApp interface +func (app *App) SimulationManager() *module.SimulationManager { + return app.sm +} + // GetMaccPerms returns a mapping of the application's module account permissions. func GetMaccPerms() map[string][]string { perms := make(map[string][]string) diff --git a/app/export.go b/app/export.go index 0b6c150c..e4a5725a 100644 --- a/app/export.go +++ b/app/export.go @@ -13,10 +13,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/staking" ) -// export the state of the app for a genesis file -func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string) ( - appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { - +// ExportAppStateAndValidators export the state of the app for a genesis file +func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList []string, +) (appState json.RawMessage, validators []tmtypes.GenesisValidator, err error) { // as if they could withdraw from the start of the next block ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) @@ -25,7 +24,6 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList [] } genState := app.mm.ExportGenesis(ctx) - appState, err = codec.MarshalJSONIndent(app.cdc, genState) if err != nil { return nil, nil, err @@ -35,6 +33,8 @@ func (app *App) ExportAppStateAndValidators(forZeroHeight bool, jailWhiteList [] } // prepare for fresh start at zero height +// NOTE zero height genesis is a temporary feature which will be deprecated +// in favour of export at a block height func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string) { applyWhiteList := false @@ -60,14 +60,24 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string // withdraw all validator commission app.stakingKeeper.IterateValidators(ctx, func(_ int64, val staking.ValidatorI) (stop bool) { - _, _ = app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + accumCommission := app.distrKeeper.GetValidatorAccumulatedCommission(ctx, val.GetOperator()) + if accumCommission.IsZero() { + return false + } + _, err := app.distrKeeper.WithdrawValidatorCommission(ctx, val.GetOperator()) + if err != nil { + log.Fatal(err) + } return false }) // withdraw all delegator rewards dels := app.stakingKeeper.GetAllDelegations(ctx) for _, delegation := range dels { - _, _ = app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) + _, err := app.distrKeeper.WithdrawDelegationRewards(ctx, delegation.DelegatorAddress, delegation.ValidatorAddress) + if err != nil { + log.Fatal(err) + } } // clear validator slash events @@ -86,7 +96,7 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string // donate any unwithdrawn outstanding reward fraction tokens to the community pool scraps := app.distrKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) feePool := app.distrKeeper.GetFeePool(ctx) - feePool.CommunityPool = feePool.CommunityPool.Add(scraps) + feePool.CommunityPool = feePool.CommunityPool.Add(scraps...) app.distrKeeper.SetFeePool(ctx, feePool) app.distrKeeper.Hooks().AfterValidatorCreated(ctx, val.GetOperator()) @@ -128,7 +138,6 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string iter := sdk.KVStoreReversePrefixIterator(store, staking.ValidatorsKey) counter := int16(0) - var valConsAddrs []sdk.ConsAddress for ; iter.Valid(); iter.Next() { addr := sdk.ValAddress(iter.Key()[1:]) validator, found := app.stakingKeeper.GetValidator(ctx, addr) @@ -137,7 +146,6 @@ func (app *App) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []string } validator.UnbondingHeight = 0 - valConsAddrs = append(valConsAddrs, validator.ConsAddress()) if applyWhiteList && !whiteListMap[addr.String()] { validator.Jailed = true } diff --git a/app/params/doc.go b/app/params/doc.go new file mode 100644 index 00000000..c8efce8f --- /dev/null +++ b/app/params/doc.go @@ -0,0 +1,19 @@ +/* +Package params defines the simulation parameters for the Kava app. + +It contains the default weights used for each transaction used on the module's +simulation. These weights define the chance for a transaction to be simulated at +any gived operation. + +You can repace the default values for the weights by providing a params.json +file with the weights defined for each of the transaction operations: + + { + "op_weight_msg_send": 60, + "op_weight_msg_delegate": 100, + } + +In the example above, the `MsgSend` has 60% chance to be simulated, while the +`MsgDelegate` will always be simulated. +*/ +package params diff --git a/app/params/params.go b/app/params/params.go new file mode 100644 index 00000000..d74f7623 --- /dev/null +++ b/app/params/params.go @@ -0,0 +1,15 @@ +package params + +// Simulation parameter constants +const ( + StakePerAccount = "stake_per_account" + InitiallyBondedValidators = "initially_bonded_validators" +) + +// Default simulation operation weights for messages and gov proposals +const ( + DefaultWeightMsgPlaceBid int = 100 + DefaultWeightMsgCreateAtomicSwap int = 100 + DefaultWeightMsgUpdatePrices int = 100 + DefaultWeightMsgCdp int = 100 +) diff --git a/app/sim_test.go b/app/sim_test.go index c4fb6bcd..d7a48802 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -3,69 +3,35 @@ package app import ( "encoding/json" "fmt" - "io/ioutil" - "math/rand" "os" "testing" "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/log" dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/simapp" + "github.com/cosmos/cosmos-sdk/simapp/helpers" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - authsimops "github.com/cosmos/cosmos-sdk/x/auth/simulation/operations" - banksimops "github.com/cosmos/cosmos-sdk/x/bank/simulation/operations" distr "github.com/cosmos/cosmos-sdk/x/distribution" - distrsimops "github.com/cosmos/cosmos-sdk/x/distribution/simulation/operations" "github.com/cosmos/cosmos-sdk/x/gov" - govsimops "github.com/cosmos/cosmos-sdk/x/gov/simulation/operations" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/params" - paramsimops "github.com/cosmos/cosmos-sdk/x/params/simulation/operations" "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/cosmos/cosmos-sdk/x/slashing" - slashingsimops "github.com/cosmos/cosmos-sdk/x/slashing/simulation/operations" "github.com/cosmos/cosmos-sdk/x/staking" - stakingsimops "github.com/cosmos/cosmos-sdk/x/staking/simulation/operations" "github.com/cosmos/cosmos-sdk/x/supply" - - auctionsimops "github.com/kava-labs/kava/x/auction/simulation/operations" - bep3simops "github.com/kava-labs/kava/x/bep3/simulation/operations" - cdpsimops "github.com/kava-labs/kava/x/cdp/simulation/operations" - pricefeedsimops "github.com/kava-labs/kava/x/pricefeed/simulation/operations" ) -// Simulation parameter constants -const ( - StakePerAccount = "stake_per_account" - InitiallyBondedValidators = "initially_bonded_validators" - OpWeightDeductFee = "op_weight_deduct_fee" - OpWeightMsgSend = "op_weight_msg_send" - OpWeightSingleInputMsgMultiSend = "op_weight_single_input_msg_multisend" - OpWeightMsgSetWithdrawAddress = "op_weight_msg_set_withdraw_address" - OpWeightMsgWithdrawDelegationReward = "op_weight_msg_withdraw_delegation_reward" - OpWeightMsgWithdrawValidatorCommission = "op_weight_msg_withdraw_validator_commission" - OpWeightSubmitVotingSlashingTextProposal = "op_weight_submit_voting_slashing_text_proposal" - OpWeightSubmitVotingSlashingCommunitySpendProposal = "op_weight_submit_voting_slashing_community_spend_proposal" - OpWeightSubmitVotingSlashingParamChangeProposal = "op_weight_submit_voting_slashing_param_change_proposal" - OpWeightMsgDeposit = "op_weight_msg_deposit" - OpWeightMsgCreateValidator = "op_weight_msg_create_validator" - OpWeightMsgEditValidator = "op_weight_msg_edit_validator" - OpWeightMsgDelegate = "op_weight_msg_delegate" - OpWeightMsgUndelegate = "op_weight_msg_undelegate" - OpWeightMsgBeginRedelegate = "op_weight_msg_begin_redelegate" - OpWeightMsgUnjail = "op_weight_msg_unjail" - OpWeightMsgPlaceBid = "op_weight_msg_place_bid" - OpWeightMsgPricefeed = "op_weight_msg_pricefeed" - OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_Swap" - OpWeightMsgCdp = "op_weight_msg_cdp" -) +type StoreKeysPrefixes struct { + A sdk.StoreKey + B sdk.StoreKey + Prefixes [][]byte +} // TestMain runs setup and teardown code before all tests. func TestMain(m *testing.M) { @@ -75,251 +41,11 @@ func TestMain(m *testing.M) { config.Seal() // load the values from simulation specific flags simapp.GetSimulatorFlags() - // run tests exitCode := m.Run() os.Exit(exitCode) } -func testAndRunTxs(app *App, config simulation.Config) []simulation.WeightedOperation { - ap := make(simulation.AppParams) - - paramChanges := app.sm.GenerateParamChanges(config.Seed) - - if config.ParamsFile != "" { - bz, err := ioutil.ReadFile(config.ParamsFile) - if err != nil { - panic(err) - } - - app.cdc.MustUnmarshalJSON(bz, &ap) - } - - // nolint: govet - return []simulation.WeightedOperation{ - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightDeductFee, &v, nil, - func(_ *rand.Rand) { - v = 5 - }) - return v - }(nil), - authsimops.SimulateDeductFee(app.accountKeeper, app.supplyKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgSend, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - banksimops.SimulateMsgSend(app.accountKeeper, app.bankKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightSingleInputMsgMultiSend, &v, nil, - func(_ *rand.Rand) { - v = 10 - }) - return v - }(nil), - banksimops.SimulateSingleInputMsgMultiSend(app.accountKeeper, app.bankKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgSetWithdrawAddress, &v, nil, - func(_ *rand.Rand) { - v = 50 - }) - return v - }(nil), - distrsimops.SimulateMsgSetWithdrawAddress(app.distrKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgWithdrawDelegationReward, &v, nil, - func(_ *rand.Rand) { - v = 50 - }) - return v - }(nil), - distrsimops.SimulateMsgWithdrawDelegatorReward(app.distrKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgWithdrawValidatorCommission, &v, nil, - func(_ *rand.Rand) { - v = 50 - }) - return v - }(nil), - distrsimops.SimulateMsgWithdrawValidatorCommission(app.distrKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingTextProposal, &v, nil, - func(_ *rand.Rand) { - v = 5 - }) - return v - }(nil), - govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, govsimops.SimulateTextProposalContent), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingCommunitySpendProposal, &v, nil, - func(_ *rand.Rand) { - v = 5 - }) - return v - }(nil), - govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, distrsimops.SimulateCommunityPoolSpendProposalContent(app.distrKeeper)), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightSubmitVotingSlashingParamChangeProposal, &v, nil, - func(_ *rand.Rand) { - v = 5 - }) - return v - }(nil), - govsimops.SimulateSubmittingVotingAndSlashingForProposal(app.govKeeper, paramsimops.SimulateParamChangeProposalContent(paramChanges)), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgDeposit, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - govsimops.SimulateMsgDeposit(app.govKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgCreateValidator, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - stakingsimops.SimulateMsgCreateValidator(app.accountKeeper, app.stakingKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgEditValidator, &v, nil, - func(_ *rand.Rand) { - v = 5 - }) - return v - }(nil), - stakingsimops.SimulateMsgEditValidator(app.stakingKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgDelegate, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - stakingsimops.SimulateMsgDelegate(app.accountKeeper, app.stakingKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgUndelegate, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - stakingsimops.SimulateMsgUndelegate(app.accountKeeper, app.stakingKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgBeginRedelegate, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - stakingsimops.SimulateMsgBeginRedelegate(app.accountKeeper, app.stakingKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgUnjail, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - slashingsimops.SimulateMsgUnjail(app.slashingKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgPlaceBid, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - auctionsimops.SimulateMsgPlaceBid(app.accountKeeper, app.auctionKeeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgCreateAtomicSwap, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - bep3simops.SimulateMsgCreateAtomicSwap(app.accountKeeper, app.bep3Keeper), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgPricefeed, &v, nil, - func(_ *rand.Rand) { - v = 100 - }) - return v - }(nil), - pricefeedsimops.SimulateMsgUpdatePrices(app.pricefeedKeeper, config.NumBlocks), - }, - { - func(_ *rand.Rand) int { - var v int - ap.GetOrGenerate(app.cdc, OpWeightMsgCdp, &v, nil, - func(_ *rand.Rand) { - v = 100 // TODO - }) - return v - }(nil), - cdpsimops.SimulateMsgCdp(app.accountKeeper, app.cdpKeeper, app.pricefeedKeeper), - }, - } -} - // fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of // an IAVLStore for faster simulation speed. func fauxMerkleModeOpt(bapp *baseapp.BaseApp) { @@ -332,197 +58,96 @@ func interBlockCacheOpt() func(*baseapp.BaseApp) { return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager()) } -// Profile with: -// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/GaiaApp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out -func BenchmarkFullAppSimulation(b *testing.B) { - logger := log.NewNopLogger() - config := simapp.NewConfigFromFlags() - - var db dbm.DB - dir, _ := ioutil.TempDir("", "goleveldb-app-sim") - db, _ = sdk.NewLevelDB("Simulation", dir) - defer func() { - db.Close() - _ = os.RemoveAll(dir) - }() - - app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) - - // Run randomized simulation - // TODO: parameterize numbers, save for a later PR - _, simParams, simErr := simulation.SimulateFromSeed( - b, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, - ) - - // export state and params before the simulation error is checked - if config.ExportStatePath != "" { - if err := ExportStateToJSON(app, config.ExportStatePath); err != nil { - fmt.Println(err) - b.Fail() - } - } - - if config.ExportParamsPath != "" { - if err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil { - fmt.Println(err) - b.Fail() - } - } - - if simErr != nil { - fmt.Println(simErr) - b.FailNow() - } - - if config.Commit { - fmt.Println("\nGoLevelDB Stats") - fmt.Println(db.Stats()["leveldb.stats"]) - fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) - } -} - -// TestFullAppSimulation runs a standard simulation of the app, modified by cmd line flag values. func TestFullAppSimulation(t *testing.T) { - if !simapp.FlagEnabledValue { + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") + if skip { t.Skip("skipping application simulation") } - - var logger log.Logger - config := simapp.NewConfigFromFlags() - - if simapp.FlagVerboseValue { - logger = log.TestingLogger() - } else { - logger = log.NewNopLogger() - } - - var db dbm.DB - dir, _ := ioutil.TempDir("", "goleveldb-app-sim") - db, _ = sdk.NewLevelDB("Simulation", dir) + require.NoError(t, err, "simulation setup failed") defer func() { db.Close() - _ = os.RemoveAll(dir) + require.NoError(t, os.RemoveAll(dir)) }() app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) - require.Equal(t, "kava", app.Name()) + require.Equal(t, appName, app.Name()) - // Run randomized simulation + // run randomized simulation _, simParams, simErr := simulation.SimulateFromSeed( - t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, - ) - - // export state and params before the simulation error is checked - if config.ExportStatePath != "" { - err := ExportStateToJSON(app, config.ExportStatePath) - require.NoError(t, err) - } - - if config.ExportParamsPath != "" { - err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath) - require.NoError(t, err) - } - - require.NoError(t, simErr) - - if config.Commit { - // for memdb: - // fmt.Println("Database Size", db.Stats()["database.size"]) - fmt.Println("\nGoLevelDB Stats") - fmt.Println(db.Stats()["leveldb.stats"]) - fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) - } -} - -// TestAppImportExport runs a simulation, exports the state, imports it, then checks the db state is same after import as it was before export. -func TestAppImportExport(t *testing.T) { - if !simapp.FlagEnabledValue { - t.Skip("skipping application import/export simulation") - } - - var logger log.Logger - config := simapp.NewConfigFromFlags() - - if simapp.FlagVerboseValue { - logger = log.TestingLogger() - } else { - logger = log.NewNopLogger() - } - - var db dbm.DB - dir, _ := ioutil.TempDir("", "goleveldb-app-sim") - db, _ = sdk.NewLevelDB("Simulation", dir) - - defer func() { - db.Close() - _ = os.RemoveAll(dir) - }() - - app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) - require.Equal(t, "kava", app.Name()) - - // Run randomized simulation - _, simParams, simErr := simulation.SimulateFromSeed( - t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()), + simapp.SimulationOperations(app, app.Codec(), config), + app.ModuleAccountAddrs(), config, ) // export state and simParams before the simulation error is checked - if config.ExportStatePath != "" { - err := ExportStateToJSON(app, config.ExportStatePath) - require.NoError(t, err) - } - - if config.ExportParamsPath != "" { - err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath) - require.NoError(t, err) - } - + err = simapp.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) require.NoError(t, simErr) if config.Commit { - // for memdb: - // fmt.Println("Database Size", db.Stats()["database.size"]) - fmt.Println("\nGoLevelDB Stats") - fmt.Println(db.Stats()["leveldb.stats"]) - fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + simapp.PrintStats(db) + } +} + +func TestAppImportExport(t *testing.T) { + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") + if skip { + t.Skip("skipping application import/export simulation") + } + require.NoError(t, err, "simulation setup failed") + + defer func() { + db.Close() + require.NoError(t, os.RemoveAll(dir)) + }() + + app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) + require.Equal(t, appName, app.Name()) + + // Run randomized simulation + _, simParams, simErr := simulation.SimulateFromSeed( + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()), + simapp.SimulationOperations(app, app.Codec(), config), + app.ModuleAccountAddrs(), config, + ) + + // export state and simParams before the simulation error is checked + err = simapp.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) + require.NoError(t, simErr) + + if config.Commit { + simapp.PrintStats(db) } fmt.Printf("exporting genesis...\n") appState, _, err := app.ExportAppStateAndValidators(false, []string{}) require.NoError(t, err) + fmt.Printf("importing genesis...\n") - newDir, _ := ioutil.TempDir("", "goleveldb-app-sim-2") - newDB, _ := sdk.NewLevelDB("Simulation-2", dir) + _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") + require.NoError(t, err, "simulation setup failed") defer func() { newDB.Close() - _ = os.RemoveAll(newDir) + require.NoError(t, os.RemoveAll(newDir)) }() newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) - require.Equal(t, "kava", newApp.Name()) + require.Equal(t, appName, newApp.Name()) - var genesisState simapp.GenesisState - err = app.cdc.UnmarshalJSON(appState, &genesisState) + var genesisState GenesisState + err = app.Codec().UnmarshalJSON(appState, &genesisState) require.NoError(t, err) + ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) ctxB := newApp.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) newApp.mm.InitGenesis(ctxB, genesisState) fmt.Printf("comparing stores...\n") - ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()}) - - type StoreKeysPrefixes struct { - A sdk.StoreKey - B sdk.StoreKey - Prefixes [][]byte - } storeKeysPrefixes := []StoreKeysPrefixes{ {app.keys[baseapp.MainStoreKey], newApp.keys[baseapp.MainStoreKey], [][]byte{}}, @@ -539,118 +164,84 @@ func TestAppImportExport(t *testing.T) { {app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}}, } - for _, storeKeysPrefix := range storeKeysPrefixes { - storeKeyA := storeKeysPrefix.A - storeKeyB := storeKeysPrefix.B - prefixes := storeKeysPrefix.Prefixes + for _, skp := range storeKeysPrefixes { + storeA := ctxA.KVStore(skp.A) + storeB := ctxB.KVStore(skp.B) - storeA := ctxA.KVStore(storeKeyA) - storeB := ctxB.KVStore(storeKeyB) - - failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, prefixes) + failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes) require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare") - fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), storeKeyA, storeKeyB) - require.Len(t, failedKVAs, 0, simapp.GetSimulationLog(storeKeyA.Name(), app.sm.StoreDecoders, app.cdc, failedKVAs, failedKVBs)) + fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B) + require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, app.Codec(), failedKVAs, failedKVBs)) } } -// TestAppSimulationAfterImport runs a simulation, exports it, imports it and runs another simulation. func TestAppSimulationAfterImport(t *testing.T) { - if !simapp.FlagEnabledValue { + config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation") + if skip { t.Skip("skipping application simulation after import") } - - var logger log.Logger - config := simapp.NewConfigFromFlags() - - if simapp.FlagVerboseValue { - logger = log.TestingLogger() - } else { - logger = log.NewNopLogger() - } - - dir, _ := ioutil.TempDir("", "goleveldb-app-sim") - db, _ := sdk.NewLevelDB("Simulation", dir) + require.NoError(t, err, "simulation setup failed") defer func() { db.Close() - _ = os.RemoveAll(dir) + require.NoError(t, os.RemoveAll(dir)) }() app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) - require.Equal(t, "kava", app.Name()) + require.Equal(t, appName, app.Name()) - // Run randomized simulation // Run randomized simulation stopEarly, simParams, simErr := simulation.SimulateFromSeed( - t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()), + simapp.SimulationOperations(app, app.Codec(), config), + app.ModuleAccountAddrs(), config, ) - // export state and params before the simulation error is checked - if config.ExportStatePath != "" { - err := ExportStateToJSON(app, config.ExportStatePath) - require.NoError(t, err) - } - - if config.ExportParamsPath != "" { - err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath) - require.NoError(t, err) - } - + // export state and simParams before the simulation error is checked + err = simapp.CheckExportSimulation(app, config, simParams) + require.NoError(t, err) require.NoError(t, simErr) if config.Commit { - // for memdb: - // fmt.Println("Database Size", db.Stats()["database.size"]) - fmt.Println("\nGoLevelDB Stats") - fmt.Println(db.Stats()["leveldb.stats"]) - fmt.Println("GoLevelDB cached block size", db.Stats()["leveldb.cachedblock"]) + simapp.PrintStats(db) } if stopEarly { - // we can't export or import a zero-validator genesis - fmt.Printf("We can't export or import a zero-validator genesis, exiting test...\n") + fmt.Println("can't export or import a zero-validator genesis, exiting test...") return } - fmt.Printf("Exporting genesis...\n") + fmt.Printf("exporting genesis...\n") appState, _, err := app.ExportAppStateAndValidators(true, []string{}) - if err != nil { - panic(err) - } + require.NoError(t, err) - fmt.Printf("Importing genesis...\n") + fmt.Printf("importing genesis...\n") - newDir, _ := ioutil.TempDir("", "goleveldb-app-sim-2") - newDB, _ := sdk.NewLevelDB("Simulation-2", dir) + _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") + require.NoError(t, err, "simulation setup failed") defer func() { newDB.Close() - _ = os.RemoveAll(newDir) + require.NoError(t, os.RemoveAll(newDir)) }() - newApp := NewApp(log.NewNopLogger(), newDB, nil, true, 0, fauxMerkleModeOpt) - require.Equal(t, "kava", newApp.Name()) + newApp := NewApp(log.NewNopLogger(), newDB, nil, true, simapp.FlagPeriodValue, fauxMerkleModeOpt) + require.Equal(t, appName, newApp.Name()) newApp.InitChain(abci.RequestInitChain{ AppStateBytes: appState, }) - // Run randomized simulation on imported app _, _, err = simulation.SimulateFromSeed( - t, os.Stdout, newApp.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(newApp, config), newApp.ModuleAccountAddrs(), config, + t, os.Stdout, newApp.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()), + simapp.SimulationOperations(newApp, newApp.Codec(), config), + newApp.ModuleAccountAddrs(), config, ) - require.NoError(t, err) } -// TODO: Make another test for the fuzzer itself, which just has noOp txs -// and doesn't depend on the application. -// TestAppStateDeterminism runs several sims with the same seed and checks the states are equal. func TestAppStateDeterminism(t *testing.T) { if !simapp.FlagEnabledValue { t.Skip("skipping application simulation") @@ -661,6 +252,7 @@ func TestAppStateDeterminism(t *testing.T) { config.ExportParamsPath = "" config.OnOperation = false config.AllInvariants = false + config.ChainID = helpers.SimAppChainID numTimesToRunPerSeed := 2 appHashList := make([]json.RawMessage, numTimesToRunPerSeed) @@ -676,8 +268,9 @@ func TestAppStateDeterminism(t *testing.T) { ) _, _, err := simulation.SimulateFromSeed( - t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, + t, os.Stdout, app.BaseApp, simapp.AppStateFn(app.Codec(), app.SimulationManager()), + simapp.SimulationOperations(app, app.Codec(), config), + app.ModuleAccountAddrs(), config, ) require.NoError(t, err) @@ -692,63 +285,3 @@ func TestAppStateDeterminism(t *testing.T) { } } } - -func BenchmarkInvariants(b *testing.B) { - logger := log.NewNopLogger() - - config := simapp.NewConfigFromFlags() - config.AllInvariants = false - - dir, _ := ioutil.TempDir("", "goleveldb-app-invariant-bench") - db, _ := sdk.NewLevelDB("simulation", dir) - - defer func() { - db.Close() - os.RemoveAll(dir) - }() - - app := NewApp(logger, db, nil, true, simapp.FlagPeriodValue, interBlockCacheOpt()) - - // 2. Run parameterized simulation (w/o invariants) - _, simParams, simErr := simulation.SimulateFromSeed( - b, ioutil.Discard, app.BaseApp, simapp.AppStateFn(app.Codec(), app.sm), - testAndRunTxs(app, config), app.ModuleAccountAddrs(), config, - ) - - // export state and params before the simulation error is checked - if config.ExportStatePath != "" { - if err := ExportStateToJSON(app, config.ExportStatePath); err != nil { - fmt.Println(err) - b.Fail() - } - } - - if config.ExportParamsPath != "" { - if err := simapp.ExportParamsToJSON(simParams, config.ExportParamsPath); err != nil { - fmt.Println(err) - b.Fail() - } - } - - if simErr != nil { - fmt.Println(simErr) - b.FailNow() - } - - ctx := app.NewContext(true, abci.Header{Height: app.LastBlockHeight() + 1}) - - // 3. Benchmark each invariant separately - // - // NOTE: We use the crisis keeper as it has all the invariants registered with - // their respective metadata which makes it useful for testing/benchmarking. - for _, cr := range app.crisisKeeper.Routes() { - b.Run(fmt.Sprintf("%s/%s", cr.ModuleName, cr.Route), func(b *testing.B) { - for n := 0; n < b.N; n++ { - if res, stop := cr.Invar(ctx); stop { - fmt.Printf("broken invariant at block %d of %d\n%s", ctx.BlockHeight()-1, config.NumBlocks, res) - b.FailNow() - } - } - }) - } -} diff --git a/app/test_common.go b/app/test_common.go index be6032dd..42bc00a5 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -50,6 +50,10 @@ type TestApp struct { } func NewTestApp() TestApp { + config := sdk.GetConfig() + SetBech32AddressPrefixes(config) + SetBip44CoinType(config) + db := tmdb.NewMemDB() app := NewApp(log.NewNopLogger(), db, nil, true, 0) return TestApp{App: *app} diff --git a/cli_test/test_helpers.go b/cli_test/test_helpers.go index 05470c99..1d82792f 100644 --- a/cli_test/test_helpers.go +++ b/cli_test/test_helpers.go @@ -14,7 +14,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" - "github.com/cosmos/cosmos-sdk/client" clientkeys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/crypto/keys" @@ -208,7 +207,7 @@ func (f *Fixtures) UnsafeResetAll(flags ...string) { // NOTE: GDInit sets the ChainID for the Fixtures instance func (f *Fixtures) GDInit(moniker string, flags ...string) { cmd := fmt.Sprintf("%s init -o --home=%s %s", f.GaiadBinary, f.GaiadHome, moniker) - _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + _, stderr := tests.ExecuteT(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) var chainID string var initRes map[string]json.RawMessage @@ -231,13 +230,13 @@ func (f *Fixtures) AddGenesisAccount(address sdk.AccAddress, coins sdk.Coins, fl // GenTx is gaiad gentx func (f *Fixtures) GenTx(name string, flags ...string) { cmd := fmt.Sprintf("%s gentx --name=%s --home=%s --home-client=%s", f.GaiadBinary, name, f.GaiadHome, f.GaiacliHome) - executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // CollectGenTxs is gaiad collect-gentxs func (f *Fixtures) CollectGenTxs(flags ...string) { cmd := fmt.Sprintf("%s collect-gentxs --home=%s", f.GaiadBinary, f.GaiadHome) - executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // GDStart runs gaiad start with the appropriate flags and returns a process @@ -276,19 +275,19 @@ func (f *Fixtures) KeysDelete(name string, flags ...string) { // KeysAdd is gaiacli keys add func (f *Fixtures) KeysAdd(name string, flags ...string) { cmd := fmt.Sprintf("%s keys add --home=%s %s", f.GaiacliBinary, f.GaiacliHome, name) - executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // KeysAddRecover prepares gaiacli keys add --recover func (f *Fixtures) KeysAddRecover(name, mnemonic string, flags ...string) (exitSuccess bool, stdout, stderr string) { cmd := fmt.Sprintf("%s keys add --home=%s --recover %s", f.GaiacliBinary, f.GaiacliHome, name) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass, mnemonic) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass, mnemonic) } // KeysAddRecoverHDPath prepares gaiacli keys add --recover --account --index func (f *Fixtures) KeysAddRecoverHDPath(name, mnemonic string, account uint32, index uint32, flags ...string) { cmd := fmt.Sprintf("%s keys add --home=%s --recover %s --account %d --index %d", f.GaiacliBinary, f.GaiacliHome, name, account, index) - executeWriteCheckErr(f.T, addFlags(cmd, flags), client.DefaultKeyPass, mnemonic) + executeWriteCheckErr(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass, mnemonic) } // KeysShow is gaiacli keys show @@ -324,25 +323,25 @@ func (f *Fixtures) CLIConfig(key, value string, flags ...string) { // TxSend is gaiacli tx send func (f *Fixtures) TxSend(from string, to sdk.AccAddress, amount sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx send %s %s %s %v", f.GaiacliBinary, from, to, amount, f.Flags()) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxSign is gaiacli tx sign func (f *Fixtures) TxSign(signer, fileName string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx sign %v --from=%s %v", f.GaiacliBinary, f.Flags(), signer, fileName) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxBroadcast is gaiacli tx broadcast func (f *Fixtures) TxBroadcast(fileName string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx broadcast %v %v", f.GaiacliBinary, f.Flags(), fileName) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxEncode is gaiacli tx encode func (f *Fixtures) TxEncode(fileName string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx encode %v %v", f.GaiacliBinary, f.Flags(), fileName) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxMultisign is gaiacli tx multisign @@ -364,13 +363,13 @@ func (f *Fixtures) TxStakingCreateValidator(from, consPubKey string, amount sdk. cmd += fmt.Sprintf(" --amount=%v --moniker=%v --commission-rate=%v", amount, from, "0.05") cmd += fmt.Sprintf(" --commission-max-rate=%v --commission-max-change-rate=%v", "0.20", "0.10") cmd += fmt.Sprintf(" --min-self-delegation=%v", "1") - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxStakingUnbond is gaiacli tx staking unbond func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress, flags ...string) bool { cmd := fmt.Sprintf("%s tx staking unbond %s %v --from=%s %v", f.GaiacliBinary, validator, shares, from, f.Flags()) - return executeWrite(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWrite(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } //___________________________________________________________________________________ @@ -380,19 +379,19 @@ func (f *Fixtures) TxStakingUnbond(from, shares string, validator sdk.ValAddress func (f *Fixtures) TxGovSubmitProposal(from, typ, title, description string, deposit sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx gov submit-proposal %v --from=%s --type=%s", f.GaiacliBinary, f.Flags(), from, typ) cmd += fmt.Sprintf(" --title=%s --description=%s --deposit=%s", title, description, deposit) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxGovDeposit is gaiacli tx gov deposit func (f *Fixtures) TxGovDeposit(proposalID int, from string, amount sdk.Coin, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx gov deposit %d %s --from=%s %v", f.GaiacliBinary, proposalID, amount, from, f.Flags()) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxGovVote is gaiacli tx gov vote func (f *Fixtures) TxGovVote(proposalID int, option gov.VoteOption, from string, flags ...string) (bool, string, string) { cmd := fmt.Sprintf("%s tx gov vote %d %s --from=%s %v", f.GaiacliBinary, proposalID, option, from, f.Flags()) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxGovSubmitParamChangeProposal executes a CLI parameter change proposal @@ -406,7 +405,7 @@ func (f *Fixtures) TxGovSubmitParamChangeProposal( f.GaiacliBinary, proposalPath, from, f.Flags(), ) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } // TxGovSubmitCommunityPoolSpendProposal executes a CLI community pool spend proposal @@ -420,7 +419,7 @@ func (f *Fixtures) TxGovSubmitCommunityPoolSpendProposal( f.GaiacliBinary, proposalPath, from, f.Flags(), ) - return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), client.DefaultKeyPass) + return executeWriteRetStdStreams(f.T, addFlags(cmd, flags), clientkeys.DefaultKeyPass) } //___________________________________________________________________________________ diff --git a/cmd/kvcli/main.go b/cmd/kvcli/main.go index 9873421f..8700f0ac 100644 --- a/cmd/kvcli/main.go +++ b/cmd/kvcli/main.go @@ -12,6 +12,7 @@ import ( "github.com/tendermint/tendermint/libs/cli" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/lcd" "github.com/cosmos/cosmos-sdk/client/rpc" sdk "github.com/cosmos/cosmos-sdk/types" @@ -48,7 +49,7 @@ func main() { } // Add --chain-id to persistent flags and mark it required - rootCmd.PersistentFlags().String(client.FlagChainID, "", "Chain ID of tendermint node") + rootCmd.PersistentFlags().String(flags.FlagChainID, "", "Chain ID of tendermint node") rootCmd.PersistentPreRunE = func(_ *cobra.Command, _ []string) error { return initConfig(rootCmd) } @@ -59,13 +60,13 @@ func main() { client.ConfigCmd(app.DefaultCLIHome), queryCmd(cdc), txCmd(cdc), - client.LineBreak, + flags.LineBreak, lcd.ServeCommand(cdc, registerRoutes), - client.LineBreak, + flags.LineBreak, getModifiedKeysCmd(), - client.LineBreak, + flags.LineBreak, version.Cmd, - client.NewCompletionCmd(rootCmd, true), + flags.NewCompletionCmd(rootCmd, true), ) // Add flags and prefix all env exposed with KA @@ -87,12 +88,12 @@ func queryCmd(cdc *amino.Codec) *cobra.Command { queryCmd.AddCommand( authcmd.GetAccountCmd(cdc), - client.LineBreak, + flags.LineBreak, rpc.ValidatorCommand(cdc), rpc.BlockCommand(), authcmd.QueryTxsByEventsCmd(cdc), authcmd.QueryTxCmd(cdc), - client.LineBreak, + flags.LineBreak, ) // add modules' query commands @@ -109,14 +110,14 @@ func txCmd(cdc *amino.Codec) *cobra.Command { txCmd.AddCommand( bankcmd.SendTxCmd(cdc), - client.LineBreak, + flags.LineBreak, authcmd.GetSignCommand(cdc), authcmd.GetMultiSignCommand(cdc), - client.LineBreak, + flags.LineBreak, authcmd.GetBroadcastCommand(cdc), authcmd.GetEncodeCommand(cdc), authcmd.GetDecodeCommand(cdc), - client.LineBreak, + flags.LineBreak, ) // add modules' tx commands @@ -156,7 +157,7 @@ func initConfig(cmd *cobra.Command) error { return err } } - if err := viper.BindPFlag(client.FlagChainID, cmd.PersistentFlags().Lookup(client.FlagChainID)); err != nil { + if err := viper.BindPFlag(flags.FlagChainID, cmd.PersistentFlags().Lookup(flags.FlagChainID)); err != nil { return err } if err := viper.BindPFlag(cli.EncodingFlag, cmd.PersistentFlags().Lookup(cli.EncodingFlag)); err != nil { diff --git a/cmd/kvd/main.go b/cmd/kvd/main.go index bef94402..148f3ba8 100644 --- a/cmd/kvd/main.go +++ b/cmd/kvd/main.go @@ -14,7 +14,7 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/server" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" @@ -59,7 +59,7 @@ func main() { app.DefaultCLIHome)) rootCmd.AddCommand(genutilcli.ValidateGenesisCmd(ctx, cdc, app.ModuleBasics)) rootCmd.AddCommand(AddGenesisAccountCmd(ctx, cdc, app.DefaultNodeHome, app.DefaultCLIHome)) - rootCmd.AddCommand(client.NewCompletionCmd(rootCmd, true)) + rootCmd.AddCommand(flags.NewCompletionCmd(rootCmd, true)) server.AddCommands(ctx, cdc, rootCmd, newApp, exportAppStateAndTMValidators) diff --git a/go.mod b/go.mod index aa784d25..2b057cfa 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,14 @@ 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/cosmos/cosmos-sdk v0.38.3 github.com/gorilla/mux v1.7.3 - 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 + github.com/spf13/cobra v0.0.6 + github.com/spf13/viper v1.6.2 + github.com/stretchr/testify v1.5.1 + github.com/tendermint/go-amino v0.15.1 + github.com/tendermint/tendermint v0.33.3 + github.com/tendermint/tm-db v0.5.0 golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 // indirect - gopkg.in/yaml.v2 v2.2.4 + gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index 4a1f0603..27038216 100644 --- a/go.sum +++ b/go.sum @@ -1,21 +1,40 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/99designs/keyring v1.1.2 h1:JJauROcU6x6Nh9uZb+8JgXFvyo0GUESLo1ixhpA0Kmw= -github.com/99designs/keyring v1.1.2/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY= +github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f h1:4O1om+UVU+Hfcihr1timk8YNXHxzZWgCo7ofnrZRApw= +github.com/ChainSafe/go-schnorrkel v0.0.0-20200102211924-4bcbc698314f/go.mod h1:URdX5+vg25ts3aCh8H5IFZybJYKWhJHYMTnf+ULtoC4= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= +github.com/Workiva/go-datastructures v1.0.52/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d h1:1aAija9gr0Hyv4KfQcRcwlmFIrhkDmIj2dz5bkg/s/8= github.com/bartekn/go-bip39 v0.0.0-20171116152956-a05967ea095d/go.mod h1:icNx/6QdFblhsEjZehARqbNumymUT/ydwlLojFdv7Sk= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0= @@ -30,24 +49,36 @@ github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVa github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1 h1:yb+E8HGzFnO0YwLS6OCBIAVWtN8KfCYoKeO9mgAmQN0= -github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1/go.mod h1:IGBhkbOK1ebLqMWjtgo99zUxWHsA5IOb6N9CI8nHs0Y= -github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= +github.com/cosmos/cosmos-sdk v0.38.3 h1:qIBTiw+2T9POaSUJ5rvbBbXeq8C8btBlJxnSegPBd3Y= +github.com/cosmos/cosmos-sdk v0.38.3/go.mod h1:rzWOofbKfRt3wxiylmYWEFHnxxGj0coyqgWl2I9obAw= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= -github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc= -github.com/cosmos/ledger-cosmos-go v0.10.3/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= +github.com/cosmos/ledger-cosmos-go v0.11.1 h1:9JIYsGnXP613pb2vPjFeMMjBI5lEDsEaF6oYorTy6J4= +github.com/cosmos/ledger-cosmos-go v0.11.1/go.mod h1:J8//BsAGTo3OC/vDLjMRFLW6q0WAaXvHnVc7ZmE8iUY= github.com/cosmos/ledger-go v0.9.2 h1:Nnao/dLwaVTk1Q5U9THldpUMMXU94BOTWPddSmVB6pI= github.com/cosmos/ledger-go v0.9.2/go.mod h1:oZJ2hHAZROdlHiwTg4t7kP+GKIIkBT+o6c9QWFanOyI= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU= github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U= github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -56,9 +87,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a h1:mq+R6XEM6lJX5VlLyZIrUSP8tSuJp82xTK89hvBwJbU= github.com/dvsekhvalnov/jose2go v0.0.0-20180829124132-7f401d37b68a/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM= -github.com/etcd-io/bbolt v1.3.2/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= @@ -67,69 +107,126 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= -github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0 h1:TrB8swr/68K7m9CcGut2g3UOihhbcbiMAYiuTXdEih4= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129 h1:tT8iWCYw4uOem71yYA3htfH+LNopJvcqZQshm56G5L4= github.com/golang/mock v1.3.1-0.20190508161146-9fa652df1129/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.0/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4 h1:87PNWwrRvUSnqS4dlcBU/ftvOIBep4sYuBLlh6rX2wk= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= -github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f h1:8N8XWLZelZNibkhM1FuF+3Ad3YIbgirjdMiVA0eUkaM= +github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U= github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM= github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc= @@ -138,7 +235,6 @@ github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQL 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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -147,108 +243,187 @@ 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.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs= github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 h1:hLDRPB66XQT/8+wG9WsDpiCvZf1yKO7sz7scAjSlBa0= +github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643/go.mod h1:43+3pMjjKimDBf5Kr4ZFNGbLql1zKkbImw+fZbw3geM= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ= -github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.5.0 h1:Ctq0iGpCmr3jeP77kbF2UxgvRwzWWz+4Bh9/vJTyg1A= +github.com/prometheus/client_golang v1.5.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rakyll/statik v0.1.6 h1:uICcfUXpgqtw2VopbIncslhAmE5hwc4g20TEyEENBNs= github.com/rakyll/statik v0.1.6/go.mod h1:OEi9wJV/fMUAGx1eNjq75DKDsJVuEv1U0oYdX6GX8Zs= -github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165 h1:nkcn14uNmFEuGCb2mBZbBb24RdNRL08b/wb+xBOYpuk= -github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= +github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= +github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stumble/gorocksdb v0.0.3 h1:9UU+QA1pqFYJuf9+5p7z1IqdE5k0mma4UAeu2wmX8kA= -github.com/stumble/gorocksdb v0.0.3/go.mod h1:v6IHdFBXk5DJ1K4FZ0xi+eY737quiiBxYtSWXadLybY= -github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965 h1:1oFLiOyVl+W7bnBzGhf7BbIv9loSFQcieWWYIjLqcAw= -github.com/syndtr/goleveldb v1.0.1-0.20190318030020-c3a204f8e965/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d h1:gZZadD8H+fF+n9CmNhYL1Y0dJB+kLOmKd7FbPJLeGHs= +github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJhf154huy1nPWmuSrkgjPUtUNhA+Zmy+6AESzuA= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c h1:g+WoO5jjkqGAzHWCjJB1zZfXPIAaDpzXIEJ0eS6B5Ok= +github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c/go.mod h1:ahpPrc7HpcfEWDQRZEmnXMzHY03mLDYMCxeDzy46i+8= github.com/tendermint/btcd v0.1.1 h1:0VcxPfflS2zZ3RiOAHkBiFUcPvbtRj5O7zHmcJWHV7s= github.com/tendermint/btcd v0.1.1/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= -github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae h1:AOXNM7c2Vvo45SjAgeWF8Wy+NS7/NCqzRNpUc+HPAec= -github.com/tendermint/crypto v0.0.0-20190823183015-45b1026d81ae/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= +github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15 h1:hqAk8riJvK4RMWx1aInLzndwxKalgi5rTqgfXxOxbEI= +github.com/tendermint/crypto v0.0.0-20191022145703-50d29ede1e15/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk= github.com/tendermint/go-amino v0.14.1/go.mod h1:i/UKE5Uocn+argJJBb12qTZsCDBcAYMbR92AaJVmKso= -github.com/tendermint/go-amino v0.15.0 h1:TC4e66P59W7ML9+bxio17CPKnxW3nKIRAYskntMAoRk= -github.com/tendermint/go-amino v0.15.0/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= -github.com/tendermint/iavl v0.12.4 h1:hd1woxUGISKkfUWBA4mmmTwOua6PQZTJM/F0FDrmMV8= -github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJkAUvEXT/o= -github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU= -github.com/tendermint/tendermint v0.32.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/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= -github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ= -github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= +github.com/tendermint/go-amino v0.15.1 h1:D2uk35eT4iTsvJd9jWIetzthE5C0/k2QmMFkCN+4JgQ= +github.com/tendermint/go-amino v0.15.1/go.mod h1:TQU0M1i/ImAo+tYpZi73AU3V/dKeCoMC9Sphe2ZwGME= +github.com/tendermint/iavl v0.13.2 h1:O1m08/Ciy53l9IYmf75uIRVvrNsfjEbre8u/yCu/oqk= +github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1TuoljBcsQoGA= +github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= +github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI= +github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= +github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY= +github.com/tendermint/tm-db v0.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ= +github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/zondax/hid v0.9.0 h1:eiT3P6vNxAEVxXMw66eZUAAnU2zD33JBkfG/EnfAKl8= @@ -256,79 +431,157 @@ github.com/zondax/hid v0.9.0/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWp go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3 h1:6KET3Sqa7fkVfD63QnAM81ZeYg5n4HwApOJkufONnHA= golang.org/x/net v0.0.0-20190930134127-c5a3c61f89f3/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2 h1:67iHsV9djwGdZpdZNbLuQj6FOzCaZe3w+vhLjn5AcFA= -google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/grpc v1.13.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/rest_test/setup/setuptest.go b/rest_test/setup/setuptest.go index dc52be9f..49267821 100644 --- a/rest_test/setup/setuptest.go +++ b/rest_test/setup/setuptest.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "bytes" "fmt" "io/ioutil" @@ -398,8 +399,8 @@ func sendMsgToBlockchain(cdc *amino.Codec, address string, keyname string, // get the account number and sequence number accountNumber, sequenceNumber := getAccountNumberAndSequenceNumber(cdc, address) - - txBldr := auth.NewTxBuilderFromCLI(). + inBuf := bufio.NewReader(os.Stdin) + txBldr := auth.NewTxBuilderFromCLI(inBuf). WithTxEncoder(authclient.GetTxEncoder(cdc)).WithChainID("testing"). WithKeybase(keybase).WithAccountNumber(accountNumber). WithSequence(sequenceNumber).WithGas(500000) diff --git a/x/auction/alias.go b/x/auction/alias.go index 3a9537ca..0ac1a7dd 100644 --- a/x/auction/alias.go +++ b/x/auction/alias.go @@ -1,8 +1,3 @@ -// nolint -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/auction/keeper -// ALIASGEN: github.com/kava-labs/kava/x/auction/types package auction import ( @@ -10,22 +5,13 @@ import ( "github.com/kava-labs/kava/x/auction/types" ) +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/kava-labs/kava/x/auction/keeper +// ALIASGEN: github.com/kava-labs/kava/x/auction/types + const ( - DefaultCodespace = types.DefaultCodespace - CodeInvalidInitialAuctionID = types.CodeInvalidInitialAuctionID - CodeInvalidModulePermissions = types.CodeInvalidModulePermissions - CodeUnrecognizedAuctionType = types.CodeUnrecognizedAuctionType - CodeAuctionNotFound = types.CodeAuctionNotFound - CodeAuctionHasNotExpired = types.CodeAuctionHasNotExpired - CodeAuctionHasExpired = types.CodeAuctionHasExpired - CodeInvalidBidDenom = types.CodeInvalidBidDenom - CodeInvalidLotDenom = types.CodeInvalidLotDenom - CodeBidTooSmall = types.CodeBidTooSmall - CodeBidTooLarge = types.CodeBidTooLarge - CodeLotTooSmall = types.CodeLotTooSmall - CodeLotTooLarge = types.CodeLotTooLarge - CodeCollateralAuctionIsInReversePhase = types.CodeCollateralAuctionIsInReversePhase - CodeCollateralAuctionIsInForwardPhase = types.CodeCollateralAuctionIsInForwardPhase EventTypeAuctionStart = types.EventTypeAuctionStart EventTypeAuctionBid = types.EventTypeAuctionBid EventTypeAuctionClose = types.EventTypeAuctionClose diff --git a/x/auction/client/cli/query.go b/x/auction/client/cli/query.go index 6d41ef3f..59ee6620 100644 --- a/x/auction/client/cli/query.go +++ b/x/auction/client/cli/query.go @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" "github.com/kava-labs/kava/x/auction/types" @@ -21,7 +21,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Querying commands for the auction module", } - auctionQueryCmd.AddCommand(client.GetCommands( + auctionQueryCmd.AddCommand(flags.GetCommands( QueryGetAuctionCmd(queryRoute, cdc), QueryGetAuctionsCmd(queryRoute, cdc), QueryParamsCmd(queryRoute, cdc), diff --git a/x/auction/client/cli/tx.go b/x/auction/client/cli/tx.go index 4f143d7a..c70c3d90 100644 --- a/x/auction/client/cli/tx.go +++ b/x/auction/client/cli/tx.go @@ -1,14 +1,15 @@ package cli import ( + "bufio" "fmt" "strconv" "strings" "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" @@ -25,7 +26,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { Short: "auction transactions subcommands", } - auctionTxCmd.AddCommand(client.PostCommands( + auctionTxCmd.AddCommand(flags.PostCommands( GetCmdPlaceBid(cdc), )...) @@ -45,8 +46,9 @@ $ %s tx %s bid 34 1000usdx --from myKeyName `, version.ClientName, types.ModuleName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) id, err := strconv.ParseUint(args[0], 10, 64) if err != nil { diff --git a/x/auction/genesis.go b/x/auction/genesis.go index ac6544b9..10862b5d 100644 --- a/x/auction/genesis.go +++ b/x/auction/genesis.go @@ -22,7 +22,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper for _, a := range gs.Auctions { keeper.SetAuction(ctx, a) // find the total coins that should be present in the module account - totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) + totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()...) } // check if the module account exists diff --git a/x/auction/handler.go b/x/auction/handler.go index 2ac42e36..26056f38 100644 --- a/x/auction/handler.go +++ b/x/auction/handler.go @@ -1,32 +1,31 @@ package auction import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/auction/types" ) // NewHandler returns a function to handle all "auction" type messages. func NewHandler(keeper Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) switch msg := msg.(type) { case MsgPlaceBid: return handleMsgPlaceBid(ctx, keeper, msg) default: - return sdk.ErrUnknownRequest(fmt.Sprintf("Unrecognized auction msg type: %T", msg)).Result() + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) } } } -func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Result { +func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) (*sdk.Result, error) { err := keeper.PlaceBid(ctx, msg.AuctionID, msg.Bidder, msg.Amount) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -37,7 +36,7 @@ func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Resu ), ) - return sdk.Result{ + return &sdk.Result{ Events: ctx.EventManager().Events(), - } + }, nil } diff --git a/x/auction/keeper/auctions.go b/x/auction/keeper/auctions.go index da257d35..c1722dee 100644 --- a/x/auction/keeper/auctions.go +++ b/x/auction/keeper/auctions.go @@ -5,18 +5,19 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/supply" "github.com/kava-labs/kava/x/auction/types" ) // StartSurplusAuction starts a new surplus (forward) auction. -func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) { - +func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, error) { auction := types.NewSurplusAuction( seller, lot, bidDenom, - types.DistantFuture) + types.DistantFuture, + ) err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { @@ -41,7 +42,7 @@ func (k Keeper) StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin } // StartDebtAuction starts a new debt (reverse) auction. -func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, sdk.Error) { +func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, error) { auction := types.NewDebtAuction( buyer, @@ -53,7 +54,7 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in // This auction type mints coins at close. Need to check module account has minting privileges to avoid potential err in endblocker. macc := k.supplyKeeper.GetModuleAccount(ctx, buyer) if !macc.HasPermission(supply.Minter) { - return 0, types.ErrInvalidModulePermissions(k.codespace, supply.Minter) + return 0, sdkerrors.Wrap(types.ErrInvalidModulePermissions, supply.Minter) } err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, buyer, types.ModuleName, sdk.NewCoins(debt)) @@ -79,8 +80,10 @@ func (k Keeper) StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, in } // StartCollateralAuction starts a new collateral (2-phase) auction. -func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, sdk.Error) { - +func (k Keeper) StartCollateralAuction( + ctx sdk.Context, seller string, lot, maxBid sdk.Coin, + lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin, +) (uint64, error) { weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights) if err != nil { return 0, err @@ -91,7 +94,8 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C types.DistantFuture, maxBid, weightedAddresses, - debt) + debt, + ) err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot)) if err != nil { @@ -120,41 +124,40 @@ func (k Keeper) StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.C } // PlaceBid places a bid on any auction. -func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) sdk.Error { +func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) error { auction, found := k.GetAuction(ctx, auctionID) if !found { - return types.ErrAuctionNotFound(k.codespace, auctionID) + return sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", auctionID) } // validation common to all auctions if ctx.BlockTime().After(auction.GetEndTime()) { - return types.ErrAuctionHasExpired(k.codespace, auctionID) + return sdkerrors.Wrapf(types.ErrAuctionHasExpired, "%d", auctionID) } // move coins and return updated auction - var err sdk.Error - var updatedAuction types.Auction + var ( + err error + updatedAuction types.Auction + ) switch a := auction.(type) { case types.SurplusAuction: - if updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount); err != nil { - return err - } + updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount) case types.DebtAuction: - if updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount); err != nil { - return err - } + updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount) case types.CollateralAuction: if !a.IsReversePhase() { updatedAuction, err = k.PlaceForwardBidCollateral(ctx, a, bidder, newAmount) } else { updatedAuction, err = k.PlaceReverseBidCollateral(ctx, a, bidder, newAmount) } - if err != nil { - return err - } default: - return types.ErrUnrecognizedAuctionType(k.codespace) + err = sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auction.GetType()) + } + + if err != nil { + return err } k.SetAuction(ctx, updatedAuction) @@ -163,10 +166,10 @@ func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddres } // PlaceBidSurplus places a forward bid on a surplus auction, moving coins and returning the updated auction. -func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, sdk.Error) { +func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.SurplusAuction, error) { // Validate new bid if bid.Denom != a.Bid.Denom { - return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom) + return a, sdkerrors.Wrapf(types.ErrInvalidBidDenom, "%s ≠ %s)", bid.Denom, a.Bid.Denom) } minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost sdk.MaxInt( @@ -175,7 +178,7 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder ), ) if bid.Amount.LT(minNewBidAmt) { - return a, types.ErrBidTooSmall(k.codespace, bid, sdk.Coin{Denom: a.Bid.Denom, Amount: minNewBidAmt}) // not using NewCoin as it can panic + return a, sdkerrors.Wrapf(types.ErrBidTooSmall, "%s ≤ %s%s", bid, minNewBidAmt, a.Bid.Denom) } // New bidder pays back old bidder @@ -223,13 +226,13 @@ func (k Keeper) PlaceBidSurplus(ctx sdk.Context, a types.SurplusAuction, bidder } // PlaceForwardBidCollateral places a forward bid on a collateral auction, moving coins and returning the updated auction. -func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, sdk.Error) { +func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, bid sdk.Coin) (types.CollateralAuction, error) { // Validate new bid if bid.Denom != a.Bid.Denom { - return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom) + return a, sdkerrors.Wrapf(types.ErrInvalidBidDenom, "%s ≠ %s", bid.Denom, a.Bid.Denom) } if a.IsReversePhase() { - return a, types.ErrCollateralAuctionIsInReversePhase(k.codespace, a.ID) + return a, sdkerrors.Wrapf(types.ErrCollateralAuctionIsInReversePhase, "%d", a.ID) } minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost sdk.MaxInt( @@ -239,10 +242,10 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc ) minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment % if bid.Amount.LT(minNewBidAmt) { - return a, types.ErrBidTooSmall(k.codespace, bid, sdk.Coin{Denom: a.Bid.Denom, Amount: minNewBidAmt}) // not using NewCoin as it can panic + return a, sdkerrors.Wrapf(types.ErrBidTooSmall, "%s ≤ %s%s", bid, minNewBidAmt, a.Bid.Denom) } if a.MaxBid.IsLT(bid) { - return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid) + return a, sdkerrors.Wrapf(types.ErrBidTooLarge, "%s > %s", bid, a.MaxBid) } // New bidder pays back old bidder @@ -299,13 +302,13 @@ func (k Keeper) PlaceForwardBidCollateral(ctx sdk.Context, a types.CollateralAuc } // PlaceReverseBidCollateral places a reverse bid on a collateral auction, moving coins and returning the updated auction. -func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, sdk.Error) { +func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.CollateralAuction, error) { // Validate new bid if lot.Denom != a.Lot.Denom { - return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom) + return a, sdkerrors.Wrapf(types.ErrInvalidLotDenom, lot.Denom, a.Lot.Denom) } if !a.IsReversePhase() { - return a, types.ErrCollateralAuctionIsInForwardPhase(k.codespace, a.ID) + return a, sdkerrors.Wrapf(types.ErrCollateralAuctionIsInForwardPhase, "%d", a.ID) } maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost sdk.MaxInt( @@ -314,10 +317,10 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc ), ) if lot.Amount.GT(maxNewLotAmt) { - return a, types.ErrLotTooLarge(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: maxNewLotAmt}) // not using NewCoin as it can panic + return a, sdkerrors.Wrapf(types.ErrLotTooLarge, "%s > %s%s", lot, maxNewLotAmt, a.Lot.Denom) } if lot.IsNegative() { - return a, types.ErrLotTooSmall(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: sdk.ZeroInt()}) + return a, sdkerrors.Wrapf(types.ErrLotTooSmall, "%s ≤ %s%s", lot, sdk.ZeroInt(), a.Lot.Denom) } // New bidder pays back old bidder @@ -368,10 +371,10 @@ func (k Keeper) PlaceReverseBidCollateral(ctx sdk.Context, a types.CollateralAuc } // PlaceBidDebt places a reverse bid on a debt auction, moving coins and returning the updated auction. -func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, sdk.Error) { +func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.AccAddress, lot sdk.Coin) (types.DebtAuction, error) { // Validate new bid if lot.Denom != a.Lot.Denom { - return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom) + return a, sdkerrors.Wrapf(types.ErrInvalidLotDenom, lot.Denom, a.Lot.Denom) } maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost sdk.MaxInt( @@ -380,10 +383,10 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac ), ) if lot.Amount.GT(maxNewLotAmt) { - return a, types.ErrLotTooLarge(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: maxNewLotAmt}) // not using NewCoin as it can panic + return a, sdkerrors.Wrapf(types.ErrLotTooLarge, "%s > %s%s", lot, maxNewLotAmt, a.Lot.Denom) } if lot.IsNegative() { - return a, types.ErrLotTooSmall(k.codespace, lot, sdk.Coin{Denom: a.Lot.Denom, Amount: sdk.ZeroInt()}) + return a, sdkerrors.Wrapf(types.ErrLotTooSmall, "%s ≤ %s%s", lot, sdk.ZeroInt(), a.Lot.Denom) } // New bidder pays back old bidder @@ -434,15 +437,15 @@ func (k Keeper) PlaceBidDebt(ctx sdk.Context, a types.DebtAuction, bidder sdk.Ac } // CloseAuction closes an auction and distributes funds to the highest bidder. -func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { +func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) error { auction, found := k.GetAuction(ctx, auctionID) if !found { - return types.ErrAuctionNotFound(k.codespace, auctionID) + return sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", auctionID) } if ctx.BlockTime().Before(auction.GetEndTime()) { - return types.ErrAuctionHasNotExpired(k.codespace, ctx.BlockTime(), auction.GetEndTime()) + return sdkerrors.Wrapf(types.ErrAuctionHasNotExpired, "block time %s, auction end time %s", ctx.BlockTime().UTC(), auction.GetEndTime().UTC()) } // payout to the last bidder @@ -460,7 +463,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { return err } default: - return types.ErrUnrecognizedAuctionType(k.codespace) + return sdkerrors.Wrap(types.ErrUnrecognizedAuctionType, auc.GetType()) } k.DeleteAuction(ctx, auctionID) @@ -475,7 +478,7 @@ func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error { } // PayoutDebtAuction pays out the proceeds for a debt auction, first minting the coins. -func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Error { +func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) error { err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot)) if err != nil { return err @@ -494,7 +497,7 @@ func (k Keeper) PayoutDebtAuction(ctx sdk.Context, a types.DebtAuction) sdk.Erro } // PayoutSurplusAuction pays out the proceeds for a surplus auction. -func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sdk.Error { +func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) error { err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { return err @@ -503,7 +506,7 @@ func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sd } // PayoutCollateralAuction pays out the proceeds for a collateral auction. -func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) sdk.Error { +func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) error { err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot)) if err != nil { return err @@ -518,7 +521,7 @@ func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAucti } // CloseExpiredAuctions finds all auctions that are past (or at) their ending times and closes them, paying out to the highest bidder. -func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) sdk.Error { +func (k Keeper) CloseExpiredAuctions(ctx sdk.Context) error { var expiredAuctions []uint64 k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool { expiredAuctions = append(expiredAuctions, id) @@ -542,10 +545,10 @@ func earliestTime(t1, t2 time.Time) time.Time { } // splitCoinIntoWeightedBuckets divides up some amount of coins according to some weights. -func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, sdk.Error) { +func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, error) { for _, bucket := range buckets { if bucket.IsNegative() { - return nil, sdk.ErrInternal("cannot split coin into bucket with negative weight") + return nil, fmt.Errorf("cannot split %s into bucket with negative weight (%s)", coin.String(), bucket.String()) } } amounts := splitIntIntoWeightedBuckets(coin.Amount, buckets) diff --git a/x/auction/keeper/auctions_test.go b/x/auction/keeper/auctions_test.go index 3813b6df..c8c19c1b 100644 --- a/x/auction/keeper/auctions_test.go +++ b/x/auction/keeper/auctions_test.go @@ -267,30 +267,31 @@ func TestStartSurplusAuction(t *testing.T) { blockTime time.Time args args expectPass bool + expPanic bool }{ { "normal", someTime, args{cdp.LiquidatorMacc, c("stable", 10), "gov"}, - true, + true, false, }, { "no module account", someTime, args{"nonExistentModule", c("stable", 10), "gov"}, - false, + false, true, }, { "not enough coins", someTime, args{cdp.LiquidatorMacc, c("stable", 101), "gov"}, - false, + false, false, }, { "incorrect denom", someTime, args{cdp.LiquidatorMacc, c("notacoin", 10), "gov"}, - false, + false, false, }, } for _, tc := range testCases { @@ -308,7 +309,15 @@ func TestStartSurplusAuction(t *testing.T) { keeper := tApp.GetAuctionKeeper() // run function under test - id, err := keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) + var ( + id uint64 + err error + ) + if tc.expPanic { + require.Panics(t, func() { _, _ = keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) }, tc.name) + } else { + id, err = keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom) + } // check sk := tApp.GetSupplyKeeper() @@ -316,11 +325,11 @@ func TestStartSurplusAuction(t *testing.T) { actualAuc, found := keeper.GetAuction(ctx, id) if tc.expectPass { - require.NoError(t, err) + require.NoError(t, err, tc.name) // check coins moved - require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins) + require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins, tc.name) // check auction in store and is correct - require.True(t, found) + require.True(t, found, tc.name) expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{ ID: id, Initiator: tc.args.seller, @@ -331,13 +340,13 @@ func TestStartSurplusAuction(t *testing.T) { EndTime: types.DistantFuture, MaxEndTime: types.DistantFuture, }}) - require.Equal(t, expectedAuction, actualAuc) - } else { - require.Error(t, err) + require.Equal(t, expectedAuction, actualAuc, tc.name) + } else if !tc.expPanic && !tc.expectPass { + require.Error(t, err, tc.name) // check coins not moved - require.Equal(t, initialLiquidatorCoins, liquidatorCoins) + require.Equal(t, initialLiquidatorCoins, liquidatorCoins, tc.name) // check auction not in store - require.False(t, found) + require.False(t, found, tc.name) } }) } diff --git a/x/auction/keeper/bidding_test.go b/x/auction/keeper/bidding_test.go index 40d2d164..9a34cabc 100644 --- a/x/auction/keeper/bidding_test.go +++ b/x/auction/keeper/bidding_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "strings" "testing" "time" @@ -55,319 +56,347 @@ func TestAuctionBidding(t *testing.T) { auctionArgs auctionArgs setupBids []bidArgs bidArgs bidArgs - expectedError sdk.CodeType + expectedError error expectedEndTime time.Time expectedBidder sdk.AccAddress expectedBid sdk.Coin - expectpass bool + expectPass bool + expectPanic bool }{ { "basic: auction doesn't exist", auctionArgs{Surplus, "", c("token1", 1), c("token2", 1), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token2", 10)}, - types.CodeAuctionNotFound, + types.ErrAuctionNotFound, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 10), false, + true, }, { "basic: closed auction", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token2", 10)}, - types.CodeAuctionHasExpired, + types.ErrAuctionHasExpired, types.DistantFuture, nil, c("token2", 0), false, + false, }, { "surplus: normal", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token2", 10)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 10), true, + false, }, { "surplus: second bidder", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, []bidArgs{{buyer, c("token2", 10)}}, bidArgs{secondBuyer, c("token2", 11)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), secondBuyer, c("token2", 11), true, + false, }, { "surplus: invalid bid denom", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("badtoken", 10)}, - types.CodeInvalidBidDenom, + types.ErrInvalidBidDenom, types.DistantFuture, nil, // surplus auctions are created with initial bidder as a nil address c("token2", 0), false, + false, }, { "surplus: invalid bid (less than)", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, []bidArgs{{buyer, c("token2", 100)}}, bidArgs{buyer, c("token2", 99)}, - types.CodeBidTooSmall, + types.ErrBidTooSmall, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 100), false, + false, }, { "surplus: invalid bid (equal)", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token2", 0)}, // min bid is technically 0 at default 5%, but it's capped at 1 - types.CodeBidTooSmall, + types.ErrBidTooSmall, types.DistantFuture, nil, // surplus auctions are created with initial bidder as a nil address c("token2", 0), false, + false, }, { "surplus: invalid bid (less than min increment)", auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}}, []bidArgs{{buyer, c("token2", 100)}}, bidArgs{buyer, c("token2", 104)}, // min bid is 105 at default 5% - types.CodeBidTooSmall, + types.ErrBidTooSmall, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 100), false, + false, }, { "debt: normal", auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot nil, bidArgs{buyer, c("token1", 10)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 100), true, + false, }, { "debt: second bidder", auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot []bidArgs{{buyer, c("token1", 10)}}, bidArgs{secondBuyer, c("token1", 9)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), secondBuyer, c("token2", 100), true, + false, }, { "debt: invalid lot denom", auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot nil, bidArgs{buyer, c("badtoken", 10)}, - types.CodeInvalidLotDenom, + types.ErrInvalidLotDenom, types.DistantFuture, supply.NewModuleAddress(modName), c("token2", 100), false, + false, }, { "debt: invalid lot size (larger)", auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token1", 21)}, - types.CodeLotTooLarge, + types.ErrLotTooLarge, types.DistantFuture, supply.NewModuleAddress(modName), c("token2", 100), false, + false, }, { "debt: invalid lot size (equal)", auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token1", 20)}, - types.CodeLotTooLarge, + types.ErrLotTooLarge, types.DistantFuture, supply.NewModuleAddress(modName), c("token2", 100), false, + false, }, { "debt: invalid lot size (larger than min increment)", auctionArgs{Debt, modName, c("token1", 60), c("token2", 100), c("debt", 100), []sdk.AccAddress{}, []sdk.Int{}}, nil, bidArgs{buyer, c("token1", 58)}, // max lot at default 5% is 57 - types.CodeLotTooLarge, + types.ErrLotTooLarge, types.DistantFuture, supply.NewModuleAddress(modName), c("token2", 100), - false, + false, false, }, { "collateral [forward]: normal", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid nil, bidArgs{buyer, c("token2", 10)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 10), true, + false, }, { "collateral [forward]: second bidder", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 10)}}, bidArgs{secondBuyer, c("token2", 11)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), secondBuyer, c("token2", 11), true, + false, }, { "collateral [forward]: invalid bid denom", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid nil, bidArgs{buyer, c("badtoken", 10)}, - types.CodeInvalidBidDenom, + types.ErrInvalidBidDenom, types.DistantFuture, nil, c("token2", 0), false, + false, }, { "collateral [forward]: invalid bid size (smaller)", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 10)}}, bidArgs{buyer, c("token2", 9)}, - types.CodeBidTooSmall, + types.ErrBidTooSmall, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 10), false, + false, }, { "collateral [forward]: invalid bid size (equal)", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid nil, bidArgs{buyer, c("token2", 0)}, - types.CodeBidTooSmall, + types.ErrBidTooSmall, types.DistantFuture, nil, c("token2", 0), false, + false, }, { "collateral [forward]: invalid bid size (less than min increment)", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}}, bidArgs{buyer, c("token2", 51)}, - types.CodeBidTooSmall, + types.ErrBidTooSmall, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 50), false, + false, }, { "collateral [forward]: less than min increment but equal to maxBid", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 99)}}, bidArgs{buyer, c("token2", 100)}, // min bid at default 5% is 104 - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 100), true, + false, }, { "collateral [forward]: invalid bid size (greater than max)", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid nil, bidArgs{buyer, c("token2", 101)}, - types.CodeBidTooLarge, + types.ErrBidTooLarge, types.DistantFuture, nil, c("token2", 0), false, + false, }, { "collateral [reverse]: normal", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase bidArgs{buyer, c("token1", 15)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 50), true, + false, }, { "collateral [reverse]: second bidder", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}, {buyer, c("token1", 15)}}, // put auction into reverse phase, and add a reverse phase bid bidArgs{secondBuyer, c("token1", 14)}, - sdk.CodeType(0), + nil, someTime.Add(types.DefaultBidDuration), secondBuyer, c("token2", 50), true, + false, }, { "collateral [reverse]: invalid lot denom", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase bidArgs{buyer, c("badtoken", 15)}, - types.CodeInvalidLotDenom, + types.ErrInvalidLotDenom, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 50), false, + false, }, { "collateral [reverse]: invalid lot size (greater)", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase bidArgs{buyer, c("token1", 21)}, - types.CodeLotTooLarge, + types.ErrLotTooLarge, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 50), false, + false, }, { "collateral [reverse]: invalid lot size (equal)", auctionArgs{Collateral, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase bidArgs{buyer, c("token1", 20)}, - types.CodeLotTooLarge, + types.ErrLotTooLarge, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 50), false, + false, }, { "collateral [reverse]: invalid lot size (larger than min increment)", auctionArgs{Collateral, modName, c("token1", 60), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid []bidArgs{{buyer, c("token2", 50)}}, // put auction into reverse phase bidArgs{buyer, c("token1", 58)}, // max lot at default 5% is 57 - types.CodeLotTooLarge, + types.ErrLotTooLarge, someTime.Add(types.DefaultBidDuration), buyer, c("token2", 50), false, + false, }, } for _, tc := range tests { @@ -394,17 +423,26 @@ func TestAuctionBidding(t *testing.T) { // Start Auction var id uint64 - var err sdk.Error + var err error switch tc.auctionArgs.auctionType { case Surplus: - id, _ = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom) + if tc.expectPanic { + require.Panics(t, func() { + id, err = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom) + }) + } else { + id, err = keeper.StartSurplusAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid.Denom) + } case Debt: - id, _ = keeper.StartDebtAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.bid, tc.auctionArgs.lot, tc.auctionArgs.debt) + id, err = keeper.StartDebtAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.bid, tc.auctionArgs.lot, tc.auctionArgs.debt) case Collateral: - id, _ = keeper.StartCollateralAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid, tc.auctionArgs.addresses, tc.auctionArgs.weights, tc.auctionArgs.debt) // seller, lot, maxBid, otherPerson + id, err = keeper.StartCollateralAuction(ctx, tc.auctionArgs.seller, tc.auctionArgs.lot, tc.auctionArgs.bid, tc.auctionArgs.addresses, tc.auctionArgs.weights, tc.auctionArgs.debt) // seller, lot, maxBid, otherPerson default: t.Fail() } + + require.NoError(t, err) + // Place setup bids for _, b := range tc.setupBids { require.NoError(t, keeper.PlaceBid(ctx, id, b.bidder, b.amount)) @@ -428,7 +466,7 @@ func TestAuctionBidding(t *testing.T) { err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.bidArgs.amount) // Check success/failure - if tc.expectpass { + if tc.expectPass { require.Nil(t, err) // Check auction was found newAuction, found := keeper.GetAuction(ctx, id) @@ -445,7 +483,8 @@ func TestAuctionBidding(t *testing.T) { case Debt: bidAmt = oldAuction.GetBid() case Collateral: - collatAuction, _ := oldAuction.(types.CollateralAuction) + collatAuction, ok := oldAuction.(types.CollateralAuction) + require.True(t, ok, tc.name) if collatAuction.IsReversePhase() { bidAmt = oldAuction.GetBid() } @@ -453,18 +492,18 @@ func TestAuctionBidding(t *testing.T) { if oldBidder.Equals(tc.bidArgs.bidder) { // same bidder require.Equal(t, newBidderOldCoins.Sub(cs(bidAmt.Sub(oldAuction.GetBid()))), bank.GetCoins(ctx, tc.bidArgs.bidder)) } else { - require.Equal(t, cs(newBidderOldCoins.Sub(cs(bidAmt))...), bank.GetCoins(ctx, tc.bidArgs.bidder)) // wrapping in cs() to avoid comparing nil and empty coins - if oldBidder.Equals(supply.NewModuleAddress(oldAuction.GetInitiator())) { // handle checking debt coins for case debt auction has had no bids placed yet TODO make this less confusing - require.Equal(t, cs(oldBidderOldCoins.Add(cs(oldAuction.GetBid()))...).Add(cs(c("debt", oldAuction.GetBid().Amount.Int64()))), bank.GetCoins(ctx, oldBidder)) + require.Equal(t, newBidderOldCoins.Sub(cs(bidAmt)), bank.GetCoins(ctx, tc.bidArgs.bidder)) // wrapping in cs() to avoid comparing nil and empty coins + if oldBidder.Equals(supply.NewModuleAddress(oldAuction.GetInitiator())) { // handle checking debt coins for case debt auction has had no bids placed yet TODO make this less confusing + require.Equal(t, oldBidderOldCoins.Add(oldAuction.GetBid()).Add(c("debt", oldAuction.GetBid().Amount.Int64())), bank.GetCoins(ctx, oldBidder)) } else { - require.Equal(t, cs(oldBidderOldCoins.Add(cs(oldAuction.GetBid()))...), bank.GetCoins(ctx, oldBidder)) + require.Equal(t, cs(oldBidderOldCoins.Add(oldAuction.GetBid())...), bank.GetCoins(ctx, oldBidder)) } } } else { // Check expected error code type require.NotNil(t, err, "PlaceBid did not return an error") // catch nil values before they cause a panic below - require.Equal(t, tc.expectedError, err.Result().Code) + require.True(t, errors.Is(err, tc.expectedError)) // Check auction values newAuction, found := keeper.GetAuction(ctx, id) diff --git a/x/auction/keeper/invariants.go b/x/auction/keeper/invariants.go index 769b9904..05eee629 100644 --- a/x/auction/keeper/invariants.go +++ b/x/auction/keeper/invariants.go @@ -21,7 +21,7 @@ func RegisterInvariants(ir sdk.InvariantRegistry, k Keeper) { ValidIndexInvariant(k)) } -// ModuleAccountInvariant checks that the module account's coins matches those stored in auctions +// ModuleAccountInvariants checks that the module account's coins matches those stored in auctions func ModuleAccountInvariants(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { @@ -31,7 +31,7 @@ func ModuleAccountInvariants(k Keeper) sdk.Invariant { if !ok { panic("stored auction type does not fulfill GenesisAuction interface") } - totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) + totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()...) return false }) diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index 7a51302b..da8caf89 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -18,7 +18,6 @@ type Keeper struct { storeKey sdk.StoreKey cdc *codec.Codec paramSubspace subspace.Subspace - codespace sdk.CodespaceType } // NewKeeper returns a new auction keeper. @@ -26,11 +25,16 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.Suppl if addr := supplyKeeper.GetModuleAddress(types.ModuleName); addr == nil { panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) } + + if !paramstore.HasKeyTable() { + paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) + } + return Keeper{ supplyKeeper: supplyKeeper, storeKey: storeKey, cdc: cdc, - paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), + paramSubspace: paramstore, } } @@ -46,17 +50,17 @@ func (k Keeper) SetNextAuctionID(ctx sdk.Context, id uint64) { } // GetNextAuctionID reads the next available global ID from store -func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, sdk.Error) { +func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, error) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.NextAuctionIDKey) if bz == nil { - return 0, types.ErrInvalidInitialAuctionID(k.codespace) + return 0, types.ErrInvalidInitialAuctionID } return types.Uint64FromBytes(bz), nil } // IncrementNextAuctionID increments the next auction ID in the store by 1. -func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { +func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) error { id, err := k.GetNextAuctionID(ctx) if err != nil { return err @@ -66,7 +70,7 @@ func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error { } // StoreNewAuction stores an auction, adding a new ID -func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, sdk.Error) { +func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, error) { newAuctionID, err := k.GetNextAuctionID(ctx) if err != nil { return 0, err diff --git a/x/auction/keeper/querier.go b/x/auction/keeper/querier.go index 08d337b3..e7d6460a 100644 --- a/x/auction/keeper/querier.go +++ b/x/auction/keeper/querier.go @@ -1,16 +1,18 @@ package keeper import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/tendermint/abci/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/auction/types" ) // NewQuerier is the module level router for state queries func NewQuerier(keeper Keeper) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) { switch path[0] { case types.QueryGetAuction: return queryAuction(ctx, req, keeper) @@ -19,35 +21,35 @@ func NewQuerier(keeper Keeper) sdk.Querier { case types.QueryGetParams: return queryGetParams(ctx, req, keeper) default: - return nil, sdk.ErrUnknownRequest("unknown auction query endpoint") + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName) } } } -func queryAuction(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryAuction(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Decode request var requestParams types.QueryAuctionParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } // Lookup auction auction, found := keeper.GetAuction(ctx, requestParams.AuctionID) if !found { - return nil, types.ErrAuctionNotFound(types.DefaultCodespace, requestParams.AuctionID) + return nil, sdkerrors.Wrapf(types.ErrAuctionNotFound, "%d", requestParams.AuctionID) } // Encode results bz, err := codec.MarshalJSONIndent(keeper.cdc, auction) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } -func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Get all auctions auctionsList := types.Auctions{} keeper.IterateAuctions(ctx, func(a types.Auction) bool { @@ -58,21 +60,21 @@ func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byt // Encode Results bz, err := codec.MarshalJSONIndent(keeper.cdc, auctionsList) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query params in the auction store -func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Get params params := keeper.GetParams(ctx) // Encode results bz, err := codec.MarshalJSONIndent(keeper.cdc, params) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil diff --git a/x/auction/module.go b/x/auction/module.go index 5e25bff6..dd1134a0 100644 --- a/x/auction/module.go +++ b/x/auction/module.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" sim "github.com/cosmos/cosmos-sdk/x/simulation" abci "github.com/tendermint/tendermint/abci/types" @@ -24,7 +25,7 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModuleSimulation = AppModule{} ) // AppModuleBasic implements the sdk.AppModuleBasic interface. @@ -72,40 +73,21 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { //____________________________________________________________________________ -// AppModuleSimulation defines the module simulation functions used by the auction module. -type AppModuleSimulation struct{} - -// RegisterStoreDecoder registers a decoder for auction module's types -func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[StoreKey] = simulation.DecodeStore -} - -// GenerateGenesisState creates a randomized GenState of the auction module -func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RandomizedParams creates randomized auction param changes for the simulator. -func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange { - return simulation.ParamChanges(r) -} - -//____________________________________________________________________________ - // AppModule implements the sdk.AppModule interface. type AppModule struct { AppModuleBasic - AppModuleSimulation - keeper Keeper - supplyKeeper types.SupplyKeeper + keeper Keeper + accountKeeper auth.AccountKeeper + supplyKeeper types.SupplyKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule { +func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper types.SupplyKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, + accountKeeper: accountKeeper, supplyKeeper: supplyKeeper, } } @@ -159,3 +141,30 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +//____________________________________________________________________________ + +// GenerateGenesisState creates a randomized GenState of the auction module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// RandomizedParams returns nil because auction has no params. +func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers a decoder for auction module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// WeightedOperations returns the all the auction module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper) +} diff --git a/x/auction/simulation/decoder.go b/x/auction/simulation/decoder.go index 232fc005..a6a58e92 100644 --- a/x/auction/simulation/decoder.go +++ b/x/auction/simulation/decoder.go @@ -5,14 +5,15 @@ import ( "encoding/binary" "fmt" + "github.com/tendermint/tendermint/libs/kv" + "github.com/cosmos/cosmos-sdk/codec" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/kava-labs/kava/x/auction/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding auction type -func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { case bytes.Equal(kvA.Key[:1], types.AuctionKeyPrefix): var auctionA, auctionB types.Auction diff --git a/x/auction/simulation/decoder_test.go b/x/auction/simulation/decoder_test.go index 1aa5f749..f1109332 100644 --- a/x/auction/simulation/decoder_test.go +++ b/x/auction/simulation/decoder_test.go @@ -6,8 +6,7 @@ import ( "time" "github.com/stretchr/testify/require" - - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/kv" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -28,11 +27,11 @@ func TestDecodeDistributionStore(t *testing.T) { oneCoin := sdk.NewCoin("coin", sdk.OneInt()) auction := types.NewSurplusAuction("me", oneCoin, "coin", time.Now().UTC()) - kvPairs := cmn.KVPairs{ - cmn.KVPair{Key: types.AuctionKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&auction)}, - cmn.KVPair{Key: types.AuctionByTimeKeyPrefix, Value: sdk.Uint64ToBigEndian(2)}, - cmn.KVPair{Key: types.NextAuctionIDKey, Value: sdk.Uint64ToBigEndian(10)}, - cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + kvPairs := kv.Pairs{ + kv.Pair{Key: types.AuctionKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&auction)}, + kv.Pair{Key: types.AuctionByTimeKeyPrefix, Value: sdk.Uint64ToBigEndian(2)}, + kv.Pair{Key: types.NextAuctionIDKey, Value: sdk.Uint64ToBigEndian(10)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } tests := []struct { diff --git a/x/auction/simulation/genesis.go b/x/auction/simulation/genesis.go index 51b14d04..3d4949f9 100644 --- a/x/auction/simulation/genesis.go +++ b/x/auction/simulation/genesis.go @@ -88,7 +88,7 @@ func RandomizedGenState(simState *module.SimulationState) { if !ok { panic("can't convert Auction to GenesisAuction") } - totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()) + totalAuctionCoins = totalAuctionCoins.Add(a.GetModuleAccountCoins()...) } auctionGenesis.NextAuctionID = startingID + uint64(len(auctions)) auctionGenesis.Auctions = append(auctionGenesis.Auctions, auctions...) @@ -112,7 +112,7 @@ func RandomizedGenState(simState *module.SimulationState) { panic("bidder not found") } bidderCoins := sdk.NewCoins(sdk.NewInt64Coin("usdx", 10000000000)) - if err := bidder.SetCoins(bidder.GetCoins().Add(bidderCoins)); err != nil { + if err := bidder.SetCoins(bidder.GetCoins().Add(bidderCoins...)); err != nil { panic(err) } authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, bidder) @@ -123,7 +123,7 @@ func RandomizedGenState(simState *module.SimulationState) { // TODO find some way for this to happen automatically / move it elsewhere var supplyGenesis supply.GenesisState simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) - supplyGenesis.Supply = supplyGenesis.Supply.Add(totalAuctionCoins).Add(bidderCoins) + supplyGenesis.Supply = supplyGenesis.Supply.Add(totalAuctionCoins...).Add(bidderCoins...) simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) // TODO liquidator mod account doesn't need to be initialized for this example diff --git a/x/auction/simulation/operations.go b/x/auction/simulation/operations.go new file mode 100644 index 00000000..84f2297c --- /dev/null +++ b/x/auction/simulation/operations.go @@ -0,0 +1,251 @@ +package simulation + +import ( + "errors" + "fmt" + "math/big" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + + appparams "github.com/kava-labs/kava/app/params" + "github.com/kava-labs/kava/x/auction/keeper" + "github.com/kava-labs/kava/x/auction/types" +) + +var ( + errorNotEnoughCoins = errors.New("account doesn't have enough coins") + errorCantReceiveBids = errors.New("auction can't receive bids (lot = 0 in reverse auction)") +) + +// Simulation operation weights constants +const ( + OpWeightMsgPlaceBid = "op_weight_msg_place_bid" +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgPlaceBid int + + appParams.GetOrGenerate(cdc, OpWeightMsgPlaceBid, &weightMsgPlaceBid, nil, + func(_ *rand.Rand) { + weightMsgPlaceBid = appparams.DefaultWeightMsgPlaceBid + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgPlaceBid, + SimulateMsgPlaceBid(ak, k), + ), + } +} + +// SimulateMsgPlaceBid returns a function that runs a random state change on the module keeper. +// There's two error paths +// - return a OpMessage, but nil error - this will log a message but keep running the simulation +// - return an error - this will stop the simulation +func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + // get open auctions + openAuctions := types.Auctions{} + keeper.IterateAuctions(ctx, func(a types.Auction) bool { + openAuctions = append(openAuctions, a) + return false + }) + + // shuffle auctions slice so that bids are evenly distributed across auctions + r.Shuffle(len(openAuctions), func(i, j int) { + openAuctions[i], openAuctions[j] = openAuctions[j], openAuctions[i] + }) + + // search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction) + blockTime := ctx.BlockHeader().Time + params := keeper.GetParams(ctx) + bidder, openAuction, found := findValidAccountAuctionPair(accs, openAuctions, func(acc simulation.Account, auc types.Auction) bool { + account := ak.GetAccount(ctx, acc.Address) + _, err := generateBidAmount(r, params, auc, account, blockTime) + if err == errorNotEnoughCoins || err == errorCantReceiveBids { + return false // keep searching + } else if err != nil { + panic(err) // raise errors + } + return true // found valid pair + }) + if !found { + return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation (no valid auction and bidder)", "", false, nil), nil, nil + } + + bidderAcc := ak.GetAccount(ctx, bidder.Address) + if bidderAcc == nil { + return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", bidder.Address) + } + + // pick a bid amount for the chosen auction and bidder + amount, err := generateBidAmount(r, params, openAuction, bidderAcc, blockTime) + if err != nil { // shouldn't happen given the checks above + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + // create and deliver a tx + msg := types.NewMsgPlaceBid(openAuction.GetID(), bidder.Address, amount) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + sdk.NewCoins(), // TODO pick a random amount fees + helpers.DefaultGenTxGas, + chainID, + []uint64{bidderAcc.GetAccountNumber()}, + []uint64{bidderAcc.GetSequence()}, + bidder.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + // to aid debugging, add the stack trace to the comment field of the returned opMsg + return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err + } + // to aid debugging, add the result log to the comment field + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil + } +} + +func generateBidAmount( + r *rand.Rand, params types.Params, auc types.Auction, + bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { + bidderBalance := bidder.SpendableCoins(blockTime) + + switch a := auc.(type) { + + case types.DebtAuction: + // Check bidder has enough (stable coin) to pay in + if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin + return sdk.Coin{}, errorNotEnoughCoins + } + // Check auction can still receive new bids + if a.Lot.Amount.Equal(sdk.ZeroInt()) { + return sdk.Coin{}, errorCantReceiveBids + } + // Generate a new lot amount (gov coin) + maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementDebt).RoundInt(), + ), + ) + amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above + if err != nil { + panic(err) + } + return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin + + case types.SurplusAuction: + // Check the bidder has enough (gov coin) to pay in + minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementSurplus).RoundInt(), + ), + ) + if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { // gov coin + return sdk.Coin{}, errorNotEnoughCoins + } + // Generate a new bid amount (gov coin) + amt, err := RandIntInclusive(r, minNewBidAmt, bidderBalance.AmountOf(a.Bid.Denom)) + if err != nil { + panic(err) + } + return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin + + case types.CollateralAuction: + // Check the bidder has enough (stable coin) to pay in + minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementCollateral).RoundInt(), + ), + ) + minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment % + if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { + return sdk.Coin{}, errorNotEnoughCoins + } + // Check auction can still receive new bids + if a.IsReversePhase() && a.Lot.Amount.Equal(sdk.ZeroInt()) { + return sdk.Coin{}, errorCantReceiveBids + } + // Generate a new bid amount (collateral coin in reverse phase) + if a.IsReversePhase() { + maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementCollateral).RoundInt(), + ), + ) + amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above + if err != nil { + panic(err) + } + return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin + + // Generate a new bid amount (stable coin in forward phase) + } else { + amt, err := RandIntInclusive(r, minNewBidAmt, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) + if err != nil { + panic(err) + } + // when the bidder has enough coins, pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase + if r.Intn(2) == 0 && bidderBalance.AmountOf(a.Bid.Denom).GTE(a.MaxBid.Amount) { // 50% + amt = a.MaxBid.Amount + } + return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin + } + + default: + return sdk.Coin{}, fmt.Errorf("unknown auction type") + } +} + +// findValidAccountAuctionPair finds an auction and account for which the callback func returns true +func findValidAccountAuctionPair(accounts []simulation.Account, auctions types.Auctions, cb func(simulation.Account, types.Auction) bool) (simulation.Account, types.Auction, bool) { + for _, auc := range auctions { + for _, acc := range accounts { + if isValid := cb(acc, auc); isValid { + return acc, auc, true + } + } + } + return simulation.Account{}, nil, false +} + +// RandIntInclusive randomly generates an sdk.Int in the range [inclusiveMin, inclusiveMax]. It works for negative and positive integers. +func RandIntInclusive(r *rand.Rand, inclusiveMin, inclusiveMax sdk.Int) (sdk.Int, error) { + if inclusiveMin.GT(inclusiveMax) { + return sdk.Int{}, fmt.Errorf("min larger than max") + } + return RandInt(r, inclusiveMin, inclusiveMax.Add(sdk.OneInt())) +} + +// RandInt randomly generates an sdk.Int in the range [inclusiveMin, exclusiveMax). It works for negative and positive integers. +func RandInt(r *rand.Rand, inclusiveMin, exclusiveMax sdk.Int) (sdk.Int, error) { + // validate input + if inclusiveMin.GTE(exclusiveMax) { + return sdk.Int{}, fmt.Errorf("min larger or equal to max") + } + // shift the range to start at 0 + shiftedRange := exclusiveMax.Sub(inclusiveMin) // should always be positive given the check above + // randomly pick from the shifted range + shiftedRandInt := sdk.NewIntFromBigInt(new(big.Int).Rand(r, shiftedRange.BigInt())) + // shift back to the original range + return shiftedRandInt.Add(inclusiveMin), nil +} diff --git a/x/auction/simulation/operations/msg.go b/x/auction/simulation/operations/msg.go deleted file mode 100644 index d38f97bc..00000000 --- a/x/auction/simulation/operations/msg.go +++ /dev/null @@ -1,177 +0,0 @@ -package operations - -import ( - "errors" - "fmt" - "math/big" - "math/rand" - "time" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/cosmos/cosmos-sdk/x/simulation" - - "github.com/kava-labs/kava/x/auction" -) - -var ( - noOpMsg = simulation.NoOpMsg(auction.ModuleName) - ErrorNotEnoughCoins = errors.New("account doesn't have enough coins") -) - -// Return a function that runs a random state change on the module keeper. -// There's two error paths -// - return a OpMessage, but nil error - this will log a message but keep running the simulation -// - return an error - this will stop the simulation -func SimulateMsgPlaceBid(authKeeper auth.AccountKeeper, keeper auction.Keeper) simulation.Operation { - handler := auction.NewHandler(keeper) - - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( - simulation.OperationMsg, []simulation.FutureOperation, error) { - - // get open auctions - openAuctions := auction.Auctions{} - keeper.IterateAuctions(ctx, func(a auction.Auction) bool { - openAuctions = append(openAuctions, a) - return false - }) - - // shuffle auctions slice so that bids are evenly distributed across auctions - r.Shuffle(len(openAuctions), func(i, j int) { - openAuctions[i], openAuctions[j] = openAuctions[j], openAuctions[i] - }) - // TODO do the same for accounts? - var accounts []authexported.Account - for _, acc := range accs { - accounts = append(accounts, authKeeper.GetAccount(ctx, acc.Address)) - } - - // search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction) - blockTime := ctx.BlockHeader().Time - bidder, openAuction, found := findValidAccountAuctionPair(accounts, openAuctions, func(acc authexported.Account, auc auction.Auction) bool { - _, err := generateBidAmount(r, auc, acc, blockTime) - if err == ErrorNotEnoughCoins { - return false // keep searching - } else if err != nil { - panic(err) // raise errors - } - return true // found valid pair - }) - if !found { - return simulation.NewOperationMsgBasic(auction.ModuleName, "no-operation (no valid auction and bidder)", "", false, nil), nil, nil - } - - // pick a bid amount for the chosen auction and bidder - amount, _ := generateBidAmount(r, openAuction, bidder, blockTime) - - // create a msg - msg := auction.NewMsgPlaceBid(openAuction.GetID(), bidder.GetAddress(), amount) - if err := msg.ValidateBasic(); err != nil { // don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic - return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - } - - // submit the msg - result := submitMsg(ctx, handler, msg) - // Return an operationMsg indicating whether the msg was submitted successfully - // Using result.Log as the comment field as it contains any error message emitted by the keeper - return simulation.NewOperationMsg(msg, result.IsOK(), result.Log), nil, nil - } -} - -func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) sdk.Result { - ctx, write := ctx.CacheContext() - result := handler(ctx, msg) - if result.IsOK() { - write() - } - return result -} - -func generateBidAmount(r *rand.Rand, auc auction.Auction, bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { - bidderBalance := bidder.SpendableCoins(blockTime) - - switch a := auc.(type) { - - case auction.DebtAuction: - if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin - return sdk.Coin{}, ErrorNotEnoughCoins - } - amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount // TODO min bid increments - if err != nil { - panic(err) - } - return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin - - case auction.SurplusAuction: - if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // gov coin // TODO account for bid increments - return sdk.Coin{}, ErrorNotEnoughCoins - } - amt, err := RandIntInclusive(r, a.Bid.Amount, bidderBalance.AmountOf(a.Bid.Denom)) - if err != nil { - panic(err) - } - return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin - - case auction.CollateralAuction: - if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin // TODO account for bid increments (in forward phase) - return sdk.Coin{}, ErrorNotEnoughCoins - } - if a.IsReversePhase() { - amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount - if err != nil { - panic(err) - } - return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin - } else { - amt, err := RandIntInclusive(r, a.Bid.Amount, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) - if err != nil { - panic(err) - } - // pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase - if r.Intn(10) == 0 { // 10% - amt = a.MaxBid.Amount - } - return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin - } - - default: - return sdk.Coin{}, fmt.Errorf("unknown auction type") - } -} - -// findValidAccountAuctionPair finds an auction and account for which the callback func returns true -func findValidAccountAuctionPair(accounts []authexported.Account, auctions auction.Auctions, cb func(authexported.Account, auction.Auction) bool) (authexported.Account, auction.Auction, bool) { - for _, auc := range auctions { - for _, acc := range accounts { - if isValid := cb(acc, auc); isValid { - return acc, auc, true - } - - } - } - return nil, nil, false -} - -// RandInt randomly generates an sdk.Int in the range [inclusiveMin, inclusiveMax]. It works for negative and positive integers. -func RandIntInclusive(r *rand.Rand, inclusiveMin, inclusiveMax sdk.Int) (sdk.Int, error) { - if inclusiveMin.GT(inclusiveMax) { - return sdk.Int{}, fmt.Errorf("min larger than max") - } - return RandInt(r, inclusiveMin, inclusiveMax.Add(sdk.OneInt())) -} - -// RandInt randomly generates an sdk.Int in the range [inclusiveMin, exclusiveMax). It works for negative and positive integers. -func RandInt(r *rand.Rand, inclusiveMin, exclusiveMax sdk.Int) (sdk.Int, error) { - // validate input - if inclusiveMin.GTE(exclusiveMax) { - return sdk.Int{}, fmt.Errorf("min larger or equal to max") - } - // shift the range to start at 0 - shiftedRange := exclusiveMax.Sub(inclusiveMin) // should always be positive given the check above - // randomly pick from the shifted range - shiftedRandInt := sdk.NewIntFromBigInt(new(big.Int).Rand(r, shiftedRange.BigInt())) - // shift back to the original range - return shiftedRandInt.Add(inclusiveMin), nil -} diff --git a/x/auction/simulation/params.go b/x/auction/simulation/params.go index 7ae658cd..a241c17c 100644 --- a/x/auction/simulation/params.go +++ b/x/auction/simulation/params.go @@ -17,29 +17,29 @@ func ParamChanges(r *rand.Rand) []simulation.ParamChange { // as strings in JSON (such as time.Duration) have the escaped quotes. // TODO should we encode the values properly with ModuleCdc.MustMarshalJSON()? return []simulation.ParamChange{ - simulation.NewSimParamChange(types.ModuleName, string(types.KeyBidDuration), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyBidDuration), func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenBidDuration(r)) + return fmt.Sprintf("%d", GenBidDuration(r)) }, ), - simulation.NewSimParamChange(types.ModuleName, string(types.KeyMaxAuctionDuration), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyMaxAuctionDuration), func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenMaxAuctionDuration(r)) + return fmt.Sprintf("%d", GenMaxAuctionDuration(r)) }, ), - simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementCollateral), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementCollateral), func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenIncrementCollateral(r)) + return fmt.Sprintf("%d", GenIncrementCollateral(r)) }, ), - simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementDebt), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementDebt), func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenIncrementDebt(r)) + return fmt.Sprintf("%d", GenIncrementDebt(r)) }, ), - simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementSurplus), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyIncrementSurplus), func(r *rand.Rand) string { - return fmt.Sprintf("\"%d\"", GenIncrementSurplus(r)) + return fmt.Sprintf("%d", GenIncrementSurplus(r)) }, ), } diff --git a/x/auction/types/auctions.go b/x/auction/types/auctions.go index e0205e65..2e06b86e 100644 --- a/x/auction/types/auctions.go +++ b/x/auction/types/auctions.go @@ -204,7 +204,7 @@ func (a CollateralAuction) GetType() string { return "collateral" } // It is used in genesis initialize the module account correctly. func (a CollateralAuction) GetModuleAccountCoins() sdk.Coins { // a.Bid is paid out on bids, so is never stored in the module account - return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt)) + return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt)...) } // IsReversePhase returns whether the auction has switched over to reverse phase or not. @@ -276,24 +276,25 @@ type WeightedAddresses struct { } // NewWeightedAddresses returns a new list addresses with weights. -func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, sdk.Error) { +func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, error) { wa := WeightedAddresses{ Addresses: addrs, Weights: weights, } if err := wa.Validate(); err != nil { - return WeightedAddresses{}, sdk.ErrInternal(err.Error()) + return WeightedAddresses{}, err } return wa, nil } +// Validate checks for that the weights are not negative and that lengths match. func (wa WeightedAddresses) Validate() error { if len(wa.Addresses) != len(wa.Weights) { - return fmt.Errorf("number of addresses doesn't match number of weights") + return fmt.Errorf("number of addresses doesn't match number of weights, %d ≠ %d", len(wa.Addresses), len(wa.Weights)) } for _, w := range wa.Weights { if w.IsNegative() { - return fmt.Errorf("weights contain a negative amount") + return fmt.Errorf("weights contain a negative amount: %s", w) } } return nil diff --git a/x/auction/types/errors.go b/x/auction/types/errors.go index 85e6b718..6aa78673 100644 --- a/x/auction/types/errors.go +++ b/x/auction/types/errors.go @@ -1,98 +1,36 @@ -// DONTCOVER package types -import ( - "fmt" - "time" +import sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - sdk "github.com/cosmos/cosmos-sdk/types" +// DONTCOVER + +var ( + // ErrInvalidInitialAuctionID error for when the initial auction ID hasn't been set + ErrInvalidInitialAuctionID = sdkerrors.Register(ModuleName, 2, "initial auction ID hasn't been set") + // ErrInvalidModulePermissions error for when module doesn't have valid permissions + ErrInvalidModulePermissions = sdkerrors.Register(ModuleName, 3, "module does not have required permission") + // ErrUnrecognizedAuctionType error for unrecognized auction type + ErrUnrecognizedAuctionType = sdkerrors.Register(ModuleName, 4, "unrecognized auction type") + // ErrAuctionNotFound error for when an auction is not found + ErrAuctionNotFound = sdkerrors.Register(ModuleName, 5, "auction not found") + // ErrAuctionHasNotExpired error for attempting to close an auction that has not passed its end time + ErrAuctionHasNotExpired = sdkerrors.Register(ModuleName, 6, "auction can't be closed as curent block time has not passed auction end time") + // ErrAuctionHasExpired error for when an auction is closed and unavailable for bidding + ErrAuctionHasExpired = sdkerrors.Register(ModuleName, 7, "auction has closed") + // ErrInvalidBidDenom error for when bid denom doesn't match auction bid denom + ErrInvalidBidDenom = sdkerrors.Register(ModuleName, 8, "bid denom doesn't match auction bid denom") + // ErrInvalidLotDenom error for when lot denom doesn't match auction lot denom + ErrInvalidLotDenom = sdkerrors.Register(ModuleName, 9, "lot denom doesn't match auction lot denom") + // ErrBidTooSmall error for when bid is not greater than auction's min bid amount + ErrBidTooSmall = sdkerrors.Register(ModuleName, 10, "bid is not greater than auction's min new bid amount") + // ErrBidTooLarge error for when bid is larger than auction's maximum allowed bid + ErrBidTooLarge = sdkerrors.Register(ModuleName, 11, "bid is greater than auction's max bid") + // ErrLotTooSmall error for when lot is less than zero + ErrLotTooSmall = sdkerrors.Register(ModuleName, 12, "lot is not greater than auction's min new lot amount") + // ErrLotTooLarge error for when lot is not smaller than auction's max new lot amount + ErrLotTooLarge = sdkerrors.Register(ModuleName, 13, "lot is greater than auction's max new lot amount") + // ErrCollateralAuctionIsInReversePhase error for when attempting to place a forward bid on a collateral auction in reverse phase + ErrCollateralAuctionIsInReversePhase = sdkerrors.Register(ModuleName, 14, "invalid bid: auction is in reverse phase") + // ErrCollateralAuctionIsInForwardPhase error for when attempting to place a reverse bid on a collateral auction in forward phase + ErrCollateralAuctionIsInForwardPhase = sdkerrors.Register(ModuleName, 15, "invalid bid: auction is in forward phase") ) - -// Error codes specific to auction module -const ( - DefaultCodespace sdk.CodespaceType = ModuleName - CodeInvalidInitialAuctionID sdk.CodeType = 1 - CodeInvalidModulePermissions sdk.CodeType = 2 - CodeUnrecognizedAuctionType sdk.CodeType = 3 - CodeAuctionNotFound sdk.CodeType = 4 - CodeAuctionHasNotExpired sdk.CodeType = 5 - CodeAuctionHasExpired sdk.CodeType = 6 - CodeInvalidBidDenom sdk.CodeType = 7 - CodeInvalidLotDenom sdk.CodeType = 8 - CodeBidTooSmall sdk.CodeType = 9 - CodeBidTooLarge sdk.CodeType = 10 - CodeLotTooSmall sdk.CodeType = 11 - CodeLotTooLarge sdk.CodeType = 12 - CodeCollateralAuctionIsInReversePhase sdk.CodeType = 13 - CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 14 -) - -// ErrInvalidInitialAuctionID error for when the initial auction ID hasn't been set -func ErrInvalidInitialAuctionID(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeInvalidInitialAuctionID, fmt.Sprintf("initial auction ID hasn't been set")) -} - -// ErrInvalidModulePermissions error for when module doesn't have valid permissions -func ErrInvalidModulePermissions(codespace sdk.CodespaceType, permission string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidModulePermissions, fmt.Sprintf("module does not have required permission '%s'", permission)) -} - -// ErrUnrecognizedAuctionType error for unrecognized auction type -func ErrUnrecognizedAuctionType(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeUnrecognizedAuctionType, fmt.Sprintf("unrecognized auction type")) -} - -// ErrAuctionNotFound error for when an auction is not found -func ErrAuctionNotFound(codespace sdk.CodespaceType, id uint64) sdk.Error { - return sdk.NewError(codespace, CodeAuctionNotFound, fmt.Sprintf("auction %d was not found", id)) -} - -// ErrAuctionHasNotExpired error for attempting to close an auction that has not passed its end time -func ErrAuctionHasNotExpired(codespace sdk.CodespaceType, blockTime time.Time, endTime time.Time) sdk.Error { - return sdk.NewError(codespace, CodeAuctionHasNotExpired, fmt.Sprintf("auction can't be closed as curent block time (%v) has not passed auction end time (%v)", blockTime, endTime)) -} - -// ErrAuctionHasExpired error for when an auction is closed and unavailable for bidding -func ErrAuctionHasExpired(codespace sdk.CodespaceType, id uint64) sdk.Error { - return sdk.NewError(codespace, CodeAuctionHasExpired, fmt.Sprintf("auction %d has closed", id)) -} - -// ErrInvalidBidDenom error for when bid denom doesn't match auction bid denom -func ErrInvalidBidDenom(codespace sdk.CodespaceType, bidDenom string, auctionBidDenom string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidBidDenom, fmt.Sprintf("bid denom %s doesn't match auction bid denom %s", bidDenom, auctionBidDenom)) -} - -// ErrInvalidLotDenom error for when lot denom doesn't match auction lot denom -func ErrInvalidLotDenom(codespace sdk.CodespaceType, lotDenom string, auctionLotDenom string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidLotDenom, fmt.Sprintf("lot denom %s doesn't match auction lot denom %s", lotDenom, auctionLotDenom)) -} - -// ErrBidTooSmall error for when bid is not greater than auction's min bid amount -func ErrBidTooSmall(codespace sdk.CodespaceType, bid sdk.Coin, minBid sdk.Coin) sdk.Error { - return sdk.NewError(codespace, CodeBidTooSmall, fmt.Sprintf("bid %s is not greater than auction's min new bid amount %s", bid.String(), minBid.String())) -} - -// ErrBidTooLarge error for when bid is larger than auction's maximum allowed bid -func ErrBidTooLarge(codespace sdk.CodespaceType, bid sdk.Coin, maxBid sdk.Coin) sdk.Error { - return sdk.NewError(codespace, CodeBidTooLarge, fmt.Sprintf("bid %s is greater than auction's max bid %s", bid.String(), maxBid.String())) -} - -// ErrLotToosmall error for when lot is less than zero -func ErrLotTooSmall(codespace sdk.CodespaceType, lot sdk.Coin, minLot sdk.Coin) sdk.Error { - return sdk.NewError(codespace, CodeLotTooSmall, fmt.Sprintf("lot %s is not greater than auction's min new lot amount %s", lot.String(), minLot.String())) -} - -// ErrLotTooLarge error for when lot is not smaller than auction's max new lot amount -func ErrLotTooLarge(codespace sdk.CodespaceType, lot sdk.Coin, maxLot sdk.Coin) sdk.Error { - return sdk.NewError(codespace, CodeLotTooLarge, fmt.Sprintf("lot %s is greater than auction's max new lot amount %s", lot.String(), maxLot.String())) -} - -// ErrCollateralAuctionIsInReversePhase error for when attempting to place a forward bid on a collateral auction in reverse phase -func ErrCollateralAuctionIsInReversePhase(codespace sdk.CodespaceType, id uint64) sdk.Error { - return sdk.NewError(codespace, CodeCollateralAuctionIsInReversePhase, fmt.Sprintf("invalid bid - auction %d is in reverse phase", id)) -} - -// ErrCollateralAuctionIsInForwardPhase error for when attempting to place a reverse bid on a collateral auction in forward phase -func ErrCollateralAuctionIsInForwardPhase(codespace sdk.CodespaceType, id uint64) sdk.Error { - return sdk.NewError(codespace, CodeCollateralAuctionIsInForwardPhase, fmt.Sprintf("invalid bid - auction %d is in forward phase", id)) -} diff --git a/x/auction/types/expected_keepers.go b/x/auction/types/expected_keepers.go index 83141c9e..bb58ef9b 100644 --- a/x/auction/types/expected_keepers.go +++ b/x/auction/types/expected_keepers.go @@ -10,10 +10,10 @@ type SupplyKeeper interface { GetModuleAddress(name string) sdk.AccAddress GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI - SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error + SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error - MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error + BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error } diff --git a/x/auction/types/msg.go b/x/auction/types/msg.go index 435ad08f..4614f512 100644 --- a/x/auction/types/msg.go +++ b/x/auction/types/msg.go @@ -2,7 +2,9 @@ package types import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // ensure Msg interface compliance at compile time @@ -31,12 +33,12 @@ func (msg MsgPlaceBid) Route() string { return RouterKey } func (msg MsgPlaceBid) Type() string { return "place_bid" } // ValidateBasic does a simple validation check that doesn't require access to state. -func (msg MsgPlaceBid) ValidateBasic() sdk.Error { +func (msg MsgPlaceBid) ValidateBasic() error { if msg.Bidder.Empty() { - return sdk.ErrInvalidAddress("invalid (empty) bidder address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "bidder address cannot be empty") } if !msg.Amount.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid bid amount: %s", msg.Amount)) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "bid amount %s", msg.Amount) } return nil } @@ -51,3 +53,12 @@ func (msg MsgPlaceBid) GetSignBytes() []byte { func (msg MsgPlaceBid) GetSigners() []sdk.AccAddress { return []sdk.AccAddress{msg.Bidder} } + +func (msg MsgPlaceBid) String() string { + // String implements the Stringer interface + return fmt.Sprintf(`Place Bid Message: + Auction ID: %d + Bidder: %s + Amount: %s +`, msg.AuctionID, msg.Bidder, msg.Amount) +} diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 74f2037a..89b82aa5 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -2,13 +2,17 @@ package types import ( "bytes" + "errors" "fmt" "time" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/params/subspace" ) +var emptyDec = sdk.Dec{} + // Defaults for auction params const ( // DefaultMaxAuctionDuration max length of auction @@ -69,11 +73,11 @@ func ParamKeyTable() subspace.KeyTable { // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs. func (p *Params) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ - {Key: KeyBidDuration, Value: &p.BidDuration}, - {Key: KeyMaxAuctionDuration, Value: &p.MaxAuctionDuration}, - {Key: KeyIncrementSurplus, Value: &p.IncrementSurplus}, - {Key: KeyIncrementDebt, Value: &p.IncrementDebt}, - {Key: KeyIncrementCollateral, Value: &p.IncrementCollateral}, + params.NewParamSetPair(KeyBidDuration, &p.BidDuration, validateBidDurationParam), + params.NewParamSetPair(KeyMaxAuctionDuration, &p.MaxAuctionDuration, validateMaxAuctionDurationParam), + params.NewParamSetPair(KeyIncrementSurplus, &p.IncrementSurplus, validateIncrementSurplusParam), + params.NewParamSetPair(KeyIncrementDebt, &p.IncrementDebt, validateIncrementDebtParam), + params.NewParamSetPair(KeyIncrementCollateral, &p.IncrementCollateral, validateIncrementCollateralParam), } } @@ -97,26 +101,102 @@ func (p Params) String() string { // Validate checks that the parameters have valid values. func (p Params) Validate() error { - if p.BidDuration < 0 { - return sdk.ErrInternal("bid duration cannot be negative") + if err := validateBidDurationParam(p.BidDuration); err != nil { + return err } - if p.MaxAuctionDuration < 0 { - return sdk.ErrInternal("max auction duration cannot be negative") + + if err := validateMaxAuctionDurationParam(p.MaxAuctionDuration); err != nil { + return err } + if p.BidDuration > p.MaxAuctionDuration { - return sdk.ErrInternal("bid duration param cannot be larger than max auction duration") + return errors.New("bid duration param cannot be larger than max auction duration") } - if p.IncrementSurplus == (sdk.Dec{}) || p.IncrementDebt == (sdk.Dec{}) || p.IncrementCollateral == (sdk.Dec{}) { - return sdk.ErrInternal("auction increment values cannot be nil") + + if err := validateIncrementSurplusParam(p.IncrementSurplus); err != nil { + return err } - if p.IncrementSurplus.IsNegative() { - return sdk.ErrInternal("surplus auction increment cannot be less than zero") + + if err := validateIncrementDebtParam(p.IncrementDebt); err != nil { + return err } - if p.IncrementDebt.IsNegative() { - return sdk.ErrInternal("debt auction increment cannot be less than zero") + + return validateIncrementCollateralParam(p.IncrementCollateral) +} + +func validateBidDurationParam(i interface{}) error { + bidDuration, ok := i.(time.Duration) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) } - if p.IncrementCollateral.IsNegative() { - return sdk.ErrInternal("collateral auction increment cannot be less than zero") + + if bidDuration < 0 { + return fmt.Errorf("bid duration cannot be negative %d", bidDuration) } + + return nil +} + +func validateMaxAuctionDurationParam(i interface{}) error { + maxAuctionDuration, ok := i.(time.Duration) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if maxAuctionDuration < 0 { + return fmt.Errorf("max auction duration cannot be negative %d", maxAuctionDuration) + } + + return nil +} + +func validateIncrementSurplusParam(i interface{}) error { + incrementSurplus, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if incrementSurplus == emptyDec || incrementSurplus.IsNil() { + return errors.New("surplus auction increment cannot be nil or empty") + } + + if incrementSurplus.IsNegative() { + return fmt.Errorf("surplus auction increment cannot be less than zero %s", incrementSurplus) + } + + return nil +} + +func validateIncrementDebtParam(i interface{}) error { + incrementDebt, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if incrementDebt == emptyDec || incrementDebt.IsNil() { + return errors.New("debt auction increment cannot be nil or empty") + } + + if incrementDebt.IsNegative() { + return fmt.Errorf("debt auction increment cannot be less than zero %s", incrementDebt) + } + + return nil +} + +func validateIncrementCollateralParam(i interface{}) error { + incrementCollateral, ok := i.(sdk.Dec) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if incrementCollateral == emptyDec || incrementCollateral.IsNil() { + return errors.New("collateral auction increment cannot be nil or empty") + } + + if incrementCollateral.IsNegative() { + return fmt.Errorf("collateral auction increment cannot be less than zero %s", incrementCollateral) + } + return nil } diff --git a/x/bep3/abci_test.go b/x/bep3/abci_test.go index 37ca6eb2..67da0c39 100644 --- a/x/bep3/abci_test.go +++ b/x/bep3/abci_test.go @@ -3,12 +3,14 @@ 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" + tmbytes "github.com/tendermint/tendermint/libs/bytes" tmtime "github.com/tendermint/tendermint/types/time" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/bep3" ) @@ -19,8 +21,8 @@ type ABCITestSuite struct { app app.TestApp ctx sdk.Context addrs []sdk.AccAddress - swapIDs []cmn.HexBytes - randomNumbers []cmn.HexBytes + swapIDs []tmbytes.HexBytes + randomNumbers []tmbytes.HexBytes } func (suite *ABCITestSuite) SetupTest() { @@ -46,8 +48,8 @@ func (suite *ABCITestSuite) SetupTest() { func (suite *ABCITestSuite) ResetKeeper() { suite.keeper = suite.app.GetBep3Keeper() - var swapIDs []cmn.HexBytes - var randomNumbers []cmn.HexBytes + var swapIDs []tmbytes.HexBytes + var randomNumbers []tmbytes.HexBytes for i := 0; i < 10; i++ { // Set up atomic swap variables expireHeight := int64(360) diff --git a/x/bep3/alias.go b/x/bep3/alias.go index c020c3af..81097b2c 100644 --- a/x/bep3/alias.go +++ b/x/bep3/alias.go @@ -1,5 +1,3 @@ -// nolint -// DO NOT EDIT - generated by aliasgen tool (github.com/rhuairahrighairidh/aliasgen) package bep3 import ( @@ -26,24 +24,8 @@ const ( 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 @@ -144,7 +126,6 @@ type ( AssetSupply = types.AssetSupply AtomicSwap = types.AtomicSwap AtomicSwaps = types.AtomicSwaps - CodeType = types.CodeType GenesisState = types.GenesisState MsgClaimAtomicSwap = types.MsgClaimAtomicSwap MsgCreateAtomicSwap = types.MsgCreateAtomicSwap diff --git a/x/bep3/client/cli/query.go b/x/bep3/client/cli/query.go index 4523970d..a60e52af 100644 --- a/x/bep3/client/cli/query.go +++ b/x/bep3/client/cli/query.go @@ -6,8 +6,8 @@ import ( "strconv" "strings" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/spf13/cobra" @@ -24,7 +24,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Querying commands for the bep3 module", } - bep3QueryCmd.AddCommand(client.GetCommands( + bep3QueryCmd.AddCommand(flags.GetCommands( QueryCalcSwapIDCmd(queryRoute, cdc), QueryCalcRandomNumberHashCmd(queryRoute, cdc), QueryGetAtomicSwapCmd(queryRoute, cdc), diff --git a/x/bep3/client/cli/tx.go b/x/bep3/client/cli/tx.go index 957b0e8a..9543d5c2 100644 --- a/x/bep3/client/cli/tx.go +++ b/x/bep3/client/cli/tx.go @@ -1,13 +1,14 @@ package cli import ( + "bufio" "encoding/hex" "fmt" "strconv" "strings" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" @@ -25,7 +26,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { Short: "bep3 transactions subcommands", } - bep3TxCmd.AddCommand(client.PostCommands( + bep3TxCmd.AddCommand(flags.PostCommands( GetCmdCreateAtomicSwap(cdc), GetCmdClaimAtomicSwap(cdc), GetCmdRefundAtomicSwap(cdc), @@ -43,8 +44,9 @@ func GetCmdCreateAtomicSwap(cdc *codec.Codec) *cobra.Command { version.ClientName, types.ModuleName), Args: cobra.ExactArgs(8), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) from := cliCtx.GetFromAddress() // same as KavaExecutor.DeputyAddress (for cross-chain) to, err := sdk.AccAddressFromBech32(args[0]) @@ -119,8 +121,9 @@ func GetCmdClaimAtomicSwap(cdc *codec.Codec) *cobra.Command { Example: fmt.Sprintf("%s tx %s claim 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af 56f13e6a5cd397447f8b5f8c82fdb5bbf56127db75269f5cc14e50acd8ac9a4c --from accA", version.ClientName, types.ModuleName), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) from := cliCtx.GetFromAddress() @@ -158,8 +161,9 @@ func GetCmdRefundAtomicSwap(cdc *codec.Codec) *cobra.Command { Example: fmt.Sprintf("%s tx %s refund 6682c03cc3856879c8fb98c9733c6b0c30758299138166b6523fe94628b1d3af --from accA", version.ClientName, types.ModuleName), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) from := cliCtx.GetFromAddress() diff --git a/x/bep3/client/rest/rest.go b/x/bep3/client/rest/rest.go index 02777077..1b5bfb21 100644 --- a/x/bep3/client/rest/rest.go +++ b/x/bep3/client/rest/rest.go @@ -3,10 +3,11 @@ package rest import ( "github.com/gorilla/mux" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/rest" - cmn "github.com/tendermint/tendermint/libs/common" ) // RegisterRoutes registers bep3-related REST handlers to a router @@ -17,30 +18,30 @@ func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { // 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"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + To sdk.AccAddress `json:"to" yaml:"to"` + RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"` + SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"` + RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"` + Timestamp int64 `json:"timestamp" yaml:"timestamp"` + Amount sdk.Coins `json:"amount" yaml:"amount"` + ExpectedIncome string `json:"expected_income" yaml:"expected_income"` + HeightSpan int64 `json:"height_span" yaml:"height_span"` + CrossChain bool `json:"cross_chain" yaml:"cross_chain"` } // PostClaimSwapReq defines the properties of a swap claim request's body type PostClaimSwapReq struct { - BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - From sdk.AccAddress `json:"from" yaml:"from"` - SwapID cmn.HexBytes `json:"swap_id" yaml:"swap_id"` - RandomNumber cmn.HexBytes `json:"random_number" yaml:"random_number"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` + RandomNumber tmbytes.HexBytes `json:"random_number" yaml:"random_number"` } // PostRefundSwapReq defines the properties of swap refund request's body type PostRefundSwapReq struct { - BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` - From sdk.AccAddress `json:"from" yaml:"from"` - SwapID cmn.HexBytes `json:"swap_id" yaml:"swap_id"` + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` } diff --git a/x/bep3/genesis.go b/x/bep3/genesis.go index 37f08878..b6d01ad4 100644 --- a/x/bep3/genesis.go +++ b/x/bep3/genesis.go @@ -71,9 +71,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper SupplyKeeper, gs G case Open: // This index expires unclaimed swaps keeper.InsertIntoByBlockIndex(ctx, swap) - incomingSupplies = incomingSupplies.Add(swap.Amount) + incomingSupplies = incomingSupplies.Add(swap.Amount...) case Expired: - incomingSupplies = incomingSupplies.Add(swap.Amount) + incomingSupplies = incomingSupplies.Add(swap.Amount...) case Completed: // This index stores swaps until deletion keeper.InsertIntoLongtermStorage(ctx, swap) @@ -84,9 +84,9 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper SupplyKeeper, gs G switch swap.Status { case Open: keeper.InsertIntoByBlockIndex(ctx, swap) - outgoingSupplies = outgoingSupplies.Add(swap.Amount) + outgoingSupplies = outgoingSupplies.Add(swap.Amount...) case Expired: - outgoingSupplies = outgoingSupplies.Add(swap.Amount) + outgoingSupplies = outgoingSupplies.Add(swap.Amount...) case Completed: keeper.InsertIntoLongtermStorage(ctx, swap) default: diff --git a/x/bep3/handler.go b/x/bep3/handler.go index 27accffe..fa92f055 100644 --- a/x/bep3/handler.go +++ b/x/bep3/handler.go @@ -1,14 +1,13 @@ package bep3 import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // NewHandler creates an sdk.Handler for all the bep3 type messages func NewHandler(k Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { ctx = ctx.WithEventManager(sdk.NewEventManager()) switch msg := msg.(type) { case MsgCreateAtomicSwap: @@ -18,19 +17,18 @@ func NewHandler(k Keeper) sdk.Handler { case MsgRefundAtomicSwap: return handleMsgRefundAtomicSwap(ctx, k, msg) default: - errMsg := fmt.Sprintf("unrecognized %s message type: %T", ModuleName, msg) - return sdk.ErrUnknownRequest(errMsg).Result() + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) } } } // handleMsgCreateAtomicSwap handles requests to create a new AtomicSwap -func handleMsgCreateAtomicSwap(ctx sdk.Context, k Keeper, msg MsgCreateAtomicSwap) sdk.Result { +func handleMsgCreateAtomicSwap(ctx sdk.Context, k Keeper, msg MsgCreateAtomicSwap) (*sdk.Result, error) { err := k.CreateAtomicSwap(ctx, msg.RandomNumberHash, msg.Timestamp, msg.HeightSpan, msg.From, msg.To, msg.SenderOtherChain, msg.RecipientOtherChain, msg.Amount, msg.ExpectedIncome, msg.CrossChain) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -41,17 +39,17 @@ func handleMsgCreateAtomicSwap(ctx sdk.Context, k Keeper, msg MsgCreateAtomicSwa ), ) - return sdk.Result{ + return &sdk.Result{ Events: ctx.EventManager().Events(), - } + }, nil } // handleMsgClaimAtomicSwap handles requests to claim funds in an active AtomicSwap -func handleMsgClaimAtomicSwap(ctx sdk.Context, k Keeper, msg MsgClaimAtomicSwap) sdk.Result { +func handleMsgClaimAtomicSwap(ctx sdk.Context, k Keeper, msg MsgClaimAtomicSwap) (*sdk.Result, error) { err := k.ClaimAtomicSwap(ctx, msg.From, msg.SwapID, msg.RandomNumber) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -62,17 +60,17 @@ func handleMsgClaimAtomicSwap(ctx sdk.Context, k Keeper, msg MsgClaimAtomicSwap) ), ) - return sdk.Result{ + return &sdk.Result{ Events: ctx.EventManager().Events(), - } + }, nil } // handleMsgRefundAtomicSwap handles requests to refund an active AtomicSwap -func handleMsgRefundAtomicSwap(ctx sdk.Context, k Keeper, msg MsgRefundAtomicSwap) sdk.Result { +func handleMsgRefundAtomicSwap(ctx sdk.Context, k Keeper, msg MsgRefundAtomicSwap) (*sdk.Result, error) { err := k.RefundAtomicSwap(ctx, msg.From, msg.SwapID) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -83,7 +81,7 @@ func handleMsgRefundAtomicSwap(ctx sdk.Context, k Keeper, msg MsgRefundAtomicSwa ), ) - return sdk.Result{ + return &sdk.Result{ Events: ctx.EventManager().Events(), - } + }, nil } diff --git a/x/bep3/handler_test.go b/x/bep3/handler_test.go index b905b23d..888d819d 100644 --- a/x/bep3/handler_test.go +++ b/x/bep3/handler_test.go @@ -1,17 +1,16 @@ 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" + tmbytes "github.com/tendermint/tendermint/libs/bytes" tmtime "github.com/tendermint/tendermint/types/time" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/bep3" ) @@ -47,7 +46,7 @@ func (suite *HandlerTestSuite) SetupTest() { suite.ctx = ctx } -func (suite *HandlerTestSuite) AddAtomicSwap() (cmn.HexBytes, cmn.HexBytes) { +func (suite *HandlerTestSuite) AddAtomicSwap() (tmbytes.HexBytes, tmbytes.HexBytes) { expireHeight := int64(360) amount := cs(c("bnb", int64(50000))) timestamp := ts(0) @@ -74,8 +73,9 @@ func (suite *HandlerTestSuite) TestMsgCreateAtomicSwap() { 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()) + res, err := suite.handler(suite.ctx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) } func (suite *HandlerTestSuite) TestMsgClaimAtomicSwap() { @@ -84,15 +84,16 @@ func (suite *HandlerTestSuite) TestMsgClaimAtomicSwap() { 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)))) + badRes, err := suite.handler(suite.ctx, badMsg) + suite.Require().Error(err) + suite.Require().Nil(badRes) // Add an atomic swap before attempting new claim msg swapID, randomNumber := suite.AddAtomicSwap() msg := bep3.NewMsgClaimAtomicSwap(suite.addrs[0], swapID, randomNumber) - res := suite.handler(suite.ctx, msg) - suite.True(res.IsOK()) + res, err := suite.handler(suite.ctx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(res) } func (suite *HandlerTestSuite) TestMsgRefundAtomicSwap() { @@ -101,30 +102,31 @@ func (suite *HandlerTestSuite) TestMsgRefundAtomicSwap() { 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)))) + badRes, err := suite.handler(suite.ctx, badMsg) + suite.Require().Error(err) + suite.Require().Nil(badRes) // Add an atomic swap and build refund msg swapID, _ := suite.AddAtomicSwap() msg := bep3.NewMsgRefundAtomicSwap(suite.addrs[0], swapID) // Attempt to refund active atomic swap - res1 := suite.handler(suite.ctx, msg) - suite.True(strings.Contains(res1.Log, "atomic swap is still active and cannot be refunded")) - suite.False(res1.IsOK()) + res1, err := suite.handler(suite.ctx, msg) + suite.Require().Error(err) + suite.Require().Nil(res1) // Expire the atomic swap with begin blocker and attempt refund laterCtx := suite.ctx.WithBlockHeight(suite.ctx.BlockHeight() + 400) bep3.BeginBlocker(laterCtx, suite.keeper) - res2 := suite.handler(laterCtx, msg) - suite.True(res2.IsOK()) + res2, err := suite.handler(laterCtx, msg) + suite.Require().NoError(err) + suite.Require().NotNil(res2) } 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")) + res, err := suite.handler(suite.ctx, sdk.NewTestMsg()) + suite.Require().Error(err) + suite.Require().Nil(res) } func TestHandlerTestSuite(t *testing.T) { diff --git a/x/bep3/keeper/asset.go b/x/bep3/keeper/asset.go index df586cee..cf8f1ef7 100644 --- a/x/bep3/keeper/asset.go +++ b/x/bep3/keeper/asset.go @@ -2,19 +2,21 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/kava-labs/kava/x/bep3/types" ) // IncrementCurrentAssetSupply increments an asset's supply by the coin -func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error { supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom)) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } // Resulting current supply must be under asset's limit if !supply.Limit.IsGTE(supply.CurrentSupply.Add(coin)) { - return types.ErrExceedsSupplyLimit(k.codespace, coin, supply.CurrentSupply, supply.Limit) + return sdkerrors.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, supply.CurrentSupply, supply.Limit) } supply.CurrentSupply = supply.CurrentSupply.Add(coin) @@ -23,16 +25,16 @@ func (k Keeper) IncrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk. } // DecrementCurrentAssetSupply decrement an asset's supply by the coin -func (k Keeper) DecrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) DecrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) error { supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom)) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } // Resulting current supply must be greater than or equal to 0 // Use sdk.Int instead of sdk.Coin to prevent panic if true if supply.CurrentSupply.Amount.Sub(coin.Amount).IsNegative() { - return types.ErrInvalidCurrentSupply(k.codespace, coin, supply.CurrentSupply) + return sdkerrors.Wrapf(types.ErrInvalidCurrentSupply, "decrease %s, asset supply %s", coin, supply.CurrentSupply) } supply.CurrentSupply = supply.CurrentSupply.Sub(coin) @@ -41,16 +43,16 @@ func (k Keeper) DecrementCurrentAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk. } // IncrementIncomingAssetSupply increments an asset's incoming supply -func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error { supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom)) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } // Result of (current + incoming + amount) must be under asset's limit totalSupply := supply.CurrentSupply.Add(supply.IncomingSupply) if !supply.Limit.IsGTE(totalSupply.Add(coin)) { - return types.ErrExceedsSupplyLimit(k.codespace, coin, totalSupply, supply.Limit) + return sdkerrors.Wrapf(types.ErrExceedsSupplyLimit, "increase %s, asset supply %s, limit %s", coin, totalSupply, supply.Limit) } supply.IncomingSupply = supply.IncomingSupply.Add(coin) @@ -59,16 +61,16 @@ func (k Keeper) IncrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk } // DecrementIncomingAssetSupply decrements an asset's incoming supply -func (k Keeper) DecrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) DecrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) error { supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom)) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } // Resulting incoming supply must be greater than or equal to 0 // Use sdk.Int instead of sdk.Coin to prevent panic if true if supply.IncomingSupply.Amount.Sub(coin.Amount).IsNegative() { - return types.ErrInvalidIncomingSupply(k.codespace, coin, supply.IncomingSupply) + return sdkerrors.Wrapf(types.ErrInvalidIncomingSupply, "decrease %s, incoming supply %s", coin, supply.IncomingSupply) } supply.IncomingSupply = supply.IncomingSupply.Sub(coin) @@ -77,15 +79,15 @@ func (k Keeper) DecrementIncomingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk } // IncrementOutgoingAssetSupply increments an asset's outoing supply -func (k Keeper) IncrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) IncrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) error { supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom)) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } // Result of (outgoing + amount) must be less than current supply if !supply.CurrentSupply.IsGTE(supply.OutgoingSupply.Add(coin)) { - return types.ErrExceedsAvailableSupply(k.codespace, coin, + return sdkerrors.Wrapf(types.ErrExceedsAvailableSupply, "swap amount %s, available supply %s", coin, supply.CurrentSupply.Amount.Sub(supply.OutgoingSupply.Amount)) } @@ -95,16 +97,16 @@ func (k Keeper) IncrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk } // DecrementOutgoingAssetSupply decrements an asset's outoing supply -func (k Keeper) DecrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) DecrementOutgoingAssetSupply(ctx sdk.Context, coin sdk.Coin) error { supply, found := k.GetAssetSupply(ctx, []byte(coin.Denom)) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } // Resulting outgoing supply must be greater than or equal to 0 // Use sdk.Int instead of sdk.Coin to prevent panic if true if supply.OutgoingSupply.Amount.Sub(coin.Amount).IsNegative() { - return types.ErrInvalidOutgoingSupply(k.codespace, coin, supply.OutgoingSupply) + return sdkerrors.Wrapf(types.ErrInvalidOutgoingSupply, "decrease %s, outgoing supply %s", coin, supply.OutgoingSupply) } supply.OutgoingSupply = supply.OutgoingSupply.Sub(coin) diff --git a/x/bep3/keeper/keeper.go b/x/bep3/keeper/keeper.go index 0a0641af..3a800422 100644 --- a/x/bep3/keeper/keeper.go +++ b/x/bep3/keeper/keeper.go @@ -17,20 +17,23 @@ type Keeper struct { 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 { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk types.SupplyKeeper, paramstore subspace.Subspace) Keeper { if addr := sk.GetModuleAddress(types.ModuleName); addr == nil { panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) } + + if !paramstore.HasKeyTable() { + paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) + } + keeper := Keeper{ key: key, cdc: cdc, - paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), + paramSubspace: paramstore, supplyKeeper: sk, - codespace: codespace, } return keeper } diff --git a/x/bep3/keeper/params.go b/x/bep3/keeper/params.go index 7b8cc88c..f3dc1abf 100644 --- a/x/bep3/keeper/params.go +++ b/x/bep3/keeper/params.go @@ -2,6 +2,8 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/kava-labs/kava/x/bep3/types" ) @@ -63,13 +65,13 @@ func (k Keeper) GetAssetByCoinID(ctx sdk.Context, coinID int) (types.AssetParam, } // ValidateLiveAsset checks if an asset is both supported and active -func (k Keeper) ValidateLiveAsset(ctx sdk.Context, coin sdk.Coin) sdk.Error { +func (k Keeper) ValidateLiveAsset(ctx sdk.Context, coin sdk.Coin) error { asset, found := k.GetAssetByDenom(ctx, coin.Denom) if !found { - return types.ErrAssetNotSupported(k.codespace, coin.Denom) + return sdkerrors.Wrap(types.ErrAssetNotSupported, coin.Denom) } if !asset.Active { - return types.ErrAssetNotActive(k.codespace, asset.Denom) + return sdkerrors.Wrap(types.ErrAssetNotActive, asset.Denom) } return nil } diff --git a/x/bep3/keeper/params_test.go b/x/bep3/keeper/params_test.go index 2e3fd4b7..3c4bae7d 100644 --- a/x/bep3/keeper/params_test.go +++ b/x/bep3/keeper/params_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -92,7 +93,7 @@ func (suite *AssetTestSuite) TestValidateLiveAsset() { testCases := []struct { name string args args - expectedError sdk.CodeType + expectedError error expectPass bool }{ { @@ -100,7 +101,7 @@ func (suite *AssetTestSuite) TestValidateLiveAsset() { args{ coin: c("bnb", 1), }, - sdk.CodeType(0), + nil, true, }, { @@ -108,7 +109,7 @@ func (suite *AssetTestSuite) TestValidateLiveAsset() { args{ coin: c("bad", 1), }, - types.CodeAssetNotSupported, + types.ErrAssetNotSupported, false, }, { @@ -116,7 +117,7 @@ func (suite *AssetTestSuite) TestValidateLiveAsset() { args{ coin: c("inc", 1), }, - types.CodeAssetNotActive, + types.ErrAssetNotActive, false, }, } @@ -127,10 +128,10 @@ func (suite *AssetTestSuite) TestValidateLiveAsset() { err := suite.keeper.ValidateLiveAsset(suite.ctx, tc.args.coin) if tc.expectPass { - suite.NoError(err) + suite.Require().NoError(err) } else { - suite.Error(err) - suite.Equal(tc.expectedError, err.Result().Code) + suite.Require().Error(err) + suite.Require().True(errors.Is(err, tc.expectedError)) } }) } diff --git a/x/bep3/keeper/querier.go b/x/bep3/keeper/querier.go index a64db015..8cc573b6 100644 --- a/x/bep3/keeper/querier.go +++ b/x/bep3/keeper/querier.go @@ -1,15 +1,18 @@ package keeper import ( + abci "github.com/tendermint/tendermint/abci/types" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "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) { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) { switch path[0] { case types.QueryGetAssetSupply: return queryAssetSupply(ctx, req, keeper) @@ -20,57 +23,57 @@ func NewQuerier(keeper Keeper) sdk.Querier { case types.QueryGetParams: return queryGetParams(ctx, req, keeper) default: - return nil, sdk.ErrUnknownRequest("unknown bep3 query endpoint") + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName) } } } -func queryAssetSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryAssetSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Decode request var requestParams types.QueryAssetSupply - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } assetSupply, found := keeper.GetAssetSupply(ctx, []byte(requestParams.Denom)) if !found { - return nil, sdk.ErrInternal("Not found") + return nil, sdkerrors.Wrap(types.ErrAssetSupplyNotFound, string(requestParams.Denom)) } // Encode results - bz, err := codec.MarshalJSONIndent(keeper.cdc, assetSupply) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, assetSupply) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } -func queryAtomicSwap(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryAtomicSwap(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Decode request var requestParams types.QueryAtomicSwapByID - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } // Lookup atomic swap atomicSwap, found := keeper.GetAtomicSwap(ctx, requestParams.SwapID) if !found { - return nil, sdk.ErrInternal("Not found") + return nil, sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%d", requestParams.SwapID) } // Encode results - bz, err := codec.MarshalJSONIndent(keeper.cdc, atomicSwap) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, atomicSwap) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } -func queryAtomicSwaps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { +func queryAtomicSwaps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err error) { var swaps types.AtomicSwaps keeper.IterateAtomicSwaps(ctx, func(s types.AtomicSwap) bool { @@ -78,23 +81,23 @@ func queryAtomicSwaps(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (re return false }) - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, swaps) + bz, err2 := codec.MarshalJSONIndent(types.ModuleCdc, swaps) if err2 != nil { - return nil, sdk.ErrInternal("could not marshal result to JSON") + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query params in the bep3 store -func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Get params params := keeper.GetParams(ctx) // Encode results - bz, err := codec.MarshalJSONIndent(keeper.cdc, params) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, params) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } diff --git a/x/bep3/keeper/querier_test.go b/x/bep3/keeper/querier_test.go index c7355e4d..ddf705e5 100644 --- a/x/bep3/keeper/querier_test.go +++ b/x/bep3/keeper/querier_test.go @@ -5,14 +5,17 @@ import ( "strings" "testing" + "github.com/stretchr/testify/suite" + + abci "github.com/tendermint/tendermint/abci/types" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmtime "github.com/tendermint/tendermint/types/time" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/bep3/keeper" "github.com/kava-labs/kava/x/bep3/types" - "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 ( @@ -27,7 +30,7 @@ type QuerierTestSuite struct { querier sdk.Querier addrs []sdk.AccAddress isSupplyDenom map[string]bool - swapIDs []cmn.HexBytes + swapIDs []tmbytes.HexBytes isSwapID map[string]bool } @@ -55,7 +58,7 @@ func (suite *QuerierTestSuite) SetupTest() { suite.addrs = addrs // Create atomic swaps and save IDs - var swapIDs []cmn.HexBytes + var swapIDs []tmbytes.HexBytes isSwapID := make(map[string]bool) for i := 0; i < 10; i++ { // Set up atomic swap variables @@ -87,7 +90,7 @@ func (suite *QuerierTestSuite) TestQueryAssetSupply() { denom := "bnb" query := abci.RequestQuery{ Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAssetSupply}, "/"), - Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryAssetSupply(cmn.HexBytes(denom))), + Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryAssetSupply(tmbytes.HexBytes(denom))), } // Execute query and check the []byte result diff --git a/x/bep3/keeper/swap.go b/x/bep3/keeper/swap.go index cbc9a26c..21fd2e08 100644 --- a/x/bep3/keeper/swap.go +++ b/x/bep3/keeper/swap.go @@ -7,40 +7,37 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/kava-labs/kava/x/bep3/types" ) // CreateAtomicSwap creates a new AtomicSwap func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, timestamp int64, heightSpan int64, sender sdk.AccAddress, recipient sdk.AccAddress, senderOtherChain, recipientOtherChain string, - amount sdk.Coins, expectedIncome string, crossChain bool) sdk.Error { + amount sdk.Coins, expectedIncome string, crossChain bool) error { // Confirm that this is not a duplicate swap swapID := types.CalculateSwapID(randomNumberHash, sender, senderOtherChain) _, found := k.GetAtomicSwap(ctx, swapID) if found { - return types.ErrAtomicSwapAlreadyExists(k.codespace, swapID) + return sdkerrors.Wrap(types.ErrAtomicSwapAlreadyExists, hex.EncodeToString(swapID)) } // The heightSpan period should be more than 10 minutes and less than one week // Assume average block time interval is 10 second. 10 mins = 60 blocks, 1 week = 60480 blocks if heightSpan < k.GetMinBlockLock(ctx) || heightSpan > k.GetMaxBlockLock(ctx) { - return types.ErrInvalidHeightSpan(k.codespace, heightSpan, k.GetMinBlockLock(ctx), k.GetMaxBlockLock(ctx)) + return sdkerrors.Wrapf(types.ErrInvalidHeightSpan, "height span %d, range %d - %d", heightSpan, k.GetMinBlockLock(ctx), k.GetMaxBlockLock(ctx)) } // Unix timestamp must be in range [-15 mins, 30 mins] of the current time pastTimestampLimit := ctx.BlockTime().Add(time.Duration(-15) * time.Minute).Unix() futureTimestampLimit := ctx.BlockTime().Add(time.Duration(30) * time.Minute).Unix() if timestamp < pastTimestampLimit || timestamp >= futureTimestampLimit { - return types.ErrInvalidTimestamp(k.codespace) - } - - // Sanity check on recipient address - if recipient.Empty() { - return sdk.ErrInvalidAddress("invalid (empty) recipient address") + return sdkerrors.Wrap(types.ErrInvalidTimestamp, time.Unix(timestamp, 0).UTC().String()) } if len(amount) != 1 { - return sdk.ErrInternal("amount must contain exactly one coin") + return fmt.Errorf("amount must contain exactly one coin") } err := k.ValidateLiveAsset(ctx, amount[0]) @@ -58,17 +55,15 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times switch direction { case types.Incoming: - err := k.IncrementIncomingAssetSupply(ctx, amount[0]) - if err != nil { - return err - } + err = k.IncrementIncomingAssetSupply(ctx, amount[0]) case types.Outgoing: - err := k.IncrementOutgoingAssetSupply(ctx, amount[0]) - if err != nil { - return err - } + err = k.IncrementOutgoingAssetSupply(ctx, amount[0]) default: - return sdk.ErrInternal("invalid swap direction") + err = fmt.Errorf("invalid swap direction: %s", direction.String()) + } + + if err != nil { + return err } // Transfer coins to module @@ -86,16 +81,16 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times 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.AttributeKeySender, atomicSwap.Sender.String()), + sdk.NewAttribute(types.AttributeKeyRecipient, atomicSwap.Recipient.String()), + sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())), + sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)), sdk.NewAttribute(types.AttributeKeyTimestamp, fmt.Sprintf("%d", atomicSwap.Timestamp)), - sdk.NewAttribute(types.AttributeKeySenderOtherChain, fmt.Sprintf("%s", atomicSwap.SenderOtherChain)), + sdk.NewAttribute(types.AttributeKeySenderOtherChain, 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())), + sdk.NewAttribute(types.AttributeKeyAmount, atomicSwap.Amount[0].String()), + sdk.NewAttribute(types.AttributeKeyExpectedIncome, expectedIncome), + sdk.NewAttribute(types.AttributeKeyDirection, atomicSwap.Direction.String()), ), ) @@ -105,15 +100,15 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times } // 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 { +func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte, randomNumber []byte) error { atomicSwap, found := k.GetAtomicSwap(ctx, swapID) if !found { - return types.ErrAtomicSwapNotFound(k.codespace, swapID) + return sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%d", swapID) } // Only open atomic swaps can be claimed if atomicSwap.Status != types.Open { - return types.ErrSwapNotClaimable(k.codespace) + return types.ErrSwapNotClaimable } // Calculate hashed secret using submitted number @@ -122,34 +117,33 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b // Confirm that secret unlocks the atomic swap if !bytes.Equal(hashedSecret, atomicSwap.GetSwapID()) { - return types.ErrInvalidClaimSecret(k.codespace, hashedSecret, atomicSwap.GetSwapID()) + return sdkerrors.Wrapf(types.ErrInvalidClaimSecret, "%s ≠ %s", hex.EncodeToString(hashedSecret), hex.EncodeToString(atomicSwap.GetSwapID())) } + var err error switch atomicSwap.Direction { case types.Incoming: err := k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0]) if err != nil { - return err + break } err = k.IncrementCurrentAssetSupply(ctx, atomicSwap.Amount[0]) - if err != nil { - return err - } case types.Outgoing: - err := k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0]) + err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0]) if err != nil { - return err + break } err = k.DecrementCurrentAssetSupply(ctx, atomicSwap.Amount[0]) - if err != nil { - return err - } default: - return sdk.ErrInternal("invalid swap direction") + err = fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String()) + } + + if err != nil { + return err } // Send intended recipient coins - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount) + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Recipient, atomicSwap.Amount) if err != nil { return err } @@ -158,11 +152,11 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b 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))), + sdk.NewAttribute(types.AttributeKeyClaimSender, from.String()), + sdk.NewAttribute(types.AttributeKeyRecipient, atomicSwap.Recipient.String()), + sdk.NewAttribute(types.AttributeKeyAtomicSwapID, hex.EncodeToString(atomicSwap.GetSwapID())), + sdk.NewAttribute(types.AttributeKeyRandomNumberHash, hex.EncodeToString(atomicSwap.RandomNumberHash)), + sdk.NewAttribute(types.AttributeKeyRandomNumber, hex.EncodeToString(randomNumber)), ), ) @@ -178,33 +172,32 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b } // 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 { +func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte) error { atomicSwap, found := k.GetAtomicSwap(ctx, swapID) if !found { - return types.ErrAtomicSwapNotFound(k.codespace, swapID) + return sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%s", swapID) } // Only expired swaps may be refunded if atomicSwap.Status != types.Expired { - return types.ErrSwapNotRefundable(k.codespace) + return types.ErrSwapNotRefundable } + var err error switch atomicSwap.Direction { case types.Incoming: - err := k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0]) - if err != nil { - return err - } + err = k.DecrementIncomingAssetSupply(ctx, atomicSwap.Amount[0]) case types.Outgoing: - err := k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0]) - if err != nil { - return err - } + err = k.DecrementOutgoingAssetSupply(ctx, atomicSwap.Amount[0]) default: - return sdk.ErrInternal("invalid swap direction") + err = fmt.Errorf("invalid swap direction: %s", atomicSwap.Direction.String()) + } + + if err != nil { + return err } // Refund coins to original swap sender - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount) + err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, atomicSwap.Sender, atomicSwap.Amount) if err != nil { return err } @@ -231,7 +224,7 @@ func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID [] } // UpdateExpiredAtomicSwaps finds all AtomicSwaps that are past (or at) their ending times and expires them. -func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) sdk.Error { +func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) error { var expiredSwaps [][]byte k.IterateAtomicSwapsByBlock(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool { expiredSwaps = append(expiredSwaps, id) @@ -249,7 +242,7 @@ func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) sdk.Error { } // DeleteClosedAtomicSwapsFromLongtermStorage removes swaps one week after completion -func (k Keeper) DeleteClosedAtomicSwapsFromLongtermStorage(ctx sdk.Context) sdk.Error { +func (k Keeper) DeleteClosedAtomicSwapsFromLongtermStorage(ctx sdk.Context) error { var swapsToDelete [][]byte k.IterateAtomicSwapsLongtermStorage(ctx, uint64(ctx.BlockHeight()), func(id []byte) bool { swapsToDelete = append(swapsToDelete, id) diff --git a/x/bep3/keeper/swap_test.go b/x/bep3/keeper/swap_test.go index 7cfb1447..1440e3e2 100644 --- a/x/bep3/keeper/swap_test.go +++ b/x/bep3/keeper/swap_test.go @@ -5,15 +5,18 @@ import ( "testing" "time" + "github.com/stretchr/testify/suite" + + abci "github.com/tendermint/tendermint/abci/types" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + tmtime "github.com/tendermint/tendermint/types/time" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" "github.com/kava-labs/kava/x/bep3" "github.com/kava-labs/kava/x/bep3/keeper" "github.com/kava-labs/kava/x/bep3/types" - "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 { @@ -25,9 +28,9 @@ type AtomicSwapTestSuite struct { deputy sdk.AccAddress addrs []sdk.AccAddress timestamps []int64 - randomNumberHashes []cmn.HexBytes - swapIDs []cmn.HexBytes - randomNumbers []cmn.HexBytes + randomNumberHashes []tmbytes.HexBytes + swapIDs []tmbytes.HexBytes + randomNumbers []tmbytes.HexBytes } const ( @@ -65,8 +68,8 @@ func (suite *AtomicSwapTestSuite) SetupTest() { func (suite *AtomicSwapTestSuite) GenerateSwapDetails() { var timestamps []int64 - var randomNumberHashes []cmn.HexBytes - var randomNumbers []cmn.HexBytes + var randomNumberHashes []tmbytes.HexBytes + var randomNumbers []tmbytes.HexBytes for i := 0; i < 10; i++ { // Set up atomic swap details timestamp := ts(i) diff --git a/x/bep3/module.go b/x/bep3/module.go index c11054c3..bf675bff 100644 --- a/x/bep3/module.go +++ b/x/bep3/module.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" sim "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -21,7 +22,7 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModuleSimulation = AppModule{} ) // AppModuleBasic defines the basic application module used by the bep3 module. @@ -70,40 +71,21 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { //____________________________________________________________________________ -// AppModuleSimulation defines the module simulation functions used by the auction module. -type AppModuleSimulation struct{} - -// RegisterStoreDecoder registers a decoder for auction module's types -func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[StoreKey] = simulation.DecodeStore -} - -// GenerateGenesisState creates a randomized GenState of the auction module -func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RandomizedParams creates randomized auction param changes for the simulator. -func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange { - return simulation.ParamChanges(r) -} - -//____________________________________________________________________________ - // AppModule implements the sdk.AppModule interface. type AppModule struct { AppModuleBasic - AppModuleSimulation - keeper Keeper - supplyKeeper SupplyKeeper + keeper Keeper + accountKeeper auth.AccountKeeper + supplyKeeper SupplyKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper, supplyKeeper SupplyKeeper) AppModule { +func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper SupplyKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, + accountKeeper: accountKeeper, supplyKeeper: supplyKeeper, } } @@ -161,3 +143,30 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +//____________________________________________________________________________ + +// GenerateGenesisState creates a randomized GenState of the bep3 module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// RandomizedParams returns nil because bep3 has no params. +func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers a decoder for bep3 module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// WeightedOperations returns the all the bep3 module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper) +} diff --git a/x/bep3/simulation/decoder.go b/x/bep3/simulation/decoder.go index 351b0b00..8fc8dd61 100644 --- a/x/bep3/simulation/decoder.go +++ b/x/bep3/simulation/decoder.go @@ -4,14 +4,16 @@ import ( "bytes" "fmt" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/libs/kv" + "github.com/cosmos/cosmos-sdk/codec" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/kava-labs/kava/x/bep3/types" ) // DecodeStore unmarshals the KVPair's Value to the module's corresponding type -func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { case bytes.Equal(kvA.Key[:1], types.AtomicSwapKeyPrefix): var swapA, swapB types.AtomicSwap @@ -27,8 +29,8 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { case bytes.Equal(kvA.Key[:1], types.AtomicSwapByBlockPrefix), bytes.Equal(kvA.Key[:1], types.AtomicSwapLongtermStoragePrefix): - var bytesA cmn.HexBytes = kvA.Value - var bytesB cmn.HexBytes = kvA.Value + var bytesA tmbytes.HexBytes = kvA.Value + var bytesB tmbytes.HexBytes = kvA.Value return fmt.Sprintf("%s\n%s", bytesA.String(), bytesB.String()) default: diff --git a/x/bep3/simulation/decoder_test.go b/x/bep3/simulation/decoder_test.go index e9fc0a75..a5b6ce08 100644 --- a/x/bep3/simulation/decoder_test.go +++ b/x/bep3/simulation/decoder_test.go @@ -6,7 +6,8 @@ import ( "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tendermint/libs/common" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + "github.com/tendermint/tendermint/libs/kv" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -27,14 +28,14 @@ func TestDecodeDistributionStore(t *testing.T) { oneCoin := sdk.NewCoin("coin", sdk.OneInt()) swap := types.NewAtomicSwap(sdk.Coins{oneCoin}, nil, 10, 100, nil, nil, "otherChainSender", "otherChainRec", 200, types.Completed, true, types.Outgoing) supply := types.AssetSupply{Denom: "coin", IncomingSupply: oneCoin, OutgoingSupply: oneCoin, CurrentSupply: oneCoin, Limit: oneCoin} - bz := cmn.HexBytes([]byte{1, 2}) + bz := tmbytes.HexBytes([]byte{1, 2}) - kvPairs := cmn.KVPairs{ - cmn.KVPair{Key: types.AtomicSwapKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(swap)}, - cmn.KVPair{Key: types.AssetSupplyKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(supply)}, - cmn.KVPair{Key: types.AtomicSwapByBlockPrefix, Value: bz}, - cmn.KVPair{Key: types.AtomicSwapByBlockPrefix, Value: bz}, - cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + kvPairs := kv.Pairs{ + kv.Pair{Key: types.AtomicSwapKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(swap)}, + kv.Pair{Key: types.AssetSupplyKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(supply)}, + kv.Pair{Key: types.AtomicSwapByBlockPrefix, Value: bz}, + kv.Pair{Key: types.AtomicSwapByBlockPrefix, Value: bz}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } tests := []struct { diff --git a/x/bep3/simulation/genesis.go b/x/bep3/simulation/genesis.go index c05dad1d..bfac2d88 100644 --- a/x/bep3/simulation/genesis.go +++ b/x/bep3/simulation/genesis.go @@ -25,13 +25,14 @@ const ( var ( MaxSupplyLimit = sdk.NewInt(1000000000000) - Accs []simulation.Account + accs []simulation.Account ConsistentDenoms = [3]string{"bnb", "xrp", "btc"} ) -// GenBnbDeputyAddress randomized BnbDeputyAddress -func GenBnbDeputyAddress(r *rand.Rand) sdk.AccAddress { - return simulation.RandomAcc(r, Accs).Address +// GenRandBnbDeputy randomized BnbDeputyAddress +func GenRandBnbDeputy(r *rand.Rand) simulation.Account { + acc, _ := simulation.RandomAcc(r, accs) + return acc } // GenMinBlockLock randomized MinBlockLock @@ -77,7 +78,7 @@ func genSupportedAsset(r *rand.Rand, denom string) types.AssetParam { // RandomizedGenState generates a random GenesisState func RandomizedGenState(simState *module.SimulationState) { - Accs = simState.Accounts + accs = simState.Accounts bep3Genesis := loadRandomBep3GenState(simState) fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, bep3Genesis)) @@ -90,13 +91,13 @@ func RandomizedGenState(simState *module.SimulationState) { var supplyGenesis supply.GenesisState simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) for _, deputyCoin := range totalCoins { - supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin) + supplyGenesis.Supply = supplyGenesis.Supply.Add(deputyCoin...) } simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) } func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState { - bnbDeputyAddress := simulation.RandomAcc(simState.Rand, simState.Accounts).Address + bnbDeputy := GenRandBnbDeputy(simState.Rand) // min/max block lock are hardcoded to 50/100 for expected -NumBlocks=100 minBlockLock := int64(types.AbsoluteMinimumBlockLock) @@ -110,7 +111,7 @@ func loadRandomBep3GenState(simState *module.SimulationState) types.GenesisState bep3Genesis := types.GenesisState{ Params: types.Params{ - BnbDeputyAddress: bnbDeputyAddress, + BnbDeputyAddress: bnbDeputy.Address, MinBlockLock: minBlockLock, MaxBlockLock: maxBlockLock, SupportedAssets: supportedAssets, @@ -134,7 +135,7 @@ func loadAuthGenState(simState *module.SimulationState, bep3Genesis types.Genesi var totalCoins []sdk.Coins for _, asset := range bep3Genesis.Params.SupportedAssets { assetCoin := sdk.NewCoins(sdk.NewCoin(asset.Denom, asset.Limit)) - if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin)); err != nil { + if err := deputy.SetCoins(deputy.GetCoins().Add(assetCoin...)); err != nil { panic(err) } totalCoins = append(totalCoins, assetCoin) diff --git a/x/bep3/simulation/operations.go b/x/bep3/simulation/operations.go new file mode 100644 index 00000000..0f3592f5 --- /dev/null +++ b/x/bep3/simulation/operations.go @@ -0,0 +1,210 @@ +package simulation + +import ( + "fmt" + "math" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/simulation" + + appparams "github.com/kava-labs/kava/app/params" + "github.com/kava-labs/kava/x/bep3/keeper" + "github.com/kava-labs/kava/x/bep3/types" +) + +var ( + noOpMsg = simulation.NoOpMsg(types.ModuleName) +) + +// Simulation operation weights constants +const ( + OpWeightMsgCreateAtomicSwap = "op_weight_msg_create_atomic_swap" +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, k keeper.Keeper, +) simulation.WeightedOperations { + var weightCreateAtomicSwap int + + appParams.GetOrGenerate(cdc, OpWeightMsgCreateAtomicSwap, &weightCreateAtomicSwap, nil, + func(_ *rand.Rand) { + weightCreateAtomicSwap = appparams.DefaultWeightMsgCreateAtomicSwap + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightCreateAtomicSwap, + SimulateMsgCreateAtomicSwap(ak, k), + ), + } +} + +// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values +func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + + senderAddr := k.GetBnbDeputyAddress(ctx) + + sender, found := simulation.FindAccount(accs, senderAddr) + if !found { + return noOpMsg, nil, nil + } + + recipient, _ := simulation.RandomAcc(r, accs) + + recipientOtherChain := simulation.RandStringOfLength(r, 43) + senderOtherChain := simulation.RandStringOfLength(r, 43) + + // Generate cryptographically strong pseudo-random number + randomNumber, err := simulation.RandPositiveInt(r, sdk.NewInt(math.MaxInt64)) + if err != nil { + return noOpMsg, nil, err + } + // Must use current blocktime instead of 'now' since initial blocktime was randomly generated + timestamp := ctx.BlockTime().Unix() + randomNumberHash := types.CalculateRandomHash(randomNumber.BigInt().Bytes(), timestamp) + + // Randomly select an asset from supported assets + assets, found := k.GetAssets(ctx) + if !found { + return noOpMsg, nil, fmt.Errorf("no supported assets found") + } + asset := assets[r.Intn(len(assets))] + + // Check that the sender has coins of this type + senderAcc := ak.GetAccount(ctx, senderAddr) + fees, err := simulation.RandomFees(r, ctx, senderAcc.SpendableCoins(ctx.BlockTime())) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + availableAmount := senderAcc.SpendableCoins(ctx.BlockTime()).Sub(fees).AmountOf(asset.Denom) + // Get an amount of coins between 0.1 and 2% of total coins + amount := availableAmount.Quo(sdk.NewInt(int64(simulation.RandIntBetween(r, 50, 1000)))) + if amount.IsZero() { + return simulation.NewOperationMsgBasic(types.ModuleName, fmt.Sprintf("no-operation (all funds exhausted for asset %s)", asset.Denom), "", false, nil), nil, nil + } + coin := sdk.NewCoin(asset.Denom, amount) + coins := sdk.NewCoins(coin) + expectedIncome := coin.String() + + // We're assuming that sims are run with -NumBlocks=100 + heightSpan := int64(55) + crossChain := true + + msg := types.NewMsgCreateAtomicSwap( + senderAddr, recipient.Address, recipientOtherChain, senderOtherChain, randomNumberHash, + timestamp, coins, expectedIncome, heightSpan, crossChain, + ) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{senderAcc.GetAccountNumber()}, + []uint64{senderAcc.GetSequence()}, + sender.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + // If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation + var futureOp simulation.FutureOperation + swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain) + if r.Intn(100) < 50 { + // Claim future operation + executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) + futureOp = simulation.FutureOperation{ + BlockHeight: int(executionBlock), + Op: operationClaimAtomicSwap(ak, k, swapID, randomNumber.BigInt().Bytes()), + } + } else { + // Refund future operation + executionBlock := ctx.BlockHeight() + msg.HeightSpan + futureOp = simulation.FutureOperation{ + BlockHeight: int(executionBlock), + Op: operationRefundAtomicSwap(ak, k, swapID), + } + } + + return simulation.NewOperationMsg(msg, true, result.Log), []simulation.FutureOperation{futureOp}, nil + } +} + +func operationClaimAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper, swapID []byte, randomNumber []byte) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + simAccount, _ := simulation.RandomAcc(r, accs) + acc := ak.GetAccount(ctx, simAccount.Address) + + msg := types.NewMsgClaimAtomicSwap(acc.GetAddress(), swapID, randomNumber) + + fees, err := simulation.RandomFees(r, ctx, acc.SpendableCoins(ctx.BlockTime())) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil + } +} + +func operationRefundAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper, swapID []byte) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + simAccount, _ := simulation.RandomAcc(r, accs) + acc := ak.GetAccount(ctx, simAccount.Address) + + msg := types.NewMsgRefundAtomicSwap(acc.GetAddress(), swapID) + + fees, err := simulation.RandomFees(r, ctx, acc.SpendableCoins(ctx.BlockTime())) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil + } +} diff --git a/x/bep3/simulation/operations/msg.go b/x/bep3/simulation/operations/msg.go deleted file mode 100644 index 1fdaff6b..00000000 --- a/x/bep3/simulation/operations/msg.go +++ /dev/null @@ -1,146 +0,0 @@ -package operations - -import ( - "fmt" - "math" - "math/rand" - - "github.com/cosmos/cosmos-sdk/baseapp" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/simulation" - - "github.com/kava-labs/kava/x/bep3" - "github.com/kava-labs/kava/x/bep3/keeper" - "github.com/kava-labs/kava/x/bep3/types" -) - -var ( - noOpMsg = simulation.NoOpMsg(bep3.ModuleName) -) - -// SimulateMsgCreateAtomicSwap generates a MsgCreateAtomicSwap with random values -func SimulateMsgCreateAtomicSwap(ak auth.AccountKeeper, k keeper.Keeper) simulation.Operation { - handler := bep3.NewHandler(k) - - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( - simulation.OperationMsg, []simulation.FutureOperation, error) { - - sender := k.GetBnbDeputyAddress(ctx) - recipient := simulation.RandomAcc(r, accs).Address - - recipientOtherChain := simulation.RandStringOfLength(r, 43) - senderOtherChain := simulation.RandStringOfLength(r, 43) - - // Generate cryptographically strong pseudo-random number - randomNumber, err := simulation.RandPositiveInt(r, sdk.NewInt(math.MaxInt64)) - if err != nil { - return noOpMsg, nil, err - } - // Must use current blocktime instead of 'now' since initial blocktime was randomly generated - timestamp := ctx.BlockTime().Unix() - randomNumberHash := types.CalculateRandomHash(randomNumber.BigInt().Bytes(), timestamp) - - // Randomly select an asset from supported assets - assets, found := k.GetAssets(ctx) - if !found { - return noOpMsg, nil, fmt.Errorf("no supported assets found") - } - asset := assets[r.Intn(len(assets))] - - // Check that the sender has coins of this type - availableAmount := ak.GetAccount(ctx, sender).GetCoins().AmountOf(asset.Denom) - // Get an amount of coins between 0.1 and 2% of total coins - amount := availableAmount.Quo(sdk.NewInt(int64(simulation.RandIntBetween(r, 50, 1000)))) - if amount.IsZero() { - return simulation.NewOperationMsgBasic(bep3.ModuleName, fmt.Sprintf("no-operation (all funds exhausted for asset %s)", asset.Denom), "", false, nil), nil, nil - } - coin := sdk.NewCoin(asset.Denom, amount) - coins := sdk.NewCoins(coin) - expectedIncome := coin.String() - - // We're assuming that sims are run with -NumBlocks=100 - heightSpan := int64(55) - crossChain := true - - msg := types.NewMsgCreateAtomicSwap( - sender, recipient, recipientOtherChain, senderOtherChain, randomNumberHash, - timestamp, coins, expectedIncome, heightSpan, crossChain) - - if err := msg.ValidateBasic(); err != nil { - return noOpMsg, nil, fmt.Errorf("expected MsgCreateAtomicSwap to pass ValidateBasic: %s", err) - } - - // Submit msg - ok := submitMsg(ctx, handler, msg) - - // If created, construct a MsgClaimAtomicSwap or MsgRefundAtomicSwap future operation - var futureOp simulation.FutureOperation - if ok { - swapID := types.CalculateSwapID(msg.RandomNumberHash, msg.From, msg.SenderOtherChain) - acc := simulation.RandomAcc(r, accs) - evenOdd := r.Intn(2) + 1 - if evenOdd%2 == 0 { - // Claim future operation - executionBlock := ctx.BlockHeight() + (msg.HeightSpan / 2) - futureOp = loadClaimFutureOp(acc.Address, swapID, randomNumber.BigInt().Bytes(), executionBlock, handler) - } else { - // Refund future operation - executionBlock := ctx.BlockHeight() + msg.HeightSpan - futureOp = loadRefundFutureOp(acc.Address, swapID, executionBlock, handler) - } - } - - return simulation.NewOperationMsg(msg, ok, ""), []simulation.FutureOperation{futureOp}, nil - } -} - -func loadClaimFutureOp(sender sdk.AccAddress, swapID []byte, randomNumber []byte, height int64, handler sdk.Handler) simulation.FutureOperation { - claimOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( - simulation.OperationMsg, []simulation.FutureOperation, error) { - - // Build the refund msg and validate basic - claimMsg := types.NewMsgClaimAtomicSwap(sender, swapID, randomNumber) - if err := claimMsg.ValidateBasic(); err != nil { - return noOpMsg, nil, fmt.Errorf("expected MsgClaimAtomicSwap to pass ValidateBasic: %s", err) - } - - // Test msg submission at target block height - ok := handler(ctx.WithBlockHeight(height), claimMsg).IsOK() - return simulation.NewOperationMsg(claimMsg, ok, ""), nil, nil - } - - return simulation.FutureOperation{ - BlockHeight: int(height), - Op: claimOp, - } -} - -func loadRefundFutureOp(sender sdk.AccAddress, swapID []byte, height int64, handler sdk.Handler) simulation.FutureOperation { - refundOp := func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( - simulation.OperationMsg, []simulation.FutureOperation, error) { - // Build the refund msg and validate basic - refundMsg := types.NewMsgRefundAtomicSwap(sender, swapID) - if err := refundMsg.ValidateBasic(); err != nil { - return noOpMsg, nil, fmt.Errorf("expected MsgRefundAtomicSwap to pass ValidateBasic: %s", err) - } - - // Test msg submission at target block height - ok := handler(ctx.WithBlockHeight(height), refundMsg).IsOK() - return simulation.NewOperationMsg(refundMsg, ok, ""), nil, nil - } - - return simulation.FutureOperation{ - BlockHeight: int(height), - Op: refundOp, - } -} - -func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { - ctx, write := ctx.CacheContext() - ok = handler(ctx, msg).IsOK() - if ok { - write() - } - return ok -} diff --git a/x/bep3/simulation/params.go b/x/bep3/simulation/params.go index 41c3a346..76656750 100644 --- a/x/bep3/simulation/params.go +++ b/x/bep3/simulation/params.go @@ -22,22 +22,22 @@ func ParamChanges(r *rand.Rand) []simulation.ParamChange { minBlockLockVal := GenMinBlockLock(r) return []simulation.ParamChange{ - simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress, "", + simulation.NewSimParamChange(types.ModuleName, keyBnbDeputyAddress, func(r *rand.Rand) string { - return fmt.Sprintf("\"%s\"", GenBnbDeputyAddress(r)) + return fmt.Sprintf("\"%s\"", GenRandBnbDeputy(r).Address) }, ), - simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock, "", + simulation.NewSimParamChange(types.ModuleName, keyMinBlockLock, func(r *rand.Rand) string { return fmt.Sprintf("\"%d\"", minBlockLockVal) }, ), - simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock, "", + simulation.NewSimParamChange(types.ModuleName, keyMaxBlockLock, func(r *rand.Rand) string { return fmt.Sprintf("\"%d\"", GenMaxBlockLock(r, minBlockLockVal)) }, ), - simulation.NewSimParamChange(types.ModuleName, keySupportedAssets, "", + simulation.NewSimParamChange(types.ModuleName, keySupportedAssets, func(r *rand.Rand) string { return fmt.Sprintf("\"%v\"", GenSupportedAssets(r)) }, diff --git a/x/bep3/types/errors.go b/x/bep3/types/errors.go index 06eb951f..71939c46 100644 --- a/x/bep3/types/errors.go +++ b/x/bep3/types/errors.go @@ -1,118 +1,40 @@ package types import ( - "encoding/hex" - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" - cmn "github.com/tendermint/tendermint/libs/common" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) -// CodeType is the local code type -type CodeType = sdk.CodeType +// DONTCOVER -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 +var ( + // ErrInvalidTimestamp error for when an timestamp is outside of bounds. Assumes block time of 10 seconds. + ErrInvalidTimestamp = sdkerrors.Register(ModuleName, 2, "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 + ErrInvalidHeightSpan = sdkerrors.Register(ModuleName, 3, "height span is outside acceptable range") + // ErrAssetNotSupported error for when an asset is not supported + ErrAssetNotSupported = sdkerrors.Register(ModuleName, 4, "asset not on the list of supported assets") + // ErrAssetNotActive error for when an asset is currently inactive + ErrAssetNotActive = sdkerrors.Register(ModuleName, 5, "asset is currently inactive") + // ErrAssetSupplyNotFound error for when an asset's supply is not found in the store + ErrAssetSupplyNotFound = sdkerrors.Register(ModuleName, 6, "asset supply not found in store") + // ErrExceedsSupplyLimit error for when the proposed supply increase would put the supply above limit + ErrExceedsSupplyLimit = sdkerrors.Register(ModuleName, 7, "asset supply over limit") + // ErrExceedsAvailableSupply error for when the proposed outgoing amount exceeds the total available supply + ErrExceedsAvailableSupply = sdkerrors.Register(ModuleName, 8, "outgoing swap exceeds total available supply") + // ErrInvalidCurrentSupply error for when the proposed decrease would result in a negative current supplyx + ErrInvalidCurrentSupply = sdkerrors.Register(ModuleName, 9, "supply decrease puts current asset supply below 0") + // ErrInvalidIncomingSupply error for when the proposed decrease would result in a negative incoming supply + ErrInvalidIncomingSupply = sdkerrors.Register(ModuleName, 10, "supply decrease puts incoming asset supply below 0") + // ErrInvalidOutgoingSupply error for when the proposed decrease would result in a negative outgoing supply + ErrInvalidOutgoingSupply = sdkerrors.Register(ModuleName, 11, "supply decrease puts outgoing asset supply below 0") + // ErrInvalidClaimSecret error when a submitted secret doesn't match an AtomicSwap's swapID + ErrInvalidClaimSecret = sdkerrors.Register(ModuleName, 12, "hashed claim attempt does not match") + // ErrAtomicSwapAlreadyExists error for when an AtomicSwap with this swapID already exists + ErrAtomicSwapAlreadyExists = sdkerrors.Register(ModuleName, 13, "atomic swap already exists") + // ErrAtomicSwapNotFound error for when an atomic swap is not found + ErrAtomicSwapNotFound = sdkerrors.Register(ModuleName, 14, "atomic swap not found") + // ErrSwapNotRefundable error for when an AtomicSwap has not expired and cannot be refunded + ErrSwapNotRefundable = sdkerrors.Register(ModuleName, 15, "atomic swap is still active and cannot be refunded") + // ErrSwapNotClaimable error for when an atomic swap is not open and cannot be claimed + ErrSwapNotClaimable = sdkerrors.Register(ModuleName, 16, "atomic swap is not claimable") ) - -// 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")) -} diff --git a/x/bep3/types/expected_keepers.go b/x/bep3/types/expected_keepers.go index 83141c9e..bb58ef9b 100644 --- a/x/bep3/types/expected_keepers.go +++ b/x/bep3/types/expected_keepers.go @@ -10,10 +10,10 @@ type SupplyKeeper interface { GetModuleAddress(name string) sdk.AccAddress GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI - SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error + SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) error + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error - BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error - MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error + BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error } diff --git a/x/bep3/types/msg.go b/x/bep3/types/msg.go index 309fa2af..92be18da 100644 --- a/x/bep3/types/msg.go +++ b/x/bep3/types/msg.go @@ -1,11 +1,15 @@ package types import ( + "errors" "fmt" + "strings" + + "github.com/tendermint/tendermint/crypto" + tmbytes "github.com/tendermint/tendermint/libs/bytes" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/tendermint/tendermint/crypto" - cmn "github.com/tendermint/tendermint/libs/common" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) const ( @@ -36,21 +40,21 @@ var ( // MsgCreateAtomicSwap contains an AtomicSwap struct type MsgCreateAtomicSwap struct { - 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"` + From sdk.AccAddress `json:"from" yaml:"from"` + To sdk.AccAddress `json:"to" yaml:"to"` + RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"` + SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"` + RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"` + Timestamp int64 `json:"timestamp" yaml:"timestamp"` + Amount sdk.Coins `json:"amount" yaml:"amount"` + ExpectedIncome string `json:"expected_income" yaml:"expected_income"` + HeightSpan int64 `json:"height_span" yaml:"height_span"` + CrossChain bool `json:"cross_chain" yaml:"cross_chain"` } // NewMsgCreateAtomicSwap initializes a new MsgCreateAtomicSwap func NewMsgCreateAtomicSwap(from sdk.AccAddress, to sdk.AccAddress, recipientOtherChain, - senderOtherChain string, randomNumberHash cmn.HexBytes, timestamp int64, + senderOtherChain string, randomNumberHash tmbytes.HexBytes, timestamp int64, amount sdk.Coins, expectedIncome string, heightSpan int64, crossChain bool) MsgCreateAtomicSwap { return MsgCreateAtomicSwap{ From: from, @@ -91,49 +95,58 @@ func (msg MsgCreateAtomicSwap) GetSigners() []sdk.AccAddress { } // ValidateBasic validates the MsgCreateAtomicSwap -func (msg MsgCreateAtomicSwap) ValidateBasic() sdk.Error { +func (msg MsgCreateAtomicSwap) ValidateBasic() error { + if msg.From.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") + } if len(msg.From) != AddrByteCount { - return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From))) + return fmt.Errorf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From)) + } + if msg.To.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "recipient address cannot be empty") } if len(msg.To) != AddrByteCount { - return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.To))) + return fmt.Errorf("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 && msg.RecipientOtherChain != "" { + return errors.New("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 && msg.SenderOtherChain != "" { + return errors.New("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 msg.CrossChain && strings.TrimSpace(msg.RecipientOtherChain) == "" { + return errors.New("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)) + return fmt.Errorf("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)) + return fmt.Errorf("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)) + return fmt.Errorf("the length of random number hash should be %d", RandomNumberHashLength) } if msg.Timestamp <= 0 { - return sdk.ErrInternal("timestamp must be positive") + return errors.New("timestamp must be positive") } - if !msg.Amount.IsAllPositive() { - return sdk.ErrInternal(fmt.Sprintf("the swapped out coin must be positive")) + if len(msg.Amount) == 0 { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "amount cannot be empty") + } + if !msg.Amount.IsValid() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, msg.Amount.String()) } if len(msg.ExpectedIncome) > MaxExpectedIncomeLength { - return sdk.ErrInternal(fmt.Sprintf("the length of expected income should be less than %d", MaxExpectedIncomeLength)) + return fmt.Errorf("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)) + return fmt.Errorf("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())) + return fmt.Errorf("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 errors.New("height span must be positive") } return nil } @@ -146,9 +159,9 @@ func (msg MsgCreateAtomicSwap) GetSignBytes() []byte { // MsgClaimAtomicSwap defines a AtomicSwap claim type MsgClaimAtomicSwap struct { - 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"` + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` + RandomNumber tmbytes.HexBytes `json:"random_number" yaml:"random_number"` } // NewMsgClaimAtomicSwap initializes a new MsgClaimAtomicSwap @@ -182,15 +195,18 @@ func (msg MsgClaimAtomicSwap) GetSigners() []sdk.AccAddress { } // ValidateBasic validates the MsgClaimAtomicSwap -func (msg MsgClaimAtomicSwap) ValidateBasic() sdk.Error { +func (msg MsgClaimAtomicSwap) ValidateBasic() error { + if msg.From.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") + } if len(msg.From) != AddrByteCount { - return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From))) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "actual address length ≠ expected length (%d ≠ %d)", len(msg.From), AddrByteCount) } if len(msg.SwapID) != SwapIDLength { - return sdk.ErrInternal(fmt.Sprintf("the length of swapID should be %d", SwapIDLength)) + return fmt.Errorf("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 errors.New("the length of random number cannot be 0") } return nil } @@ -203,8 +219,8 @@ func (msg MsgClaimAtomicSwap) GetSignBytes() []byte { // MsgRefundAtomicSwap defines a refund msg type MsgRefundAtomicSwap struct { - From sdk.AccAddress `json:"from" yaml:"from"` - SwapID cmn.HexBytes `json:"swap_id" yaml:"swap_id"` + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` } // NewMsgRefundAtomicSwap initializes a new MsgRefundAtomicSwap @@ -237,12 +253,15 @@ func (msg MsgRefundAtomicSwap) GetSigners() []sdk.AccAddress { } // ValidateBasic validates the MsgRefundAtomicSwap -func (msg MsgRefundAtomicSwap) ValidateBasic() sdk.Error { +func (msg MsgRefundAtomicSwap) ValidateBasic() error { + if msg.From.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") + } if len(msg.From) != AddrByteCount { - return sdk.ErrInternal(fmt.Sprintf("the expected address length is %d, actual length is %d", AddrByteCount, len(msg.From))) + return fmt.Errorf("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 fmt.Errorf("the length of swapID should be %d", SwapIDLength) } return nil } diff --git a/x/bep3/types/msg_test.go b/x/bep3/types/msg_test.go index 30cdd2ef..3940e0b6 100644 --- a/x/bep3/types/msg_test.go +++ b/x/bep3/types/msg_test.go @@ -3,11 +3,14 @@ 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" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/kava-labs/kava/x/bep3/types" ) var ( @@ -33,7 +36,7 @@ func TestMsgCreateAtomicSwap(t *testing.T) { to sdk.AccAddress recipientOtherChain string senderOtherChain string - randomNumberHash cmn.HexBytes + randomNumberHash tmbytes.HexBytes timestamp int64 amount sdk.Coins expectedIncome string @@ -75,8 +78,8 @@ func TestMsgClaimAtomicSwap(t *testing.T) { tests := []struct { description string from sdk.AccAddress - swapID cmn.HexBytes - randomNumber cmn.HexBytes + swapID tmbytes.HexBytes + randomNumber tmbytes.HexBytes expectPass bool }{ {"normal", binanceAddrs[0], swapID, randomNumberHash, true}, @@ -102,7 +105,7 @@ func TestMsgRefundAtomicSwap(t *testing.T) { tests := []struct { description string from sdk.AccAddress - swapID cmn.HexBytes + swapID tmbytes.HexBytes expectPass bool }{ {"normal", binanceAddrs[0], swapID, true}, diff --git a/x/bep3/types/params.go b/x/bep3/types/params.go index cc8cc18f..b7d04493 100644 --- a/x/bep3/types/params.go +++ b/x/bep3/types/params.go @@ -1,12 +1,18 @@ package types import ( + "errors" "fmt" + "strings" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) +const ( + bech32MainPrefix = "kava" +) + // Parameter keys var ( KeyBnbDeputyAddress = []byte("BnbDeputyAddress") @@ -59,7 +65,11 @@ func NewParams(bnbDeputyAddress sdk.AccAddress, minBlockLock, maxBlockLock int64 // DefaultParams returns default params for bep3 module func DefaultParams() Params { - defaultBnbDeputyAddress, _ := sdk.AccAddressFromBech32("kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj") + defaultBnbDeputyAddress, err := sdk.AccAddressFromBech32("kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj") + if err != nil { + panic(err) + } + return NewParams(defaultBnbDeputyAddress, DefaultMinBlockLock, DefaultMaxBlockLock, DefaultSupportedAssets) } @@ -103,44 +113,112 @@ func ParamKeyTable() params.KeyTable { // 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}, + params.NewParamSetPair(KeyBnbDeputyAddress, &p.BnbDeputyAddress, validateBnbDeputyAddressParam), + params.NewParamSetPair(KeyMinBlockLock, &p.MinBlockLock, validateMinBlockLockParam), + params.NewParamSetPair(KeyMaxBlockLock, &p.MaxBlockLock, validateMaxBlockLockParam), + params.NewParamSetPair(KeySupportedAssets, &p.SupportedAssets, validateSupportedAssetsParams), } } // 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 err := validateBnbDeputyAddressParam(p.BnbDeputyAddress); err != nil { + return err } + + if err := validateMinBlockLockParam(p.MinBlockLock); err != nil { + return err + } + + if err := validateMaxBlockLockParam(p.MaxBlockLock); err != nil { + return err + } + if p.MinBlockLock >= p.MaxBlockLock { - return fmt.Errorf("maximum block lock must be greater than minimum block lock") + return fmt.Errorf("minimum block lock cannot be ≥ maximum block lock, got %d ≥ %d", p.MinBlockLock, p.MaxBlockLock) } - if p.MaxBlockLock > AbsoluteMaximumBlockLock { - return fmt.Errorf(fmt.Sprintf("maximum block lock cannot be greater than %d", AbsoluteMaximumBlockLock)) + + return validateSupportedAssetsParams(p.SupportedAssets) +} + +func validateBnbDeputyAddressParam(i interface{}) error { + addr, ok := i.(sdk.AccAddress) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) } + + if addr.Empty() { + return errors.New("bnb deputy address cannot be empty") + } + + if len(addr.Bytes()) != sdk.AddrLen { + return fmt.Errorf("bnb deputy address invalid bytes length got %d, want %d", len(addr.Bytes()), sdk.AddrLen) + } + + return nil +} + +func validateMinBlockLockParam(i interface{}) error { + minBlockLock, ok := i.(int64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if minBlockLock < AbsoluteMinimumBlockLock { + return fmt.Errorf("minimum block lock cannot be less than %d, got %d", AbsoluteMinimumBlockLock, minBlockLock) + } + + return nil +} + +func validateMaxBlockLockParam(i interface{}) error { + maxBlockLock, ok := i.(int64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if maxBlockLock > AbsoluteMaximumBlockLock { + return fmt.Errorf("maximum block lock cannot be greater than %d, got %d", AbsoluteMaximumBlockLock, maxBlockLock) + } + + return nil +} + +func validateSupportedAssetsParams(i interface{}) error { + assetParams, ok := i.(AssetParams) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + 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") + for _, asset := range assetParams { + if strings.TrimSpace(asset.Denom) == "" { + return errors.New("asset denom cannot be empty") } + if asset.CoinID < 0 { - return fmt.Errorf(fmt.Sprintf("asset %s must be a positive integer", asset.Denom)) + return fmt.Errorf(fmt.Sprintf("asset %s must be a non negative 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] { + + _, found := coinDenoms[asset.Denom] + if found { return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate denom", asset.Denom)) } + coinDenoms[asset.Denom] = true - if coinIDs[asset.CoinID] { + + _, found = coinIDs[asset.CoinID] + if found { return fmt.Errorf(fmt.Sprintf("asset %s cannot have duplicate coin id %d", asset.Denom, asset.CoinID)) } + coinIDs[asset.CoinID] = true } + return nil } diff --git a/x/bep3/types/params_test.go b/x/bep3/types/params_test.go index 26a2e7e8..65e49cfd 100644 --- a/x/bep3/types/params_test.go +++ b/x/bep3/types/params_test.go @@ -1,7 +1,6 @@ package types_test import ( - "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -204,10 +203,9 @@ func (suite *ParamsTestSuite) TestParamValidation() { err := params.Validate() if tc.expectPass { - suite.Nil(err) + suite.Require().NoError(err, tc.name) } else { - suite.NotNil(err) - suite.True(strings.Contains(err.Error(), tc.expectedErr)) + suite.Require().Error(err, tc.name) } } } diff --git a/x/bep3/types/querier.go b/x/bep3/types/querier.go index 8a88456a..034a851b 100644 --- a/x/bep3/types/querier.go +++ b/x/bep3/types/querier.go @@ -1,8 +1,6 @@ package types -import ( - cmn "github.com/tendermint/tendermint/libs/common" -) +import tmbytes "github.com/tendermint/tendermint/libs/bytes" const ( // QueryGetAssetSupply command for getting info about an asset's supply @@ -17,11 +15,11 @@ const ( // QueryAssetSupply contains the params for query 'custom/bep3/supply' type QueryAssetSupply struct { - Denom cmn.HexBytes `json:"denom" yaml:"denom"` + Denom tmbytes.HexBytes `json:"denom" yaml:"denom"` } // NewQueryAssetSupply creates a new QueryAssetSupply -func NewQueryAssetSupply(denom cmn.HexBytes) QueryAssetSupply { +func NewQueryAssetSupply(denom tmbytes.HexBytes) QueryAssetSupply { return QueryAssetSupply{ Denom: denom, } @@ -29,11 +27,11 @@ func NewQueryAssetSupply(denom cmn.HexBytes) QueryAssetSupply { // QueryAtomicSwapByID contains the params for query 'custom/bep3/swap' type QueryAtomicSwapByID struct { - SwapID cmn.HexBytes `json:"swap_id" yaml:"swap_id"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` } // NewQueryAtomicSwapByID creates a new QueryAtomicSwapByID -func NewQueryAtomicSwapByID(swapBytes cmn.HexBytes) QueryAtomicSwapByID { +func NewQueryAtomicSwapByID(swapBytes tmbytes.HexBytes) QueryAtomicSwapByID { return QueryAtomicSwapByID{ SwapID: swapBytes, } diff --git a/x/bep3/types/swap.go b/x/bep3/types/swap.go index 1a7dbacb..6fee11dc 100644 --- a/x/bep3/types/swap.go +++ b/x/bep3/types/swap.go @@ -5,13 +5,14 @@ import ( "encoding/json" "fmt" + tmbytes "github.com/tendermint/tendermint/libs/bytes" + 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 + GetSwapID() tmbytes.HexBytes GetModuleAccountCoins() sdk.Coins Validate() error } @@ -19,22 +20,22 @@ type Swap interface { // 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"` + Amount sdk.Coins `json:"amount" yaml:"amount"` + RandomNumberHash tmbytes.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, +func NewAtomicSwap(amount sdk.Coins, randomNumberHash tmbytes.HexBytes, expireHeight, timestamp int64, sender, recipient sdk.AccAddress, senderOtherChain string, recipientOtherChain string, closedBlock int64, status SwapStatus, crossChain bool, direction SwapDirection) AtomicSwap { return AtomicSwap{ @@ -54,7 +55,7 @@ func NewAtomicSwap(amount sdk.Coins, randomNumberHash cmn.HexBytes, expireHeight } // GetSwapID calculates the ID of an atomic swap -func (a AtomicSwap) GetSwapID() cmn.HexBytes { +func (a AtomicSwap) GetSwapID() tmbytes.HexBytes { return CalculateSwapID(a.RandomNumberHash, a.Sender, a.SenderOtherChain) } diff --git a/x/bep3/types/swap_test.go b/x/bep3/types/swap_test.go index db22dab0..fb7fcf20 100644 --- a/x/bep3/types/swap_test.go +++ b/x/bep3/types/swap_test.go @@ -3,18 +3,21 @@ package types_test import ( "testing" + "github.com/stretchr/testify/suite" + + tmbytes "github.com/tendermint/tendermint/libs/bytes" + 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 + randomNumberHashes []tmbytes.HexBytes } func (suite *AtomicSwapTestSuite) SetupTest() { @@ -25,7 +28,7 @@ func (suite *AtomicSwapTestSuite) SetupTest() { // Generate 10 timestamps and random number hashes var timestamps []int64 - var randomNumberHashes []cmn.HexBytes + var randomNumberHashes []tmbytes.HexBytes for i := 0; i < 10; i++ { timestamp := ts(i) randomNumber, _ := types.GenerateSecureRandomNumber() @@ -43,7 +46,7 @@ func (suite *AtomicSwapTestSuite) SetupTest() { func (suite *AtomicSwapTestSuite) TestNewAtomicSwap() { type args struct { amount sdk.Coins - randomNumberHash cmn.HexBytes + randomNumberHash tmbytes.HexBytes expireHeight int64 timestamp int64 sender sdk.AccAddress @@ -127,7 +130,7 @@ func (suite *AtomicSwapTestSuite) TestNewAtomicSwap() { 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()) + suite.Equal(tmbytes.HexBytes(expectedSwapID), swap.GetSwapID()) } else { suite.Error(swap.Validate()) } diff --git a/x/cdp/alias.go b/x/cdp/alias.go index 7cae19d8..9688a153 100644 --- a/x/cdp/alias.go +++ b/x/cdp/alias.go @@ -1,8 +1,3 @@ -// nolint -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/cdp/keeper -// ALIASGEN: github.com/kava-labs/kava/x/cdp/types package cdp import ( @@ -10,26 +5,14 @@ import ( "github.com/kava-labs/kava/x/cdp/types" ) +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/kava-labs/kava/x/cdp/keeper +// ALIASGEN: github.com/kava-labs/kava/x/cdp/types + const ( BaseDigitFactor = keeper.BaseDigitFactor - DefaultCodespace = types.DefaultCodespace - CodeCdpAlreadyExists = types.CodeCdpAlreadyExists - CodeCollateralLengthInvalid = types.CodeCollateralLengthInvalid - CodeCollateralNotSupported = types.CodeCollateralNotSupported - CodeDebtNotSupported = types.CodeDebtNotSupported - CodeExceedsDebtLimit = types.CodeExceedsDebtLimit - CodeInvalidCollateralRatio = types.CodeInvalidCollateralRatio - CodeCdpNotFound = types.CodeCdpNotFound - CodeDepositNotFound = types.CodeDepositNotFound - CodeInvalidDepositDenom = types.CodeInvalidDepositDenom - CodeInvalidPaymentDenom = types.CodeInvalidPaymentDenom - CodeDepositNotAvailable = types.CodeDepositNotAvailable - CodeInvalidCollateralDenom = types.CodeInvalidCollateralDenom - CodeInvalidWithdrawAmount = types.CodeInvalidWithdrawAmount - CodeCdpNotAvailable = types.CodeCdpNotAvailable - CodeBelowDebtFloor = types.CodeBelowDebtFloor - CodePaymentExceedsDebt = types.CodePaymentExceedsDebt - CodeLoadingAugmentedCDP = types.CodeLoadingAugmentedCDP EventTypeCreateCdp = types.EventTypeCreateCdp EventTypeCdpDeposit = types.EventTypeCdpDeposit EventTypeCdpDraw = types.EventTypeCdpDraw @@ -75,14 +58,13 @@ var ( ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio ErrCdpNotFound = types.ErrCdpNotFound ErrDepositNotFound = types.ErrDepositNotFound - ErrInvalidDepositDenom = types.ErrInvalidDepositDenom - ErrInvalidPaymentDenom = types.ErrInvalidPaymentDenom + ErrInvalidDeposit = types.ErrInvalidDeposit + ErrInvalidPayment = types.ErrInvalidPayment ErrDepositNotAvailable = types.ErrDepositNotAvailable - ErrInvalidCollateralDenom = types.ErrInvalidCollateralDenom + ErrInvalidCollateral = types.ErrInvalidCollateral ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount ErrCdpNotAvailable = types.ErrCdpNotAvailable ErrBelowDebtFloor = types.ErrBelowDebtFloor - ErrPaymentExceedsDebt = types.ErrPaymentExceedsDebt ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP NewGenesisState = types.NewGenesisState DefaultGenesisState = types.DefaultGenesisState diff --git a/x/cdp/client/cli/query.go b/x/cdp/client/cli/query.go index 095b3fcb..12bb542a 100644 --- a/x/cdp/client/cli/query.go +++ b/x/cdp/client/cli/query.go @@ -6,8 +6,8 @@ import ( "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" @@ -23,7 +23,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Querying commands for the cdp module", } - cdpQueryCmd.AddCommand(client.GetCommands( + cdpQueryCmd.AddCommand(flags.GetCommands( QueryCdpCmd(queryRoute, cdc), QueryCdpsByDenomCmd(queryRoute, cdc), QueryCdpsByDenomAndRatioCmd(queryRoute, cdc), @@ -131,9 +131,9 @@ $ %s query %s cdps-by-ratio uatom 1.5 cliCtx := context.NewCLIContext().WithCodec(cdc) // Prepare params for querier - ratio, errSdk := sdk.NewDecFromStr(args[1]) - if errSdk != nil { - return fmt.Errorf(errSdk.Error()) + ratio, err := sdk.NewDecFromStr(args[1]) + if err != nil { + return err } bz, err := cdc.MarshalJSON(types.QueryCdpsByRatioParams{ CollateralDenom: args[0], diff --git a/x/cdp/client/cli/tx.go b/x/cdp/client/cli/tx.go index 9214aa1b..8f15888e 100644 --- a/x/cdp/client/cli/tx.go +++ b/x/cdp/client/cli/tx.go @@ -1,13 +1,14 @@ package cli import ( + "bufio" "fmt" "strings" "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/version" @@ -24,7 +25,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command { Short: "cdp transactions subcommands", } - cdpTxCmd.AddCommand(client.PostCommands( + cdpTxCmd.AddCommand(flags.PostCommands( GetCmdCreateCdp(cdc), GetCmdDeposit(cdc), GetCmdWithdraw(cdc), @@ -48,8 +49,9 @@ $ %s tx %s create 10000000uatom 1000usdx --from myKeyName `, version.ClientName, types.ModuleName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) collateral, err := sdk.ParseCoins(args[0]) if err != nil { @@ -82,8 +84,9 @@ $ %s tx %s deposit kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw 10000000uatom --f `, version.ClientName, types.ModuleName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) collateral, err := sdk.ParseCoins(args[1]) if err != nil { @@ -116,8 +119,9 @@ $ %s tx %s withdraw kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw 10000000uatom -- `, version.ClientName, types.ModuleName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) collateral, err := sdk.ParseCoins(args[1]) if err != nil { @@ -150,8 +154,9 @@ $ %s tx %s draw uatom 1000usdx --from myKeyName `, version.ClientName, types.ModuleName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) debt, err := sdk.ParseCoins(args[1]) if err != nil { @@ -180,8 +185,9 @@ $ %s tx %s repay uatom 1000usdx --from myKeyName `, version.ClientName, types.ModuleName)), Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) payment, err := sdk.ParseCoins(args[1]) if err != nil { diff --git a/x/cdp/genesis.go b/x/cdp/genesis.go index 0a4e6880..3fa365ef 100644 --- a/x/cdp/genesis.go +++ b/x/cdp/genesis.go @@ -58,7 +58,7 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, sk SupplyKeeper, } k.SetCDP(ctx, cdp) k.IndexCdpByOwner(ctx, cdp) - ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + ratio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.IndexCdpByCollateralRatio(ctx, cdp.Collateral[0].Denom, cdp.ID, ratio) k.IncrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, cdp.Principal) } diff --git a/x/cdp/handler.go b/x/cdp/handler.go index 13e1ef17..e1adfc4b 100644 --- a/x/cdp/handler.go +++ b/x/cdp/handler.go @@ -1,14 +1,13 @@ package cdp import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // NewHandler creates an sdk.Handler for cdp messages func NewHandler(k Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { switch msg := msg.(type) { case MsgCreateCDP: return handleMsgCreateCDP(ctx, k, msg) @@ -21,16 +20,15 @@ func NewHandler(k Keeper) sdk.Handler { case MsgRepayDebt: return handleMsgRepayDebt(ctx, k, msg) default: - errMsg := fmt.Sprintf("unrecognized cdp msg type: %T", msg) - return sdk.ErrUnknownRequest(errMsg).Result() + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) } } } -func handleMsgCreateCDP(ctx sdk.Context, k Keeper, msg MsgCreateCDP) sdk.Result { +func handleMsgCreateCDP(ctx sdk.Context, k Keeper, msg MsgCreateCDP) (*sdk.Result, error) { err := k.AddCdp(ctx, msg.Sender, msg.Collateral, msg.Principal) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -42,16 +40,16 @@ func handleMsgCreateCDP(ctx sdk.Context, k Keeper, msg MsgCreateCDP) sdk.Result ) id, _ := k.GetCdpID(ctx, msg.Sender, msg.Collateral[0].Denom) - return sdk.Result{ + return &sdk.Result{ Data: GetCdpIDBytes(id), Events: ctx.EventManager().Events(), - } + }, nil } -func handleMsgDeposit(ctx sdk.Context, k Keeper, msg MsgDeposit) sdk.Result { +func handleMsgDeposit(ctx sdk.Context, k Keeper, msg MsgDeposit) (*sdk.Result, error) { err := k.DepositCollateral(ctx, msg.Owner, msg.Depositor, msg.Collateral) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -61,13 +59,13 @@ func handleMsgDeposit(ctx sdk.Context, k Keeper, msg MsgDeposit) sdk.Result { sdk.NewAttribute(sdk.AttributeKeySender, msg.Depositor.String()), ), ) - return sdk.Result{Events: ctx.EventManager().Events()} + return &sdk.Result{Events: ctx.EventManager().Events()}, nil } -func handleMsgWithdraw(ctx sdk.Context, k Keeper, msg MsgWithdraw) sdk.Result { +func handleMsgWithdraw(ctx sdk.Context, k Keeper, msg MsgWithdraw) (*sdk.Result, error) { err := k.WithdrawCollateral(ctx, msg.Owner, msg.Depositor, msg.Collateral) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -77,13 +75,13 @@ func handleMsgWithdraw(ctx sdk.Context, k Keeper, msg MsgWithdraw) sdk.Result { sdk.NewAttribute(sdk.AttributeKeySender, msg.Depositor.String()), ), ) - return sdk.Result{Events: ctx.EventManager().Events()} + return &sdk.Result{Events: ctx.EventManager().Events()}, nil } -func handleMsgDrawDebt(ctx sdk.Context, k Keeper, msg MsgDrawDebt) sdk.Result { +func handleMsgDrawDebt(ctx sdk.Context, k Keeper, msg MsgDrawDebt) (*sdk.Result, error) { err := k.AddPrincipal(ctx, msg.Sender, msg.CdpDenom, msg.Principal) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -93,13 +91,13 @@ func handleMsgDrawDebt(ctx sdk.Context, k Keeper, msg MsgDrawDebt) sdk.Result { sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), ), ) - return sdk.Result{Events: ctx.EventManager().Events()} + return &sdk.Result{Events: ctx.EventManager().Events()}, nil } -func handleMsgRepayDebt(ctx sdk.Context, k Keeper, msg MsgRepayDebt) sdk.Result { +func handleMsgRepayDebt(ctx sdk.Context, k Keeper, msg MsgRepayDebt) (*sdk.Result, error) { err := k.RepayPrincipal(ctx, msg.Sender, msg.CdpDenom, msg.Payment) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -109,5 +107,5 @@ func handleMsgRepayDebt(ctx sdk.Context, k Keeper, msg MsgRepayDebt) sdk.Result sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), ), ) - return sdk.Result{Events: ctx.EventManager().Events()} + return &sdk.Result{Events: ctx.EventManager().Events()}, nil } diff --git a/x/cdp/handler_test.go b/x/cdp/handler_test.go index 0652c040..ed3cc576 100644 --- a/x/cdp/handler_test.go +++ b/x/cdp/handler_test.go @@ -1,7 +1,6 @@ package cdp_test import ( - "strings" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -47,16 +46,16 @@ func (suite *HandlerTestSuite) TestMsgCreateCdp() { cs(c("xrp", 200000000)), cs(c("usdx", 10000000)), ) - res := suite.handler(suite.ctx, msg) - suite.True(res.IsOK()) - suite.Equal(cdp.GetCdpIDBytes(uint64(1)), res.Data) + res, err := suite.handler(suite.ctx, msg) + suite.Require().NoError(err) + suite.Require().Equal(cdp.GetCdpIDBytes(uint64(1)), res.Data) } func (suite *HandlerTestSuite) TestInvalidMsg() { - res := suite.handler(suite.ctx, sdk.NewTestMsg()) - suite.False(res.IsOK()) - suite.True(strings.Contains(res.Log, "unrecognized cdp msg type")) + res, err := suite.handler(suite.ctx, sdk.NewTestMsg()) + suite.Require().Error(err) + suite.Require().Nil(res) } func TestHandlerTestSuite(t *testing.T) { diff --git a/x/cdp/keeper/auctions.go b/x/cdp/keeper/auctions.go index 1d74f6ba..3042ad78 100644 --- a/x/cdp/keeper/auctions.go +++ b/x/cdp/keeper/auctions.go @@ -43,7 +43,7 @@ func (pd partialDeposits) SumDebt() (sum sdk.Int) { } // AuctionCollateral creates auctions from the input deposits which attempt to raise the corresponding amount of debt -func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) sdk.Error { +func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt sdk.Int, bidDenom string) error { auctionSize := k.getAuctionSize(ctx, deposits[0].Amount[0].Denom) partialAuctionDeposits := partialDeposits{} totalCollateral := deposits.SumCollateral() @@ -110,7 +110,7 @@ func (k Keeper) AuctionCollateral(ctx sdk.Context, deposits types.Deposits, debt } // CreateAuctionsFromDeposit creates auctions from the input deposit until there is less than auctionSize left on the deposit -func (k Keeper) CreateAuctionsFromDeposit(ctx sdk.Context, dep types.Deposit, debt sdk.Int, totalCollateral sdk.Int, auctionSize sdk.Int, principalDenom string) (debtChange sdk.Int, collateralChange sdk.Int, err sdk.Error) { +func (k Keeper) CreateAuctionsFromDeposit(ctx sdk.Context, dep types.Deposit, debt sdk.Int, totalCollateral sdk.Int, auctionSize sdk.Int, principalDenom string) (debtChange sdk.Int, collateralChange sdk.Int, err error) { debtChange = sdk.ZeroInt() collateralChange = sdk.ZeroInt() depositAmount := dep.Amount[0].Amount @@ -138,7 +138,7 @@ func (k Keeper) CreateAuctionsFromDeposit(ctx sdk.Context, dep types.Deposit, de } // CreateAuctionFromPartialDeposits creates an auction from the input partial deposits -func (k Keeper) CreateAuctionFromPartialDeposits(ctx sdk.Context, partialDeps partialDeposits, debt sdk.Int, collateral sdk.Int, auctionSize sdk.Int, bidDenom string) (debtChange, collateralChange sdk.Int, err sdk.Error) { +func (k Keeper) CreateAuctionFromPartialDeposits(ctx sdk.Context, partialDeps partialDeposits, debt sdk.Int, collateral sdk.Int, auctionSize sdk.Int, bidDenom string) (debtChange, collateralChange sdk.Int, err error) { returnAddrs := []sdk.AccAddress{} returnWeights := []sdk.Int{} @@ -159,7 +159,7 @@ func (k Keeper) CreateAuctionFromPartialDeposits(ctx sdk.Context, partialDeps pa // NetSurplusAndDebt burns surplus and debt coins equal to the minimum of surplus and debt balances held by the liquidator module account // for example, if there is 1000 debt and 100 surplus, 100 surplus and 100 debt are burned, netting to 900 debt -func (k Keeper) NetSurplusAndDebt(ctx sdk.Context) sdk.Error { +func (k Keeper) NetSurplusAndDebt(ctx sdk.Context) error { totalSurplus := k.GetTotalSurplus(ctx, types.LiquidatorMacc) debt := k.GetTotalDebt(ctx, types.LiquidatorMacc) netAmount := sdk.MinInt(totalSurplus, debt) @@ -210,7 +210,7 @@ func (k Keeper) GetTotalDebt(ctx sdk.Context, accountName string) sdk.Int { } // RunSurplusAndDebtAuctions nets the surplus and debt balances and then creates surplus or debt auctions if the remaining balance is above the auction threshold parameter -func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) sdk.Error { +func (k Keeper) RunSurplusAndDebtAuctions(ctx sdk.Context) error { k.NetSurplusAndDebt(ctx) remainingDebt := k.GetTotalDebt(ctx, types.LiquidatorMacc) params := k.GetParams(ctx) diff --git a/x/cdp/keeper/cdp.go b/x/cdp/keeper/cdp.go index 9e3611ee..beff0c2e 100644 --- a/x/cdp/keeper/cdp.go +++ b/x/cdp/keeper/cdp.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/cdp/types" ) @@ -13,7 +14,7 @@ import ( const BaseDigitFactor = 1000000000000000000 // AddCdp adds a cdp for a specific owner and collateral type -func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coins, principal sdk.Coins) sdk.Error { +func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coins, principal sdk.Coins) error { // validation err := k.ValidateCollateral(ctx, collateral) if err != nil { @@ -21,7 +22,7 @@ func (k Keeper) AddCdp(ctx sdk.Context, owner sdk.AccAddress, collateral sdk.Coi } _, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral[0].Denom) if found { - return types.ErrCdpAlreadyExists(k.codespace, owner, collateral[0].Denom) + return sdkerrors.Wrapf(types.ErrCdpAlreadyExists, "owner %s, denom %s", owner, collateral[0].Denom) } err = k.ValidatePrincipalAdd(ctx, principal) if err != nil { @@ -103,10 +104,10 @@ func (k Keeper) SetCdpAndCollateralRatioIndex(ctx sdk.Context, cdp types.CDP, ra } // MintDebtCoins mints debt coins in the cdp module account -func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coins) sdk.Error { +func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom string, principalCoins sdk.Coins) error { coinsToMint := sdk.NewCoins() for _, sc := range principalCoins { - coinsToMint = coinsToMint.Add(sdk.NewCoins(sdk.NewCoin(denom, sc.Amount))) + coinsToMint = coinsToMint.Add(sdk.NewCoin(denom, sc.Amount)) } err := k.supplyKeeper.MintCoins(ctx, moduleAccount, coinsToMint) if err != nil { @@ -116,10 +117,10 @@ func (k Keeper) MintDebtCoins(ctx sdk.Context, moduleAccount string, denom strin } // BurnDebtCoins burns debt coins from the cdp module account -func (k Keeper) BurnDebtCoins(ctx sdk.Context, moduleAccount string, denom string, paymentCoins sdk.Coins) sdk.Error { +func (k Keeper) BurnDebtCoins(ctx sdk.Context, moduleAccount string, denom string, paymentCoins sdk.Coins) error { coinsToBurn := sdk.NewCoins() for _, pc := range paymentCoins { - coinsToBurn = coinsToBurn.Add(sdk.NewCoins(sdk.NewCoin(denom, pc.Amount))) + coinsToBurn = coinsToBurn.Add(sdk.NewCoin(denom, pc.Amount)) } err := k.supplyKeeper.BurnCoins(ctx, moduleAccount, coinsToBurn) if err != nil { @@ -340,64 +341,65 @@ func (k Keeper) SetGovDenom(ctx sdk.Context, denom string) { } // ValidateCollateral validates that a collateral is valid for use in cdps -func (k Keeper) ValidateCollateral(ctx sdk.Context, collateral sdk.Coins) sdk.Error { +func (k Keeper) ValidateCollateral(ctx sdk.Context, collateral sdk.Coins) error { if len(collateral) != 1 { - return types.ErrInvalidCollateralLength(k.codespace, len(collateral)) + return sdkerrors.Wrapf(types.ErrInvalidCollateralLength, "%d", len(collateral)) } _, found := k.GetCollateral(ctx, collateral[0].Denom) if !found { - return types.ErrCollateralNotSupported(k.codespace, collateral[0].Denom) + return sdkerrors.Wrap(types.ErrCollateralNotSupported, collateral[0].Denom) } return nil } // ValidatePrincipalAdd validates that an asset is valid for use as debt when creating a new cdp -func (k Keeper) ValidatePrincipalAdd(ctx sdk.Context, principal sdk.Coins) sdk.Error { +func (k Keeper) ValidatePrincipalAdd(ctx sdk.Context, principal sdk.Coins) error { for _, dc := range principal { dp, found := k.GetDebtParam(ctx, dc.Denom) if !found { - return types.ErrDebtNotSupported(k.codespace, dc.Denom) + return sdkerrors.Wrap(types.ErrDebtNotSupported, dc.Denom) } if dc.Amount.LT(dp.DebtFloor) { - return types.ErrBelowDebtFloor(k.codespace, sdk.NewCoins(dc), dp.DebtFloor) + return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", dc, dp.DebtFloor) } } return nil } // ValidatePrincipalDraw validates that an asset is valid for use as debt when drawing debt off an existing cdp -func (k Keeper) ValidatePrincipalDraw(ctx sdk.Context, principal sdk.Coins) sdk.Error { +func (k Keeper) ValidatePrincipalDraw(ctx sdk.Context, principal sdk.Coins) error { for _, dc := range principal { _, found := k.GetDebtParam(ctx, dc.Denom) if !found { - return types.ErrDebtNotSupported(k.codespace, dc.Denom) + return sdkerrors.Wrap(types.ErrDebtNotSupported, dc.Denom) } } return nil } // ValidateDebtLimit validates that the input debt amount does not exceed the global debt limit or the debt limit for that collateral -func (k Keeper) ValidateDebtLimit(ctx sdk.Context, collateralDenom string, principal sdk.Coins) sdk.Error { +func (k Keeper) ValidateDebtLimit(ctx sdk.Context, collateralDenom string, principal sdk.Coins) error { cp, found := k.GetCollateral(ctx, collateralDenom) if !found { - return types.ErrCollateralNotSupported(k.codespace, collateralDenom) + return sdkerrors.Wrap(types.ErrCollateralNotSupported, collateralDenom) } + for _, dc := range principal { totalPrincipal := k.GetTotalPrincipal(ctx, collateralDenom, dc.Denom).Add(dc.Amount) collateralLimit := cp.DebtLimit.AmountOf(dc.Denom) if totalPrincipal.GT(collateralLimit) { - return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, collateralLimit))) + return sdkerrors.Wrapf(types.ErrExceedsDebtLimit, "debt increase %s > collateral debt limit %s", sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, collateralLimit))) } globalLimit := k.GetParams(ctx).GlobalDebtLimit.AmountOf(dc.Denom) if totalPrincipal.GT(globalLimit) { - return types.ErrExceedsDebtLimit(k.codespace, sdk.NewCoins(sdk.NewCoin(dc.Denom, totalPrincipal)), sdk.NewCoins(sdk.NewCoin(dc.Denom, globalLimit))) + return sdkerrors.Wrapf(types.ErrExceedsDebtLimit, "debt increase %s > global debt limit %s", sdk.NewCoin(dc.Denom, totalPrincipal), sdk.NewCoin(dc.Denom, globalLimit)) } } return nil } // ValidateCollateralizationRatio validate that adding the input principal doesn't put the cdp below the liquidation ratio -func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coins, principal sdk.Coins, fees sdk.Coins) sdk.Error { +func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coins, principal sdk.Coins, fees sdk.Coins) error { // collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, collateral, principal, fees) if err != nil { @@ -405,7 +407,7 @@ func (k Keeper) ValidateCollateralizationRatio(ctx sdk.Context, collateral sdk.C } liquidationRatio := k.getLiquidationRatio(ctx, collateral[0].Denom) if collateralizationRatio.LT(liquidationRatio) { - return types.ErrInvalidCollateralRatio(k.codespace, collateral[0].Denom, collateralizationRatio, liquidationRatio) + return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ratio %s", collateral[0].Denom, collateralizationRatio, liquidationRatio) } return nil } @@ -427,12 +429,11 @@ func (k Keeper) CalculateCollateralToDebtRatio(ctx sdk.Context, collateral sdk.C } // LoadAugmentedCDP creates a new augmented CDP from an existing CDP -func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.AugmentedCDP, sdk.Error) { +func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.AugmentedCDP, error) { // calculate additional fees periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom) - totalFees := cdp.AccumulatedFees.Add(fees) - + fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) + totalFees := cdp.AccumulatedFees.Add(fees...) // calculate collateralization ratio collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal, totalFees) if err != nil { @@ -444,7 +445,7 @@ func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.Augmente for _, principalCoin := range cdp.Principal { totalDebt += principalCoin.Amount.Int64() } - for _, feeCoin := range cdp.AccumulatedFees.Add(fees) { + for _, feeCoin := range cdp.AccumulatedFees.Add(fees...) { totalDebt += feeCoin.Amount.Int64() } @@ -459,7 +460,7 @@ func (k Keeper) LoadAugmentedCDP(ctx sdk.Context, cdp types.CDP) (types.Augmente } // CalculateCollateralizationRatio returns the collateralization ratio of the input collateral to the input debt plus fees -func (k Keeper) CalculateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coins, principal sdk.Coins, fees sdk.Coins) (sdk.Dec, sdk.Error) { +func (k Keeper) CalculateCollateralizationRatio(ctx sdk.Context, collateral sdk.Coins, principal sdk.Coins, fees sdk.Coins) (sdk.Dec, error) { if collateral.IsZero() { return sdk.ZeroDec(), nil } @@ -485,7 +486,7 @@ func (k Keeper) CalculateCollateralizationRatio(ctx sdk.Context, collateral sdk. } // CalculateCollateralizationRatioFromAbsoluteRatio takes a coin's denom and an absolute ratio and returns the respective collateralization ratio -func (k Keeper) CalculateCollateralizationRatioFromAbsoluteRatio(ctx sdk.Context, collateralDenom string, absoluteRatio sdk.Dec) (sdk.Dec, sdk.Error) { +func (k Keeper) CalculateCollateralizationRatioFromAbsoluteRatio(ctx sdk.Context, collateralDenom string, absoluteRatio sdk.Dec) (sdk.Dec, error) { // get price collateral marketID := k.getMarketID(ctx, collateralDenom) price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) diff --git a/x/cdp/keeper/cdp_test.go b/x/cdp/keeper/cdp_test.go index f7ca7982..0ca288d6 100644 --- a/x/cdp/keeper/cdp_test.go +++ b/x/cdp/keeper/cdp_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "testing" "time" @@ -44,17 +45,17 @@ func (suite *CdpTestSuite) TestAddCdp() { acc.SetCoins(cs(c("xrp", 200000000), c("btc", 500000000))) ak.SetAccount(suite.ctx, acc) err := suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("usdx", 26000000))) - suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 500000000)), cs(c("usdx", 26000000))) suite.Error(err) // insufficient balance err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 200000000)), cs(c("xusd", 10000000))) - suite.Equal(types.CodeDebtNotSupported, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrDebtNotSupported)) acc2 := ak.NewAccountWithAddress(suite.ctx, addrs[1]) acc2.SetCoins(cs(c("btc", 500000000000))) ak.SetAccount(suite.ctx, acc2) err = suite.keeper.AddCdp(suite.ctx, addrs[1], cs(c("btc", 500000000000)), cs(c("usdx", 500000000001))) - suite.Equal(types.CodeExceedsDebtLimit, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrExceedsDebtLimit)) ctx := suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 2)) pk := suite.app.GetPriceFeedKeeper() @@ -76,7 +77,7 @@ func (suite *CdpTestSuite) TestAddCdp() { suite.Equal(cs(c("usdx", 10000000), c("xrp", 100000000), c("btc", 500000000)), acc.GetCoins()) err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("btc", 500000000)), cs(c("usdx", 26667000000))) - suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("btc", 500000000)), cs(c("usdx", 100000000))) suite.NoError(err) @@ -90,9 +91,9 @@ func (suite *CdpTestSuite) TestAddCdp() { suite.Equal(cs(c("usdx", 110000000), c("xrp", 100000000)), acc.GetCoins()) err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("lol", 100)), cs(c("usdx", 10))) - suite.Equal(types.CodeCollateralNotSupported, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCollateralNotSupported)) err = suite.keeper.AddCdp(suite.ctx, addrs[0], cs(c("xrp", 100)), cs(c("usdx", 10))) - suite.Equal(types.CodeCdpAlreadyExists, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpAlreadyExists)) } func (suite *CdpTestSuite) TestGetSetDenomByte() { @@ -247,10 +248,10 @@ func (suite *CdpTestSuite) TestValidateCollateral() { suite.NoError(err) c = sdk.NewCoins(sdk.NewCoin("lol", sdk.NewInt(1))) err = suite.keeper.ValidateCollateral(suite.ctx, c) - suite.Equal(types.CodeCollateralNotSupported, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCollateralNotSupported)) c = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(1)), sdk.NewCoin("xrp", sdk.NewInt(1))) err = suite.keeper.ValidateCollateral(suite.ctx, c) - suite.Equal(types.CodeCollateralLengthInvalid, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralLength)) } func (suite *CdpTestSuite) TestValidatePrincipal() { @@ -262,10 +263,10 @@ func (suite *CdpTestSuite) TestValidatePrincipal() { suite.NoError(err) d = sdk.NewCoins(sdk.NewCoin("xusd", sdk.NewInt(1))) err = suite.keeper.ValidatePrincipalAdd(suite.ctx, d) - suite.Equal(types.CodeDebtNotSupported, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrDebtNotSupported)) d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(1000000000001))) err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d) - suite.Equal(types.CodeExceedsDebtLimit, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrExceedsDebtLimit)) d = sdk.NewCoins(sdk.NewCoin("usdx", sdk.NewInt(100000000))) err = suite.keeper.ValidateDebtLimit(suite.ctx, "xrp", d) suite.NoError(err) @@ -290,16 +291,19 @@ func (suite *CdpTestSuite) TestMintBurnDebtCoins() { cd := cdps()[1] err := suite.keeper.MintDebtCoins(suite.ctx, types.ModuleName, suite.keeper.GetDebtDenom(suite.ctx), cd.Principal) suite.NoError(err) - err = suite.keeper.MintDebtCoins(suite.ctx, "notamodule", suite.keeper.GetDebtDenom(suite.ctx), cd.Principal) - suite.Error(err) + suite.Require().Panics(func() { + _ = suite.keeper.MintDebtCoins(suite.ctx, "notamodule", suite.keeper.GetDebtDenom(suite.ctx), cd.Principal) + }) + sk := suite.app.GetSupplyKeeper() acc := sk.GetModuleAccount(suite.ctx, types.ModuleName) suite.Equal(cs(c("debt", 10000000)), acc.GetCoins()) err = suite.keeper.BurnDebtCoins(suite.ctx, types.ModuleName, suite.keeper.GetDebtDenom(suite.ctx), cd.Principal) suite.NoError(err) - err = suite.keeper.BurnDebtCoins(suite.ctx, "notamodule", suite.keeper.GetDebtDenom(suite.ctx), cd.Principal) - suite.Error(err) + suite.Require().Panics(func() { + _ = suite.keeper.BurnDebtCoins(suite.ctx, "notamodule", suite.keeper.GetDebtDenom(suite.ctx), cd.Principal) + }) sk = suite.app.GetSupplyKeeper() acc = sk.GetModuleAccount(suite.ctx, types.ModuleName) suite.Equal(sdk.Coins(nil), acc.GetCoins()) diff --git a/x/cdp/keeper/deposit.go b/x/cdp/keeper/deposit.go index 38b8dd85..6dcbf7ab 100644 --- a/x/cdp/keeper/deposit.go +++ b/x/cdp/keeper/deposit.go @@ -5,23 +5,24 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/cdp/types" ) // DepositCollateral adds collateral to a cdp -func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) sdk.Error { +func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) error { err := k.ValidateCollateral(ctx, collateral) if err != nil { return err } cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral[0].Denom) if !found { - return types.ErrCdpNotFound(k.codespace, owner, collateral[0].Denom) + return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateral[0].Denom) } deposit, found := k.GetDeposit(ctx, cdp.ID, depositor) if found { - deposit.Amount = deposit.Amount.Add(collateral) + deposit.Amount = deposit.Amount.Add(collateral...) } else { deposit = types.NewDeposit(cdp.ID, depositor, collateral) } @@ -40,45 +41,45 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, deposit k.SetDeposit(ctx, deposit) periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom) - oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees) + cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() - cdp.Collateral = cdp.Collateral.Add(collateral) - collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + cdp.Collateral = cdp.Collateral.Add(collateral...) + collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) return nil } // WithdrawCollateral removes collateral from a cdp if it does not put the cdp below the liquidation ratio -func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) sdk.Error { +func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, depositor sdk.AccAddress, collateral sdk.Coins) error { err := k.ValidateCollateral(ctx, collateral) if err != nil { return err } cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, collateral[0].Denom) if !found { - return types.ErrCdpNotFound(k.codespace, owner, collateral[0].Denom) + return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, collateral %s", owner, collateral[0].Denom) } deposit, found := k.GetDeposit(ctx, cdp.ID, depositor) if !found { - return types.ErrDepositNotFound(k.codespace, depositor, cdp.ID) + return sdkerrors.Wrapf(types.ErrDepositNotFound, "depositor %s, collateral %s", depositor, collateral[0].Denom) } if collateral.IsAnyGT(deposit.Amount) { - return types.ErrInvalidWithdrawAmount(k.codespace, collateral, deposit.Amount) + return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.Amount) } periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom) - collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Principal, cdp.AccumulatedFees.Add(fees)) + fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) + collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Principal, cdp.AccumulatedFees.Add(fees...)) if err != nil { return err } liquidationRatio := k.getLiquidationRatio(ctx, collateral[0].Denom) if collateralizationRatio.LT(liquidationRatio) { - return types.ErrInvalidCollateralRatio(k.codespace, collateral[0].Denom, collateralizationRatio, liquidationRatio) + return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "colateral %s, collateral ratio %s, liquidation ration %s", collateral[0].Denom, collateralizationRatio, liquidationRatio) } ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -92,13 +93,13 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi if err != nil { panic(err) } - oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees) + cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() cdp.Collateral = cdp.Collateral.Sub(collateral) - collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) deposit.Amount = deposit.Amount.Sub(collateral) diff --git a/x/cdp/keeper/deposit_test.go b/x/cdp/keeper/deposit_test.go index d9a5e6b0..b543ce3d 100644 --- a/x/cdp/keeper/deposit_test.go +++ b/x/cdp/keeper/deposit_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -76,10 +77,10 @@ func (suite *DepositTestSuite) TestDepositCollateral() { suite.Equal(i(90000000), acc.GetCoins().AmountOf("xrp")) err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("btc", 1))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], cs(c("xrp", 1))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) err = suite.keeper.DepositCollateral(suite.ctx, suite.addrs[0], suite.addrs[1], cs(c("xrp", 10000000))) suite.NoError(err) @@ -94,17 +95,17 @@ func (suite *DepositTestSuite) TestDepositCollateral() { func (suite *DepositTestSuite) TestWithdrawCollateral() { err := suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 400000000))) - suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 321000000))) - suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[0], cs(c("xrp", 10000000))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) cd, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) cd.AccumulatedFees = cs(c("usdx", 1)) suite.keeper.SetCDP(suite.ctx, cd) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 320000000))) - suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[0], cs(c("xrp", 10000000))) suite.NoError(err) @@ -116,7 +117,7 @@ func (suite *DepositTestSuite) TestWithdrawCollateral() { suite.Equal(i(110000000), acc.GetCoins().AmountOf("xrp")) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[0], suite.addrs[1], cs(c("xrp", 10000000))) - suite.Equal(types.CodeDepositNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrDepositNotFound)) } func TestDepositTestSuite(t *testing.T) { diff --git a/x/cdp/keeper/draw.go b/x/cdp/keeper/draw.go index 94665f5b..876795d1 100644 --- a/x/cdp/keeper/draw.go +++ b/x/cdp/keeper/draw.go @@ -4,15 +4,16 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/cdp/types" ) // AddPrincipal adds debt to a cdp if the additional debt does not put the cdp below the liquidation ratio -func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, principal sdk.Coins) sdk.Error { +func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, principal sdk.Coins) error { // validation cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, denom) if !found { - return types.ErrCdpNotFound(k.codespace, owner, denom) + return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom) } err := k.ValidatePrincipalDraw(ctx, principal) if err != nil { @@ -26,9 +27,9 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string // fee calculation periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom) + fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) - err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal), cdp.AccumulatedFees.Add(fees)) + err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal...), cdp.AccumulatedFees.Add(fees...)) if err != nil { return err } @@ -59,19 +60,19 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string ) // remove old collateral:debt index - oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, denom, cdp.ID, oldCollateralToDebtRatio) // update cdp state - cdp.Principal = cdp.Principal.Add(principal) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees) + cdp.Principal = cdp.Principal.Add(principal...) + cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() // increment total principal for the input collateral type k.IncrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, principal) // set cdp state and indexes in the store - collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) return nil @@ -79,32 +80,32 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string // RepayPrincipal removes debt from the cdp // If all debt is repaid, the collateral is returned to depositors and the cdp is removed from the store -func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, payment sdk.Coins) sdk.Error { +func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string, payment sdk.Coins) error { // validation cdp, found := k.GetCdpByOwnerAndDenom(ctx, owner, denom) if !found { - return types.ErrCdpNotFound(k.codespace, owner, denom) + return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom) } // calculate fees periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom) - err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees).Add(fees)) + fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) + err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees...).Add(fees...)) if err != nil { return err } // calculate fee and principal payment - feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees).Add(fees), cdp.AccumulatedFees.Add(fees), payment) + feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees...).Add(fees...), cdp.AccumulatedFees.Add(fees...), payment) // send the payment from the sender to the cpd module - err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, feePayment.Add(principalPayment)) + err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, feePayment.Add(principalPayment...)) if err != nil { return err } // burn the payment coins - err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, feePayment.Add(principalPayment)) + err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, feePayment.Add(principalPayment...)) if err != nil { panic(err) } @@ -112,7 +113,7 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri // burn the corresponding amount of debt coins cdpDebt := k.getModAccountDebt(ctx, types.ModuleName) paymentAmount := sdk.ZeroInt() - for _, c := range feePayment.Add(principalPayment) { + for _, c := range feePayment.Add(principalPayment...) { paymentAmount = paymentAmount.Add(c.Amount) } coinsToBurn := sdk.NewCoins(sdk.NewCoin(k.GetDebtDenom(ctx), paymentAmount)) @@ -128,24 +129,24 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeCdpRepay, - sdk.NewAttribute(sdk.AttributeKeyAmount, feePayment.Add(principalPayment).String()), + sdk.NewAttribute(sdk.AttributeKeyAmount, feePayment.Add(principalPayment...).String()), sdk.NewAttribute(types.AttributeKeyCdpID, fmt.Sprintf("%d", cdp.ID)), ), ) // remove the old collateral:debt ratio index - oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, denom, cdp.ID, oldCollateralToDebtRatio) // update cdp state if !principalPayment.IsZero() { cdp.Principal = cdp.Principal.Sub(principalPayment) } - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees).Sub(feePayment) + cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...).Sub(feePayment) cdp.FeesUpdated = ctx.BlockTime() // decrement the total principal for the input collateral type - k.DecrementTotalPrincipal(ctx, denom, feePayment.Add(principalPayment)) + k.DecrementTotalPrincipal(ctx, denom, feePayment.Add(principalPayment...)) // if the debt is fully paid, return collateral to depositors, // and remove the cdp and indexes from the store @@ -165,13 +166,13 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri } // set cdp state and update indexes - collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.SetCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio) return nil } // ValidatePaymentCoins validates that the input coins are valid for repaying debt -func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk.Coins, debt sdk.Coins) sdk.Error { +func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk.Coins, debt sdk.Coins) error { subset := payment.DenomsSubsetOf(cdp.Principal) if !subset { var paymentDenoms []string @@ -182,13 +183,13 @@ func (k Keeper) ValidatePaymentCoins(ctx sdk.Context, cdp types.CDP, payment sdk for _, pc := range payment { paymentDenoms = append(paymentDenoms, pc.Denom) } - return types.ErrInvalidPaymentDenom(k.codespace, cdp.ID, principalDenoms, paymentDenoms) + return sdkerrors.Wrapf(types.ErrInvalidPayment, "cdp %d: expected %s, got %s", cdp.ID, principalDenoms, paymentDenoms) } for _, dc := range payment { dp, _ := k.GetDebtParam(ctx, dc.Denom) proposedBalance := cdp.Principal.AmountOf(dc.Denom).Sub(dc.Amount) if proposedBalance.GT(sdk.ZeroInt()) && proposedBalance.LT(dp.DebtFloor) { - return types.ErrBelowDebtFloor(k.codespace, sdk.NewCoins(sdk.NewCoin(dc.Denom, proposedBalance)), dp.DebtFloor) + return sdkerrors.Wrapf(types.ErrBelowDebtFloor, "proposed %s < minimum %s", sdk.NewCoins(sdk.NewCoin(dc.Denom, proposedBalance)), dp.DebtFloor) } } return nil @@ -223,12 +224,12 @@ func (k Keeper) calculatePayment(ctx sdk.Context, owed sdk.Coins, fees sdk.Coins for _, fc := range fees { if payment.AmountOf(fc.Denom).IsPositive() { if payment.AmountOf(fc.Denom).GT(fc.Amount) { - feePayment = feePayment.Add(sdk.NewCoins(fc)) + feePayment = feePayment.Add(fc) pc := sdk.NewCoin(fc.Denom, payment.AmountOf(fc.Denom).Sub(fc.Amount)) - principalPayment = principalPayment.Add(sdk.NewCoins(pc)) + principalPayment = principalPayment.Add(pc) } else { fc := sdk.NewCoin(fc.Denom, payment.AmountOf(fc.Denom)) - feePayment = feePayment.Add(sdk.NewCoins(fc)) + feePayment = feePayment.Add(fc) } } } diff --git a/x/cdp/keeper/draw_test.go b/x/cdp/keeper/draw_test.go index 9239ffe8..d353265e 100644 --- a/x/cdp/keeper/draw_test.go +++ b/x/cdp/keeper/draw_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "testing" "time" @@ -53,7 +54,7 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() { t, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) suite.Equal(cs(c("usdx", 20000000)), t.Principal) - ctd := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees)) + ctd := suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...)) suite.Equal(d("20.0"), ctd) ts := suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("20.0")) suite.Equal(0, len(ts)) @@ -69,7 +70,7 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() { suite.NoError(err) t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) suite.Equal(cs(c("usdx", 20000000), c("susd", 10000000)), t.Principal) - ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees)) + ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...)) suite.Equal(d("400000000").Quo(d("30000000")), ctd) ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("400").Quo(d("30"))) suite.Equal(0, len(ts)) @@ -82,18 +83,18 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() { suite.Equal(cs(c("xrp", 400000000), c("debt", 30000000)), acc.GetCoins()) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[1], "xrp", cs(c("usdx", 10000000))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("xusd", 10000000))) - suite.Equal(types.CodeDebtNotSupported, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrDebtNotSupported)) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 311000000))) - suite.Equal(types.CodeInvalidCollateralRatio, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidCollateralRatio)) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) suite.NoError(err) t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) suite.Equal(cs(c("usdx", 10000000), c("susd", 10000000)), t.Principal) - ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees)) + ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...)) suite.Equal(d("20.0"), ctd) ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("20.0")) suite.Equal(0, len(ts)) @@ -110,7 +111,7 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() { t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(1)) suite.Equal(cs(c("usdx", 10000000)), t.Principal) - ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees)) + ctd = suite.keeper.CalculateCollateralToDebtRatio(suite.ctx, t.Collateral, t.Principal.Add(t.AccumulatedFees...)) suite.Equal(d("40.0"), ctd) ts = suite.keeper.GetAllCdpsByDenomAndRatio(suite.ctx, "xrp", d("40.0")) suite.Equal(0, len(ts)) @@ -123,12 +124,12 @@ func (suite *DrawTestSuite) TestAddRepayPrincipal() { suite.Equal(cs(c("xrp", 400000000), c("debt", 10000000)), acc.GetCoins()) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("xusd", 10000000))) - suite.Equal(types.CodeInvalidPaymentDenom, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrInvalidPayment)) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[1], "xrp", cs(c("xusd", 10000000))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 9000000))) - suite.Equal(types.CodeBelowDebtFloor, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrBelowDebtFloor)) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[0], "xrp", cs(c("usdx", 10000000))) suite.NoError(err) diff --git a/x/cdp/keeper/fees.go b/x/cdp/keeper/fees.go index 936cfa74..e1f3c94e 100644 --- a/x/cdp/keeper/fees.go +++ b/x/cdp/keeper/fees.go @@ -22,7 +22,7 @@ func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coins, periods sdk. accumulator := sdk.NewDecFromInt(types.RelativePow(feeRateInt, periods, scalar)).Mul(sdk.SmallestDec()) feesAccumulated := (sdk.NewDecFromInt(pc.Amount).Mul(accumulator)).Sub(sdk.NewDecFromInt(pc.Amount)) // TODO this will always round down, causing precision loss between the sum of all fees in CDPs and surplus coins in liquidator account - newFees = newFees.Add(sdk.NewCoins(sdk.NewCoin(pc.Denom, feesAccumulated.TruncateInt()))) + newFees = newFees.Add(sdk.NewCoin(pc.Denom, feesAccumulated.TruncateInt())) } return newFees } @@ -33,7 +33,7 @@ func (k Keeper) CalculateFees(ctx sdk.Context, principal sdk.Coins, periods sdk. // 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 { +func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, marketID string) error { price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) if err != nil { @@ -50,7 +50,7 @@ func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, // now iterate over all the cdps based on collateral ratio k.IterateCdpsByCollateralRatio(ctx, collateralDenom, normalizedRatio, func(cdp types.CDP) bool { - oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) // get the number of periods periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) @@ -58,11 +58,11 @@ func (k Keeper) UpdateFeesForRiskyCdps(ctx sdk.Context, collateralDenom string, 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) + 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)) + collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) 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 diff --git a/x/cdp/keeper/fees_test.go b/x/cdp/keeper/fees_test.go index 4367d368..1c6ca211 100644 --- a/x/cdp/keeper/fees_test.go +++ b/x/cdp/keeper/fees_test.go @@ -48,7 +48,7 @@ func (suite *FeeTestSuite) TestCalculateFeesPrecisionLoss() { suite.NoError(err) c := sdk.NewCoins(sdk.NewCoin("usdx", ri)) coins = append(coins, c) - total = total.Add(cs(sdk.NewCoin("usdx", ri))) + total = total.Add(sdk.NewCoin("usdx", ri)) } numBlocks := []int{100, 1000, 10000, 100000} @@ -57,13 +57,13 @@ func (suite *FeeTestSuite) TestCalculateFeesPrecisionLoss() { bulkFees := sdk.NewCoins() individualFees := sdk.NewCoins() for x := 0; x < nb; x++ { - fee := suite.keeper.CalculateFees(suite.ctx, total.Add(bulkFees), i(7), "xrp") - bulkFees = bulkFees.Add(fee) + fee := suite.keeper.CalculateFees(suite.ctx, total.Add(bulkFees...), i(7), "xrp") + bulkFees = bulkFees.Add(fee...) } for _, cns := range coins { fee := suite.keeper.CalculateFees(suite.ctx, cns, i(int64(nb*7)), "xrp") - individualFees = individualFees.Add(fee) + individualFees = individualFees.Add(fee...) } absError := (sdk.OneDec().Sub(sdk.NewDecFromInt(bulkFees[0].Amount).Quo(sdk.NewDecFromInt(individualFees[0].Amount)))).Abs() diff --git a/x/cdp/keeper/keeper.go b/x/cdp/keeper/keeper.go index 98d416a1..f7d3e585 100644 --- a/x/cdp/keeper/keeper.go +++ b/x/cdp/keeper/keeper.go @@ -19,21 +19,22 @@ type Keeper struct { 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, ack types.AccountKeeper, codespace sdk.CodespaceType) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, ak types.AuctionKeeper, sk types.SupplyKeeper, ack types.AccountKeeper) Keeper { + if !paramstore.HasKeyTable() { + paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) + } return Keeper{ key: key, cdc: cdc, - paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), + paramSubspace: paramstore, pricefeedKeeper: pfk, auctionKeeper: ak, supplyKeeper: sk, accountKeeper: ack, - codespace: codespace, } } diff --git a/x/cdp/keeper/keeper_bench_test.go b/x/cdp/keeper/keeper_bench_test.go index 20b88c82..bee4d37b 100644 --- a/x/cdp/keeper/keeper_bench_test.go +++ b/x/cdp/keeper/keeper_bench_test.go @@ -115,7 +115,7 @@ func BenchmarkCdpIteration(b *testing.B) { } -var errResult sdk.Error +var errResult error func BenchmarkCdpCreation(b *testing.B) { tApp := app.NewTestApp() diff --git a/x/cdp/keeper/querier.go b/x/cdp/keeper/querier.go index 440b4e1a..ba69c490 100644 --- a/x/cdp/keeper/querier.go +++ b/x/cdp/keeper/querier.go @@ -1,16 +1,20 @@ package keeper import ( + "fmt" + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/kava-labs/kava/x/cdp/types" ) // NewQuerier returns a new querier function func NewQuerier(keeper Keeper) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) { switch path[0] { case types.QueryGetCdp: return queryGetCdp(ctx, req, keeper) @@ -23,85 +27,85 @@ func NewQuerier(keeper Keeper) sdk.Querier { case types.QueryGetCdpDeposits: return queryGetDeposits(ctx, req, keeper) default: - return nil, sdk.ErrUnknownRequest("unknown cdp query endpoint") + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint %s", types.ModuleName, path[0]) } } } // query a specific cdp -func queryGetCdp(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetCdp(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { var requestParams types.QueryCdpParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) if !valid { - return nil, types.ErrInvalidCollateralDenom(keeper.codespace, requestParams.CollateralDenom) + return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) } cdp, found := keeper.GetCdpByOwnerAndDenom(ctx, requestParams.Owner, requestParams.CollateralDenom) if !found { - return nil, types.ErrCdpNotFound(keeper.codespace, requestParams.Owner, requestParams.CollateralDenom) + return nil, sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", requestParams.Owner, requestParams.CollateralDenom) } augmentedCDP, err := keeper.LoadAugmentedCDP(ctx, cdp) if err != nil { - return nil, types.ErrLoadingAugmentedCDP(keeper.codespace, cdp.ID) + return nil, sdkerrors.Wrap(types.ErrLoadingAugmentedCDP, fmt.Sprintf("%v: %d", err.Error(), cdp.ID)) } - bz, err := codec.MarshalJSONIndent(keeper.cdc, augmentedCDP) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, augmentedCDP) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query deposits on a particular cdp -func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetDeposits(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { var requestParams types.QueryCdpDeposits - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) if !valid { - return nil, types.ErrInvalidCollateralDenom(keeper.codespace, requestParams.CollateralDenom) + return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) } cdp, found := keeper.GetCdpByOwnerAndDenom(ctx, requestParams.Owner, requestParams.CollateralDenom) if !found { - return nil, types.ErrCdpNotFound(keeper.codespace, requestParams.Owner, requestParams.CollateralDenom) + return nil, sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", requestParams.Owner, requestParams.CollateralDenom) } deposits := keeper.GetDeposits(ctx, cdp.ID) - bz, err := codec.MarshalJSONIndent(keeper.cdc, deposits) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, deposits) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query cdps with matching denom and ratio LESS THAN the input ratio -func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { var requestParams types.QueryCdpsByRatioParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) if !valid { - return nil, types.ErrInvalidCollateralDenom(keeper.codespace, requestParams.CollateralDenom) + return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) } ratio, err := keeper.CalculateCollateralizationRatioFromAbsoluteRatio(ctx, requestParams.CollateralDenom, requestParams.Ratio) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could get collateralization ratio from absolute ratio", err.Error())) + return nil, sdkerrors.Wrap(err, "couldn't get collateralization ratio from absolute ratio") } cdps := keeper.GetAllCdpsByDenomAndRatio(ctx, requestParams.CollateralDenom, ratio) @@ -113,23 +117,23 @@ func queryGetCdpsByRatio(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) augmentedCDPs = append(augmentedCDPs, augmentedCDP) } } - bz, err := codec.MarshalJSONIndent(keeper.cdc, augmentedCDPs) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, augmentedCDPs) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query all cdps with matching collateral denom -func queryGetCdpsByDenom(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetCdpsByDenom(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { var requestParams types.QueryCdpsParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } _, valid := keeper.GetDenomPrefix(ctx, requestParams.CollateralDenom) if !valid { - return nil, types.ErrInvalidCollateralDenom(keeper.codespace, requestParams.CollateralDenom) + return nil, sdkerrors.Wrap(types.ErrInvalidCollateral, requestParams.CollateralDenom) } cdps := keeper.GetAllCdpsByDenom(ctx, requestParams.CollateralDenom) @@ -141,22 +145,22 @@ func queryGetCdpsByDenom(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) augmentedCDPs = append(augmentedCDPs, augmentedCDP) } } - bz, err := codec.MarshalJSONIndent(keeper.cdc, augmentedCDPs) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, augmentedCDPs) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query params in the cdp store -func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { // Get params params := keeper.GetParams(ctx) // Encode results - bz, err := codec.MarshalJSONIndent(keeper.cdc, params) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, params) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } diff --git a/x/cdp/keeper/savings.go b/x/cdp/keeper/savings.go index 68678e24..ec9c5944 100644 --- a/x/cdp/keeper/savings.go +++ b/x/cdp/keeper/savings.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" 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" @@ -13,10 +14,10 @@ import ( ) // 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 { +func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) error { dp, found := k.GetDebtParam(ctx, debtDenom) if !found { - return types.ErrDebtNotSupported(k.codespace, debtDenom) + return sdkerrors.Wrap(types.ErrDebtNotSupported, debtDenom) } savingsRateMacc := k.supplyKeeper.GetModuleAccount(ctx, types.SavingsRateMacc) surplusToDistribute := savingsRateMacc.GetCoins().AmountOf(dp.Denom) @@ -27,7 +28,7 @@ func (k Keeper) DistributeSavingsRate(ctx sdk.Context, debtDenom string) sdk.Err modAccountCoins := k.getModuleAccountCoins(ctx, dp.Denom) totalSupplyLessModAccounts := k.supplyKeeper.GetSupply(ctx).GetTotal().Sub(modAccountCoins) surplusDistributed := sdk.ZeroInt() - var iterationErr sdk.Error + var iterationErr error k.accountKeeper.IterateAccounts(ctx, func(acc authexported.Account) (stop bool) { _, ok := acc.(supplyexported.ModuleAccountI) if ok { diff --git a/x/cdp/keeper/seize.go b/x/cdp/keeper/seize.go index 6ef5d3e3..730c335b 100644 --- a/x/cdp/keeper/seize.go +++ b/x/cdp/keeper/seize.go @@ -15,13 +15,13 @@ import ( // 3. moves debt coins from the cdp module to the liquidator module account, // 4. decrements the total amount of principal outstanding for that collateral type // (this is the equivalent of saying that fees are no longer accumulated by a cdp once it gets liquidated) -func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) sdk.Error { +func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error { // Calculate the previous collateral ratio - oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees)) + oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) // Update fees periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees), periods, cdp.Collateral[0].Denom) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees) + fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) + cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() // Move debt coins from cdp to liquidator account @@ -70,7 +70,7 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) sdk.Error { coinsToDecrement := sdk.NewCoins(dc) if feeAmount.IsPositive() { feeCoins := sdk.NewCoins(sdk.NewCoin(dc.Denom, feeAmount)) - coinsToDecrement = coinsToDecrement.Add(feeCoins) + coinsToDecrement = coinsToDecrement.Add(feeCoins...) } k.DecrementTotalPrincipal(ctx, cdp.Collateral[0].Denom, coinsToDecrement) } @@ -100,11 +100,11 @@ func (k Keeper) HandleNewDebt(ctx sdk.Context, collateralDenom string, principal k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), 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)) + k.SetTotalPrincipal(ctx, collateralDenom, principalDenom, feeCoins.Add(newFees...).AmountOf(principalDenom)) } // LiquidateCdps seizes collateral from all CDPs below the input liquidation ratio -func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, liquidationRatio sdk.Dec) sdk.Error { +func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, liquidationRatio sdk.Dec) error { price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) if err != nil { return err diff --git a/x/cdp/keeper/seize_test.go b/x/cdp/keeper/seize_test.go index 084423fc..355333bd 100644 --- a/x/cdp/keeper/seize_test.go +++ b/x/cdp/keeper/seize_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "math/rand" "testing" "time" @@ -145,7 +146,7 @@ func (suite *SeizeTestSuite) TestSeizeCollateral() { acc := ak.GetAccount(suite.ctx, suite.addrs[1]) suite.Equal(p.Int64(), acc.GetCoins().AmountOf("usdx").Int64()) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[1], cs(c("xrp", 10))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) } func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() { @@ -170,7 +171,7 @@ func (suite *SeizeTestSuite) TestSeizeCollateralMultiDeposit() { acc := ak.GetAccount(suite.ctx, suite.addrs[1]) suite.Equal(p.Int64(), acc.GetCoins().AmountOf("usdx").Int64()) err = suite.keeper.WithdrawCollateral(suite.ctx, suite.addrs[1], suite.addrs[1], cs(c("xrp", 10))) - suite.Equal(types.CodeCdpNotFound, err.Result().Code) + suite.Require().True(errors.Is(err, types.ErrCdpNotFound)) } func (suite *SeizeTestSuite) TestLiquidateCdps() { diff --git a/x/cdp/module.go b/x/cdp/module.go index 5c2febb1..1794aba2 100644 --- a/x/cdp/module.go +++ b/x/cdp/module.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" sim "github.com/cosmos/cosmos-sdk/x/simulation" abci "github.com/tendermint/tendermint/abci/types" @@ -22,7 +23,7 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModuleSimulation = AppModule{} ) // AppModuleBasic app module basics object @@ -70,41 +71,22 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { //____________________________________________________________________________ -// 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 + accountKeeper auth.AccountKeeper pricefeedKeeper PricefeedKeeper supplyKeeper SupplyKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper, pricefeedKeeper PricefeedKeeper, supplyKeeper SupplyKeeper) AppModule { +func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, pricefeedKeeper PricefeedKeeper, supplyKeeper SupplyKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, + accountKeeper: accountKeeper, pricefeedKeeper: pricefeedKeeper, supplyKeeper: supplyKeeper, } @@ -162,3 +144,30 @@ func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +//____________________________________________________________________________ + +// GenerateGenesisState creates a randomized GenState of the cdp module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// RandomizedParams returns nil because cdp has no params. +func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers a decoder for cdp module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// WeightedOperations returns the all the cdp module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper, am.pricefeedKeeper) +} diff --git a/x/cdp/simulation/decoder.go b/x/cdp/simulation/decoder.go index b2bc1fd0..1b0e347e 100644 --- a/x/cdp/simulation/decoder.go +++ b/x/cdp/simulation/decoder.go @@ -6,15 +6,16 @@ import ( "fmt" "time" + "github.com/tendermint/tendermint/libs/kv" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/kava-labs/kava/x/cdp/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding cdp type -func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { case bytes.Equal(kvA.Key[:1], types.CdpIDKeyPrefix): var cdpIDsA, cdpIDsB []uint64 diff --git a/x/cdp/simulation/decoder_test.go b/x/cdp/simulation/decoder_test.go index 52927624..7d092e6c 100644 --- a/x/cdp/simulation/decoder_test.go +++ b/x/cdp/simulation/decoder_test.go @@ -6,8 +6,7 @@ import ( "time" "github.com/stretchr/testify/require" - - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/kv" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -34,17 +33,17 @@ func TestDecodeDistributionStore(t *testing.T) { prevDistTime := time.Now().UTC() cdp := types.CDP{ID: 1, FeesUpdated: prevDistTime, Collateral: oneCoins, Principal: oneCoins, AccumulatedFees: oneCoins} - kvPairs := cmn.KVPairs{ - cmn.KVPair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)}, - cmn.KVPair{Key: types.CdpKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdp)}, - cmn.KVPair{Key: types.CdpIDKey, Value: sdk.Uint64ToBigEndian(2)}, - cmn.KVPair{Key: types.CollateralRatioIndexPrefix, Value: sdk.Uint64ToBigEndian(10)}, - cmn.KVPair{Key: []byte(types.DebtDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, - cmn.KVPair{Key: []byte(types.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, - cmn.KVPair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)}, - cmn.KVPair{Key: []byte(types.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)}, - cmn.KVPair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)}, - cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + kvPairs := kv.Pairs{ + kv.Pair{Key: types.CdpIDKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdpIds)}, + kv.Pair{Key: types.CdpKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(cdp)}, + kv.Pair{Key: types.CdpIDKey, Value: sdk.Uint64ToBigEndian(2)}, + kv.Pair{Key: types.CollateralRatioIndexPrefix, Value: sdk.Uint64ToBigEndian(10)}, + kv.Pair{Key: []byte(types.DebtDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, + kv.Pair{Key: []byte(types.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, + kv.Pair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)}, + kv.Pair{Key: []byte(types.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)}, + kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } tests := []struct { diff --git a/x/cdp/simulation/genesis.go b/x/cdp/simulation/genesis.go index 8f817997..6fa7680e 100644 --- a/x/cdp/simulation/genesis.go +++ b/x/cdp/simulation/genesis.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/supply" - supplyExported "github.com/cosmos/cosmos-sdk/x/supply/exported" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" "github.com/kava-labs/kava/x/cdp/types" ) @@ -23,7 +23,7 @@ func RandomizedGenState(simState *module.SimulationState) { simState.Cdc.MustUnmarshalJSON(simState.GenState[auth.ModuleName], &authGenesis) totalCdpCoins := sdk.NewCoins() for _, acc := range authGenesis.Accounts { - _, ok := acc.(supplyExported.ModuleAccountI) + _, ok := acc.(supplyexported.ModuleAccountI) if ok { continue } @@ -33,18 +33,18 @@ func RandomizedGenState(simState *module.SimulationState) { sdk.NewCoin("btc", sdk.NewInt(int64(simState.Rand.Intn(500000000)))), sdk.NewCoin("usdx", sdk.NewInt(int64(simState.Rand.Intn(1000000000)))), ) - err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd)) + err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd...)) if err != nil { panic(err) } - totalCdpCoins = totalCdpCoins.Add(coinsToAdd) + totalCdpCoins = totalCdpCoins.Add(coinsToAdd...) authGenesis.Accounts = replaceOrAppendAccount(authGenesis.Accounts, acc) } simState.GenState[auth.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) var supplyGenesis supply.GenesisState simState.Cdc.MustUnmarshalJSON(simState.GenState[supply.ModuleName], &supplyGenesis) - supplyGenesis.Supply = supplyGenesis.Supply.Add(totalCdpCoins) + supplyGenesis.Supply = supplyGenesis.Supply.Add(totalCdpCoins...) simState.GenState[supply.ModuleName] = simState.Cdc.MustMarshalJSON(supplyGenesis) fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, cdpGenesis)) diff --git a/x/cdp/simulation/operations/msgs.go b/x/cdp/simulation/operations.go similarity index 50% rename from x/cdp/simulation/operations/msgs.go rename to x/cdp/simulation/operations.go index 4afe59b4..014b7fa1 100644 --- a/x/cdp/simulation/operations/msgs.go +++ b/x/cdp/simulation/operations.go @@ -1,48 +1,86 @@ -package operations +package simulation import ( - "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/kava-labs/kava/x/cdp" - "github.com/kava-labs/kava/x/pricefeed" + + appparams "github.com/kava-labs/kava/app/params" + "github.com/kava-labs/kava/x/cdp/keeper" + "github.com/kava-labs/kava/x/cdp/types" ) -// SimulateMsgCdp generates a MsgCreateCdp or MsgDepositCdp with random values. -func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) simulation.Operation { - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( - opMsg simulation.OperationMsg, fOps []simulation.FutureOperation, err error) { +// Simulation operation weights constants +const ( + OpWeightMsgCdp = "op_weight_msg_cdp" +) - handler := cdp.NewHandler(k) - simacc := simulation.RandomAcc(r, accs) - acc := ak.GetAccount(ctx, simacc.Address) +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, + k keeper.Keeper, pfk types.PricefeedKeeper, +) simulation.WeightedOperations { + var weightMsgCdp int + + appParams.GetOrGenerate(cdc, OpWeightMsgCdp, &weightMsgCdp, nil, + func(_ *rand.Rand) { + weightMsgCdp = appparams.DefaultWeightMsgCdp + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgCdp, + SimulateMsgCdp(ak, k, pfk), + ), + } +} + +// SimulateMsgCdp generates a MsgCreateCdp or MsgDepositCdp with random values. +func SimulateMsgCdp(ak auth.AccountKeeper, k keeper.Keeper, pfk types.PricefeedKeeper) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + + simAccount, _ := simulation.RandomAcc(r, accs) + acc := ak.GetAccount(ctx, simAccount.Address) if acc == nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, nil + return simulation.NoOpMsg(types.ModuleName), nil, nil } + coins := acc.GetCoins() collateralParams := k.GetParams(ctx).CollateralParams if len(collateralParams) == 0 { - return simulation.NoOpMsg(cdp.ModuleName), nil, nil + return simulation.NoOpMsg(types.ModuleName), nil, nil } + randCollateralParam := collateralParams[r.Intn(len(collateralParams))] randDebtAsset := randCollateralParam.DebtLimit[r.Intn(len(randCollateralParam.DebtLimit))] randDebtParam, _ := k.GetDebtParam(ctx, randDebtAsset.Denom) if coins.AmountOf(randCollateralParam.Denom).IsZero() { - return simulation.NoOpMsg(cdp.ModuleName), nil, nil + return simulation.NoOpMsg(types.ModuleName), nil, nil } price, err := pfk.GetCurrentPrice(ctx, randCollateralParam.MarketID) if err != nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, err + return simulation.NoOpMsg(types.ModuleName), nil, err } // convert the price to the same units as the debt param priceShifted := ShiftDec(price.Price, randDebtParam.ConversionFactor) + spendableCoins := acc.SpendableCoins(ctx.BlockTime()) + fees, err := simulation.RandomFees(r, ctx, spendableCoins) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + spendableCoins = spendableCoins.Sub(fees) + existingCDP, found := k.GetCdpByOwnerAndDenom(ctx, acc.GetAddress(), randCollateralParam.Denom) if !found { // calculate the minimum amount of collateral that is needed to create a cdp with the debt floor amount of debt and the minimum liquidation ratio @@ -52,12 +90,12 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s minCollateralDeposit = ShiftDec(minCollateralDeposit, randCollateralParam.ConversionFactor) // convert to integer and always round up minCollateralDepositRounded := minCollateralDeposit.TruncateInt().Add(sdk.OneInt()) - if coins.AmountOf(randCollateralParam.Denom).LT(minCollateralDepositRounded) { + if spendableCoins.AmountOf(randCollateralParam.Denom).LT(minCollateralDepositRounded) { // account doesn't have enough funds to open a cdp for the min debt amount - return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "insufficient funds to open cdp", false, nil), nil, nil + return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "insufficient funds to open cdp", false, nil), nil, nil } // set the max collateral deposit to the amount of coins in the account - maxCollateralDeposit := coins.AmountOf(randCollateralParam.Denom) + maxCollateralDeposit := spendableCoins.AmountOf(randCollateralParam.Denom) // randomly select a collateral deposit amount collateralDeposit := sdk.NewInt(int64(simulation.RandIntBetween(r, int(minCollateralDepositRounded.Int64()), int(maxCollateralDeposit.Int64())))) @@ -69,53 +107,78 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s availableAssetDebt := randCollateralParam.DebtLimit.AmountOf(randDebtParam.Denom).Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, randDebtParam.Denom)) if availableAssetDebt.LTE(randDebtParam.DebtFloor) { // debt limit has been reached - return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "debt limit reached, cannot open cdp", false, nil), nil, nil + return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot open cdp", false, nil), nil, nil } // ensure that the debt draw does not exceed the debt limit maxDebtDraw = sdk.MinInt(maxDebtDraw, availableAssetDebt) // randomly select a debt draw amount debtDraw := sdk.NewInt(int64(simulation.RandIntBetween(r, int(randDebtParam.DebtFloor.Int64()), int(maxDebtDraw.Int64())))) - msg := cdp.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, collateralDeposit)), sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, debtDraw))) - err := msg.ValidateBasic() + + msg := types.NewMsgCreateCDP(acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, collateralDeposit)), sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, debtDraw))) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) if err != nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected msg to pass ValidateBasic: %v", err) + return simulation.NoOpMsg(types.ModuleName), nil, err } - ok := submitMsg(msg, handler, ctx) - if !ok { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit create cdp msg") - } - return simulation.NewOperationMsg(msg, ok, "create cdp"), nil, nil + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } // a cdp already exists, deposit to it, draw debt from it, or repay debt to it // close 25% of the time if canClose(acc, existingCDP, randDebtParam.Denom) && shouldClose(r) { - repaymentAmount := coins.AmountOf(randDebtParam.Denom) - msg := cdp.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, repaymentAmount))) - err := msg.ValidateBasic() + repaymentAmount := spendableCoins.AmountOf(randDebtParam.Denom) + msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, repaymentAmount))) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) if err != nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected repay (close) msg to pass ValidateBasic: %v", err) + return simulation.NoOpMsg(types.ModuleName), nil, err } - ok := submitMsg(msg, handler, ctx) - if !ok { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit repay (close) msg") - } - return simulation.NewOperationMsg(msg, ok, "repay debt (close) cdp"), nil, nil + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } // deposit 25% of the time if hasCoins(acc, randCollateralParam.Denom) && shouldDeposit(r) { - randDepositAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(acc.GetCoins().AmountOf(randCollateralParam.Denom).Int64())))) - msg := cdp.NewMsgDeposit(acc.GetAddress(), acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, randDepositAmount))) - err := msg.ValidateBasic() + randDepositAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(spendableCoins.AmountOf(randCollateralParam.Denom).Int64())))) + msg := types.NewMsgDeposit(acc.GetAddress(), acc.GetAddress(), sdk.NewCoins(sdk.NewCoin(randCollateralParam.Denom, randDepositAmount))) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) if err != nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected deposit msg to pass ValidateBasic: %v", err) + return simulation.NoOpMsg(types.ModuleName), nil, err } - ok := submitMsg(msg, handler, ctx) - if !ok { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit deposit msg") - } - return simulation.NewOperationMsg(msg, ok, "deposit to cdp"), nil, nil + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } // draw debt 25% of the time @@ -130,109 +193,97 @@ func SimulateMsgCdp(ak auth.AccountKeeper, k cdp.Keeper, pfk pricefeed.Keeper) s maxDebt := (maxTotalDebt.Sub(sdk.NewDecFromInt(debt))).Mul(sdk.MustNewDecFromStr("0.95")).TruncateInt() if maxDebt.LTE(sdk.OneInt()) { // debt in cdp is maxed out - return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil + return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "cdp debt maxed out, cannot draw more debt", false, nil), nil, nil } // check if the debt limit has been reached availableAssetDebt := randCollateralParam.DebtLimit.AmountOf(randDebtParam.Denom).Sub(k.GetTotalPrincipal(ctx, randCollateralParam.Denom, randDebtParam.Denom)) if availableAssetDebt.LTE(sdk.OneInt()) { // debt limit has been reached - return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation", "debt limit reached, cannot draw more debt", false, nil), nil, nil + return simulation.NewOperationMsgBasic(types.ModuleName, "no-operation", "debt limit reached, cannot draw more debt", false, nil), nil, nil } maxDraw := sdk.MinInt(maxDebt, availableAssetDebt) randDrawAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxDraw.Int64())))) - msg := cdp.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randDrawAmount))) - err := msg.ValidateBasic() - if err != nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected draw msg to pass ValidateBasic: %v", err) - } - ok := submitMsg(msg, handler, ctx) - if !ok { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit draw msg") - } - return simulation.NewOperationMsg(msg, ok, "draw debt from cdp"), nil, nil + msg := types.NewMsgDrawDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randDrawAmount))) + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } // repay debt 25% of the time if hasCoins(acc, randDebtParam.Denom) { debt := existingCDP.Principal.AmountOf(randDebtParam.Denom) - maxRepay := acc.GetCoins().AmountOf(randDebtParam.Denom) + maxRepay := spendableCoins.AmountOf(randDebtParam.Denom) payableDebt := debt.Sub(randDebtParam.DebtFloor) if maxRepay.GT(payableDebt) { maxRepay = payableDebt } randRepayAmount := sdk.NewInt(int64(simulation.RandIntBetween(r, 1, int(maxRepay.Int64())))) - if debt.Equal(randDebtParam.DebtFloor) { - if acc.GetCoins().AmountOf(randDebtParam.Denom).GTE(debt) { - randRepayAmount = debt - } + if debt.Equal(randDebtParam.DebtFloor) && spendableCoins.AmountOf(randDebtParam.Denom).GTE(debt) { + randRepayAmount = debt } - msg := cdp.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randRepayAmount))) - err := msg.ValidateBasic() + + msg := types.NewMsgRepayDebt(acc.GetAddress(), randCollateralParam.Denom, sdk.NewCoins(sdk.NewCoin(randDebtParam.Denom, randRepayAmount))) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{acc.GetAccountNumber()}, + []uint64{acc.GetSequence()}, + simAccount.PrivKey, + ) + + _, result, err := app.Deliver(tx) if err != nil { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("expected repay msg to pass ValidateBasic: %v", err) + return simulation.NoOpMsg(types.ModuleName), nil, err } - ok := submitMsg(msg, handler, ctx) - if !ok { - return simulation.NoOpMsg(cdp.ModuleName), nil, fmt.Errorf("could not submit repay msg") - } - return simulation.NewOperationMsg(msg, ok, "repay debt cdp"), nil, nil + + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } - return simulation.NewOperationMsgBasic(cdp.ModuleName, "no-operation (no valid actions)", "", false, nil), nil, nil + return simulation.NoOpMsg(types.ModuleName), nil, nil } } -func submitMsg(msg sdk.Msg, handler sdk.Handler, ctx sdk.Context) (ok bool) { - ctx, write := ctx.CacheContext() - res := handler(ctx, msg) - if res.IsOK() { - write() - } else { - fmt.Println(res.Log) - } - return res.IsOK() -} - func shouldDraw(r *rand.Rand) bool { threshold := 50 value := simulation.RandIntBetween(r, 1, 100) - if value > threshold { - return true - } - return false + return value > threshold } func shouldDeposit(r *rand.Rand) bool { threshold := 66 value := simulation.RandIntBetween(r, 1, 100) - if value > threshold { - return true - } - return false + return value > threshold } func hasCoins(acc authexported.Account, denom string) bool { - if acc.GetCoins().AmountOf(denom).IsZero() { - return false - } - return true + return acc.GetCoins().AmountOf(denom).IsPositive() } func shouldClose(r *rand.Rand) bool { threshold := 75 value := simulation.RandIntBetween(r, 1, 100) - if value > threshold { - return true - } - return false + return value > threshold } -func canClose(acc authexported.Account, c cdp.CDP, denom string) bool { - repaymentAmount := c.Principal.Add(c.AccumulatedFees).AmountOf(denom) - if acc.GetCoins().AmountOf(denom).GTE(repaymentAmount) { - return true - } - return false +func canClose(acc authexported.Account, c types.CDP, denom string) bool { + repaymentAmount := c.Principal.Add(c.AccumulatedFees...).AmountOf(denom) + return acc.GetCoins().AmountOf(denom).GTE(repaymentAmount) } diff --git a/x/cdp/simulation/operations/utils.go b/x/cdp/simulation/utils.go similarity index 95% rename from x/cdp/simulation/operations/utils.go rename to x/cdp/simulation/utils.go index bf8a804e..f9daac89 100644 --- a/x/cdp/simulation/operations/utils.go +++ b/x/cdp/simulation/utils.go @@ -1,4 +1,4 @@ -package operations +package simulation import ( sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/x/cdp/simulation/operations/utils_test.go b/x/cdp/simulation/utils_test.go similarity index 81% rename from x/cdp/simulation/operations/utils_test.go rename to x/cdp/simulation/utils_test.go index 5fedbef0..448366fa 100644 --- a/x/cdp/simulation/operations/utils_test.go +++ b/x/cdp/simulation/utils_test.go @@ -1,10 +1,10 @@ -package operations_test +package simulation_test import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/kava-labs/kava/x/cdp/simulation/operations" + "github.com/kava-labs/kava/x/cdp/simulation" "github.com/stretchr/testify/require" ) @@ -22,7 +22,7 @@ func TestShiftDec(t *testing.T) { for _, tt := range tests { t.Run(tt.value.String(), func(t *testing.T) { - require.Equal(t, tt.expected, operations.ShiftDec(tt.value, tt.shift)) + require.Equal(t, tt.expected, simulation.ShiftDec(tt.value, tt.shift)) }) } } diff --git a/x/cdp/types/errors.go b/x/cdp/types/errors.go index 95dc05b9..92953230 100644 --- a/x/cdp/types/errors.go +++ b/x/cdp/types/errors.go @@ -1,115 +1,42 @@ -// DONTCOVER package types import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) -// Error codes specific to cdp module -const ( - DefaultCodespace sdk.CodespaceType = ModuleName - CodeCdpAlreadyExists sdk.CodeType = 1 - CodeCollateralLengthInvalid sdk.CodeType = 2 - CodeCollateralNotSupported sdk.CodeType = 3 - CodeDebtNotSupported sdk.CodeType = 4 - CodeExceedsDebtLimit sdk.CodeType = 5 - CodeInvalidCollateralRatio sdk.CodeType = 6 - CodeCdpNotFound sdk.CodeType = 7 - CodeDepositNotFound sdk.CodeType = 8 - CodeInvalidDepositDenom sdk.CodeType = 9 - CodeInvalidPaymentDenom sdk.CodeType = 10 - CodeDepositNotAvailable sdk.CodeType = 11 - CodeInvalidCollateralDenom sdk.CodeType = 12 - CodeInvalidWithdrawAmount sdk.CodeType = 13 - CodeCdpNotAvailable sdk.CodeType = 14 - CodeBelowDebtFloor sdk.CodeType = 15 - CodePaymentExceedsDebt sdk.CodeType = 16 - CodeLoadingAugmentedCDP sdk.CodeType = 17 +// DONTCOVER + +var ( + // ErrCdpAlreadyExists error for duplicate cdps + ErrCdpAlreadyExists = sdkerrors.Register(ModuleName, 2, "cdp already exists") + // ErrInvalidCollateralLength error for invalid collateral input length + ErrInvalidCollateralLength = sdkerrors.Register(ModuleName, 3, "only one collateral type per cdp") + // ErrCollateralNotSupported error for unsupported collateral + ErrCollateralNotSupported = sdkerrors.Register(ModuleName, 4, "collateral not supported") + // ErrDebtNotSupported error for unsupported debt + ErrDebtNotSupported = sdkerrors.Register(ModuleName, 5, "debt not supported") + // ErrExceedsDebtLimit error for attempted draws that exceed debt limit + ErrExceedsDebtLimit = sdkerrors.Register(ModuleName, 6, "proposed debt increase would exceed debt limit") + // ErrInvalidCollateralRatio error for attempted draws that are below liquidation ratio + ErrInvalidCollateralRatio = sdkerrors.Register(ModuleName, 7, "proposed collateral ratio is below liquidation ratio") + // ErrCdpNotFound error cdp not found + ErrCdpNotFound = sdkerrors.Register(ModuleName, 8, "cdp not found") + // ErrDepositNotFound error for deposit not found + ErrDepositNotFound = sdkerrors.Register(ModuleName, 9, "deposit not found") + // ErrInvalidDeposit error for invalid deposit + ErrInvalidDeposit = sdkerrors.Register(ModuleName, 10, "invalid deposit") + // ErrInvalidCollateral error for invalid collateral + ErrInvalidCollateral = sdkerrors.Register(ModuleName, 11, "collateral not supported") + // ErrInvalidPayment error for invalid payment + ErrInvalidPayment = sdkerrors.Register(ModuleName, 12, "invalid payment") + //ErrDepositNotAvailable error for withdrawing deposits in liquidation + ErrDepositNotAvailable = sdkerrors.Register(ModuleName, 13, "deposit in liquidation") + // ErrInvalidWithdrawAmount error for invalid withdrawal amount + ErrInvalidWithdrawAmount = sdkerrors.Register(ModuleName, 14, "withdrawal amount exceeds deposit") + //ErrCdpNotAvailable error for depositing to a CDP in liquidation + ErrCdpNotAvailable = sdkerrors.Register(ModuleName, 15, "cannot modify cdp in liquidation") + // ErrBelowDebtFloor error for creating a cdp with debt below the minimum + ErrBelowDebtFloor = sdkerrors.Register(ModuleName, 16, "proposed cdp debt is below minimum") + // ErrLoadingAugmentedCDP error loading augmented cdp + ErrLoadingAugmentedCDP = sdkerrors.Register(ModuleName, 17, "augmented cdp could not be loaded from cdp") ) - -// ErrCdpAlreadyExists error for duplicate cdps -func ErrCdpAlreadyExists(codespace sdk.CodespaceType, owner sdk.AccAddress, denom string) sdk.Error { - return sdk.NewError(codespace, CodeCdpAlreadyExists, fmt.Sprintf("cdp for owner %s and collateral %s already exists", owner, denom)) -} - -// ErrInvalidCollateralLength error for invalid collateral input length -func ErrInvalidCollateralLength(codespace sdk.CodespaceType, length int) sdk.Error { - return sdk.NewError(codespace, CodeCollateralLengthInvalid, fmt.Sprintf("only one collateral type per cdp, has %d", length)) -} - -// ErrCollateralNotSupported error for unsupported collateral -func ErrCollateralNotSupported(codespace sdk.CodespaceType, denom string) sdk.Error { - return sdk.NewError(codespace, CodeCollateralNotSupported, fmt.Sprintf("collateral %s not supported", denom)) -} - -// ErrDebtNotSupported error for unsupported debt -func ErrDebtNotSupported(codespace sdk.CodespaceType, denom string) sdk.Error { - return sdk.NewError(codespace, CodeDebtNotSupported, fmt.Sprintf("collateral %s not supported", denom)) -} - -// ErrExceedsDebtLimit error for attempted draws that exceed debt limit -func ErrExceedsDebtLimit(codespace sdk.CodespaceType, proposed sdk.Coins, limit sdk.Coins) sdk.Error { - return sdk.NewError(codespace, CodeExceedsDebtLimit, fmt.Sprintf("proposed debt increase %s would exceed debt limit of %s", proposed, limit)) -} - -// ErrInvalidCollateralRatio error for attempted draws that are below liquidation ratio -func ErrInvalidCollateralRatio(codespace sdk.CodespaceType, denom string, collateralRatio sdk.Dec, liquidationRatio sdk.Dec) sdk.Error { - return sdk.NewError(codespace, CodeInvalidCollateralRatio, fmt.Sprintf("proposed collateral ratio of %s is below liqudation ratio of %s for collateral %s", collateralRatio, liquidationRatio, denom)) -} - -// ErrCdpNotFound error cdp not found -func ErrCdpNotFound(codespace sdk.CodespaceType, owner sdk.AccAddress, denom string) sdk.Error { - return sdk.NewError(codespace, CodeCdpNotFound, fmt.Sprintf("cdp for owner %s and collateral %s not found", owner, denom)) -} - -// ErrDepositNotFound error for deposit not found -func ErrDepositNotFound(codespace sdk.CodespaceType, depositor sdk.AccAddress, cdpID uint64) sdk.Error { - return sdk.NewError(codespace, CodeDepositNotFound, fmt.Sprintf("deposit for cdp %d not found for %s", cdpID, depositor)) -} - -// ErrInvalidDepositDenom error for invalid deposit denoms -func ErrInvalidDepositDenom(codespace sdk.CodespaceType, cdpID uint64, expected string, actual string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidDepositDenom, fmt.Sprintf("invalid deposit for cdp %d, expects %s, got %s", cdpID, expected, actual)) -} - -// ErrInvalidPaymentDenom error for invalid payment denoms -func ErrInvalidPaymentDenom(codespace sdk.CodespaceType, cdpID uint64, expected []string, actual []string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidPaymentDenom, fmt.Sprintf("invalid payment for cdp %d, expects %s, got %s", cdpID, expected, actual)) -} - -//ErrDepositNotAvailable error for withdrawing deposits in liquidation -func ErrDepositNotAvailable(codespace sdk.CodespaceType, cdpID uint64, depositor sdk.AccAddress) sdk.Error { - return sdk.NewError(codespace, CodeDepositNotAvailable, fmt.Sprintf("deposit from %s for cdp %d in liquidation", depositor, cdpID)) -} - -// ErrInvalidCollateralDenom error for invalid collateral denoms -func ErrInvalidCollateralDenom(codespace sdk.CodespaceType, denom string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidDepositDenom, fmt.Sprintf("invalid denom: %s", denom)) -} - -// ErrInvalidWithdrawAmount error for invalid withdrawal amount -func ErrInvalidWithdrawAmount(codespace sdk.CodespaceType, withdraw sdk.Coins, deposit sdk.Coins) sdk.Error { - return sdk.NewError(codespace, CodeInvalidWithdrawAmount, fmt.Sprintf("withdrawal amount of %s exceeds deposit of %s", withdraw, deposit)) -} - -//ErrCdpNotAvailable error for depositing to a CDP in liquidation -func ErrCdpNotAvailable(codespace sdk.CodespaceType, cdpID uint64) sdk.Error { - return sdk.NewError(codespace, CodeCdpNotAvailable, fmt.Sprintf("cannot modify cdp %d, in liquidation", cdpID)) -} - -// ErrBelowDebtFloor error for creating a cdp with debt below the minimum -func ErrBelowDebtFloor(codespace sdk.CodespaceType, debt sdk.Coins, floor sdk.Int) sdk.Error { - return sdk.NewError(codespace, CodeBelowDebtFloor, fmt.Sprintf("proposed cdp debt of %s is below the minimum of %s", debt, floor)) -} - -// ErrPaymentExceedsDebt error for repayments that are greater than the debt amount -func ErrPaymentExceedsDebt(codespace sdk.CodespaceType, payment sdk.Coins, principal sdk.Coins) sdk.Error { - return sdk.NewError(codespace, CodePaymentExceedsDebt, fmt.Sprintf("payment of %s exceeds debt of %s", payment, principal)) -} - -// ErrLoadingAugmentedCDP error loading augmented cdp -func ErrLoadingAugmentedCDP(codespace sdk.CodespaceType, cdpID uint64) sdk.Error { - return sdk.NewError(codespace, CodeCdpNotFound, fmt.Sprintf("augmented cdp could not be loaded from cdp id %d", cdpID)) -} diff --git a/x/cdp/types/expected_keepers.go b/x/cdp/types/expected_keepers.go index dcbd8314..ae5bb5bd 100644 --- a/x/cdp/types/expected_keepers.go +++ b/x/cdp/types/expected_keepers.go @@ -17,29 +17,29 @@ type SupplyKeeper interface { // TODO remove with genesis 2-phases refactor https://github.com/cosmos/cosmos-sdk/issues/2862 SetModuleAccount(sdk.Context, supplyexported.ModuleAccountI) - SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error - SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error - SendCoinsFromModuleToModule(ctx sdk.Context, 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 + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error GetSupply(ctx sdk.Context) (supply supplyexported.SupplyI) } // PricefeedKeeper defines the expected interface for the pricefeed type PricefeedKeeper interface { - GetCurrentPrice(sdk.Context, string) (pftypes.CurrentPrice, sdk.Error) + GetCurrentPrice(sdk.Context, string) (pftypes.CurrentPrice, error) GetParams(sdk.Context) pftypes.Params // These are used for testing TODO replace mockApp with keeper in tests to remove these SetParams(sdk.Context, pftypes.Params) - SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, sdk.Error) - SetCurrentPrices(sdk.Context, string) sdk.Error + SetPrice(sdk.Context, sdk.AccAddress, string, sdk.Dec, time.Time) (pftypes.PostedPrice, error) + SetCurrentPrices(sdk.Context, string) error } // AuctionKeeper expected interface for the auction keeper (noalias) type AuctionKeeper interface { - StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, sdk.Error) - 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) + StartSurplusAuction(ctx sdk.Context, seller string, lot sdk.Coin, bidDenom string) (uint64, error) + StartDebtAuction(ctx sdk.Context, buyer string, bid sdk.Coin, initialLot sdk.Coin, debt sdk.Coin) (uint64, error) + StartCollateralAuction(ctx sdk.Context, seller string, lot sdk.Coin, maxBid sdk.Coin, lotReturnAddrs []sdk.AccAddress, lotReturnWeights []sdk.Int, debt sdk.Coin) (uint64, error) } // AccountKeeper expected interface for the account keeper (noalias) diff --git a/x/cdp/types/msg.go b/x/cdp/types/msg.go index 1fdcc669..9b6df344 100644 --- a/x/cdp/types/msg.go +++ b/x/cdp/types/msg.go @@ -1,9 +1,12 @@ package types import ( + "errors" "fmt" + "strings" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // ensure Msg interface compliance at compile time @@ -38,24 +41,18 @@ func (msg MsgCreateCDP) Route() string { return RouterKey } func (msg MsgCreateCDP) Type() string { return "create_cdp" } // ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgCreateCDP) ValidateBasic() sdk.Error { +func (msg MsgCreateCDP) ValidateBasic() error { if msg.Sender.Empty() { - return sdk.ErrInternal("invalid (empty) sender address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") } - if len(msg.Collateral) != 1 { - return sdk.ErrInvalidCoins(fmt.Sprintf("cdps do not support multiple collateral types: received %s", msg.Collateral)) + if msg.Collateral.Len() != 1 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "cdps do not support multiple collateral types: %s", msg.Collateral) } if !msg.Collateral.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid collateral amount: %s", msg.Collateral)) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral) } - if !msg.Collateral.IsAllPositive() { - return sdk.ErrInvalidCoins(fmt.Sprintf("negative collateral amount: %s", msg.Collateral)) - } - if !msg.Principal.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid principal amount: %s", msg.Principal)) - } - if !msg.Principal.IsAllPositive() { - return sdk.ErrInvalidCoins(fmt.Sprintf("negative principal amount: %s", msg.Principal)) + if msg.Principal.Empty() || !msg.Principal.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal amount %s", msg.Principal) } return nil } @@ -103,21 +100,18 @@ func (msg MsgDeposit) Route() string { return RouterKey } func (msg MsgDeposit) Type() string { return "deposit_cdp" } // ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgDeposit) ValidateBasic() sdk.Error { +func (msg MsgDeposit) ValidateBasic() error { if msg.Owner.Empty() { - return sdk.ErrInternal("invalid (empty) sender address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "owner address cannot be empty") } if msg.Depositor.Empty() { - return sdk.ErrInternal("invalid (empty) owner address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") } - if len(msg.Collateral) != 1 { - return sdk.ErrInvalidCoins(fmt.Sprintf("cdps do not support multiple collateral types: received %s", msg.Collateral)) + if msg.Collateral.Len() != 1 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "cdps do not support multiple collateral types: %s", msg.Collateral) } if !msg.Collateral.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid collateral amount: %s", msg.Collateral)) - } - if !msg.Collateral.IsAllPositive() { - return sdk.ErrInvalidCoins(fmt.Sprintf("negative collateral amount: %s", msg.Collateral)) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral) } return nil } @@ -165,21 +159,18 @@ func (msg MsgWithdraw) Route() string { return RouterKey } func (msg MsgWithdraw) Type() string { return "withdraw_cdp" } // ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgWithdraw) ValidateBasic() sdk.Error { +func (msg MsgWithdraw) ValidateBasic() error { if msg.Owner.Empty() { - return sdk.ErrInternal("invalid (empty) sender address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "owner address cannot be empty") } if msg.Depositor.Empty() { - return sdk.ErrInternal("invalid (empty) owner address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") } - if len(msg.Collateral) != 1 { - return sdk.ErrInvalidCoins(fmt.Sprintf("cdps do not support multiple collateral types: received %s", msg.Collateral)) + if msg.Collateral.Len() != 1 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "cdps do not support multiple collateral types: %s", msg.Collateral) } if !msg.Collateral.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid collateral amount: %s", msg.Collateral)) - } - if !msg.Collateral.IsAllPositive() { - return sdk.ErrInvalidCoins(fmt.Sprintf("negative collateral amount: %s", msg.Collateral)) + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "collateral amount %s", msg.Collateral) } return nil } @@ -227,18 +218,15 @@ func (msg MsgDrawDebt) Route() string { return RouterKey } func (msg MsgDrawDebt) Type() string { return "draw_cdp" } // ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgDrawDebt) ValidateBasic() sdk.Error { +func (msg MsgDrawDebt) ValidateBasic() error { if msg.Sender.Empty() { - return sdk.ErrInternal("invalid (empty) sender address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") } - if msg.CdpDenom == "" { - return sdk.ErrInternal("invalid (empty) cdp denom") + if strings.TrimSpace(msg.CdpDenom) == "" { + return errors.New("cdp denom cannot be blank") } - if !msg.Principal.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid principal amount: %s", msg.Principal)) - } - if !msg.Principal.IsAllPositive() { - return sdk.ErrInvalidCoins(fmt.Sprintf("negative principal amount: %s", msg.Principal)) + if msg.Principal.Empty() || !msg.Principal.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "principal amount %s", msg.Principal) } return nil } @@ -286,18 +274,15 @@ func (msg MsgRepayDebt) Route() string { return RouterKey } func (msg MsgRepayDebt) Type() string { return "repay_cdp" } // ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgRepayDebt) ValidateBasic() sdk.Error { +func (msg MsgRepayDebt) ValidateBasic() error { if msg.Sender.Empty() { - return sdk.ErrInternal("invalid (empty) sender address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") } - if msg.CdpDenom == "" { - return sdk.ErrInternal("invalid (empty) cdp denom") + if strings.TrimSpace(msg.CdpDenom) == "" { + return errors.New("cdp denom cannot be blank") } - if !msg.Payment.IsValid() { - return sdk.ErrInvalidCoins(fmt.Sprintf("invalid payment amount: %s", msg.Payment)) - } - if !msg.Payment.IsAllPositive() { - return sdk.ErrInvalidCoins(fmt.Sprintf("negative payment amount: %s", msg.Payment)) + if msg.Payment.Empty() || !msg.Payment.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "payment amount %s", msg.Payment) } return nil } diff --git a/x/cdp/types/msg_test.go b/x/cdp/types/msg_test.go index dd034a0d..814f6c68 100644 --- a/x/cdp/types/msg_test.go +++ b/x/cdp/types/msg_test.go @@ -33,16 +33,16 @@ func TestMsgCreateCDP(t *testing.T) { {"create cdp empty owner", sdk.AccAddress{}, coinsSingle, coinsSingle, false}, } - for i, tc := range tests { + for _, tc := range tests { msg := NewMsgCreateCDP( tc.sender, tc.collateral, tc.principal, ) if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", tc.description) } else { - require.Error(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", tc.description) } } } @@ -63,16 +63,16 @@ func TestMsgDeposit(t *testing.T) { {"deposit empty depositor", addrs[0], sdk.AccAddress{}, coinsSingle, false}, } - for i, tc := range tests { + for _, tc := range tests { msg := NewMsgDeposit( tc.sender, tc.depositor, tc.collateral, ) if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", tc.description) } else { - require.Error(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", tc.description) } } } @@ -93,16 +93,16 @@ func TestMsgWithdraw(t *testing.T) { {"withdraw empty depositor", addrs[0], sdk.AccAddress{}, coinsSingle, false}, } - for i, tc := range tests { + for _, tc := range tests { msg := NewMsgWithdraw( tc.sender, tc.depositor, tc.collateral, ) if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", tc.description) } else { - require.Error(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", tc.description) } } } @@ -122,16 +122,16 @@ func TestMsgDrawDebt(t *testing.T) { {"draw debt empty denom", sdk.AccAddress{}, "", coinsSingle, false}, } - for i, tc := range tests { + for _, tc := range tests { msg := NewMsgDrawDebt( tc.sender, tc.denom, tc.principal, ) if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", tc.description) } else { - require.Error(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", tc.description) } } } @@ -151,16 +151,16 @@ func TestMsgRepayDebt(t *testing.T) { {"repay debt empty denom", sdk.AccAddress{}, "", coinsSingle, false}, } - for i, tc := range tests { + for _, tc := range tests { msg := NewMsgRepayDebt( tc.sender, tc.denom, tc.payment, ) if tc.expectPass { - require.NoError(t, msg.ValidateBasic(), "test: %v", i) + require.NoError(t, msg.ValidateBasic(), "test: %v", tc.description) } else { - require.Error(t, msg.ValidateBasic(), "test: %v", i) + require.Error(t, msg.ValidateBasic(), "test: %v", tc.description) } } } diff --git a/x/cdp/types/params.go b/x/cdp/types/params.go index db54cbf5..5f1fd938 100644 --- a/x/cdp/types/params.go +++ b/x/cdp/types/params.go @@ -2,9 +2,11 @@ package types import ( "fmt" + "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/params" tmtime "github.com/tendermint/tendermint/types/time" ) @@ -156,30 +158,49 @@ func ParamKeyTable() params.KeyTable { // nolint func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ - {Key: KeyGlobalDebtLimit, Value: &p.GlobalDebtLimit}, - {Key: KeyCollateralParams, Value: &p.CollateralParams}, - {Key: KeyDebtParams, Value: &p.DebtParams}, - {Key: KeyCircuitBreaker, Value: &p.CircuitBreaker}, - {Key: KeySurplusThreshold, Value: &p.SurplusAuctionThreshold}, - {Key: KeyDebtThreshold, Value: &p.DebtAuctionThreshold}, - {Key: KeyDistributionFrequency, Value: &p.SavingsDistributionFrequency}, + params.NewParamSetPair(KeyGlobalDebtLimit, &p.GlobalDebtLimit, validateGlobalDebtLimitParam), + params.NewParamSetPair(KeyCollateralParams, &p.CollateralParams, validateCollateralParams), + params.NewParamSetPair(KeyDebtParams, &p.DebtParams, validateDebtParams), + params.NewParamSetPair(KeyCircuitBreaker, &p.CircuitBreaker, validateCircuitBreakerParam), + params.NewParamSetPair(KeySurplusThreshold, &p.SurplusAuctionThreshold, validateSurplusAuctionThresholdParam), + params.NewParamSetPair(KeyDebtThreshold, &p.DebtAuctionThreshold, validateDebtAuctionThresholdParam), + params.NewParamSetPair(KeyDistributionFrequency, &p.SavingsDistributionFrequency, validateSavingsDistributionFrequencyParam), } } // Validate checks that the parameters have valid values. func (p Params) Validate() error { - // validate debt params - debtDenoms := make(map[string]int) - for _, dp := range p.DebtParams { - _, found := debtDenoms[dp.Denom] - 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 + if err := validateGlobalDebtLimitParam(p.GlobalDebtLimit); err != nil { + return err + } + if err := validateCollateralParams(p.CollateralParams); err != nil { + return err + } + + if err := validateDebtParams(p.DebtParams); err != nil { + return err + } + + if err := validateCircuitBreakerParam(p.CircuitBreaker); err != nil { + return err + } + + if err := validateSurplusAuctionThresholdParam(p.SurplusAuctionThreshold); err != nil { + return err + } + + if err := validateDebtAuctionThresholdParam(p.DebtAuctionThreshold); err != nil { + return err + } + + if err := validateSavingsDistributionFrequencyParam(p.SavingsDistributionFrequency); err != nil { + return err + } + + debtDenoms := make(map[string]bool) + for _, dp := range p.DebtParams { + debtDenoms[dp.Denom] = true } // validate collateral params @@ -187,38 +208,76 @@ func (p Params) Validate() error { prefixDupMap := make(map[int]int) collateralParamsDebtLimit := sdk.Coins{} for _, cp := range p.CollateralParams { + + prefix := int(cp.Prefix) + prefixDupMap[prefix] = 1 + collateralDupMap[cp.Denom] = 1 + + collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit...) + + if cp.DebtLimit.IsAnyGT(p.GlobalDebtLimit) { + return fmt.Errorf("collateral debt limit for %s exceeds global debt limit: \n\tglobal debt limit: %s\n\tcollateral debt limits: %s", + cp.Denom, p.GlobalDebtLimit, cp.DebtLimit) + } + } + + if collateralParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { + return fmt.Errorf("collateral debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tcollateral debt limits: %s", + p.GlobalDebtLimit, collateralParamsDebtLimit) + } + + return nil +} + +func validateGlobalDebtLimitParam(i interface{}) error { + globalDebtLimit, ok := i.(sdk.Coins) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if !globalDebtLimit.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "global debt limit %s", globalDebtLimit.String()) + } + + return nil +} + +func validateCollateralParams(i interface{}) error { + collateralParams, ok := i.(CollateralParams) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + collateralDupMap := make(map[string]bool) + prefixDupMap := make(map[int]bool) + for _, cp := range collateralParams { + if strings.TrimSpace(cp.Denom) == "" { + return fmt.Errorf("debt denom cannot be blank %s", cp) + } + prefix := int(cp.Prefix) if prefix < minCollateralPrefix || prefix > maxCollateralPrefix { return fmt.Errorf("invalid prefix for collateral denom %s: %b", cp.Denom, cp.Prefix) } + _, found := prefixDupMap[prefix] if found { return fmt.Errorf("duplicate prefix for collateral denom %s: %v", cp.Denom, []byte{cp.Prefix}) } - prefixDupMap[prefix] = 1 - _, found = collateralDupMap[cp.Denom] + prefixDupMap[prefix] = true + _, found = collateralDupMap[cp.Denom] if found { return fmt.Errorf("duplicate collateral denom: %s", cp.Denom) } - collateralDupMap[cp.Denom] = 1 - if cp.DebtLimit.IsAnyNegative() { + collateralDupMap[cp.Denom] = true + + if !cp.DebtLimit.IsValid() { return fmt.Errorf("debt limit for all collaterals should be positive, is %s for %s", cp.DebtLimit, cp.Denom) } - collateralParamsDebtLimit = collateralParamsDebtLimit.Add(cp.DebtLimit) - for _, dc := range cp.DebtLimit { - _, found := debtDenoms[dc.Denom] - if !found { - return fmt.Errorf("debt limit for collateral %s contains invalid debt denom %s", cp.Denom, dc.Denom) - } - } - if cp.DebtLimit.IsAnyGT(p.GlobalDebtLimit) { - return fmt.Errorf("collateral debt limit for %s exceeds global debt limit: \n\tglobal debt limit: %s\n\tcollateral debt limits: %s", - cp.Denom, p.GlobalDebtLimit, cp.DebtLimit) - } if cp.LiquidationPenalty.LT(sdk.ZeroDec()) || cp.LiquidationPenalty.GT(sdk.OneDec()) { return fmt.Errorf("liquidation penalty should be between 0 and 1, is %s for %s", cp.LiquidationPenalty, cp.Denom) } @@ -229,24 +288,82 @@ func (p Params) Validate() error { return fmt.Errorf("stability fee must be ≥ 1.0, is %s for %s", cp.StabilityFee, cp.Denom) } } - if collateralParamsDebtLimit.IsAnyGT(p.GlobalDebtLimit) { - return fmt.Errorf("collateral debt limit exceeds global debt limit:\n\tglobal debt limit: %s\n\tcollateral debt limits: %s", - p.GlobalDebtLimit, collateralParamsDebtLimit) - } - // validate global params - if p.GlobalDebtLimit.IsAnyNegative() { - return fmt.Errorf("global debt limit should be positive for all debt tokens, is %s", p.GlobalDebtLimit) - } - if !p.SurplusAuctionThreshold.IsPositive() { - return fmt.Errorf("surplus auction threshold should be positive, is %s", p.SurplusAuctionThreshold) - } - 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 +} + +func validateDebtParams(i interface{}) error { + debtParams, ok := i.(DebtParams) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + // validate debt params + debtDenoms := make(map[string]bool) + for _, dp := range debtParams { + if strings.TrimSpace(dp.Denom) == "" { + return fmt.Errorf("debt denom cannot be blank %s", dp) + } + + _, found := debtDenoms[dp.Denom] + 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] = true + } + + return nil +} + +func validateCircuitBreakerParam(i interface{}) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return nil +} + +func validateSurplusAuctionThresholdParam(i interface{}) error { + sat, ok := i.(sdk.Int) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if !sat.IsPositive() { + return fmt.Errorf("surplus auction threshold should be positive: %s", sat) + } + + return nil +} + +func validateDebtAuctionThresholdParam(i interface{}) error { + dat, ok := i.(sdk.Int) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if !dat.IsPositive() { + return fmt.Errorf("debt auction threshold should be positive: %s", dat) + } + + return nil +} + +func validateSavingsDistributionFrequencyParam(i interface{}) error { + sdf, ok := i.(time.Duration) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + if sdf.Seconds() <= float64(0) { + return fmt.Errorf("savings distribution frequency should be positive: %s", sdf) + } + return nil } diff --git a/x/kavadist/alias.go b/x/kavadist/alias.go index e9295e14..04cdf035 100644 --- a/x/kavadist/alias.go +++ b/x/kavadist/alias.go @@ -1,8 +1,3 @@ -// nolint -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/kavadist/keeper -// ALIASGEN: github.com/kava-labs/kava/x/kavadist/types package kavadist import ( @@ -10,8 +5,13 @@ import ( "github.com/kava-labs/kava/x/kavadist/types" ) +// 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 + +// nolint const ( - DefaultCodespace = types.DefaultCodespace EventTypeKavaDist = types.EventTypeKavaDist AttributeKeyInflation = types.AttributeKeyInflation ModuleName = types.ModuleName @@ -23,6 +23,7 @@ const ( QueryGetParams = types.QueryGetParams ) +// nolint var ( // functions aliases NewKeeper = keeper.NewKeeper @@ -45,6 +46,7 @@ var ( GovDenom = types.GovDenom ) +// nolint type ( Keeper = keeper.Keeper GenesisState = types.GenesisState diff --git a/x/kavadist/handler.go b/x/kavadist/handler.go index 585efbbb..ed7a1b60 100644 --- a/x/kavadist/handler.go +++ b/x/kavadist/handler.go @@ -1,18 +1,16 @@ package kavadist import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // NewHandler creates an sdk.Handler for kavadist messages func NewHandler(k Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { switch msg := msg.(type) { default: - errMsg := fmt.Sprintf("unrecognized cdp msg type: %T", msg) - return sdk.ErrUnknownRequest(errMsg).Result() + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) } } -} \ No newline at end of file +} diff --git a/x/kavadist/keeper/keeper.go b/x/kavadist/keeper/keeper.go index cf8729c6..34faa329 100644 --- a/x/kavadist/keeper/keeper.go +++ b/x/kavadist/keeper/keeper.go @@ -16,18 +16,19 @@ type Keeper struct { 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 { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper) Keeper { + if !paramstore.HasKeyTable() { + paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) + } return Keeper{ key: key, cdc: cdc, - paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), + paramSubspace: paramstore, supplyKeeper: sk, - codespace: codespace, } } diff --git a/x/kavadist/keeper/mint.go b/x/kavadist/keeper/mint.go index 4a1da2c3..c5d19215 100644 --- a/x/kavadist/keeper/mint.go +++ b/x/kavadist/keeper/mint.go @@ -7,7 +7,7 @@ import ( ) // MintPeriodInflation mints new tokens according to the inflation schedule specified in the paramters -func (k Keeper) MintPeriodInflation(ctx sdk.Context) sdk.Error { +func (k Keeper) MintPeriodInflation(ctx sdk.Context) error { params := k.GetParams(ctx) if !params.Active { ctx.EventManager().EmitEvent( @@ -59,7 +59,7 @@ func (k Keeper) MintPeriodInflation(ctx sdk.Context) sdk.Error { return nil } -func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, timePeriods sdk.Int, denom string) sdk.Error { +func (k Keeper) mintInflationaryCoins(ctx sdk.Context, inflationRate sdk.Dec, timePeriods sdk.Int, denom string) error { totalSupply := k.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf(denom) // used to scale accumulator calculations by 10^18 scalar := sdk.NewInt(1000000000000000000) diff --git a/x/kavadist/module.go b/x/kavadist/module.go index 218655c6..f24005c8 100644 --- a/x/kavadist/module.go +++ b/x/kavadist/module.go @@ -20,7 +20,7 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModuleSimulation = AppModule{} ) // AppModuleBasic app module basics object @@ -62,30 +62,9 @@ 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 @@ -94,10 +73,9 @@ type AppModule struct { // NewAppModule creates a new AppModule object func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{}, - AppModuleSimulation: AppModuleSimulation{}, - keeper: keeper, - supplyKeeper: supplyKeeper, + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + supplyKeeper: supplyKeeper, } } @@ -151,3 +129,30 @@ func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +//____________________________________________________________________________ + +// GenerateGenesisState creates a randomized GenState of the auction module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// RandomizedParams returns nil because auction has no params. +func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers a decoder for auction module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// WeightedOperations returns the all the auction module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return nil +} diff --git a/x/kavadist/simulation/decoder.go b/x/kavadist/simulation/decoder.go index d28aed07..bc3787a0 100644 --- a/x/kavadist/simulation/decoder.go +++ b/x/kavadist/simulation/decoder.go @@ -5,14 +5,15 @@ import ( "fmt" "time" + "github.com/tendermint/tendermint/libs/kv" + "github.com/cosmos/cosmos-sdk/codec" - cmn "github.com/tendermint/tendermint/libs/common" "github.com/kava-labs/kava/x/kavadist/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding cdp type -func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey): var timeA, timeB time.Time diff --git a/x/kavadist/simulation/decoder_test.go b/x/kavadist/simulation/decoder_test.go index 235b22ff..0ed937dc 100644 --- a/x/kavadist/simulation/decoder_test.go +++ b/x/kavadist/simulation/decoder_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/require" - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/kv" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -28,9 +28,9 @@ func TestDecodeDistributionStore(t *testing.T) { prevBlockTime := time.Now().UTC() - kvPairs := cmn.KVPairs{ - cmn.KVPair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)}, - cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + kvPairs := kv.Pairs{ + kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } tests := []struct { diff --git a/x/kavadist/simulation/params.go b/x/kavadist/simulation/params.go index 909e3baf..af0b75e4 100644 --- a/x/kavadist/simulation/params.go +++ b/x/kavadist/simulation/params.go @@ -20,14 +20,14 @@ func ParamChanges(r *rand.Rand) []simulation.ParamChange { } return []simulation.ParamChange{ - simulation.NewSimParamChange(types.ModuleName, string(types.KeyActive), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyActive), func(r *rand.Rand) string { - return fmt.Sprintf("\"%t\"", active) + return fmt.Sprintf("%t", active) }, ), - simulation.NewSimParamChange(types.ModuleName, string(types.KeyPeriods), "", + simulation.NewSimParamChange(types.ModuleName, string(types.KeyPeriods), func(r *rand.Rand) string { - return fmt.Sprintf("\"%v\"", periods) + return fmt.Sprintf("%v", periods) }, ), } diff --git a/x/kavadist/types/errors.go b/x/kavadist/types/errors.go deleted file mode 100644 index 173f7a49..00000000 --- a/x/kavadist/types/errors.go +++ /dev/null @@ -1,11 +0,0 @@ -// DONTCOVER -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// Error codes specific to kavadist module -const ( - DefaultCodespace sdk.CodespaceType = ModuleName -) diff --git a/x/kavadist/types/expected_keepers.go b/x/kavadist/types/expected_keepers.go index a2e54262..cbbc3cef 100644 --- a/x/kavadist/types/expected_keepers.go +++ b/x/kavadist/types/expected_keepers.go @@ -10,6 +10,6 @@ type SupplyKeeper interface { GetModuleAddress(name string) sdk.AccAddress GetModuleAccount(ctx sdk.Context, name string) exported.ModuleAccountI GetSupply(ctx sdk.Context) (supply exported.SupplyI) - SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) sdk.Error - MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error + SendCoinsFromModuleToModule(ctx sdk.Context, senderModule, recipientModule string, amt sdk.Coins) error + MintCoins(ctx sdk.Context, name string, amt sdk.Coins) error } diff --git a/x/kavadist/types/params.go b/x/kavadist/types/params.go index d2ab89e0..9eb01b08 100644 --- a/x/kavadist/types/params.go +++ b/x/kavadist/types/params.go @@ -90,22 +90,52 @@ func ParamKeyTable() params.KeyTable { // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ - {Key: KeyActive, Value: &p.Active}, - {Key: KeyPeriods, Value: &p.Periods}, + params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam), + params.NewParamSetPair(KeyPeriods, &p.Periods, validatePeriodsParams), } } // Validate checks that the parameters have valid values. func (p Params) Validate() error { + if err := validateActiveParam(p.Active); err != nil { + return err + } + + return validatePeriodsParams(p.Periods) +} + +func validateActiveParam(i interface{}) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + + return nil +} + +func validatePeriodsParams(i interface{}) error { + periods, ok := i.(Periods) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + prevEnd := tmtime.Canonical(time.Unix(0, 0)) - for _, pr := range p.Periods { + for _, pr := range periods { if pr.End.Before(pr.Start) { return fmt.Errorf("end time for period is before start time: %s", pr) } + if pr.Start.Before(prevEnd) { - return fmt.Errorf("periods must be in chronological order: %s", p.Periods) + return fmt.Errorf("periods must be in chronological order: %s", periods) } prevEnd = pr.End + + if pr.Start.IsZero() || pr.End.IsZero() { + return fmt.Errorf("start or end time cannot be zero: %s", pr) + } + + //TODO: validate period Inflation? } + return nil } diff --git a/x/pricefeed/alias.go b/x/pricefeed/alias.go index 1e5aa2be..75affe59 100644 --- a/x/pricefeed/alias.go +++ b/x/pricefeed/alias.go @@ -1,8 +1,3 @@ -// nolint -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper -// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types package pricefeed import ( @@ -10,13 +5,13 @@ import ( "github.com/kava-labs/kava/x/pricefeed/types" ) +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/types/ +// ALIASGEN: github.com/kava-labs/kava/x/pricefeed/keeper/ + +// nolint const ( - DefaultCodespace = types.DefaultCodespace - CodeEmptyInput = types.CodeEmptyInput - CodeExpired = types.CodeExpired - CodeInvalidPrice = types.CodeInvalidPrice - CodeInvalidAsset = types.CodeInvalidAsset - CodeInvalidOracle = types.CodeInvalidOracle EventTypeMarketPriceUpdated = types.EventTypeMarketPriceUpdated EventTypeOracleUpdatedPrice = types.EventTypeOracleUpdatedPrice EventTypeNoValidPrices = types.EventTypeNoValidPrices @@ -39,6 +34,7 @@ const ( QueryPrice = types.QueryPrice ) +// nolint var ( // functions aliases NewKeeper = keeper.NewKeeper @@ -69,6 +65,7 @@ var ( DefaultMarkets = types.DefaultMarkets ) +// nolint type ( Keeper = keeper.Keeper GenesisState = types.GenesisState diff --git a/x/pricefeed/client/cli/query.go b/x/pricefeed/client/cli/query.go index d1a125c5..b41d388a 100644 --- a/x/pricefeed/client/cli/query.go +++ b/x/pricefeed/client/cli/query.go @@ -5,6 +5,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/pricefeed/types" @@ -22,7 +23,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } - pricefeedQueryCmd.AddCommand(client.GetCommands( + pricefeedQueryCmd.AddCommand(flags.GetCommands( GetCmdPrice(queryRoute, cdc), GetCmdRawPrices(queryRoute, cdc), GetCmdOracles(queryRoute, cdc), diff --git a/x/pricefeed/client/cli/tx.go b/x/pricefeed/client/cli/tx.go index 0b46a6af..2aec48d6 100644 --- a/x/pricefeed/client/cli/tx.go +++ b/x/pricefeed/client/cli/tx.go @@ -1,6 +1,7 @@ package cli import ( + "bufio" "fmt" "time" @@ -8,6 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" @@ -26,7 +28,7 @@ func GetTxCmd(storeKey string, cdc *codec.Codec) *cobra.Command { RunE: client.ValidateCmd, } - pricefeedTxCmd.AddCommand(client.PostCommands( + pricefeedTxCmd.AddCommand(flags.PostCommands( GetCmdPostPrice(cdc), )...) @@ -40,8 +42,9 @@ func GetCmdPostPrice(cdc *codec.Codec) *cobra.Command { Short: "post the latest price for a particular market", Args: cobra.ExactArgs(3), RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) cliCtx := context.NewCLIContext().WithCodec(cdc) - txBldr := auth.NewTxBuilderFromCLI().WithTxEncoder(utils.GetTxEncoder(cdc)) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) price, err := sdk.NewDecFromStr(args[1]) if err != nil { diff --git a/x/pricefeed/handler.go b/x/pricefeed/handler.go index 628f8951..c43277e3 100644 --- a/x/pricefeed/handler.go +++ b/x/pricefeed/handler.go @@ -1,20 +1,18 @@ package pricefeed import ( - "fmt" - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) // NewHandler handles all pricefeed type messages func NewHandler(k Keeper) sdk.Handler { - return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { switch msg := msg.(type) { case MsgPostPrice: return HandleMsgPostPrice(ctx, k, msg) default: - errMsg := fmt.Sprintf("unrecognized pricefeed message type: %T", msg) - return sdk.ErrUnknownRequest(errMsg).Result() + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) } } } @@ -26,15 +24,15 @@ func NewHandler(k Keeper) sdk.Handler { func HandleMsgPostPrice( ctx sdk.Context, k Keeper, - msg MsgPostPrice) sdk.Result { + msg MsgPostPrice) (*sdk.Result, error) { _, err := k.GetOracle(ctx, msg.MarketID, msg.From) if err != nil { - return err.Result() + return nil, err } _, err = k.SetPrice(ctx, msg.From, msg.MarketID, msg.Price, msg.Expiry) if err != nil { - return err.Result() + return nil, err } ctx.EventManager().EmitEvent( @@ -45,5 +43,5 @@ func HandleMsgPostPrice( ), ) - return sdk.Result{Events: ctx.EventManager().Events()} + return &sdk.Result{Events: ctx.EventManager().Events()}, nil } diff --git a/x/pricefeed/keeper/keeper.go b/x/pricefeed/keeper/keeper.go index 211e797a..c25e08f2 100644 --- a/x/pricefeed/keeper/keeper.go +++ b/x/pricefeed/keeper/keeper.go @@ -1,12 +1,12 @@ package keeper import ( - "fmt" "sort" "time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/params/subspace" "github.com/kava-labs/kava/x/pricefeed/types" @@ -20,19 +20,20 @@ type Keeper struct { cdc *codec.Codec // The reference to the Paramstore to get and set pricefeed specific params paramSubspace subspace.Subspace - // Reserved codespace - codespace sdk.CodespaceType } // NewKeeper returns a new keeper for the pricefeed module. func NewKeeper( - cdc *codec.Codec, key sdk.StoreKey, paramSubspace subspace.Subspace, codespace sdk.CodespaceType, + cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, ) Keeper { + if !paramstore.HasKeyTable() { + paramstore = paramstore.WithKeyTable(types.ParamKeyTable()) + } + return Keeper{ - paramSubspace: paramSubspace.WithKeyTable(types.ParamKeyTable()), - key: key, cdc: cdc, - codespace: codespace, + key: key, + paramSubspace: paramstore, } } @@ -42,55 +43,55 @@ func (k Keeper) SetPrice( oracle sdk.AccAddress, marketID string, price sdk.Dec, - expiry time.Time) (types.PostedPrice, sdk.Error) { + expiry time.Time) (types.PostedPrice, error) { // If the expiry is less than or equal to the current blockheight, we consider the price valid - if expiry.After(ctx.BlockTime()) { - store := ctx.KVStore(k.key) - prices, err := k.GetRawPrices(ctx, marketID) - if err != nil { - return types.PostedPrice{}, err - } - var index int - found := false - for i := range prices { - if prices[i].OracleAddress.Equals(oracle) { - index = i - found = true - break - } - } - // set the price for that particular oracle - if found { - prices[index] = types.NewPostedPrice(marketID, oracle, price, expiry) - } else { - prices = append(prices, types.NewPostedPrice(marketID, oracle, price, expiry)) - index = len(prices) - 1 - } - - // Emit an event containing the oracle's new price - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventTypeOracleUpdatedPrice, - sdk.NewAttribute(types.AttributeMarketID, marketID), - sdk.NewAttribute(types.AttributeOracle, oracle.String()), - sdk.NewAttribute(types.AttributeMarketPrice, price.String()), - sdk.NewAttribute(types.AttributeExpiry, fmt.Sprintf("%d", expiry.Unix())), - ), - ) - store.Set( - types.RawPriceKey(marketID), k.cdc.MustMarshalBinaryBare(prices), - ) - return prices[index], nil + if !expiry.After(ctx.BlockTime()) { + return types.PostedPrice{}, types.ErrExpired } - return types.PostedPrice{}, types.ErrExpired(k.codespace) + store := ctx.KVStore(k.key) + prices, err := k.GetRawPrices(ctx, marketID) + if err != nil { + return types.PostedPrice{}, err + } + var index int + found := false + for i := range prices { + if prices[i].OracleAddress.Equals(oracle) { + index = i + found = true + break + } + } + + // set the price for that particular oracle + if found { + prices[index] = types.NewPostedPrice(marketID, oracle, price, expiry) + } else { + prices = append(prices, types.NewPostedPrice(marketID, oracle, price, expiry)) + index = len(prices) - 1 + } + + // Emit an event containing the oracle's new price + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeOracleUpdatedPrice, + sdk.NewAttribute(types.AttributeMarketID, marketID), + sdk.NewAttribute(types.AttributeOracle, oracle.String()), + sdk.NewAttribute(types.AttributeMarketPrice, price.String()), + sdk.NewAttribute(types.AttributeExpiry, expiry.UTC().String()), + ), + ) + + store.Set(types.RawPriceKey(marketID), k.cdc.MustMarshalBinaryBare(prices)) + return prices[index], nil } // SetCurrentPrices updates the price of an asset to the median of all valid oracle inputs -func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { +func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) error { _, ok := k.GetMarket(ctx, marketID) if !ok { - return types.ErrInvalidMarket(k.codespace, marketID) + return sdkerrors.Wrap(types.ErrInvalidMarket, marketID) } // store current price validPrevPrice := true @@ -115,8 +116,9 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { store.Set( types.CurrentPriceKey(marketID), k.cdc.MustMarshalBinaryBare(types.CurrentPrice{}), ) - return types.ErrNoValidPrice(k.codespace) + return types.ErrNoValidPrice } + medianPrice := k.CalculateMedianPrice(ctx, notExpiredPrices) // check case that market price was not set in genesis @@ -126,8 +128,8 @@ func (k Keeper) SetCurrentPrices(ctx sdk.Context, marketID string) sdk.Error { ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeMarketPriceUpdated, - sdk.NewAttribute(types.AttributeMarketID, fmt.Sprintf("%s", marketID)), - sdk.NewAttribute(types.AttributeMarketPrice, fmt.Sprintf("%s", medianPrice.String())), + sdk.NewAttribute(types.AttributeMarketID, marketID), + sdk.NewAttribute(types.AttributeMarketPrice, medianPrice.String()), ), ) } @@ -172,26 +174,26 @@ func (k Keeper) calculateMeanPrice(ctx sdk.Context, prices types.CurrentPrices) } // GetCurrentPrice fetches the current median price of all oracles for a specific market -func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, sdk.Error) { +func (k Keeper) GetCurrentPrice(ctx sdk.Context, marketID string) (types.CurrentPrice, error) { store := ctx.KVStore(k.key) bz := store.Get(types.CurrentPriceKey(marketID)) if bz == nil { - return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) + return types.CurrentPrice{}, types.ErrNoValidPrice } var price types.CurrentPrice err := k.cdc.UnmarshalBinaryBare(bz, &price) if err != nil { - return types.CurrentPrice{}, sdk.ErrInternal(sdk.AppendMsgToErr("failed to unmarshal result", err.Error())) + return types.CurrentPrice{}, err } if price.Price.Equal(sdk.ZeroDec()) { - return types.CurrentPrice{}, types.ErrNoValidPrice(k.codespace) + return types.CurrentPrice{}, types.ErrNoValidPrice } return price, nil } // GetRawPrices fetches the set of all prices posted by oracles for an asset -func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) (types.PostedPrices, sdk.Error) { +func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) (types.PostedPrices, error) { store := ctx.KVStore(k.key) bz := store.Get(types.RawPriceKey(marketID)) if bz == nil { @@ -200,12 +202,7 @@ func (k Keeper) GetRawPrices(ctx sdk.Context, marketID string) (types.PostedPric var prices types.PostedPrices err := k.cdc.UnmarshalBinaryBare(bz, &prices) if err != nil { - return types.PostedPrices{}, sdk.ErrInternal(sdk.AppendMsgToErr("failed to unmarshal result", err.Error())) + return types.PostedPrices{}, err } return prices, nil } - -// Codespace return the codespace for the keeper -func (k Keeper) Codespace() sdk.CodespaceType { - return k.codespace -} diff --git a/x/pricefeed/keeper/params.go b/x/pricefeed/keeper/params.go index 8b297f57..16836359 100644 --- a/x/pricefeed/keeper/params.go +++ b/x/pricefeed/keeper/params.go @@ -2,6 +2,7 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/pricefeed/types" ) @@ -24,28 +25,27 @@ func (k Keeper) GetMarkets(ctx sdk.Context) types.Markets { } // GetOracles returns the oracles in the pricefeed store -func (k Keeper) GetOracles(ctx sdk.Context, marketID string) ([]sdk.AccAddress, sdk.Error) { - +func (k Keeper) GetOracles(ctx sdk.Context, marketID string) ([]sdk.AccAddress, error) { for _, m := range k.GetMarkets(ctx) { if marketID == m.MarketID { return m.Oracles, nil } } - return []sdk.AccAddress{}, types.ErrInvalidMarket(k.Codespace(), marketID) + return []sdk.AccAddress{}, sdkerrors.Wrap(types.ErrInvalidMarket, marketID) } // GetOracle returns the oracle from the store or an error if not found -func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (sdk.AccAddress, sdk.Error) { +func (k Keeper) GetOracle(ctx sdk.Context, marketID string, address sdk.AccAddress) (sdk.AccAddress, error) { oracles, err := k.GetOracles(ctx, marketID) if err != nil { - return sdk.AccAddress{}, types.ErrInvalidMarket(k.Codespace(), marketID) + return sdk.AccAddress{}, sdkerrors.Wrap(types.ErrInvalidMarket, marketID) } for _, addr := range oracles { if address.Equals(addr) { return addr, nil } } - return sdk.AccAddress{}, types.ErrInvalidOracle(k.codespace, address) + return sdk.AccAddress{}, sdkerrors.Wrap(types.ErrInvalidOracle, address.String()) } // GetMarket returns the market if it is in the pricefeed system diff --git a/x/pricefeed/keeper/querier.go b/x/pricefeed/keeper/querier.go index 1a111fdf..cef65d4c 100644 --- a/x/pricefeed/keeper/querier.go +++ b/x/pricefeed/keeper/querier.go @@ -1,19 +1,18 @@ package keeper import ( - "fmt" + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - abci "github.com/tendermint/tendermint/abci/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/kava-labs/kava/x/pricefeed/types" ) // NewQuerier is the module level router for state queries func NewQuerier(keeper Keeper) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) { switch path[0] { case types.QueryPrice: return queryPrice(ctx, req, keeper) @@ -26,95 +25,97 @@ func NewQuerier(keeper Keeper) sdk.Querier { case types.QueryGetParams: return queryGetParams(ctx, req, keeper) default: - return nil, sdk.ErrUnknownRequest("unknown pricefeed query endpoint") + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName) } } } -func queryPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { +func queryPrice(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr error) { var requestParams types.QueryWithMarketIDParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } _, found := keeper.GetMarket(ctx, requestParams.MarketID) if !found { - return []byte{}, sdk.ErrUnknownRequest("asset not found") + return []byte{}, sdkerrors.Wrap(types.ErrAssetNotFound, requestParams.MarketID) } currentPrice, sdkErr := keeper.GetCurrentPrice(ctx, requestParams.MarketID) if sdkErr != nil { return nil, sdkErr } - bz, err := codec.MarshalJSONIndent(keeper.cdc, currentPrice) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, currentPrice) if err != nil { - panic("could not marshal result to JSON") + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } -func queryRawPrices(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { +func queryRawPrices(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr error) { var requestParams types.QueryWithMarketIDParams - err2 := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) - if err2 != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err2)) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } _, found := keeper.GetMarket(ctx, requestParams.MarketID) if !found { - return []byte{}, sdk.ErrUnknownRequest("asset not found") + return []byte{}, sdkerrors.Wrap(types.ErrAssetNotFound, requestParams.MarketID) } + rawPrices, err := keeper.GetRawPrices(ctx, requestParams.MarketID) if err != nil { return nil, err } - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, rawPrices) - if err2 != nil { - panic("could not marshal result to JSON") + + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, rawPrices) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } -func queryOracles(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { +func queryOracles(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr error) { var requestParams types.QueryWithMarketIDParams - err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams) + err := types.ModuleCdc.UnmarshalJSON(req.Data, &requestParams) if err != nil { - return nil, sdk.ErrInternal(fmt.Sprintf("failed to parse params: %s", err)) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } oracles, err := keeper.GetOracles(ctx, requestParams.MarketID) if err != nil { - return []byte{}, sdk.ErrUnknownRequest("market not found") + return []byte{}, sdkerrors.Wrap(types.ErrAssetNotFound, requestParams.MarketID) } - bz, err := codec.MarshalJSONIndent(keeper.cdc, oracles) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, oracles) if err != nil { - panic("could not marshal result to JSON") + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } -func queryMarkets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr sdk.Error) { +func queryMarkets(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, sdkErr error) { markets := keeper.GetMarkets(ctx) - bz, err := codec.MarshalJSONIndent(keeper.cdc, markets) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, markets) if err != nil { - panic("could not marshal result to JSON") + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } // query params in the pricefeed store -func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { params := keeper.GetParams(ctx) // Encode results - bz, err := codec.MarshalJSONIndent(keeper.cdc, params) + bz, err := codec.MarshalJSONIndent(types.ModuleCdc, params) if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } return bz, nil } diff --git a/x/pricefeed/module.go b/x/pricefeed/module.go index ab167e02..3426a860 100644 --- a/x/pricefeed/module.go +++ b/x/pricefeed/module.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" sim "github.com/cosmos/cosmos-sdk/x/simulation" abci "github.com/tendermint/tendermint/abci/types" @@ -22,7 +23,7 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModuleSimulation = AppModule{} ) // AppModuleBasic app module basics object @@ -70,39 +71,20 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { //____________________________________________________________________________ -// AppModuleSimulation defines the module simulation functions used by the pricefeed module. -type AppModuleSimulation struct{} - -// RegisterStoreDecoder registers a decoder for pricefeed module's types -func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[StoreKey] = simulation.DecodeStore -} - -// GenerateGenesisState creates a randomized GenState of the pricefeed module -func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RandomizedParams creates randomized pricefeed 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 + keeper Keeper + accountKeeper auth.AccountKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper) AppModule { +func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, + accountKeeper: accountKeeper, } } @@ -156,3 +138,32 @@ func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { gs := ExportGenesis(ctx, am.keeper) return ModuleCdc.MustMarshalJSON(gs) } + +//____________________________________________________________________________ + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the price feed module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// RandomizedParams returns nil because price feed has no params. +func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +// RegisterStoreDecoder registers a decoder for price feed module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// WeightedOperations returns the all the price feed module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.keeper) +} diff --git a/x/pricefeed/simulation/decoder.go b/x/pricefeed/simulation/decoder.go index 9693b6e3..e40c1932 100644 --- a/x/pricefeed/simulation/decoder.go +++ b/x/pricefeed/simulation/decoder.go @@ -5,13 +5,13 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/codec" - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/kv" "github.com/kava-labs/kava/x/pricefeed/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding pricefeed type -func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { case bytes.Contains(kvA.Key, []byte(types.CurrentPricePrefix)): var priceA, priceB types.CurrentPrice diff --git a/x/pricefeed/simulation/decoder_test.go b/x/pricefeed/simulation/decoder_test.go index bc886819..33ef65a8 100644 --- a/x/pricefeed/simulation/decoder_test.go +++ b/x/pricefeed/simulation/decoder_test.go @@ -6,8 +6,7 @@ import ( "time" "github.com/stretchr/testify/require" - - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/kv" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -29,10 +28,10 @@ func TestDecodeDistributionStore(t *testing.T) { currentPrice := types.CurrentPrice{MarketID: "current", Price: sdk.OneDec()} postedPrice := []types.PostedPrice{{MarketID: "posted", Price: sdk.OneDec(), Expiry: time.Now().UTC()}} - kvPairs := cmn.KVPairs{ - cmn.KVPair{Key: []byte(types.CurrentPricePrefix), Value: cdc.MustMarshalBinaryBare(currentPrice)}, - cmn.KVPair{Key: []byte(types.RawPriceFeedPrefix), Value: cdc.MustMarshalBinaryBare(postedPrice)}, - cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + kvPairs := kv.Pairs{ + kv.Pair{Key: []byte(types.CurrentPricePrefix), Value: cdc.MustMarshalBinaryBare(currentPrice)}, + kv.Pair{Key: []byte(types.RawPriceFeedPrefix), Value: cdc.MustMarshalBinaryBare(postedPrice)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } tests := []struct { diff --git a/x/pricefeed/simulation/genesis.go b/x/pricefeed/simulation/genesis.go index 272695aa..a1d049a0 100644 --- a/x/pricefeed/simulation/genesis.go +++ b/x/pricefeed/simulation/genesis.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/kava-labs/kava/x/pricefeed/types" pricefeed "github.com/kava-labs/kava/x/pricefeed/types" ) @@ -31,7 +32,7 @@ func loadPricefeedGenState(simState *module.SimulationState) pricefeed.GenesisSt var postedPrices []pricefeed.PostedPrice for _, denom := range BaseAssets { // Select an account to be the oracle - oracle := simState.Accounts[simulation.RandIntBetween(simState.Rand, 0, len(simState.Accounts))] + oracle, _ := simulation.RandomAcc(simState.Rand, simState.Accounts) marketID := fmt.Sprintf("%s:%s", denom, QuoteAsset) // Construct market for asset diff --git a/x/pricefeed/simulation/operations/msg.go b/x/pricefeed/simulation/operations.go similarity index 61% rename from x/pricefeed/simulation/operations/msg.go rename to x/pricefeed/simulation/operations.go index 385168a8..f620d172 100644 --- a/x/pricefeed/simulation/operations/msg.go +++ b/x/pricefeed/simulation/operations.go @@ -1,35 +1,62 @@ -package operations +package simulation import ( - "fmt" "math/rand" "sync" "time" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/kava-labs/kava/x/pricefeed" + appparams "github.com/kava-labs/kava/app/params" "github.com/kava-labs/kava/x/pricefeed/keeper" "github.com/kava-labs/kava/x/pricefeed/types" ) var ( - noOpMsg = simulation.NoOpMsg(pricefeed.ModuleName) + noOpMsg = simulation.NoOpMsg(types.ModuleName) btcPrices = []sdk.Dec{} bnbPrices = []sdk.Dec{} xrpPrices = []sdk.Dec{} genPrices sync.Once ) -// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price -func SimulateMsgUpdatePrices(keeper keeper.Keeper, blocks int) simulation.Operation { - // get a pricefeed handler - handler := pricefeed.NewHandler(keeper) +// Simulation operation weights constants +const ( + OpWeightMsgUpdatePrices = "op_weight_msg_update_prices" +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgUpdatePrices int + // var numBlocks int + + appParams.GetOrGenerate(cdc, OpWeightMsgUpdatePrices, &weightMsgUpdatePrices, nil, + func(_ *rand.Rand) { + weightMsgUpdatePrices = appparams.DefaultWeightMsgUpdatePrices + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgUpdatePrices, + SimulateMsgUpdatePrices(ak, k, 10000), + ), + } +} + +// SimulateMsgUpdatePrices updates the prices of various assets by randomly varying them based on current price +func SimulateMsgUpdatePrices(ak auth.AccountKeeper, keeper keeper.Keeper, blocks int) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account) ( - simulation.OperationMsg, []simulation.FutureOperation, error) { genPrices.Do(func() { // generate a random walk for each asset exactly once, with observations equal to the number of blocks in the sim for _, m := range keeper.GetMarkets(ctx) { @@ -63,24 +90,46 @@ func SimulateMsgUpdatePrices(keeper keeper.Keeper, blocks int) simulation.Operat randomMarket := pickRandomAsset(ctx, keeper, r) marketID := randomMarket.MarketID address := getRandomOracle(r, randomMarket) + + oracle, found := simulation.FindAccount(accs, address) + if !found { + return simulation.NoOpMsg(types.ModuleName), nil, nil + } + + oracleAcc := ak.GetAccount(ctx, oracle.Address) + if oracleAcc == nil { + return simulation.NoOpMsg(types.ModuleName), nil, nil + } + price := pickNewRandomPrice(marketID, int(ctx.BlockHeight())) // get the expiry time based off the current time expiry := getExpiryTime(ctx) // now create the msg to post price - msg := types.NewMsgPostPrice(address, marketID, price, expiry) + msg := types.NewMsgPostPrice(oracle.Address, marketID, price, expiry) - // Perform basic validation of the msg - don't submit errors that fail ValidateBasic, use unit tests for testing ValidateBasic - if err := msg.ValidateBasic(); err != nil { - return noOpMsg, nil, fmt.Errorf("expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + spendable := oracleAcc.SpendableCoins(ctx.BlockTime()) + fees, err := simulation.RandomFees(r, ctx, spendable) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err } - // now we submit the pricefeed update message - if ok := submitMsg(ctx, handler, msg); !ok { - return noOpMsg, nil, fmt.Errorf("could not submit pricefeed msg") + tx := helpers.GenTx( + []sdk.Msg{msg}, + fees, + helpers.DefaultGenTxGas, + chainID, + []uint64{oracleAcc.GetAccountNumber()}, + []uint64{oracleAcc.GetSequence()}, + oracle.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err } - return simulation.NewOperationMsg(msg, true, "pricefeed update submitted"), nil, nil + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } } @@ -130,10 +179,9 @@ func pickNewRandomPrice(marketID string, blockHeight int) (newPrice sdk.Dec) { } // getRandomOracle picks a random oracle from the list of oracles -func getRandomOracle(r *rand.Rand, market pricefeed.Market) sdk.AccAddress { +func getRandomOracle(r *rand.Rand, market types.Market) sdk.AccAddress { randomIndex := simulation.RandIntBetween(r, 0, len(market.Oracles)) - oracle := market.Oracles[randomIndex] - return oracle + return market.Oracles[randomIndex] } // pickRandomAsset picks a random asset out of the assets with equal probability @@ -143,25 +191,11 @@ func pickRandomAsset(ctx sdk.Context, keeper keeper.Keeper, r *rand.Rand) (marke params := keeper.GetParams(ctx) // now pick a random asset randomIndex := simulation.RandIntBetween(r, 0, len(params.Markets)) - market = params.Markets[randomIndex] - return market + return params.Markets[randomIndex] } // getExpiryTime gets a price expiry time by taking the current time and adding a delta to it func getExpiryTime(ctx sdk.Context) (t time.Time) { // need to use the blocktime from the context as the context generates random start time when running simulations - t = ctx.BlockTime().Add(time.Second * 1000000) - return t -} - -// submitMsg submits a message to the current instance of the keeper and returns a boolean whether the operation completed successfully or not -func submitMsg(ctx sdk.Context, handler sdk.Handler, msg sdk.Msg) (ok bool) { - ctx, write := ctx.CacheContext() - got := handler(ctx, msg) - - ok = got.IsOK() - if ok { - write() - } - return ok + return ctx.BlockTime().Add(time.Second * 1000000) } diff --git a/x/pricefeed/simulation/params.go b/x/pricefeed/simulation/params.go index 8c1f7aff..36ff612a 100644 --- a/x/pricefeed/simulation/params.go +++ b/x/pricefeed/simulation/params.go @@ -9,6 +9,5 @@ import ( // 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{} } diff --git a/x/pricefeed/types/errors.go b/x/pricefeed/types/errors.go index 6d545f02..1d14aa38 100644 --- a/x/pricefeed/types/errors.go +++ b/x/pricefeed/types/errors.go @@ -1,48 +1,22 @@ package types import ( - "fmt" - - sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) -const ( - // DefaultCodespace codespace for the module - DefaultCodespace sdk.CodespaceType = ModuleName +// DONTCOVER - // CodeEmptyInput error code for empty input errors - CodeEmptyInput sdk.CodeType = 1 - // CodeExpired error code for expired prices - CodeExpired sdk.CodeType = 2 - // CodeInvalidPrice error code for all input prices expired - CodeInvalidPrice sdk.CodeType = 3 - // CodeInvalidAsset error code for invalid asset - CodeInvalidAsset sdk.CodeType = 4 - // CodeInvalidOracle error code for invalid oracle - CodeInvalidOracle sdk.CodeType = 5 +var ( + // ErrEmptyInput error for empty input + ErrEmptyInput = sdkerrors.Register(ModuleName, 2, "input must not be empty") + // ErrExpired error for posted price messages with expired price + ErrExpired = sdkerrors.Register(ModuleName, 3, "price is expired") + // ErrNoValidPrice error for posted price messages with expired price + ErrNoValidPrice = sdkerrors.Register(ModuleName, 4, "all input prices are expired") + // ErrInvalidMarket error for posted price messages for invalid markets + ErrInvalidMarket = sdkerrors.Register(ModuleName, 5, "market does not exist") + // ErrInvalidOracle error for posted price messages for invalid oracles + ErrInvalidOracle = sdkerrors.Register(ModuleName, 6, "oracle does not exist or not authorized") + // ErrAssetNotFound error for not found asset + ErrAssetNotFound = sdkerrors.Register(ModuleName, 7, "asset not found") ) - -// ErrEmptyInput Error constructor -func ErrEmptyInput(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeEmptyInput, fmt.Sprintf("Input must not be empty.")) -} - -// ErrExpired Error constructor for posted price messages with expired price -func ErrExpired(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeExpired, fmt.Sprintf("Price is expired.")) -} - -// ErrNoValidPrice Error constructor for posted price messages with expired price -func ErrNoValidPrice(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeInvalidPrice, fmt.Sprintf("All input prices are expired.")) -} - -// ErrInvalidMarket Error constructor for posted price messages for invalid markets -func ErrInvalidMarket(codespace sdk.CodespaceType, marketId string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidAsset, fmt.Sprintf("market %s does not exist", marketId)) -} - -// ErrInvalidOracle Error constructor for posted price messages for invalid oracles -func ErrInvalidOracle(codespace sdk.CodespaceType, addr sdk.AccAddress) sdk.Error { - return sdk.NewError(codespace, CodeInvalidOracle, fmt.Sprintf("oracle %s does not exist or not authorized", addr)) -} diff --git a/x/pricefeed/types/msgs.go b/x/pricefeed/types/msgs.go index 059c3866..77476699 100644 --- a/x/pricefeed/types/msgs.go +++ b/x/pricefeed/types/msgs.go @@ -1,9 +1,13 @@ package types import ( + "errors" + "fmt" + "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) const ( @@ -55,16 +59,18 @@ func (msg MsgPostPrice) GetSigners() []sdk.AccAddress { } // ValidateBasic does a simple validation check that doesn't require access to any other information. -func (msg MsgPostPrice) ValidateBasic() sdk.Error { +func (msg MsgPostPrice) ValidateBasic() error { if msg.From.Empty() { - return sdk.ErrInternal("invalid (empty) from address") + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") } - if len(msg.MarketID) == 0 { - return sdk.ErrInternal("invalid (empty) market id") + if strings.TrimSpace(msg.MarketID) == "" { + return errors.New("market id cannot be blank") } - if msg.Price.LT(sdk.ZeroDec()) { - return sdk.ErrInternal("invalid (negative) price") + if msg.Price.IsNegative() { + return fmt.Errorf("price cannot be negative: %s", msg.Price.String()) + } + if msg.Expiry.IsZero() { + return errors.New("must set an expiration time") } - // TODO check coin denoms return nil } diff --git a/x/pricefeed/types/params.go b/x/pricefeed/types/params.go index 6236a8eb..acb748d4 100644 --- a/x/pricefeed/types/params.go +++ b/x/pricefeed/types/params.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/params" ) @@ -39,7 +40,7 @@ func ParamKeyTable() params.KeyTable { // pairs of pricefeed module's parameters. func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ - {Key: KeyMarkets, Value: &p.Markets}, + params.NewParamSetPair(KeyMarkets, &p.Markets, validateMarketParams), } } @@ -54,11 +55,21 @@ func (p Params) String() string { // Validate ensure that params have valid values func (p Params) Validate() error { + return validateMarketParams(p.Markets) +} + +func validateMarketParams(i interface{}) error { + markets, ok := i.(Markets) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + // iterate over assets and verify them - for _, asset := range p.Markets { - if asset.MarketID == "" { - return fmt.Errorf("invalid market: %s. missing market ID", asset.String()) + for _, asset := range markets { + if strings.TrimSpace(asset.MarketID) == "" { + return sdkerrors.Wrapf(ErrInvalidMarket, "market id for asset %s cannot be blank", asset) } } + return nil } diff --git a/x/validator-vesting/client/cli/query.go b/x/validator-vesting/client/cli/query.go index ce00168f..b4d45251 100644 --- a/x/validator-vesting/client/cli/query.go +++ b/x/validator-vesting/client/cli/query.go @@ -3,11 +3,12 @@ package cli import ( "fmt" - "github.com/kava-labs/kava/x/validator-vesting/types" "github.com/spf13/cobra" - "github.com/cosmos/cosmos-sdk/client" + "github.com/kava-labs/kava/x/validator-vesting/types" + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/codec" ) @@ -19,7 +20,7 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { Short: "Querying commands for the validator vesting module", } - queryValidatorVestingCmd.AddCommand(client.GetCommands( + queryValidatorVestingCmd.AddCommand(flags.GetCommands( QueryCirculatingSupplyCmd(queryRoute, cdc), QueryTotalSupplyCmd(queryRoute, cdc), )...) diff --git a/x/validator-vesting/keeper/keeper.go b/x/validator-vesting/keeper/keeper.go index 54662879..f6bdade1 100644 --- a/x/validator-vesting/keeper/keeper.go +++ b/x/validator-vesting/keeper/keeper.go @@ -138,7 +138,7 @@ func (k Keeper) SetVestingProgress(ctx sdk.Context, addr sdk.AccAddress, period // AddDebt adds the input amount to DebtAfterFailedVesting field func (k Keeper) AddDebt(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coins) { vv := k.GetAccountFromAuthKeeper(ctx, addr) - vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(amount) + vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(amount...) k.ak.SetAccount(ctx, vv) } diff --git a/x/validator-vesting/keeper/keeper_test.go b/x/validator-vesting/keeper/keeper_test.go index 51d940ed..c3aaafb8 100644 --- a/x/validator-vesting/keeper/keeper_test.go +++ b/x/validator-vesting/keeper/keeper_test.go @@ -349,7 +349,7 @@ func TestHandleVestingDebtReturn(t *testing.T) { require.Equal(t, sdk.NewCoins(sdk.NewInt64Coin(stakeDenom, 30000000)), vva.DebtAfterFailedVesting) initialBalance := ak.GetAccount(ctx, TestAddrs[2]).GetCoins() - expectedBalance := initialBalance.Add(vva.DebtAfterFailedVesting) + expectedBalance := initialBalance.Add(vva.DebtAfterFailedVesting...) // Context needs the block time because bank keeper calls 'SpendableCoins' by getting the header from the context. ctx = ctx.WithBlockTime(now.Add(12 * time.Hour)) keeper.HandleVestingDebt(ctx, vva.Address, now.Add(12*time.Hour)) diff --git a/x/validator-vesting/keeper/querier.go b/x/validator-vesting/keeper/querier.go index 32c7a11e..4c5727e9 100644 --- a/x/validator-vesting/keeper/querier.go +++ b/x/validator-vesting/keeper/querier.go @@ -2,37 +2,40 @@ package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/kava-labs/kava/x/validator-vesting/types" + abci "github.com/tendermint/tendermint/abci/types" ) // NewQuerier returns a new querier function func NewQuerier(keeper Keeper) sdk.Querier { - return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) { switch path[0] { case types.QueryCirculatingSupply: return queryGetCirculatingSupply(ctx, req, keeper) case types.QueryTotalSupply: return queryGetTotalSupply(ctx, req, keeper) default: - return nil, sdk.ErrUnknownRequest("unknown cdp query endpoint") + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint: %s", types.ModuleName, path[0]) } } } -func queryGetTotalSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetTotalSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { totalSupply := keeper.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf("ukava") supplyInt := sdk.NewDecFromInt(totalSupply).Mul(sdk.MustNewDecFromStr("0.000001")).TruncateInt64() - bz, err := keeper.cdc.MarshalJSON(supplyInt) + bz, err := types.ModuleCdc.MarshalJSON(supplyInt) if err != nil { - return nil, sdk.ErrInternal(err.Error()) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } return bz, nil } -func queryGetCirculatingSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { +func queryGetCirculatingSupply(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) { circulatingSupply := keeper.supplyKeeper.GetSupply(ctx).GetTotal().AmountOf("ukava") keeper.ak.IterateAccounts(ctx, func(acc authexported.Account) (stop bool) { @@ -54,9 +57,9 @@ func queryGetCirculatingSupply(ctx sdk.Context, req abci.RequestQuery, keeper Ke return false }) supplyInt := sdk.NewDecFromInt(circulatingSupply).Mul(sdk.MustNewDecFromStr("0.000001")).TruncateInt64() - bz, err := keeper.cdc.MarshalJSON(supplyInt) + bz, err := types.ModuleCdc.MarshalJSON(supplyInt) if err != nil { - return nil, sdk.ErrInternal(err.Error()) + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } return bz, nil } diff --git a/x/validator-vesting/keeper/test_common.go b/x/validator-vesting/keeper/test_common.go index 36ba430e..fbb817ca 100644 --- a/x/validator-vesting/keeper/test_common.go +++ b/x/validator-vesting/keeper/test_common.go @@ -120,12 +120,12 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context cdc := MakeTestCodec() - pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace) + pk := params.NewKeeper(cdc, keyParams, tkeyParams) - stakingParams := staking.NewParams(time.Hour, 100, uint16(7), sdk.DefaultBondDenom) + stakingParams := staking.NewParams(time.Hour, 100, uint16(7), 0, sdk.DefaultBondDenom) accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) - bankKeeper := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) + bankKeeper := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), blacklistedAddrs) maccPerms := map[string][]string{ auth.FeeCollectorName: nil, staking.NotBondedPoolName: {supply.Burner, supply.Staking}, @@ -134,7 +134,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context } supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bankKeeper, maccPerms) - stakingKeeper := staking.NewKeeper(cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) + stakingKeeper := staking.NewKeeper(cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace)) stakingKeeper.SetParams(ctx, stakingParams) keeper := NewKeeper(cdc, keyValidatorVesting, accountKeeper, bankKeeper, supplyKeeper, stakingKeeper) diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go index 903e8b8a..f918d028 100644 --- a/x/validator-vesting/module.go +++ b/x/validator-vesting/module.go @@ -22,7 +22,7 @@ import ( var ( _ module.AppModule = AppModule{} _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} + _ module.AppModuleSimulation = AppModule{} ) // AppModuleBasic defines the basic application module used by the auth module. @@ -66,28 +66,10 @@ func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { return cli.GetQueryCmd(StoreKey, cdc) } -// AppModuleSimulation defines the module simulation functions used by the auth module. -type AppModuleSimulation struct{} - -// RegisterStoreDecoder registers a decoder for auth module's types -func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { - sdr[StoreKey] = simulation.DecodeStore -} - -// GenerateGenesisState creates a randomized GenState of the auth module -func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RandomizedParams returns nil because validatorvesting has no params. -func (AppModuleSimulation) RandomizedParams(_ *rand.Rand) []sim.ParamChange { - return []sim.ParamChange{} -} - // AppModule implements an application module for the validator-vesting module. type AppModule struct { AppModuleBasic - AppModuleSimulation + keeper Keeper accountKeeper types.AccountKeeper } @@ -95,10 +77,9 @@ type AppModule struct { // NewAppModule creates a new AppModule object func NewAppModule(keeper Keeper, ak types.AccountKeeper) AppModule { return AppModule{ - AppModuleBasic: AppModuleBasic{}, - AppModuleSimulation: AppModuleSimulation{}, - keeper: keeper, - accountKeeper: ak, + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + accountKeeper: ak, } } @@ -152,3 +133,32 @@ func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { return []abci.ValidatorUpdate{} } + +//____________________________________________________________________________ + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the auth module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// RandomizedParams returns nil because validatorvesting has no params. +func (AppModuleBasic) RandomizedParams(_ *rand.Rand) []sim.ParamChange { + return []sim.ParamChange{} +} + +// RegisterStoreDecoder registers a decoder for auth module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// WeightedOperations returns the all the validator vesting module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return nil +} diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go index 445828a6..6e3207ab 100644 --- a/x/validator-vesting/simulation/decoder.go +++ b/x/validator-vesting/simulation/decoder.go @@ -5,15 +5,14 @@ import ( "fmt" "time" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/kava-labs/kava/x/validator-vesting/types" + "github.com/tendermint/tendermint/libs/kv" ) // DecodeStore unmarshals the KVPair's Value to the corresponding auth type -func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { switch { case bytes.Equal(kvA.Key[:1], types.ValidatorVestingAccountPrefix): var accA, accB exported.Account diff --git a/x/validator-vesting/simulation/decoder_test.go b/x/validator-vesting/simulation/decoder_test.go index 7cb532ca..adc0cb24 100644 --- a/x/validator-vesting/simulation/decoder_test.go +++ b/x/validator-vesting/simulation/decoder_test.go @@ -6,8 +6,7 @@ import ( "time" "github.com/stretchr/testify/require" - - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/kv" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -32,10 +31,10 @@ func TestDecodeDistributionStore(t *testing.T) { acc := types.ValidatorVestingAccount{SigningThreshold: 1} now := time.Now().UTC() - kvPairs := cmn.KVPairs{ - cmn.KVPair{Key: types.ValidatorVestingAccountPrefix, Value: cdc.MustMarshalBinaryBare(acc)}, - cmn.KVPair{Key: types.BlocktimeKey, Value: cdc.MustMarshalBinaryLengthPrefixed(now)}, - cmn.KVPair{Key: []byte{0x99}, Value: []byte{0x99}}, + kvPairs := kv.Pairs{ + kv.Pair{Key: types.ValidatorVestingAccountPrefix, Value: cdc.MustMarshalBinaryBare(acc)}, + kv.Pair{Key: types.BlocktimeKey, Value: cdc.MustMarshalBinaryLengthPrefixed(now)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } tests := []struct { diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index c77bb9a3..7b133dff 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -101,12 +101,12 @@ func getRandomVestingPeriods(duration int64, r *rand.Rand, origCoins sdk.Coins) vestingCoins := sdk.NewCoins() for _, ic := range origCoins { amountVesting := ic.Amount.Int64() / int64(coinFraction) - vestingCoins = vestingCoins.Add(sdk.NewCoins(sdk.NewInt64Coin(ic.Denom, amountVesting))) + vestingCoins = vestingCoins.Add(sdk.NewInt64Coin(ic.Denom, amountVesting)) } periodCoins := sdk.NewCoins() for _, c := range vestingCoins { amountPeriod := c.Amount.Int64() / int64(numPeriods) - periodCoins = periodCoins.Add(sdk.NewCoins(sdk.NewInt64Coin(c.Denom, amountPeriod))) + periodCoins = periodCoins.Add(sdk.NewInt64Coin(c.Denom, amountPeriod)) } vestingPeriods := make([]vestingtypes.Period, numPeriods) @@ -121,7 +121,7 @@ func getRandomVestingPeriods(duration int64, r *rand.Rand, origCoins sdk.Coins) func getVestingCoins(periods vestingtypes.Periods) sdk.Coins { vestingCoins := sdk.NewCoins() for _, p := range periods { - vestingCoins = vestingCoins.Add(p.Amount) + vestingCoins = vestingCoins.Add(p.Amount...) } return vestingCoins } diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go index 0bc8c141..fdb4f83f 100644 --- a/x/validator-vesting/test_common.go +++ b/x/validator-vesting/test_common.go @@ -60,7 +60,7 @@ func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAc pk := mApp.ParamsKeeper - bk := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs) + bk := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), blacklistedAddrs) maccPerms := map[string][]string{ types.ModuleName: {supply.Burner}, @@ -69,7 +69,7 @@ func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAc } supplyKeeper := supply.NewKeeper(mApp.Cdc, keySupply, mApp.AccountKeeper, bk, maccPerms) sk := staking.NewKeeper( - mApp.Cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace, + mApp.Cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), ) keeper := keeper.NewKeeper( diff --git a/x/validator-vesting/types/expected_keepers.go b/x/validator-vesting/types/expected_keepers.go index 74969143..afdf6c16 100644 --- a/x/validator-vesting/types/expected_keepers.go +++ b/x/validator-vesting/types/expected_keepers.go @@ -19,7 +19,7 @@ type AccountKeeper interface { // BankKeeper defines the expected bank keeper (noalias) type BankKeeper interface { - SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) sdk.Error + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error } // StakingKeeper defines the expected staking keeper (noalias) @@ -28,13 +28,13 @@ type StakingKeeper interface { fn func(index int64, delegation stakingexported.DelegationI) (stop bool)) Undelegate( ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec, - ) (time.Time, sdk.Error) + ) (time.Time, error) } // SupplyKeeper defines the expected supply keeper for module accounts (noalias) type SupplyKeeper interface { - 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 + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) error SetModuleAccount(sdk.Context, supplyexported.ModuleAccountI) GetSupply(ctx sdk.Context) (supply supplyexported.SupplyI) } diff --git a/x/validator-vesting/types/validator_vesting_account.go b/x/validator-vesting/types/validator_vesting_account.go index b09521a4..27eb452e 100644 --- a/x/validator-vesting/types/validator_vesting_account.go +++ b/x/validator-vesting/types/validator_vesting_account.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "errors" "time" @@ -11,6 +12,7 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/tendermint/tendermint/crypto" ) // Assert ValidatorVestingAccount implements the vestexported.VestingAccount interface @@ -143,7 +145,7 @@ func (vva ValidatorVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins x := blockTime.Unix() - currentPeriodStartTime if x >= vva.VestingPeriods[i].Length { if vva.VestingPeriodProgress[i].PeriodComplete { - vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].Amount) + vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].Amount...) } currentPeriodStartTime += vva.VestingPeriods[i].Length } else { @@ -161,7 +163,7 @@ func (vva ValidatorVestingAccount) GetFailedVestedCoins() sdk.Coins { for i := 0; i < numberPeriods; i++ { if vva.VestingPeriodProgress[i].PeriodComplete { if !vva.VestingPeriodProgress[i].VestingSuccessful { - failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].Amount) + failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].Amount...) } } else { break @@ -199,6 +201,97 @@ func (vva ValidatorVestingAccount) Validate() error { return vva.PeriodicVestingAccount.Validate() } +type validatorVestingAccountPretty struct { + Address sdk.AccAddress `json:"address" yaml:"address"` + Coins sdk.Coins `json:"coins" yaml:"coins"` + PubKey string `json:"public_key" yaml:"public_key"` + AccountNumber uint64 `json:"account_number" yaml:"account_number"` + Sequence uint64 `json:"sequence" yaml:"sequence"` + OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` + DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` + DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` + EndTime int64 `json:"end_time" yaml:"end_time"` + StartTime int64 `json:"start_time" yaml:"start_time"` + VestingPeriods vestingtypes.Periods `json:"vesting_periods" yaml:"vesting_periods"` + ValidatorAddress sdk.ConsAddress `json:"validator_address" yaml:"validator_address"` + ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"` + SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"` + CurrentPeriodProgress CurrentPeriodProgress `json:"current_period_progress" yaml:"current_period_progress"` + VestingPeriodProgress []VestingProgress `json:"vesting_period_progress" yaml:"vesting_period_progress"` + DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` +} + +// MarshalJSON returns the JSON representation of a PeriodicVestingAccount. +func (vva ValidatorVestingAccount) MarshalJSON() ([]byte, error) { + alias := validatorVestingAccountPretty{ + Address: vva.Address, + Coins: vva.Coins, + AccountNumber: vva.AccountNumber, + Sequence: vva.Sequence, + OriginalVesting: vva.OriginalVesting, + DelegatedFree: vva.DelegatedFree, + DelegatedVesting: vva.DelegatedVesting, + EndTime: vva.EndTime, + StartTime: vva.StartTime, + VestingPeriods: vva.VestingPeriods, + ValidatorAddress: vva.ValidatorAddress, + ReturnAddress: vva.ReturnAddress, + SigningThreshold: vva.SigningThreshold, + CurrentPeriodProgress: vva.CurrentPeriodProgress, + VestingPeriodProgress: vva.VestingPeriodProgress, + DebtAfterFailedVesting: vva.DebtAfterFailedVesting, + } + + if vva.PubKey != nil { + pks, err := sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, vva.PubKey) + if err != nil { + return nil, err + } + + alias.PubKey = pks + } + + return json.Marshal(alias) +} + +// UnmarshalJSON unmarshals raw JSON bytes into a PeriodicVestingAccount. +func (vva *ValidatorVestingAccount) UnmarshalJSON(bz []byte) error { + var alias validatorVestingAccountPretty + if err := json.Unmarshal(bz, &alias); err != nil { + return err + } + + var ( + pk crypto.PubKey + err error + ) + + if alias.PubKey != "" { + pk, err = sdk.GetPubKeyFromBech32(sdk.Bech32PubKeyTypeAccPub, alias.PubKey) + if err != nil { + return err + } + } + + ba := authtypes.NewBaseAccount(alias.Address, alias.Coins, pk, alias.AccountNumber, alias.Sequence) + bva := &vestingtypes.BaseVestingAccount{ + BaseAccount: ba, + OriginalVesting: alias.OriginalVesting, + DelegatedFree: alias.DelegatedFree, + DelegatedVesting: alias.DelegatedVesting, + EndTime: alias.EndTime, + } + pva := vestingtypes.NewPeriodicVestingAccountRaw(bva, alias.StartTime, alias.VestingPeriods) + vva.PeriodicVestingAccount = pva + vva.ValidatorAddress = alias.ValidatorAddress + vva.ReturnAddress = alias.ReturnAddress + vva.SigningThreshold = alias.SigningThreshold + vva.CurrentPeriodProgress = alias.CurrentPeriodProgress + vva.VestingPeriodProgress = alias.VestingPeriodProgress + vva.DebtAfterFailedVesting = alias.DebtAfterFailedVesting + return nil +} + // MarshalYAML returns the YAML representation of an account. func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) { var bs []byte @@ -206,7 +299,7 @@ func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) { var pubkey string if vva.PubKey != nil { - pubkey, err = sdk.Bech32ifyAccPub(vva.PubKey) + pubkey, err = sdk.Bech32ifyPubKey(sdk.Bech32PubKeyTypeAccPub, vva.PubKey) if err != nil { return nil, err } diff --git a/x/validator-vesting/types/validator_vesting_account_test.go b/x/validator-vesting/types/validator_vesting_account_test.go index 4c19c68e..5bb91d76 100644 --- a/x/validator-vesting/types/validator_vesting_account_test.go +++ b/x/validator-vesting/types/validator_vesting_account_test.go @@ -222,7 +222,7 @@ func TestSpendableCoinsValidatorVestingAccount(t *testing.T) { // receive some coins recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)} - vva.SetCoins(vva.GetCoins().Add(recvAmt)) + vva.SetCoins(vva.GetCoins().Add(recvAmt...)) // require that all vested coins (50%) are spendable plus any received after period 1 completes successfully vva.VestingPeriodProgress[0] = VestingProgress{true, true} From cd6cb852ad0516f8178da0b8ecb94ac109274bcd Mon Sep 17 00:00:00 2001 From: Ruaridh Date: Thu, 23 Apr 2020 18:59:57 +0100 Subject: [PATCH 39/45] Add genesis example (#462) * remove duplicate genesis * add testnet-5k genesis copy * update for v0.38 * update formatting --- .../genesis_examples/wip_genesis.json} | 251 ++++++++++-------- 1 file changed, 133 insertions(+), 118 deletions(-) rename contrib/{testnet-5000/genesis_examples/bep3_genesis.json => testnet-6000/genesis_examples/wip_genesis.json} (79%) diff --git a/contrib/testnet-5000/genesis_examples/bep3_genesis.json b/contrib/testnet-6000/genesis_examples/wip_genesis.json similarity index 79% rename from contrib/testnet-5000/genesis_examples/bep3_genesis.json rename to contrib/testnet-6000/genesis_examples/wip_genesis.json index 4853c045..c921ef73 100644 --- a/contrib/testnet-5000/genesis_examples/bep3_genesis.json +++ b/contrib/testnet-6000/genesis_examples/wip_genesis.json @@ -1,21 +1,35 @@ { - "genesis_time": "2020-03-27T21:00:00Z", + "genesis_time": "2020-04-23T16:32:31.393515Z", "chain_id": "testing", "consensus_params": { "block": { - "max_bytes": "200000", - "max_gas": "2000000", + "max_bytes": "22020096", + "max_gas": "-1", "time_iota_ms": "1000" }, "evidence": { - "max_age": "1000000" + "max_age_num_blocks": "100000", + "max_age_duration": "172800000000000" }, "validator": { - "pub_key_types": ["ed25519"] + "pub_key_types": [ + "ed25519" + ] } }, "app_hash": "", "app_state": { + "auction": { + "auctions": [], + "next_auction_id": "1", + "params": { + "bid_duration": "600000000000", + "increment_collateral": "0.010000000000000000", + "increment_debt": "0.010000000000000000", + "increment_surplus": "0.010000000000000000", + "max_auction_duration": "172800000000000" + } + }, "auth": { "accounts": [], "params": { @@ -26,63 +40,24 @@ "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" - }, + "bep3": { + "assets_supplies": [], + "atomic_swaps": [], "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" + "bnb_deputy_address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj", + "max_block_lock": "600", + "min_block_lock": "80", + "supported_assets": [ + { + "active": true, + "coin_id": "714", + "denom": "bnb", + "limit": "100000000000" + } + ] } }, "cdp": { @@ -139,6 +114,40 @@ "previous_distribution_time": "1970-01-01T00:00:00Z", "starting_cdp_id": "1" }, + "crisis": { + "constant_fee": { + "amount": "1333000000", + "denom": "ukava" + } + }, + "distribution": { + "delegator_starting_infos": [], + "delegator_withdraw_infos": [], + "fee_pool": { + "community_pool": [] + }, + "outstanding_rewards": [], + "params": { + "base_proposer_reward": "0.010000000000000000", + "bonus_proposer_reward": "0.040000000000000000", + "community_tax": "0.000000000000000000", + "withdraw_addr_enabled": true + }, + "previous_proposer": "", + "validator_accumulated_commissions": [], + "validator_current_rewards": [], + "validator_historical_rewards": [], + "validator_slash_events": [] + }, + "evidence": { + "evidence": [], + "params": { + "max_evidence_age": "120000000000" + } + }, + "genutil": { + "gentxs": [] + }, "gov": { "deposit_params": { "max_deposit_period": "600000000000", @@ -159,9 +168,71 @@ }, "votes": null, "voting_params": { - "voting_period": "3600000000000" + "voting_period": "600000000000" } }, + "kavadist": { + "params": { + "active": true, + "periods": [ + { + "end": "2021-03-28T15:20:00Z", + "inflation": "1.000000003022265980", + "start": "2020-03-28T15:20:00Z" + } + ] + }, + "previous_block_time": "1970-01-01T00:00:00Z" + }, + "mint": { + "minter": { + "annual_provisions": "0.000000000000000000", + "inflation": "0.020000000000000000" + }, + "params": { + "blocks_per_year": "6311520", + "goal_bonded": "0.670000000000000000", + "inflation_max": "0.130000000000000000", + "inflation_min": "0.010000000000000000", + "inflation_rate_change": "0.130000000000000000", + "mint_denom": "ukava" + } + }, + "params": null, + "pricefeed": { + "params": { + "markets": [ + { + "active": true, + "base_asset": "bnb", + "market_id": "bnb:usd", + "oracles": [ + "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw" + ], + "quote_asset": "usd" + } + ] + }, + "posted_prices": [ + { + "expiry": "2021-04-20T00:00:00Z", + "market_id": "bnb:usd", + "oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw", + "price": "12.2639061184" + } + ] + }, + "slashing": { + "missed_blocks": {}, + "params": { + "downtime_jail_duration": "600000000000", + "min_signed_per_window": "0.010000000000000000", + "signed_blocks_window": "1000", + "slash_fraction_double_sign": "0.050000000000000000", + "slash_fraction_downtime": "0.000100000000000000" + }, + "signing_infos": {} + }, "staking": { "delegations": null, "exported": false, @@ -169,6 +240,7 @@ "last_validator_powers": null, "params": { "bond_denom": "ukava", + "historical_entries": 0, "max_entries": 7, "max_validators": 100, "unbonding_time": "3600000000000" @@ -180,65 +252,8 @@ "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 + } } -} +} \ No newline at end of file From a4c5a1382274706430fb445856d67f30c87cd865 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Thu, 23 Apr 2020 13:57:25 -0700 Subject: [PATCH 40/45] [R4R] BEP3 module spec and clean up (#450) * bump SDK version to v0.38.2 * fix module.go and remove codespaces * fix coins Add() * fixes to handlers * migrate errors * more fixes * fixes fixes fixes * build * check for paramstore keytable * empty param validation function (TODO) * param validations * fix some tests * fix all tests * simulation fixes (WIP) * auction and bep3 sim refactor * fixes * bep3 sims fixes * auction and pricefeed fix * cdp sims fixes * fix tests * Update x/auction/keeper/auctions.go Co-Authored-By: Denali Marsh * Update x/bep3/types/params.go Co-Authored-By: Denali Marsh * Apply suggestions from code review Co-Authored-By: Denali Marsh * Update x/bep3/keeper/swap.go Co-Authored-By: Denali Marsh * address comments from review * address comments from review * fix: run sims * fix: implement marshal/unmarshal JSON for validator vesting account * fix: don't call set on sealed config * remove swap interface * add concepts spec * add state spec * add messages spec * update event names * implement swap expired event * add events spec * add params spec * add begin block spec * add module readme * update alias * revisions * aggregate expired swap ids for event emisison * markdown-link-check-disable for circleci * exclude api-endpoint links in Makefile Co-authored-by: Federico Kunze Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Kevin Davis --- Makefile | 2 +- x/auction/simulation/operations.go | 112 ++++++------------ x/bep3/alias.go | 4 +- x/bep3/keeper/swap.go | 59 +++++---- x/bep3/keeper/swap_test.go | 4 +- x/bep3/spec/01_concepts.md | 36 ++++++ x/bep3/spec/02_state.md | 82 +++++++++++++ x/bep3/spec/03_messages.md | 48 ++++++++ x/bep3/spec/04_events.md | 52 ++++++++ x/bep3/spec/05_params.md | 16 +++ x/bep3/spec/06_begin_block.md | 19 +++ x/bep3/spec/README.md | 24 ++-- .../diagrams/BEP3_binance_chain_to_kava.jpg | Bin 0 -> 30574 bytes .../diagrams/BEP3_kava_to_binance_chain.jpg | Bin 0 -> 31192 bytes x/bep3/types/codec.go | 2 - x/bep3/types/common_test.go | 2 +- x/bep3/types/events.go | 26 ++-- x/bep3/types/swap.go | 12 +- x/bep3/types/swap_test.go | 2 +- 19 files changed, 357 insertions(+), 145 deletions(-) create mode 100644 x/bep3/spec/01_concepts.md create mode 100644 x/bep3/spec/02_state.md create mode 100644 x/bep3/spec/03_messages.md create mode 100644 x/bep3/spec/04_events.md create mode 100644 x/bep3/spec/05_params.md create mode 100644 x/bep3/spec/06_begin_block.md create mode 100644 x/bep3/spec/diagrams/BEP3_binance_chain_to_kava.jpg create mode 100644 x/bep3/spec/diagrams/BEP3_kava_to_binance_chain.jpg diff --git a/Makefile b/Makefile index ec0884fa..7becb364 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,7 @@ clean: # This tool checks local markdown links as well. # Set to exclude riot links as they trigger false positives link-check: - @go run github.com/raviqqe/liche -r . --exclude "^http://127.*|^https://riot.im/app*" + @go run github.com/raviqqe/liche -r . --exclude "^http://127.*|^https://riot.im/app*|^http://kava-testnet*|^https://testnet-dex*" ######################################## ### Testing diff --git a/x/auction/simulation/operations.go b/x/auction/simulation/operations.go index 84f2297c..503c78b9 100644 --- a/x/auction/simulation/operations.go +++ b/x/auction/simulation/operations.go @@ -21,8 +21,8 @@ import ( ) var ( - errorNotEnoughCoins = errors.New("account doesn't have enough coins") - errorCantReceiveBids = errors.New("auction can't receive bids (lot = 0 in reverse auction)") + noOpMsg = simulation.NoOpMsg(types.ModuleName) + ErrorNotEnoughCoins = errors.New("account doesn't have enough coins") ) // Simulation operation weights constants @@ -72,11 +72,10 @@ func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation // search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction) blockTime := ctx.BlockHeader().Time - params := keeper.GetParams(ctx) bidder, openAuction, found := findValidAccountAuctionPair(accs, openAuctions, func(acc simulation.Account, auc types.Auction) bool { account := ak.GetAccount(ctx, acc.Address) - _, err := generateBidAmount(r, params, auc, account, blockTime) - if err == errorNotEnoughCoins || err == errorCantReceiveBids { + _, err := generateBidAmount(r, auc, account, blockTime) + if err == ErrorNotEnoughCoins { return false // keep searching } else if err != nil { panic(err) // raise errors @@ -89,21 +88,27 @@ func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation bidderAcc := ak.GetAccount(ctx, bidder.Address) if bidderAcc == nil { - return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", bidder.Address) + return simulation.NoOpMsg(types.ModuleName), nil, nil } // pick a bid amount for the chosen auction and bidder - amount, err := generateBidAmount(r, params, openAuction, bidderAcc, blockTime) - if err != nil { // shouldn't happen given the checks above + amount, err := generateBidAmount(r, openAuction, bidderAcc, blockTime) + if err != nil { return simulation.NoOpMsg(types.ModuleName), nil, err } - // create and deliver a tx + // create a msg msg := types.NewMsgPlaceBid(openAuction.GetID(), bidder.Address, amount) + spendable := bidderAcc.SpendableCoins(ctx.BlockTime()) + fees, err := simulation.RandomFees(r, ctx, spendable) + if err != nil { + return simulation.NoOpMsg(types.ModuleName), nil, err + } + tx := helpers.GenTx( []sdk.Msg{msg}, - sdk.NewCoins(), // TODO pick a random amount fees + fees, helpers.DefaultGenTxGas, chainID, []uint64{bidderAcc.GetAccountNumber()}, @@ -113,103 +118,60 @@ func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation _, result, err := app.Deliver(tx) if err != nil { - // to aid debugging, add the stack trace to the comment field of the returned opMsg - return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err + return simulation.NoOpMsg(types.ModuleName), nil, err } - // to aid debugging, add the result log to the comment field + + // Return an operationMsg indicating whether the msg was submitted successfully + // Using result.Log as the comment field as it contains any error message emitted by the keeper return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } } -func generateBidAmount( - r *rand.Rand, params types.Params, auc types.Auction, - bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { +func generateBidAmount(r *rand.Rand, auc types.Auction, bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { bidderBalance := bidder.SpendableCoins(blockTime) switch a := auc.(type) { case types.DebtAuction: - // Check bidder has enough (stable coin) to pay in if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin - return sdk.Coin{}, errorNotEnoughCoins + return sdk.Coin{}, ErrorNotEnoughCoins } - // Check auction can still receive new bids - if a.Lot.Amount.Equal(sdk.ZeroInt()) { - return sdk.Coin{}, errorCantReceiveBids - } - // Generate a new lot amount (gov coin) - maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost - sdk.MaxInt( - sdk.NewInt(1), - sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementDebt).RoundInt(), - ), - ) - amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above + amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount // TODO min bid increments if err != nil { panic(err) } return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin case types.SurplusAuction: - // Check the bidder has enough (gov coin) to pay in - minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost - sdk.MaxInt( - sdk.NewInt(1), - sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementSurplus).RoundInt(), - ), - ) - if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { // gov coin - return sdk.Coin{}, errorNotEnoughCoins + if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // gov coin // TODO account for bid increments + return sdk.Coin{}, ErrorNotEnoughCoins } - // Generate a new bid amount (gov coin) - amt, err := RandIntInclusive(r, minNewBidAmt, bidderBalance.AmountOf(a.Bid.Denom)) + amt, err := RandIntInclusive(r, a.Bid.Amount, bidderBalance.AmountOf(a.Bid.Denom)) if err != nil { panic(err) } return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin case types.CollateralAuction: - // Check the bidder has enough (stable coin) to pay in - minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost - sdk.MaxInt( - sdk.NewInt(1), - sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementCollateral).RoundInt(), - ), - ) - minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment % - if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { - return sdk.Coin{}, errorNotEnoughCoins + if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin // TODO account for bid increments (in forward phase) + return sdk.Coin{}, ErrorNotEnoughCoins } - // Check auction can still receive new bids - if a.IsReversePhase() && a.Lot.Amount.Equal(sdk.ZeroInt()) { - return sdk.Coin{}, errorCantReceiveBids - } - // Generate a new bid amount (collateral coin in reverse phase) if a.IsReversePhase() { - maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost - sdk.MaxInt( - sdk.NewInt(1), - sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementCollateral).RoundInt(), - ), - ) - amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above + amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount if err != nil { panic(err) } return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin - - // Generate a new bid amount (stable coin in forward phase) - } else { - amt, err := RandIntInclusive(r, minNewBidAmt, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) - if err != nil { - panic(err) - } - // when the bidder has enough coins, pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase - if r.Intn(2) == 0 && bidderBalance.AmountOf(a.Bid.Denom).GTE(a.MaxBid.Amount) { // 50% - amt = a.MaxBid.Amount - } - return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin } + amt, err := RandIntInclusive(r, a.Bid.Amount, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) + if err != nil { + panic(err) + } + // pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase + if r.Intn(10) == 0 { // 10% + amt = a.MaxBid.Amount + } + return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin default: return sdk.Coin{}, fmt.Errorf("unknown auction type") diff --git a/x/bep3/alias.go b/x/bep3/alias.go index 81097b2c..1a315ad9 100644 --- a/x/bep3/alias.go +++ b/x/bep3/alias.go @@ -10,6 +10,7 @@ const ( AddrByteCount = types.AddrByteCount AttributeKeyAmount = types.AttributeKeyAmount AttributeKeyAtomicSwapID = types.AttributeKeyAtomicSwapID + AttributeKeyAtomicSwapIDs = types.AttributeKeyAtomicSwapIDs AttributeKeyClaimSender = types.AttributeKeyClaimSender AttributeKeyDirection = types.AttributeKeyDirection AttributeKeyExpectedIncome = types.AttributeKeyExpectedIncome @@ -31,8 +32,8 @@ const ( DepositAtomicSwap = types.DepositAtomicSwap EventTypeClaimAtomicSwap = types.EventTypeClaimAtomicSwap EventTypeCreateAtomicSwap = types.EventTypeCreateAtomicSwap - EventTypeDepositAtomicSwap = types.EventTypeDepositAtomicSwap EventTypeRefundAtomicSwap = types.EventTypeRefundAtomicSwap + EventTypeSwapsExpired = types.EventTypeSwapsExpired Expired = types.Expired INVALID = types.INVALID Incoming = types.Incoming @@ -135,7 +136,6 @@ type ( QueryAtomicSwapByID = types.QueryAtomicSwapByID QueryAtomicSwaps = types.QueryAtomicSwaps SupplyKeeper = types.SupplyKeeper - Swap = types.Swap SwapDirection = types.SwapDirection SwapStatus = types.SwapStatus ) diff --git a/x/bep3/keeper/swap.go b/x/bep3/keeper/swap.go index 21fd2e08..58d53121 100644 --- a/x/bep3/keeper/swap.go +++ b/x/bep3/keeper/swap.go @@ -77,6 +77,9 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times timestamp, sender, recipient, senderOtherChain, recipientOtherChain, 0, types.Open, crossChain, direction) + k.SetAtomicSwap(ctx, atomicSwap) + k.InsertIntoByBlockIndex(ctx, atomicSwap) + // Emit 'create_atomic_swap' event ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -94,8 +97,6 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times ), ) - k.SetAtomicSwap(ctx, atomicSwap) - k.InsertIntoByBlockIndex(ctx, atomicSwap) return nil } @@ -103,7 +104,7 @@ func (k Keeper) CreateAtomicSwap(ctx sdk.Context, randomNumberHash []byte, times func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []byte, randomNumber []byte) error { atomicSwap, found := k.GetAtomicSwap(ctx, swapID) if !found { - return sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%d", swapID) + return sdkerrors.Wrapf(types.ErrAtomicSwapNotFound, "%s", swapID) } // Only open atomic swaps can be claimed @@ -148,6 +149,15 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b return err } + // Complete swap + atomicSwap.Status = types.Completed + atomicSwap.ClosedBlock = ctx.BlockHeight() + k.SetAtomicSwap(ctx, atomicSwap) + + // Remove from byBlock index and transition to longterm storage + k.RemoveFromByBlockIndex(ctx, atomicSwap) + k.InsertIntoLongtermStorage(ctx, atomicSwap) + // Emit 'claim_atomic_swap' event ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -160,14 +170,6 @@ func (k Keeper) ClaimAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID []b ), ) - // 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 } @@ -202,6 +204,14 @@ func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID [] return err } + // Complete swap + atomicSwap.Status = types.Completed + atomicSwap.ClosedBlock = ctx.BlockHeight() + k.SetAtomicSwap(ctx, atomicSwap) + + // Transition to longterm storage + k.InsertIntoLongtermStorage(ctx, atomicSwap) + // Emit 'refund_atomic_swap' event ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -213,13 +223,6 @@ func (k Keeper) RefundAtomicSwap(ctx sdk.Context, from sdk.AccAddress, swapID [] ), ) - // Complete swap - atomicSwap.Status = types.Completed - atomicSwap.ClosedBlock = ctx.BlockHeight() - k.SetAtomicSwap(ctx, atomicSwap) - - // Transition to longterm storage - k.InsertIntoLongtermStorage(ctx, atomicSwap) return nil } @@ -232,12 +235,24 @@ func (k Keeper) UpdateExpiredAtomicSwaps(ctx sdk.Context) error { }) // Expire incomplete swaps (claimed swaps have already been removed from byBlock index) + var expiredSwapIDs []string for _, id := range expiredSwaps { - swap, _ := k.GetAtomicSwap(ctx, id) - swap.Status = types.Expired - k.SetAtomicSwap(ctx, swap) - k.RemoveFromByBlockIndex(ctx, swap) + atomicSwap, _ := k.GetAtomicSwap(ctx, id) + atomicSwap.Status = types.Expired + k.SetAtomicSwap(ctx, atomicSwap) + k.RemoveFromByBlockIndex(ctx, atomicSwap) + expiredSwapIDs = append(expiredSwapIDs, hex.EncodeToString(atomicSwap.GetSwapID())) } + + // Emit 'swaps_expired' event + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSwapsExpired, + sdk.NewAttribute(types.AttributeKeyAtomicSwapIDs, fmt.Sprintf("%s", expiredSwapIDs)), + sdk.NewAttribute(types.AttributeExpirationBlock, fmt.Sprintf("%d", ctx.BlockHeight())), + ), + ) + return nil } diff --git a/x/bep3/keeper/swap_test.go b/x/bep3/keeper/swap_test.go index 1440e3e2..3389cf58 100644 --- a/x/bep3/keeper/swap_test.go +++ b/x/bep3/keeper/swap_test.go @@ -337,7 +337,7 @@ func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() { suite.NotNil(actualSwap) // Confirm swap contents - expectedSwap := types.Swap( + expectedSwap := types.AtomicSwap{ Amount: tc.args.coins, RandomNumberHash: tc.args.randomNumberHash, @@ -351,7 +351,7 @@ func (suite *AtomicSwapTestSuite) TestCreateAtomicSwap() { Status: types.Open, CrossChain: tc.args.crossChain, Direction: tc.args.direction, - }) + } suite.Equal(expectedSwap, actualSwap) } else { suite.Error(err) diff --git a/x/bep3/spec/01_concepts.md b/x/bep3/spec/01_concepts.md new file mode 100644 index 00000000..f897237c --- /dev/null +++ b/x/bep3/spec/01_concepts.md @@ -0,0 +1,36 @@ +# Concepts + + The BEP3 module implements the [BEP3 protocol](https://github.com/binance-chain/BEPs/blob/master/BEP3.md) for secure cross-chain asset transfers between Kava and other BEP3 compatible chains, such as Binance Chain. Tranactions are witnessed and relayed between the two blockchains by Binance's BEP3 deputy process. The deputy maintains an address on both chains and is responsible for delivering tokens upon the successful completion of an Atomic Swap. Learn more about the BEP3 deputy process [here](https://github.com/binance-chain/bep3-deputy). + +## Requirements +Kava +- The deputy’s Kava testnet-5000 address is **kava1aphsdnz5hu2t5ty2au6znprug5kx3zpy6zwq29**. +- We recommend using http://kava-testnet-5000.kava.io:1317 as Kava’s API endpoint. + +Binance Chain +- The deputy’s Binance Chain testnet address is **tbnb1et8vmd0dgvswjnyaf73ez8ye0jehc8a7t7fljv**. +- We recommend using https://testnet-dex.binance.org/ as Binance Chain’s API endpoint. + +Kava's [JavaScript SDK](https://github.com/Kava-Labs/javascript-sdk) and Binance Chain’s [JavaScript SDK](https://github.com/binance-chain/javascript-sdk) can be used to create, claim, and refund swaps. + +## Binance Chain to Kava + +When a user wants to transfer tokens from Binance Chain to Kava, the following steps are taken: +1. User’s tokens are locked on Binance Chain along with the hash of a secret only known to the user. If the secret is not revealed before the deadline, the tokens are refundable. +2. The deputy sends a message to Kava saying “a user has locked X tokens, if their secret is revealed before the deadline issue them an equivalent amount of pegged tokens”. +3. The user reveals the secret on Kava and receives the pegged tokens. +4. The deputy relays the secret to Binance Chain and the original tokens are locked permanently. + + +![Binance Chain to Kava Diagram](./diagrams/BEP3_binance_chain_to_kava.jpg) + +## Kava to Binance Chain +1. When a user wants to transfer tokens from Kava to Binance Chain by redeeming pegged tokens, the following steps are taken: +User’s pegged tokens are locked on Kava along with the hash of a secret only known to the user. If the secret is not revealed before the deadline, the tokens are refundable. +2. The deputy sends a message to Binance Chain saying “a user has locked X pegged tokens, if their secret is revealed before the deadline issue them an equivalent amount of tokens”. +3. The user reveals the secret on Binance Chain and receives the tokens. +4. The deputy relays the secret to Kava and the pegged tokens are locked permanently. + + +![Kava to Binance Chain Diagram](./diagrams/BEP3_kava_to_binance_chain.jpg) + diff --git a/x/bep3/spec/02_state.md b/x/bep3/spec/02_state.md new file mode 100644 index 00000000..7ce2df13 --- /dev/null +++ b/x/bep3/spec/02_state.md @@ -0,0 +1,82 @@ +# State + +## Parameters and genesis state + +`Paramaters` define the rules according to which swaps are executed. Parameter updates can be made via on-chain parameter update proposals. + +```go +// Params governance parameters for bep3 module +type Params struct { + BnbDeputyAddress sdk.AccAddress `json:"bnb_deputy_address" yaml:"bnb_deputy_address"` // deputy's address on Kava + MinBlockLock int64 `json:"min_block_lock" yaml:"min_block_lock"` // minimum swap expire height + MaxBlockLock int64 `json:"max_block_lock" yaml:"max_block_lock"` // maximum swap expire height + SupportedAssets AssetParams `json:"supported_assets" yaml:"supported_assets"` // array of supported asset +} + +// 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 active or paused +} +``` + +`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the bep3 module to resume. + +```go +// 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"` +} +``` + +## Types + +```go +// AtomicSwap contains the information for an atomic swap +type AtomicSwap struct { + Amount sdk.Coins `json:"amount" yaml:"amount"` + RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"` + ExpireHeight 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"` +} + +// SwapStatus is the status of an AtomicSwap +type SwapStatus byte + +const ( + NULL SwapStatus = 0x00 + Open SwapStatus = 0x01 + Completed SwapStatus = 0x02 + Expired SwapStatus = 0x03 +) + +// SwapDirection is the direction of an AtomicSwap +type SwapDirection byte + +const ( + INVALID SwapDirection = 0x00 + Incoming SwapDirection = 0x01 + Outgoing SwapDirection = 0x02 +) + +// 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"` +} +``` \ No newline at end of file diff --git a/x/bep3/spec/03_messages.md b/x/bep3/spec/03_messages.md new file mode 100644 index 00000000..97ef3912 --- /dev/null +++ b/x/bep3/spec/03_messages.md @@ -0,0 +1,48 @@ +# Messages + +## Create swap + +Swaps are created using the `MsgCreateAtomicSwap` message type. + +```go +// MsgCreateAtomicSwap contains an AtomicSwap struct +type MsgCreateAtomicSwap struct { + From sdk.AccAddress `json:"from" yaml:"from"` + To sdk.AccAddress `json:"to" yaml:"to"` + RecipientOtherChain string `json:"recipient_other_chain" yaml:"recipient_other_chain"` + SenderOtherChain string `json:"sender_other_chain" yaml:"sender_other_chain"` + RandomNumberHash tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"` + Timestamp int64 `json:"timestamp" yaml:"timestamp"` + Amount sdk.Coins `json:"amount" yaml:"amount"` + ExpectedIncome string `json:"expected_income" yaml:"expected_income"` + HeightSpan int64 `json:"height_span" yaml:"height_span"` + CrossChain bool `json:"cross_chain" yaml:"cross_chain"` +} +``` + +## Claim swap + +Active swaps are claimed using the `MsgClaimAtomicSwap` message type. + +```go +// MsgClaimAtomicSwap defines a AtomicSwap claim +type MsgClaimAtomicSwap struct { + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` + RandomNumber tmbytes.HexBytes `json:"random_number" yaml:"random_number"` +} +``` + + + +## Refund swap + +Expired swaps are refunded using the `MsgRefundAtomicSwap` message type. + +```go +// MsgRefundAtomicSwap defines a refund msg +type MsgRefundAtomicSwap struct { + From sdk.AccAddress `json:"from" yaml:"from"` + SwapID tmbytes.HexBytes `json:"swap_id" yaml:"swap_id"` +} +``` \ No newline at end of file diff --git a/x/bep3/spec/04_events.md b/x/bep3/spec/04_events.md new file mode 100644 index 00000000..059a2055 --- /dev/null +++ b/x/bep3/spec/04_events.md @@ -0,0 +1,52 @@ +# Events + +The `x/bep3` module emits the following events: + +## Handlers + +### MsgCreateAtomicSwap + +| Type | Attribute Key | Attribute Value | +|--------------------|--------------------|--------------------------| +| create_atomic_swap | sender | {sender address} | +| create_atomic_swap | recipient | {recipient address} | +| create_atomic_swap | atomic_swap_id | {swap ID} | +| create_atomic_swap | random_number_hash | {random number hash} | +| create_atomic_swap | timestamp | {timestamp} | +| create_atomic_swap | sender_other_chain | {sender other chain} | +| create_atomic_swap | expire_height | {swap expiration block} | +| create_atomic_swap | amount | {coin amount} | +| create_atomic_swap | expected_income | {expected value received}| +| create_atomic_swap | direction | {incoming or outgoing} | +| message | module | bep3 | +| message | sender | {sender address} | + +### MsgClaimAtomicSwap + +| Type | Attribute Key | Attribute Value | +|--------------------|--------------------|--------------------------| +| claim_atomic_swap | claim_sender | {sender address} | +| claim_atomic_swap | recipient | {recipient address} | +| claim_atomic_swap | atomic_swap_id | {swap ID} | +| claim_atomic_swap | random_number_hash | {random number hash} | +| claim_atomic_swap | random_number | {secret random number} | +| message | module | bep3 | +| message | sender | {sender address} | + +## MsgRefundAtomicSwap + +| Type | Attribute Key | Attribute Value | +|--------------------|--------------------|--------------------------| +| refund_atomic_swap | refund_sender | {sender address} | +| refund_atomic_swap | sender | {swap creator address} | +| refund_atomic_swap | atomic_swap_id | {swap ID} | +| refund_atomic_swap | random_number_hash | {random number hash} | +| message | module | bep3 | +| message | sender | {sender address} | + +## BeginBlock + +| Type | Attribute Key | Attribute Value | +|---------------|------------------|------------------------------| +| swaps_expired | atomic_swap_ids | {array of swap IDs} | +| swaps_expired | expiration_block | {block height at expiration} | diff --git a/x/bep3/spec/05_params.md b/x/bep3/spec/05_params.md new file mode 100644 index 00000000..bcb0b51f --- /dev/null +++ b/x/bep3/spec/05_params.md @@ -0,0 +1,16 @@ +# Parameters + +The bep3 module contains the following parameters: + +| Key | Type | Example | Description | +|-------------------|-------------------------|-----------------------------------------------|-------------------------------| +| BnbDeputyAddress | string (sdk.AccAddress) | "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj" | deputy's Kava address | +| MinBlockLock | int64 | 80 | minimum swap expire height | +| MaxBlockLock | int64 | 600 | maximum swap expire height | +| SupportedAssets | AssetParams | []AssetParam | array of supported assets | +|-------------------|-------------------------|-----------------------------------------------|-------------------------------| +| AssetParam | AssetParam | AssetParam{"bnb", 714, sdk.NewInt(100), true} | a supported asset | +| AssetParam.Denom | string | "bnb" | asset's name | +| AssetParam.CoinID | int64 | 714 | asset's international coin ID | +| AssetParam.Limit | sdk.Int | sdk.NewInt(100) | asset's supply limit | +| AssetParam.Active | boolean | true | asset's state: live or paused | diff --git a/x/bep3/spec/06_begin_block.md b/x/bep3/spec/06_begin_block.md new file mode 100644 index 00000000..44a8f328 --- /dev/null +++ b/x/bep3/spec/06_begin_block.md @@ -0,0 +1,19 @@ +# Begin Block + +At the start of each block, atomic swaps that have reached `ExpireHeight` are expired. The logic to expire atomic swaps is as follows: + +```go + 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 { + atomicSwap, _ := k.GetAtomicSwap(ctx, id) + atomicSwap.Status = types.Expired + k.SetAtomicSwap(ctx, atomicSwap) + k.RemoveFromByBlockIndex(ctx, atomicSwap) + } +``` diff --git a/x/bep3/spec/README.md b/x/bep3/spec/README.md index 86472c50..226e2c66 100644 --- a/x/bep3/spec/README.md +++ b/x/bep3/spec/README.md @@ -1,17 +1,13 @@ -# bep3 module specification +# `bep3` module specification + + +1. **[Concepts](01_concepts.md)** +2. **[State](02_state.md)** +3. **[Messages](03_messages.md)** +4. **[Events](04_events.md)** +5. **[Params](05_params.md)** +6. **[BeginBlock](06_begin_block.md)** ## Abstract - - -## Contents - -// TODO: Create the below files if they are needed. - +`x/bep3` is an implementation of a Cosmos SDK Module that handles cross-chain Atomic Swaps between Kava and blockchains that implement the BEP3 protocol. Atomic Swaps are created, then either claimed before their expiration block or refunded after they've expired. diff --git a/x/bep3/spec/diagrams/BEP3_binance_chain_to_kava.jpg b/x/bep3/spec/diagrams/BEP3_binance_chain_to_kava.jpg new file mode 100644 index 0000000000000000000000000000000000000000..350d659a916042d9be98e27ca323eeff7bc14173 GIT binary patch literal 30574 zcmdqJ1yo$iwkX;Jfnm()M;%@ftE8w9V2n+(Cpa1|U$REJn0zeXg zdhd_+$6r)rLqkXVqhX?>-$%#7#Ky+L#KOYHeSnLNgO7uSg-3vg|KQ<6f`{0+ghYf7 ziIATk{xJy3A5Ws9VIm)Vh=YZLy!-#K-F*ZQU;>WsU7(^+1MU%^pc0_mbpyzele~wD zf^2}lwfh*T=$L3&_x|Wseh2`d-$S{Ff`NmAhJk(`4HbZL4;Ae`ItBqIA<^S!RN4uN z6OW!!bMR_tx>k=*ia()Iso~_4Fm`l_$5MTlR5b&*d5?Q+$WxBeG9K40Gk6l&zKi*Pr(3GBTCEUVz&0j?QIMDJDZ8GR%67q|n&us^jvE+2HhrsO-P{F;h- z2Z%;e^%oz)4@q>Bmh=rC&!PMx`tRL%@(1+NZZCgv`WKtBeNa`e2F48HesNmzmuzGK zCfkl(w9GL6&gsv;WP|=A4|_|-|0SfD{}wNG5C4jDpNGSS0*O)^hJPkfTb@07aQvN& zLbTGR=i72xb*HY$gb~aVfReZm0Q}n=+`MT2cn3iHOnGgKl>YMDjy3;-f)M}PN8v{& z{y4MwaLK2u-kLvB{(r@We^=m$i3qM~v75RU^f03gff0F=<|Jy;TdIU}H%}JVGN%xc z6CKRZ6RZkO8a7<1c?hx5CnAE!={p;&$7r+D4yJPw4(YM12+{GO8w5kxxc>L^^f6GK}{M%(c(8j)`Rfb5TjxpMBtf_n+D19IYqnwM`w@$g#a z6(-hN=gn8$j~3y@@Lakq8FxC?c%_{?K!wNh)izbu9Uz!S=A))a@%0+}H?gCh)jd&- z3nl-+%{u_gR^q|FRqOfD*xrT+_ez;} z#9$qf-2t|lZ*yiaPIkp6=`4Q0`+lS*XL8;WQZo~GHyauG1|Y2;L+B*~(JJb1U)F|* zX2dkMw%#JGDbk^0u(+;7t60e7I4Wc|He;vxma5^~Y2gk$sHy|zT8ZMu2GVezJeziJ zOYHPgc^CC&DbqIBgsb>CqUkE4K+TNp@fV-`@uB!HF}109K7~wjP$@Gr%Pr9f<%CK) zI-iygTaUuSew;OPV*oe57+Oy7qT9!82Ta8?)h;H?C~KO>u{~Ny8Odqfq~guQLWA8N zbUVXShpusltLTeMQ>vWsOvOJ`D7eYhDbdqVt4D>Hj?^mEQ5=3b@_Ce+DW@)H2Ze$Q zZN$%Lg+po;g#`OC=(4o*C;}LrnHra`a|N3(DTCb+&fH&TDq z>(7ldYmgO{)5U?|}O}UoQ+ExNHtWi`s>ySV} zyGG^?;6FZ@TIv9xi1;)3q5ElnS=H@N_3qXH)rzF8^bVk@9lQQQZ0J1%m56e6VMlC1 z4QFL$3jlaP|DN%S?yw+36kE?tzUE&YDq|_8 zm=dy?9*5!84gXZ#TGWF-kx!9_Jn;!xkSr;Q?UsPbIlzO7Q`O;@3Q_$oA@A!J_!_eJ z`JV7d$1V)P^G!50Zy9-2@!-XO8cF=YxS|p)KlH1`3zFyw!}m302J3h zX@J64>_3%AbAgL=Tn_{6HSYk$EHcN>C^tR}2-|mF-O`S&_|Y$1LjVBIROAEsu4l6> z_QzrV+3!T3^ZfcO8jDmeO;(W?fT%&y;e|UufBy~71K7Fio8Zv#M?w=@d{(oTvLE1U z#0iRz>P+~!Ta2(RhhA*~Q|$)ma@|rKU^L()nk*usY$tEk4EwUz0z{8z38*bd0$j?0 zn<*&PJEYXZL~IfXRJ{bGy>hB@KvuQ}{X%(}Fi_U1GL(YFzUVe5<74gq@vv(h4hCSD zmD$EzI$Bh9FIlP{4F9A|`xR#JZBdcZ=}HmJW_@^|n37A$APzODzFd_wPBSmc#Q%4YM{SM_alk~ zX2VwuoVL@+R0>+6j4QLd1l!t8cd6-V0aus<#Y@|;m`^BkV)k}Q?MB&pxYu>lDYh>4 z>?UZ6x)*9=2ZT@>V^8^yAAt;tfP_%F^{8n5`1nJ!$&zUZC5@7a39VPQ&r@199Oq~1 ziT&_8a3>EUcAHCiT9S`;)Sh<0Uwq^#Qlb2|IEe)^g-i_) zZ$O^YneeM`H!C$Z5b^>rNux_7Yz_9AP4Im}#mVVyH}B&oD9MfGR@wc2O6-nQ+j@28 zR2C5Ec(!LQl_H5I*kL#pN^|05-BUskYH9a&V~e!jcX3bVBnIA55mc`cHo5Gn^*o7{ zW0m%VrR6^FD4U=SHzK|xKR{vxF!q9% zC-Yq*+Piwu-L zFjvF4*l?O=3%9A}Z|bWK@q+R1Ygo`mcMosn)!5L}jThEl=gz3+5))Ky3*k<5P>Ow4 zFqyUlS}(B7c;M@p7PMYL3SBp?De*sYq@=WfpG>^r^wy#tpOEBnmxTDsW_X^ZsA|o_ z!Us~L>iiYKnCa{nwda4GO@ zqC>$%(~{bngSadX@buMx5D;JJs{{Y+4gJ|oJIft_^2+ZH5Nh{}^7njR+Wwsl_k7Gv zyuNMk=jQ( z)0`}2VS*i1?($bxu`jxJy&?u2bg#AW>n)$h#;yz`AHYkVo%HFa5URrHKIiawSSw@S zwp`+gFx`WN5(x|*d4>)26F-&r=6f`H`nb2MqZVIJt73Pt#YEGbK!!7*W8INTN~$ZE z3Z~-`E>40QH5{8I7i-koS-07=t*mNt+1B;6et=`o()9Rb9&~JmBSkhHA@M3XIt9e# z*y%`Kz`21Y_YL-H?`X|{M>26_SwI6%)2^bRbVLn{@AF_3Ja6EWP&m37$eQNsa9EEe zx+pkG=b}+`1~F2f^i>aset=U!lWp!3r;X2tEEsI3AjlOoCmxcU3Z6owNrWpAdT#gy zWszl0g>3D=FZT*7TWa>}!wkh|qvs-hlNcbCpYZI9XFb*^!6sBGH7e=LZ8&``npC-5 zC>W5z)R)Rb+qr;huxKqBu*>82b;wb0XqRImB~ZCIxmH9}T?Y&H@bg5!T!cirN{y8` zOE~klEb!5|0h_H5s~W;_(Va#$eSq%6P)YDcynC}XhsAZ~8hgojJsvU$4ZzfP=da_6 z7eiMPkC~clMQZ2Z`$kSu`y%G$8&I^FMX>)htRwto8JXtX9bh=psE88)@M8E=a?+;S zw}%L>L;gti-`iaNuI9K3{*m@2cL0b2d#Dit01(c6`5H;Y z8MUv70#IuQ{~UeFt*oVRL2wy3T_4*_@dT`U<6T}OWoy|=2X+Md1#%Y$6HvY%Zly^r zttr=;D=Utk$TWc3$~I(Yb0}ITbU2wN!k8BJz_|p8bG+f$5Hxu%Bl~eJGW>QFc~#bL z%$h&6`1VHlYR%?53cDTgTq}7N)H$~Gx_?N)3>6al#-!>f6x-`PoAUDS+c`E!eCOSO1bq|h6r8$P3uZ==K&Y@`vH-DW%@pm zcI~a_1i9MBO1bIw@h55Z2BUTGZQVURyHCTZCyCCuAzb5Z1*U=>oQ~S2o!l&G;HN=m z+c=*=WgfujcCgEj8i!$+>N+tZ-5hJCfn`z9)OCW=Q}xN43Y8`MRyYA-!89R0g+#p! z6K)tk@th!7JAHy5#sPynXU6{+PF4EYUwG`rK#bNJs!>tVm%*2Ap7P4%1H6_87*xn; zgdcxy(S`-qLJZY-b4FvdHwEyWcMfJfOBX?i>X@l!+@IW|AarW23~_N+^3#qb0_r7y1gupx(aS(q)Plw&n~c9WR_s!!1(!9 zEIN5E(FaiW9s9Q+GO10)O;h@(1}FA45Ab_To*(*TTpNkB4H@>98TxFq&Ze)03<#E^ z4QHA&b5Zk7#%wS|4Y#!@yCk%g=q)M?YgU%q+L@@>%!MmCnMfg$9!-a6OMnCUbX3Vy z6VI@;{DF$`4no@ZU2HP+7)PA*Iqf7}*Y&6eef4S+&lAXT@LueMT$)K7`DidQi38*O zsJ#{77Cw|f$>oW};(k|j0^4Y_YG%Y>229_8w}Fin0wdfgMQ%ve0Ivp^s8t-yWJb~b zwQB)}j0E4XNAg|*o@o4*X^_60`aQ9GmV5_zvaEAEpKOHRE(-v7ilF=!zua5>O%=JX zF|zLf+Sd&}Mn&!Zojw2zTlgQkjiP#B0FK+k4b;>f8!9o6qU4E$YSWoU%4Nh%u)QB2 zpv9&YSX@;I2SHCL$Ei@#zU%k47ufk1h_IV08t~eht7T}+w(M~17e~h8t*0rL-vJ_Q z1AwVvVf|1LBAy|^=PhEKe#EM#>)mb>`O9PnA)BTNdp!0MWBdOF0`=O3BoyJuFQMGUZ3t(ux=pllsJ*Oj`Oe zte#8`#uL+6HBzhsW- z#R1aghvc;Yb887FZIpARU?(^yXj{{)lQE6S$PD^FT@yYA`5`}*bv!5L zD&29F7JRA5a?Z9!t->6mD17|k={jrL8U73Zn^T#?UNVm;VIzC~sCnl=FK+=ZK-Zt0 z9D8$=T@R@2mKz*n-}K);%TN&gzDbbpn;-J}nNM)F-%77YGtIg2FIc=k0PuwNg#T&; z8{;#X7uL(xE6M1TzsB{%vAF4q@^+Is>{5HsBoL@{rHuPg_;FkvYptEUcJeL1f}(LF z9#K*(bN~m;&>WUcOmJw9l;|UhtY3rJtRRQZ_*b~2{}sT$HMuUdi^`qX{S}ETRAHs8 z-yT_Z&@$3+iCsRtz4-f5`ih!Snrh&aQRDiAVV&dKw$DkX7mC5hFwZHdVt0Hzr7;!I z!Vsq7g{H*&m_|rfbRddFUP%S(#v5JH5H(s;!EPa*Fq0Jj{cTMt%LgX*%lPQG0F$LJ zZfVLRg}Nu*F9G)o?~A-6s0~q*H2N_6Vj`G`{o&6$zU%i<>uKV^Mj9i~+ zo9KFhD=IP9?yyGjCI0%w@sp9O&}XMe;E+5@7iY|W1%RG$no0cD_}62<;oy03m6ObU zP;)qp`rsF4&tfV&J)7CYN{(xXAEkv%J8&LCD1p(sIV(6 zEaZM@YUF3WqpmfMLXQYlXV-H(W=)G}0Kh@;BLGGDwG_k{{(0uR8!qqHwydjiPD7$_ zu`}prq`Cg6{J`}V$3#>4%PLn6$jhV!?2wx)Lw?4;)-4A+N!2+t=#|RWB1`q$kYN9v z{n7nL&pg_e9Eq#)(N9jDEdEnCOc$!eLMZ+&0vv+LNZQlRZoi2Np?Q^>K61^)$d?-eWb~P+hGV3yBd8v;@JqlhZGGO6xUY8+1R4fob71)S{gdvcs)( z*(aFkc1$3*8eiBm?ZotKqRYRO9Z~?Gr?qL1H!DrGb}J0IzpjeyeUHW#H!q}JvckCj z{>dHSizoCj9ef99Neq5^dvAqAxqVtp|dJPNVE=Vn2sg9ZG#ljbNSsshu5n zfU|#e7YBbZ(ss>x#H%g_NXf{6)E2?;^wTRczS+ppSQmYfSDcMLk8<@sRThK&*pJqMuU zsQ;OF{TZNP1^Gt4zb<3n0xE_n(CivuY&iOZsqOv_@J~LV(Gi@o1s*rFBjS0fUc^?q zP$u-;0NE_tWP@GfqyjEeR0NrWG(--$l`=npmpa_MsN0Gq?vzXU zKI|hh8vk1h>J;sL<)`!X1WMP9WDIBEasMBT`CIotyWS(hym~5rp>H%UoAxkSX13|U z;OZ^(rQ+Lv2K>i8B#xfXep-LS@i*rG75E?bf5-9Y26WkRRHWNPO2Qg(lB{v0>*#v9 zOr1q8^EGFDG3-!tB6sT!0LD@AZ@K^RUeS-l8KY3%@>{hHqbmo`A=~n- z!~LTFvKu{e>TLctL+o~`40(3sb5!+HxrPhk8Et$*GI#!}3KE$-V_d1}8|a;6pYvLUU#GMvA3O$X0BMxexr|9C!efvpC% z@0B}wdfHO`6)H3EG(Sf6B<0*Mr=Md>ow#g#yO~KvA=p|)aM{R+O%X3j3(syOD6fzd zC=9Xy+0r7$E%dz<(8&`f6OXqd3n zjEwKe(0#*JDGpc_G_3zD@ED{>WNa=XzsWl%Dw?k2o_M+ZTz!_u?2_`3hR9{WoGzaf zBCngKn`pg0e7}5rdT;nLqTkuz9jE`P|I@rmO)C)+H}=~+D`*O&)HKUAvi!<063$-dYEd0GudpCDUUi8vZ)U1Gd&nc| zUz4%$)AJ6XHY(YhzTF8zD#%%96Hmq++Sp(BO9PVM^jQT0BI&`zLuaHZJ9I(-(z$4zU~6{n(l}u z)CH$Ok8g|{9mY#4d~P#Nx!043?s+2J38Dt-gVFGIOhV|sT~a^C&n1H;{ta^l4? zCvsbdkshhR8yy&LzGD5WEt&Xb&9+lT$OrwWHenZgs@@6xsvgp!6K!SNbZMAdd}wnj z`4Foc+lpaVzsOG=j)=m;INaSg_Z0Y^5sWnl>ELfzE zeadZ=Fl6MtM4Uj-5|s5mGE^umtSw12-g(D2H^-F?PqEBFfyQR$^XFcMinZ%0^EV`F z1BG&`35FKAjB={*98rZy3)B=Zk6}JKjzS*I0P9^NeUQi>Q&|NL>mBOrrd<+*tR7p;^(sZqMM;ru9v6`I{WQ$;rCmD-R+A{h${G zQCXqK2qvL0YH?sXEw33=6twGgB`(valat#o7+n;v2p<}9^k$gvdw!uQz@)mUA3~in zk*Y=9<+*g4NiP)cZ}dJ2VO^<|^}L&YAMq2#H*R3q#yL_y!mpw~#y2C_2I@%SUiCgr z!HgCyz`BMe=vXS7iwmV(*VVV4#%1GyXTqMdTbevyWY`+KlHp5*63KkJCq6txPaK@a zgBi1(xQqr``M@Baef7C{DbBl1xz{XnN< zc$;k0q#=}o0)F;saJbIJPSOlAnlP{2mn1lYGfI|aS{4xqqu9;85D3D)&i z7(N%1A(^fXEUM67%@Q4OR?6Yp!PTwO0CA_oG?z%_J z0JrDZ#h>&H{K)3pFO(Df_d33@mbK$UjIQ$s+fTnfDfq|Uu6S;-o5EP7`Ei#ghaD`r zOTA5A5#*-=@~qX#Yi|mx)Fbj?#|7J*GEPvb-1wCEnwW{NLNl0G#F7_ge{-mVlta|F zP^sDf%0XiO7HvrwQ<{7iV~)H8qx_}g{@tPea@zlAas1)8YbP=UtVi&kNbuAVKf%4& z%$?QiSIyNK)lFE;6=R%GE8fsAu5Yf18V#T_2mG-J^9LK1?Z}W3xd>CMcGl=iFwyJ( zYT>^^#_-Bt(I?tp66>E6>K6_gy7k#FkS`Worg54*4NdYprR9ElRxlnGS|*PKKCLog^L`wWI4AGM`AAl8z%uhEZvrDDPrL&tn?_V2C~keP!y2aXGZ(!N zHQ#23u6SGkI6v6aB+IB8;bIS~$t6y*(RT&LDD=0|Z#~|u-QF=-F|L33wIuCuqnnHVRkiB$DrQ{U8jZ?B_xJeEK7WxtSSb0X#|&(5 zA7QTkq!vHpGd(-r+52eM)H8`%*)dywm#R~qNv&9=1TGIw5`J7f#%9oVTGtL*TDaIy zzuc)#BFu*3aXBWCb8NN@40xUtjP#Mwo$n&;FaqkAK=RfF&rc>+PiJ0r%5Y}50^wR` zw<-e-r3hM!hDCo^2aYHfDi$(Rs}Q8BgA4-+X#&})4^H{)H3dSl=YM+Gv8W72y2^M{ z+Vl)r+pUeIACl=6Q5$FW4hVmsYx|~fu|i&yFnXCWM||%^ibwAhi^kQSyG~L*MNfr} zj^c1iRT>{I;(4l`3RMz=oWisc%hfFuEc|xwD;aZ~D^GhBt8!i)w&nba$0y@RA#>Qq z?5Ag*B=2?UyXeyN)ed-RSi+|meIv!dR7~I-s>uwYZ)$Ez$vr=WSh`3Db7!S>#GGSb z;qQ19o9eEms3vnE1y?w&wFGVgOId|LRM+?YmDgptk=MGXZJSkEo*cYws=yZqCL`E; zj_B4}*m6)G%J<{uKjY#lMVgF9PRrCnw%???YC3(wwe2}3`?P&slw1L~qrNyV0mV$l z=z`NCRTFb*E$89!Za)Tgr^w~)OWF)Dls+Ff>9z*5o0;+RgxHM8zo}~AK8zO_uHwzh z2qO)roVvdXaX3nc$V*G_SkRo!aGDEiYXaY+SxtJ`eGYv^?ZqCa0975yBhn34u{Hi| zm7MRV1T3)coMLYjr|6anCsOnFTEp*2-~OoeGksFSUtN!D%A?rFw-`-G#K5;HI!8#V znnAN-$XLh-^g?Zta`t?TT7O^?ZTQk)(kgn?tCsVXQ(DyOWRP%P;{n}d-857s3EfJC z;26aJhF&8>f^Oz~m2Z#gMLfjf)KQIei-)m%{8= z%$rY>N5Qt-h)>WjX^fCyl8OQm?Oey zI;%fLh=%6tZ51aW>Nk7wg&!;<#>OOa0ZtXW{`cOe_&c3!AL}nxQnJSd+BA2A-)D2T z5&jt1=+c2cl>ItQvR0CDlgbgf*TDPqjDXWFJkjVQ)ea8PJM8#5&}hWe9MAl2$o!tC zJkL4E3;~somGvb==@58wuwB-y2l4x$J6{v^*_J#IqKNZUiMR8*Sc`Bnr>tNX}m6g?^X)&t&bN4tP)``jAxd`UXEn9;F6 z;z-fz61Z&Qt#Ea*j$t3>*_cRWV9uM_#grc@TGc#6WYQ{t5Fx>EnmEpXXtOUVT({y8 zL9NW7;{2V1k@$4I&qiK}ca>a1Goh~SgdwVtMy|ck|J@xxrQ6rq$ZVRAbOE!VqO2$y zN1D%Ry+*yy$t;L$?zD${sBU2oc;*nO3Wx40qGwBAz9 zp4A$V3OmSU;!gR{O`IzwCl?77D~Xbe$bS%+YP*#3p|3|f8ot3j3tcLVQqg@oxn&)b z+iTioAH)kP$7_?1JrczocE!$Z484T>G6|-0yuZfI3 zx>AzNT$4({{m>2jWu>27WOJ64xVeeF{fT}$(6iiI?Tsc|(Xm`)BgEtKsj$1Qpc*zb zDlY-9Wn}ZcpHdJOzi<**im~!7HD)}^canD=r%_%v3<|vi5OH0Gj#`PRxC+I(tbm)V zuDA2dz9hh_gVtV9t=hoWodv4mHee%tTyCaIxLV#cb?_wM@&`E{g7Z3y$u9zJ5zB(B zXRHJE48;{Z%i_bVwZl=4bLa-&Q`C1jNg%bv`CYo?*1&AJsV$X!=!5JhPh4wx#^12o z(MY_*3LbzlNfts9btDw!9c@*hNRJXV0`egsNQvEZjrQ&F(X&Yz+w4dk1opy+CmSaA zSb+%sV8k#y0odQ)KO7Ym6%2Mdl?*wFcjlemhzeQCzn8(Nx~avPh$wxiuZz{c4w{ee zprwP+&=OG6ZgG4fMfJ58wx^sK&1Xm}In-&I&fF*|%K5;M%vk6u75Cy-^paSe73K8(Y&-^)8C1Y6b)Q2c#@OAduv&sH1TPXctqvEqmPKjjeOg)Mpzq zll{*5ETh4}O~oWSCe=y%`+18}ZiFFM@YTph3k!Iv2hZc+cR%k>CSEqrTn5y2lxigO z_pox7B%gc_5eBNas>`k4m)-ORJ zy(X(u&)PfEgMcHb*xc$6Hx{T>FS06Yj1qfqxWg-dIEiUBZ!Wd74)WY=49Rwsl7Hws zOcw0zU7|gHe#*O@t6Mc5o@A&Z2$r`2b4$hCdU7!fQO&igTBazSXT95Fx~2=%dRbOo zPzsBxWYbUjf-v!p2R0ns0a&wD3Ze$+R!H+t!s5U$_EgBl<4rFN^xTtMH+);2hg|D6 zUqZuiOCb$;i(poCYcBaP-ADSDj}izjW&7yYNV9x@(kD-^36={L%y1OdE{P=T$+?9$ z^Ndv1n=RRJ@a_c95o3yOTxnZH(fvm_KH}+)A+PrA(m2cM;~D8! z;n}L=D})~QfnPuSP;y*oX$vhK-vP|bQ%{e#@(!aCA^2iJ9n-wLN9<_)>G)jLfYemNJM;LmAW>M&|j@kdO~Ht zI5No*Lb+&o@FK~!?F*tNE56W@=q2wPByQF}is!Pm6FJ9Vi)gz?SLCcwjfIwiPZTs` z4NaqgSV;NrhT4huS6&I(GLb9{UTCWKj@x~xq{Z^^#U|^m zdWGX^N43s4-5+g5OHhccUrO;;@({bk*WIGSch_idiYS;PHt>`8#U3It-_d?0M`+Hs zUZp0dhn7q9#=}OOA?$pghLf6xN_{s9NqH-Sqsh%V#2|4)fs1IT}u6BMZzl7n*prK4PO- zSew1;RhklDu3QQHZaNJv%rd}YE3e48dED#xfJ8PCR8z>fVL6JCb5cA-r{-P!X(YEX z*0_mOU2`NYR_@THwG>^LO12!_3qT`Of}EK^@?gTmpduZ&Ls827jE3;1p&VLPM? zk7Kw*XQ zt>s|qpz4#gSd)=81z?^=(S=5lH5m3uq9 zSz@!SJ63iUTLn4U{V!&eDf$cG#}7l}J;t9vh8(LMt!WuCt3yW9HcDb`{8>VNzOXc=NQR_(A`E%A+kF`?W z$N6a>x6TVC)zA? zLJmGYnSdCtA^u>ctDXQn=R6@u3LO#3jn&N1*oU}xJmB6>6w!6nYOGyGL&K*T7sXR3 z=^f=8;%p6!`{okS$GU$kP+BI(G@LLjJ78S55HsTx?L#Gec9_G($tJAbC&OPOD9GB75IUq#Lh59lX4^ zbPMuU%`~26v+EHj;_NVg_Y<)uafYj_+bsH@z66FB@D&HjwPNm-gc%kVX8Tu*<(_V0?)<*xFgt%{vN;=libv6j=O%4L zO}*KUZ5J%-#{zFAR}h|wXj_l31Ht4JVlzE|3XH|nTHep;DLB@z%D$p#k5A|t&QY}1 zifn*=IWDvptb%y35SXEbmK5Nj}s>uJicsJ^K}94hE1Im)4WgGrBWLCV1?uB5%exfHL^r>+n4|kN z@`JN;TQy3x>{Q)cGM7Gwww4jgJaO_zxYbAv>juj>OjJZn_RZ37vK`iSsj=m!NeiY^ zR=lj8$ta7eCo`nf&t(OTD+~|Mtq+f0zU@&Pr>J{hICaR#?qtQ~$}+Q|vcN+pHe_TX zk?3R$R;jRZ66$Vy0{_6Y!nmiZ5cTN+#rMGby?aI3owFCm6>_BId-pZbn&~1Eft?;p z;smtWo_16dM}&hJ+=Q)kApN){;fW^gpVm``n=?KSf8)tZ4^}Vjnd!?|p$Wn_oR7xs zn43v=_{OLL;KSinx9Z#4Dpgi;Se+|C1jB%6AeWeI;Oq^>XFKNMB6&R{;l9e<3rJ(4 zDUa&fW8Wg^1ccFOsiIz+kJK8|VP03!%;@t4*ZlQgV3u8ZhOZnAVOgM!k5klSw7r*z zhexve=)~ryl=Wo4V~7x8*no4lIUPJT8q}F+vjUbgIi*)j<{fSwuEN>Tn+94w6MwCV z6?Vlasjoutsr1nvCbQuQkL zw4q_pvQukZnT)yS4lvHfF9vpCA~VfN*ClUu^QJf5)Z+o(MS${gPJ5O4>|;zl&Nd67HT{~iY6==FKOgI|*uZGqrUyq1tS zr#U>oq#dBlPhGu2FeQB|T_kd_!zDjYZXpxxn78J5yrKI&x)hi6yO-c+WRA7JEs^Rsn!n8`pGY|fkdDQ+0| zDY)PiE3mn!SMbm@FLQ{x-x3N$B$QzjuMt1iubZ!VyK8j^pqINEsfPIOIVC;JyLtrj zmug@W@%v4yf1AN`nxmM+!=fl6p)St!B--H(yIuoR`j*zj1y~-H0x6~hUft&f25WS) z`BwqJto(7)euL-Rc_(->$|{>?xKpp>q%#Z_oK@4p>cZj*u_~kj#OXJH+tX$HpB^*} z)dzl6y!aK4{KIM|RmR+KoFgqxg!qIq7=2+42H zJ_l}$$~9gFV901-jZoX^!(8psVKg?0egaGe@L5o}1~d(rZfZ^dT`&ud7fh&wmrNTe z%Ciybx&Ic@Q2e%fJi)FUTve`TX_seK-)31qF-Aytwp4<7InCxK`@pn$8@auY#*r^( zAsT|Hz^bt^H~}ojP23&NKX%3%T3jR&cn3-(=skE|lsZ(s*#rL;$=Ddmn6DSVDr|Aa zB*W-%1?%uapZbll{}5V9f~699(J?ZvzY2E%C*3#H+9Vh^b5)Jw$g%v&Ik%c zmY`f4GRU+I`|==%O!M@(gRsucA)R`e3Z!Nl=#$4(1XNal4~oepBVP~7lG9gXbb#7? z+h!MkBl_Die`hi?rUB84C%IYZie2lw*wx0O*`|Rb>`30^lA7eau{Bni71BMTUg(kP zLOAUmph3@(n~oS=a|HIpyx3o`v}&r^+BI8Bz1A=civgi{^I%^Od?R`X$Q`&blCn(> zm1|`m=wU<*Y_)4P?NmI|UHrpCFoFWOxVS7~$p3jl!2TOG-cD|uKq92QiL>7AUY}uA z^&RnROQm6h<5h2371RlJ6tdj}7mi>ZsfG|tUGJ&B`}5$+laTc86a32j$p)@z{IRhj zl`eq;+L;0N0XGR~ZZ0DYuyBJ`G{ze&oRUT7kogxhU{FfDq)*Vw|G7@ynq*m;9bj$L z`cAGg%9Ue}6uwrI*jMS{Av@cWy!~EO%EpzHT`Ltq~ zx=u}x0iq{`bV%hKbR~6!Xfdl>1^e%lmj8vg;Tvz$fHtQE+C~dHep1XvQi_&akW$X! z=GCTKK6m+$3w4@Gx-t{r6(w~*R`Gx1U7q&sm7%GqG1Pf$U>w%xKwfuQDdIl?QWASc{Wr^jSZ(CR7Cj;~l8oD8chlViygbX=(0LWrlL~E;M{8ZRUX4e$aYDD3N@p`xz(HdaF)S+-~fyY;4 zNzQT|)8E2IAv1P_Q{A=F!!Q&eiuRXiWeiSiqh1T3bBKZ_XLVTdF>3|F*R(Vt0tT;g zAc}R?bn}w2q>81owmrI6gnlu~fu#2Yb!Pg<$6UbrW}+{{eRQya=^>)pPI8NTLW+J* zEkoB*@?$>vh{=d7D|om3MNDEnS~I75KK!<#S7;{@Lwl@C>p<+kAJY+|GjJzPg3sZKn~v%X?BDDdUxE}$JOMe z84+d{VG{(bxoK~>2M0itfwZ|&V2KeQdYFXt#NTTjo#8xJmmRwH4vJ(`Z*{`I28_MQ|J#00|0vME0p1;o)jF2 zX~narj)>SmQPDO5&Xc$O0GjVkB)CPT%`GO!ZL5tNO$*M?!BQbl>K4`B022ebQ*ygm zvDtjF<-%@&X7^qq$$$}Xc^mmSFD2gW?3E2u@jTzgrwl4wnV30WTSsIaC>Hw>7d6TO z%7w%@vYJwbk%o&2ixIc?gQJz}DO}P_a z*swV<^%Ip6UcT>1V6Dr9C#ozR)dUlh4Al72y2YR7kNZ5tdPzYdKd7IYO(7nmFHu`u zVa`dd6iD@;FK#>%URwXqA^|Zp4g#5(E+XgpAC05t>H#!5G;W0o6io6+WI0_rWb7ePJNUh1UJqTpOV`3MFhV_q| zA$RnpUNhnKeP}X7Ox4mRt53d$Yb$JTxjuD;)M9HWB(C$STt6MaRv}sBem)e)b9U(~ zOsujjkd)e)@2PFNfF6sNVBeOY+AM_K_ynptk?`UX;$=XWt$L%)E|@BZTG>7m8wu+8c2FpsZ>he3oM~Vk1T3K zYuCt4a5G9&F;e{WxWahxV?5~wPhCXFqBJekF__nmF|QxK5U!I_aGNys(K)SE&Tc1K zx`D~blRjSP-A04HU^8wpwzOGeJc~sq50i#%Kak3LU#G4ekA6!s2rRlJ(yZ*|sv{qB zP}&u-hwFzglJ|plYG&=&F%Q8s1+T|b;-Z%EQV$tVk+S9{;J4v%Y7>$zb88~|7@gh` zshCu;r!4H3>U%Y@YtnMkI0apZr_l23yTlf|k4N%QYK-sXRS+?7$mV}l_nlEqWqrTS zSdpTDQbhz3dKp@%!bndDAVnaAA`k*Z0@Az8qk#01gwR1cNN=HoASE=FfRs>_D!mCv zlewAqS$BqK)_B{xU*7X+@3pee-lyz+^8f$JCN>(6HGo>l@-u&ZJ~KZ7sLPhPyq9^n()8R*CwRY|9U&whL_AzdZ*BFLjUrLPr>@FP8I3rQ=Fy}Drh_CmyfP_w9OZu6ZpH{=x z#>PUE?_OBcyz1Q1=-?}|;ifa!xS`RzEbMj3s5Aw?USx;@li3}#My@v@gxMx8ATf8~ zH}_h)sa3-pJyIE8-+Q5z)Ya>@3Rj(6nH+MukXgmJlOn6sshj%NHBBvVvdIo>!m9MlZ(A2d&P4vp%Ky3UWvcJ{s?>GJ$Lr>e{isT|~E?_MEQA#hf3^G?;vTlfGI=5g#8-YcdoDijIQp^jQ<%@t|wkW_hSF_q~0QfMv^ zwdF)=q+9JSkq|Nk`O-^rT6BCI6;qYh!>4%)mtltT3^2XyOXZ9mK%mz8mu37~D*D;$ zMUp3Vwd?%`u(oF2F8dY1qN#3EOfe{1C01a3*U}&y>{3Yrup<%`@Hqwp;Q-?j(U;q1 zCth=E<40?zjLdChjfzV!i4-ZFoilwhn726yOQAB&9NrbP4=RYcf6*r>|MafL?b-@; z@(hl)-P+pLXIANbG{;0dTf~aGGIcE#@A!jUd?F1FB`GO-?M13gtbzX+$2;LAtwGC= zEZ9enm}C=5PJxqA&-TaW88K|t8GXhJfV@Q?t6$@CbDMRIG}J^hYLVIRvr97(lRFt| z%@4kj7EA(+jUg&f9Qjamfm0Dvzn6GL@u)yU_Q2r#2r9avLOeo|K)A1~uWcg8*XzuJ zl;H8^PmigUm>Wln)DxmPI<53MdA(xVhYw>qM?j5`puW|eCbL*>R^w9I7B+!?2?IkbP_i;RTsYtOPbZ={#>>X&ytH?PBqPno(E?y`wThSl~Zau841J_^bV zN%3cFDrZ9^MK?+NDBXO*-G$l@5ClGK%)56Qu{4izG^#5)A;f?$Ur78hXh$&=m-|gbS+DkfC)NNv-KW(h zov?4;T2jT4$aiJHIn^I#0qK4%UT;?A*%^>+=l9TnSt7t|B&;uRi~gGG6PLH(EXYDE zc5PX{`fk6|zE^)$^5g0J%UASAYu<)w)14ByO})FpuSUuk%nNG4pU+ygKS)8xvZqvq ziMy$6gP9fLW;6%-y8Yr2LcFGBb#V?54p)mofl-|Wpn!jes}dLUp0XD0MZVQxrBM>7 z=U`MYHPDnp!@XPxpvt?D=g#nO%yNZ6Ym~5_7W0CK2gbO%>>q8L_Ky zSKYjVM6N_5UB*bWW(1r0BI?*YnKkH_yOH~ZrjYPQBX+D#DW^=9D&H@`+~!NgS?isK zc;$-Qf%U;`Q*dN0Tdyvs8{VA$GEX5$S)bkPS;1%$U1!!CRB6qKa0dx0Y{s*S)b9sT ziL)q;nt^v?vmFf>aI6ek15vpZ&$8ryo2->DFd9aBjE~yIHzeHEN#IUy06-0NWf876 zvgOpBaXDzn=H9(PjhjwX>8HJ$eNh@%C)U5DheMY$hEo1_;%eh=aQ{&Jb)F(v?NknA#6k~E&K?tH53WnqF$rE0M?vBLGdh%xzO{jMb%VYCX;n)2Go713WPiJs?P zyrvR4V4&iTY6Y6As#O9Kois=!hx^-1t!d1c;#pOenuk*Dx!zj42*>3> z0t~>elkGw7bW!5a?z!*D{xY(WA$E1wTH893v9})?Gg6Jnx9T_v987w4lM2{X!-1K~ zCinQETg9cHe3LfdA};Y$V=`qDRP`HlKx)%*OAR98R{+I)RJh8nmhsBhmW?caIFjst zt5xerfh#(3#QUKFvrNuN!+?l2qXkL7ZI@b}so=gN+V(;LJHy~6Ge;1dKH%i%no zC1g73UIdhxRJ|2HZ4xXiEm8Zz+v_Hki0F99h19@X7lAh+N6h zyJXQbS}&$&Hg92R1)qPqkT3cmc6k2ABc&QSs+8kVOQZt2pstNH6_OXYW}?U!b6;^e ziBml(l-fI5?rToP=8hA$I)m$i1dv>3m6jd;8(C%0vnCYg@#)Gak$Na(MzNPXuMCfznT=9J@%Hm2-iHL+d~1 z#<)LT_#sYQ`|A&JVv&bGOcO*JUHB0w{olVc1D}7VJW&Afzf+#cf2KSa1_m>roo(k* zfifk0>w#jYFF22{>HPNk2e}*d*B1oK&*A3h055vfZ`XeW2AaL1XzczOqhu<)@ZY<$ zixD2)-|>ywKlfQpr0R#s<(mlltq)z7x-|CCxUqn?L(PFUIX>$O5|`H{z>%B54vt%E z{!0}57q8;~UZz2-_ZVGjRW#dOF)?|Aqsw*>R>$hi1nuM-vBdJ8s?(PcqoNMK#I6E& z(u!FI9eEJrUg|J^ed+r<1*84WQ18eLnh*0xN0Z#BmMe1ZK93uYkMr!^P*ZTLjqn;% zTO*KpDP_`EXrc=n=CFFtD%<13`nWu(6BYrGhTu^Zny@PNv-dzmVx1y!yI4sHNs&+Pb{rOa^F;(EJv)Ny z=|u3(2LQM-S5*4OCB5E37_6~i57bW=20~t@D>8?KmUr)7tv?3W-MhK z3L~L~mN99t#4FFa=?A;U*%Z`F(ohpegZ=%x*V9#Wcyk#}bih136vt!jHMW3_ zw=H|q_{BG%D@&a&OE!m|NSG?_lR<)!)>TopuuW-TA!4=>CxR=g$>XhM#n6nP7G6}o zaiz;!)%4MufO*xuZfuxvmMJNb6!tuQamAg{%dW}Qob)_S#vhm0$iyF7zAZ@|57NYfO&k>*tpO$Ip^A%?t#akzr!6DTnR=00k?_Bz}o zcVibcw-i1OW8$z&F`N@Kmu);ws$57Tpyo0Ci=z2X9&EA(>>I8c8PvF!A0FSKMN`^! z?CUI0%N`7uWxf~uWeP4){YF^mBMA6H$k*_;frEBz+nprL1c3w=6;7Guh0I#QXI{&P zz4d(eQQdQTMz`4ylRwWs;704sM=^(+o2fzyr+k}l9Y!m3m_t6~0%B{s+>nk7FP2&8 zoX32XzIDhagcKaK4weoUyG4mwLf*zNo0ZD<4%zkCb+c`cAnRA5G1^_yvKp9UD=X!9 zuiOTYB%iHKi{wilmuQF@#!kcF69f6qUBse@1q0O*P&HWKouw2#|KJ^2lvHx=Zys?s z(nqK5wo+GEV|w1KZy}^(5LK&%+t(;0&~A=SeoO%Kt?zH4g(-2fz%F_&wa|fZL7-az zEcuh+q^4zc^Dn;TwPI4?pyWa1%PazELEL}xE~6zYJ!1hIOerBHY@qjU#;;!dbXVT{ zrOmiFLBG*<9dNX0r?e#1io;4jes#NK$FxX$L#ZLQ$5AVol%~%mLU7b)Xgc~F&Ue4k z4?oFNKBqHgkCpyjil?Fm#=jhTEGc3BqEFA;CtBNKLP|9(gyA990JHkO`RD;(90%@9 zJ+`YkJPO=Wj#>)vw(BZ0&*jx#mvZDYN9QQ*b6uIEf^bXlnEB3Q7Cjb{ zs>r*LJ6?>%Xi=^kT)ap!j{KntNKert*ey$l-%|>(bSWvmwz`ILyhGn<`wh-{|J8F? zbS|S`S^Cx3E(0@eEQOB4;t(?bYjCkCr>ZlLfd|RG^!p_ zvV;gC!zCgT)8t6D5)NS#rH)Qb0lXIk~(mp;FJPKzv_Zpc`waJ}9X6=@`xLzCCi zh`d5b#|~Pwyb$zWa3+K%?i#ZJwvoAcU74=*iB~7(A~(;9yfq#aY!jJjQ2oMfG>vtA z5lHE|zDVv%OLN#E+x}Ud!`Bp%xd%>daw^3Lf&FPxrJ-_FyTHu9L3WUEI`&H$7uZ}* zde<#Uq!+1j%h6EVQ5S=$(r0CZHt>kbvW;mg@Z5jry8fPZV))g>LgSWrW>`NVge6DX zArx-6Wak4w;_>+l_wd1)mvS@j(8ts1bqE}!?|bV@jcbDIdHbRj92VDkNe9PL-razjK<9*V3in+v74>y#A=#1Pi$3H2{cC3~;y7nu!VmJs|y023%9?;5g6H~z=Z==XK zh<`0F#KfBO62iXy#x&kGY#5d<()~#9O8YN&Yh^L^Qo~(TSZ*&p8ysZ&A&N4C6%0_K z&<0K^tI?sl3^9_1Sec}PDKpNL4sv$i9SG>(c6t5L5{E71fici?h1BG_Rq>vcLBqPY zm%h08ta~hcEe?psj4lt%Bnsrge!t-R$ej(U29C*IIOH}VQ4Cm&M6Q1S)e9*zqNg{J z&>7%;bB4lv9)%tByntgnuw28Gq&8?WKacbDn#-2rh!^f;GmG%SqR(tZ4VQSBoH?Ro zhLAq`I?b|r5|x-YWgJPHRnl3;w|?C}Y1qZaL{m0K)L;CWZr_KmVR|HPUnIsA5jf?h zpjsAcKjq0ph#CSn~#c0XT4fMc&ts~DQleY)5} z72-U)gWRU!%^02M5k}o9!Vedu=D!8JZLXQ5t?402G5bXwZt9KlLkmhy%UN2+QYV2O zkS&pyFP7>T@F-JHPYS_3+?77$y~hU#{9!LWZg(Kug5rv#T=*tX_NUHCy=Ukt!8NlL zk4-8%ID%y=s_oujwt!olkSey zO?VucD+IoUSHy%4=9}UhnH@H5K=W!jHhn*n(IT?id{opkC=D#rIIw%$fwX{acUIDlf+j?3_QVZ8(FDO2DcgurJJ_1 z5R=Rf)+|oca+Q6m(n0(^$^YPVAxHcF;6uSRG4_Ar))IF|?4ySANa*e1n6VPFzgg^K zo89fVxa15iWLfv^6uSxy607a9!fh>s0bDr+kpLjepxxen!{ZE6XJ1ym6m;GcbXaOA z#C|p`#!UzNmMQi_2PuY>8IUw1JRZ*`@jyz$qR)yqZj+wlN&G{7WecXw-OK)C41JgV z-Ey1NLSbMelkx<_!V1sOKjzfqN*9Npcqxhq6fT^n_3 zTD1OT{!Xx_YPnSCl=WU-Ctp(oB@>1$&uc#_(Ju5ikIZZS=8TrV^SnuXfXiGE!@>A8 zK~H=&Jccmh9XA(!z%W_Hr7_Fagb87&H*vh`_ZvkQ{a1Vz_V03}@*lC;@8lR@en7hmF;*y(_IL(l)~lrQnd1BOo~m zcP_a5)yFGHC!t}+uY0ufK*^%qop%kZ&pXT!7&EbrsWxN7bcUuo&U6WABzR#WGvp*b zM_g+Z8fB3FzRBqszj!d_dwa?$!`V)>m(MTNXF87wE(W|b2|>hx(Ns=xxZR9l9wo1~ ze-1%!vf-XZ{i0y*FY$U}qSd}a_-mN!W8J2vj0x?qP7~U3Vki_e>3S;D*2)8slc7$a zh)%2%hm@$}3>U?#zei^*euLQ|QkFmQBS zr~HG?S-igK*0O5~<&WbgmJFXGjh{o5oR&lMTc#Du_3BW=8Q@$UO zJe~X%jBQUWX)n=4*ads-3}DLnBc7(I+-i8psXdStxNIVlf>FyBy=AvAz_-GN99}Vm zqfer$wuCYR!dU}p(U&kU1qcy>_n_uP5)0)z6G^*AiyFBUtE9>Oujfe>=NV!bq0vLm zLVRLDii03Rdw*G}crlI2;-qgSZ82LiSk#sst*K(F?F>j)N!tGSskc=7%fBQ`{ov*8 z?hyeeK=M&*pmn4%q_7){xliv{6n$@UC;Cc1ZBdUAKJ`KU94;{LP0G2q>OW|Y3zO;6 z!)T|27Y;q+6ZsstxqVXc6FytgJOL-e|d#Z`W+zkZb(PU82isFQ*^A318hM-j zBGSGgq}6%gnwQK0Hs`c5EN&rRl|F}yhLNUj4lt6?WVabaYkO9diDu5fAS34 ze`#tyfpNvA;^j?!d}j{}Ji?Xt1*{}SUIB_%Q$sDp4t3X^qrUyeW7ce3etDJ9)C5Jx zXFi(sz2Ez3diJIh**U|bOE}Ftb2IvY>}fYOujMJApZr~YnZ4V4*pe{rpLlu3WVurJ z!Eam(zuLWoL%|f^zmQ!V8?E-Xac%t=DxO@+n(69y@dxzrE0q<;Dn0H_{X7Hwp!_%O zZG?46PN9EAn^tu`39k7RnoyW#hq4=X3a^zV#UU|!$G(zh(;l8Lz(n0O+IQ~eYyk2# zmX|C!TNNT!DFVD^0tn16+nkq>IDHZmTiHwv=n-`~$bT~+ z^b~(5He%@0c-8v#V38bqjH*+XUJYouH>LrP5xY@lfX{K{MIxPck@nwzwIB2|9EI(B^_3EV7KMbsV0PG+B#ke~r8-sxA_RWQ;qc!2-E< zWtX5tI06ihVhrqh|0;C;2Y2HltY2QKv&4CA_ddGfE85zYkuy4i=zS}#-wsqJ$Et}Q zAv%&|*m(50=4Ly9oVkahLZ^wjzJt!CaCn)RB#q?lHx+OPdI@>o0YO8<=>!%MWzQ0R z)$pM12fb#!=oB3*?d3v@o%@gGd)hELh>4VxT3;qE_|7kX99&mjU5hc`gT0Sy{gO+3 zioqC0C{a@sX$)Fnk^eI0RQJ>ub!!rSQiy&ZtvXO%fH3wns*B4}KQgcV)-s!{Diep@ zqksCvm7baOg(MSn?kSwhrQYO>g6CUU2b-^@nu8z zF|sHG4gxG#5kVr#w8K($<7!3li+$fx`CP2xkx98xklA_{iLmmD9WEj4cz(1xUnPMA z*<7E!+(&aHnf#-r)qHG^IV6YW_1J{c))cEQ{lfv%(m8zHkfG_R!}kp88{AUHX|B0g z2hq1ySKHl*EeE0(AJj;nsg}AK1WL1tAR?0OSrLwlud-H6GX7_{lB&23L&;Ab>R~ z<87ny@=Pu#4otZA#beT1YHaOD17C-_zJGdLz(OIrSGjPz8VH-jE!ucb_2Pt%Wj zjv7;wwF4=?9JN-^wx0Joyw2O%Q=UEE_g0lA4eJ@bFOA%F*z8=)na2;R)ilg8G>uq3SGpdgv)nic zw#F`ys;Sr81P2z>pKTtm&*Q?WqQ*ivaM=nvgh`#;k7QiIni)lUclSqc7CmbZSbkD5 zH3dE~iO@&8vy-EeK{pp`Cie&l@$gs%=xrbS*;0O|s8hY0Q6p`IvCp^Fmp@rd{9*g1 z3F!3#W^|Ru8hW}0oLmO}GN1ffK^)7GPmaM)OL{cv_cnCkP@n53nt#OnWH{z(uM&(( z;q91cq82Cg?(EI=H`i4}6jh*l_mn?t@XqJqQqs`1A}BNpR)7_+{&JshHT?yCN$$}x z+F+)=2j1i6SGYx(Vpo$YTi%`03w{Y3!E_-m;2X;vw6`DZ-?Fa(}up$V1kHRbc9+&s~g?Rhl zFFg1!24h_Q|EyK~{U87N`dX~}x zcL2qa^7bEP@E_$j=STnd@?X#jqs?FLlvF<)9Vd>9mP-g^{ z{f|0@I(n1DFsuc+`5cf(A(w}>W^-To>3`Ga&@q1kEh0gNt565$0o&<=>R+z^$65bg ixw?2wRf{46cAk>*EG5->YHF(UR8-V7^fc5L=q^xE(K67| z(O~N2 zC>Y32x&WM~izKHYJ8gh}w6o_ZD9@jvBLCH`e-QwnBqJxMJV!}>o}7x}?CG}1DbAdw zJjZaJk?9K0ZS2^k%Pf*I{wchsW;MidzMIToSwlN-R4tXNoRP6#;G-w`)j)`peE_1@O7pC~I86rn)+GF<+KB`r^e8xeXc6^!tHLy3P8b zP3RO441R28xG7P#=7|1_9@z;X%S{C@bpm*==y~MIlk;;xeeXc!$%gR&PwZEv(x9Pd zN;!9J9}6m{zh0$xo@ROfl)j+mKz)yr@FO{|)PF5y{BM^0SC&}u3oDJ}4!MGEf zvBoSORC6fSRDVoV(Xp`y)#3^ZgE52nk0hLs*(yv=DU*~|uc1(Q0w7xqTQsCO0qhz{g`9Pm zeK7+5>3ag;xFbHcY>WB0r(Kt6|am*V+VRYF4KdUn)`X}QP8@tW&2u*RchOsL+UMO$Vh;il^ zJ4bq8ijQruH(Q<-yT{~U(UIsx^~y)FKIu?LVm~R54|)Q~O?opFJ?Oxc{s-mGol7jU ztP1+Evhf9*Ry(yc^Ah0H@(@5~48v^Y%5SD5f97OutiACtik7x@R(EWyZNQ1>yE%j@ z^&{|Sk06JF+Ww%-f1sm4@92?)eYPQuZOo?Ch!fdwvIQ%@d~xPhsB{id;H9)gYu4qN z|CybAX^9^MO((f#r`7FOfu=I~g2Z(32_CVW8RT%ovZZ!Q0La7Uu{+HvO&DY{G|j(7 zbZI!t@9Ts-l9A55vU@92fNu~%Ut4_wz)|x`@y)z6>zdP5q<4=X9m71#(U&-KF^U`B|I zmAUR}p?Q@(diE0lTeR~Dpc<9`UW9Anr+4bNoV|sw0Xf1)g(@8zCjg2grW;`&E&rpY zq!rWoEH3_Z!*UZPPt~ZytNN`b#OLX7^Z%CG{x5wHk3cl=-Ziq@>y&bo9 zlvmNdP;zPJ!jnK-p`GfbK!UU$Yv-wYSJ2^nItAL- zLjtmz{wQOy`Ie;xW2yd9Mf3d{(MMhdb0bKO*OFAV8`{8}F9%w((6SeeLxa5cn#YHs zeNsK)H=pYU!0H5;P$*$dHOZCPLMwVdkDWGZJp+(1NCpZ8j@Mn?M`v|f7Gz~PQY0J1 zmTNuh{aoZ5BElV0bj8D4?Ws)_wc5y(3iGgw3|vdveIY2_C@!gOC?52pm1uUSz_lmH zwDgN#AXP=4Nk%4k@<6IZS`q4D9hi=+70FjRfC%WmwqKcxwR7nz>v8#%GC~o3&tYob zzFVs1#^n;~;-;SOYy(bIZ=1x8Cw1HL<+rF+=MB+w{!5B8hka=5uF?;lHwQYUQ}^?W zE%SBQDk$z@FL?AQkayIHzRR$Wg!-b}UZ)5l+*Hp;Hz>Uh+hWkYU8{8h2qTWCmwN%Y zp8n3)2TDfQUT+-IU;8ws*L8W{#Nm{J6xp}+uUGnF zseR&W!D)t=$?mbxYzJwS((8 zWF1>T`O2t<3rVvuU5w)ghV134Ny(D>tSn861kuKYfvQcRiBc<=t_%r6B#bpS2#ThH zv9qH4wDnOI7TnrTfS3C#*(PYw4N_>6t23+aEaY zUwpvw?yGp>g|Eu*7F_^jm^T2xS%t!PWDa41QTu{S3lzVlt#@?c{>QcMQxRjpX?4n+ z0DiD?V{^B|=*r*R4&hO2Q)cql?3CA_DZDaN;zFolXd)1IjFqJ6(9nV*w5z!gwjV5o z^SJ(1QeHryPH6IAg#smMb0dc78j*_FW}NPLHh)q^BaPqsiLhE6udFTxkI}ZY@oF%_ zCM3nHY-cYi-hOxaNK4@nVyg)*k9^$m5^@1Hi!*dEz_h>_#E4FiVAtNEu~bLE@}~cS+HI++s{?n+9ucN@ug7W%F}x@UBG13=SfIP}RF^VCDao2t zpX}>v)E8^d2QgR}d@5!!@L4LAb97Us*EY4}wtaQD&Bn*1;hQLM9s5(4rYu!xH(!bn zNJuk$9@L2IF4NZ@%Ly(9_;^|?o_b$n8)!P`N27fcY$03XSyN^cL;jcRu|{vAcw1AN zH5c{RF4eJ_Hk|QV@WQX>D$)h4(yhEj3eyfJuIwtT9~^Ed`GndEeG3vTsCx~a zcS$PbxQTvIf`BN!KuNnIX2t;51;`gZ-pUxc=Bj7nsRNvJ6@6{4>Pg>cE)xo@t9P?2 zE-&@*N~wA_`L-)%mm5Aunqr&Dwt4EZWSPmedSK-~##hoss!MEAAZtq8mfOChX>*b5uxJDGpoS6iibaD|B#Z z)nn$bm0u?p-@Q1^NxgxrHh&j;HXq@Mav$xwG&n87^3)$71wRxnSb5Eh_Luu@-Qjjz7g z-8cmW$8!?elk>q%6Iwf38rR%;#3t;eW#88Ifd+N&#e`lEf23E_l8_!R1awDCC)F6{ z*j>R?K9{7fjLw>P=dtGOEt_<{M&Hl9xcthnvIoAL~E5E@%`A znjs`v%f0|uOl1i#X0UwQSAO{>k4jfPcb%e-BF`QA(LS?xII;?LK6JGUkw3l)AY0n` zLp)h7{-F;k3|`)s_?|cz)qPO<<4E8oAfV~SU((M(=xuj1FI4XKEiK?zp-wbh&<#p+ z->Gbd#t1mdB<^GLr=uIh$0WQSeW;7PS2b=G?>k8-tN3yu)pb>B+gL~KUPjFH7{8i6 z13Pi5erdHZg+F|G^Cr^+iM@ zPuEpw7i?$T>-u^XUp*^}x9yuaGK2R0 zm`0qOqSQ%lx;PXF}hktBN7f6agP*Q1eQ`&$FMStOt?i}YAIS%&+$%jAR zVZ#!SUxanm|6z(kZXkZVy!~!~=O^sU-ayy)*Aw#wx&VOS^V7edLbZuT-(k!-(+HDH zrxLYw zHjat1Yx~T$snz&=jj_OJa12~HhpK(Tz(jW`QqPG9I^zO{fRzqS?jLVmE=~9E%0}BC z*xJRCmeO`{(+BsFeD|x*=Fz!n@CfQ^4&}2pevnHrVfA(bT?~a@=^s{B3DkeAvujrBy8M~p^6Yx)uAI7@xA*ll zl3y(&NU^AAeYjEIFHs8ZH}2T(xrPq9r4HM?wR)9_aQmAkzR~KI)YhW<-i7DH&AWr% z@`D?aW4LgG(zH4yWfKdkc;yn8T4!Z%uvDToP;as+brjTeHU zMLz31L5|j2v$_^91kwiIBCI0gPXJA4L=`fvSo?Cq1+MnGZoDlGBR*>avwN0^yxWau z>l>y%0ely5V0>gPS6*{^;$VJZ;s^lj-1@~hWd8>Ro2{Z`by>e`wH;c}kl4b-47EHC zuB=sebI#(2dILiv5oQCt%AMd%jtOL5t24aWoIOgh1vsYNn3pH1he+x0alj@Ela`?b z2JDPX3^nqM4#ZYs){I2=f=tIq?4yX;H*?vwmH4%?jN8(32|R_CF@?xmy~UnP!;E_O ztF5@57^j0I=cUmZ%OH>yE8BUZ(+ejzo0nVu(eDo|9L!n;ga)O)lfrbKwIU`)^`h=B zV2k3#T)cY@D8MpUeD>PhxVqN!&1aW0g@-&Im z|G~vM|A&WpP9T2IHms{zQKIWGEdCjQ()h;ddEvJkX%J1Tymx_pUtOjDMh>@D%z92K zb9UOjExQ&61NpWTkT@)ET~L~P1|-C<(f)3*)(4)35a5(|&YzGM(Nj@doSMAh)70V* ztxFcGpCsTK_1MoNb;NzA7I{7L1N z{$t!hM$8`14c~fZVHofv2Y(cf&7?dUW0PNSgO9rEdVx2;gMi>iMZS+XsjM4)#oPA< zSds|UHT0)wQ?I?A8Gg$&CNwe_qD)QrZzbrRm@r;3w<_o zij#q}DOT_f)+Taa5dpq-bxrHJQmBU&b``=4hVY48Jk!IWuFDjJ8y>h0PxCGO*KB3nmdW&5lKjN~m0Mo#3|dPh8i*4e+Ewa%k=0Ii#hVEyi;K&&3G8Su;^KVT zb@Tnc!d`oDD7G!$x^;its_J?#E!y9P3a2<=23JQGa|{@=;)1^_y)tXN-B6}GWR^Bn zS9%3wS#NKgnT8SqbxY@BFQV@bPntwsBD7VTzHVZ2w4$3NvtcVV8}QZl^X2c7uH+q$ z75eTmX|-VDCo0uh!M=*RKv*@w7oS1j+=*ER8{#&CQS@PSmgY&Tlnie1jx{3qfvjX} z8<|FNF=R61a`_4oUJKms7v?o|chqX{dGOU0&}zgtaydiyAwaa`j{^si!kqYSV~}B= zhJN=G`;D&Nz50XKyW_9+{10vbDEdE92mbSjA03~kr!1PllL2Kesf+-;n z@kD`Pm)g!p8;zW&F^G3k#M_d7NppQGBhLes)4HNOHx8VKcfeo$6u|KEqz1-?enE&- zE({{+{FV0ANd9`=gY4NG?S$^n`R3UQ8H5=9;$lA^y%e29&XFiwH%K=}Q%A=!l(#`D zq4<<0%jntq*RqiH*L1jS(~>>VtW;Q*+>DW?2QP;K-nc(ev7Jqi*l!sZ(V zn?RL29z4vF?`}p-K{&4h=rw+!5j|LY^IpK@8O=#nJ=)NO zj>tKdu@ivlicX%lz)xVU;EQdh&iSy%(<#Y6?!|o?%}%R8TMHo!dqaooR~hlg1=wH~ z$x{T@uVnqW75kmJ;J7o6`)3Q^t^=K-CYMA*rY7=wN1IY6?xEekb%q9LZFG)XX8fR& z{6527!&DqK&+$s?bM2QQ3i_ft*)0x-^=cJ3SKj!$M(@OmzT-4kyB}KD5bqEl(kQtN zT|MXIdZ!#83@K?8zwlb*wm}_U8}HmoxI5N9)_ILa`S-N@vBnQR^eCUM#O@b}j>6Ew z$h%zBeU`CEE5=EFl>{cH+a7A_%Zp3ZszjK$y#i=pP!; zyu5bs`-W>`dWTV04mJ{JA`7_ve9!;y0lyOZd(D5~!kE7KdiXC8ooDu|*6;Q`$9~|6 zE*JZF$+-$7#0OE?;W_?2y0@f-thp959!IohjDAI2bGynmNh*bIB?nEQ^}0|fZXO;W z+@>IJgF$ZEByQ8?kpEhh*mZS*on4yr)^luSX)_JHAVnF@n}@{O>WHEj<8kf)3g+GQ z+Svz4*i->80B~7bXybf*891HokTaaNQKIl?=}@Zrg255g#V~A1oPl8bz8>Uw{FYB% z%DF%UpGs|M;{DDS*{oiyf*@pf4>`g>$fm;3afMbcX|HdDkD}W@AtwOqb^+?Cfr)*^ ztN)Hi`V+t}+WoP!2ft8Br>C%~2my5d#&Fah)JQv}2H*dR8kAYP*{pxVZM3?|pH^u9 zGl)cKM=_21@)$QJ?p~^xGFhHHpRd~%JO~hr9Nl}7o&O{jJ{sNudIKWHY*!00|4IpKLn6xzl@&k6w0lvn!ZMQ zd=R$Q+V7_iJOOlJpRoK?y%${h#fq`|!DognHptjzqRtaBVY$yu_4B+YIkm*oh$9%p zSU)$`zQ*w2FBuI#0sInU53T6V?%i+E!zpsIg*C~4Bf<9m|1aWph@!*df+4ch-mnnRV+CG=e))1_=B~)Wb<6>W;gV^pVrX^Q@v+i%+_^(uy-TxM4|JPdZZjOwSJ1aBe z98mHyp>-i{7f1lYoqtBCwEtFq-_7sJnxWHC={k=MuUOjXIZ{}X{1Hk-Px)$SBO1)N z1O&zn){fge7GS4FWtDR~_ArAA1tWg`E%U@b1lYfw56CbWF1Fn1x&a_BKCASIp)S%` z&DP<$UBlU(6Tn0e`cvj<+AKzlTH@%*E2+{W=B~1wmM&+_xSei4^amYSg2EQEK|4iY zQ>tdVDK196yg}-7b5vk>PX(7>>vFbjPuO~D*9Xge%NGI>|E}vhP5}SO9DM$z?(O>j zQs@7LMveG?$YT5fLUN_-Nm2bom6*N5-UmtP$R#QLJIXphF<{;bZ?vOU750$z1d!(X z;nZAZovIk6_y6Zj#(!m%e(6E({%;PG9Vk1oS85EFRwIJ1?O{fm@UTy6$)S;JDj)W_ zPg7Q$!8Q-)En%*j2yrTPJA}V<60I9@0{;Wry4KIXpxu|4l!S(ej~NEg9_fXd=A46E zxvu|U=k>!Az$oiN(CMMK!}Z}`j>WGxo@q$d3rrHz-G4C@z-Qa<-PiEd+d}qx+5AYtMq^zC3<=J$ ztAWa`u|Yl>F}gv8)HT&z#g3PN4*_RF+tqH~@G=e#vb7BGWv_f}w3M3z!&F1mXnk3I z8}ndsNm=6|g;fE7q+h_uPNB2j-0pjULCKu%BV16~;@3W20?q_CU8Z0et#bZ+Rn`{_ zsd1PRhf(W{l8lTNi(Fpgv|*Xvq^?YPgr38|1+2I@usM)dPP zM*QC{{~I8DBYjT*21wD>ii%%{R2{$3F-=f3dKK@I35}G)&(%?lv?_D^>6Ldp?Mi5yf@X9!?37^5atn=l-dBAyHOUoC@q2m#xXm%q3G#zsB4hM+ z938_A@w;Mii5*>`TeY9QQ?ja9N?45--5V)itSREZHNlh7qM1T%R{?orF_RZY5H}lc zi+N>sD1dzWRhh^{Hc=HiU1uM-@W{jQSwyHZSA>V5I(c(D_}`E>qfUeAr~ zFv;xX@FVhiU8@%N2J!ZO<5Y8a^5>0$YfV_=XU`WI(t}el^=Y4w6DS!XL;dm7A6r}r zjMDV7lQ!a>7QP&L6%IimQ6Mv-*)_BSSHhFx7phhTX#ag7Bi(GvU8rbW5(eL3s^*#b z)e84^eSU}R}NI}w<&8PQ1^uE$5v^-LK8ZhnTQ?qZjTVM$FmVhtKLT=4?AvFPrc8$dW|Dgb$^bg9W8O(_C8xU zEo1z2`MQQuflHQ+v)=kP^^nb~nCfHyjR7B5x7*T9Jlk5@d3Q0D3lhj-p=nKf4!MtV zbm5Fw1qv7OWc*V`5}PFD`1%0vSsE%GnDuOc{ zZt8J4@yits!rVo9A8>b|;W|CHcGg*)7?+fCejgGNmLsvK$NKT{nq+YsL(U3UC z-3KK{%4&8O>fAzom8vrb>?^Z*)cLK{3}aI1@lYPl(oJ*8fJ+IPUJa( z3->|!b>)-QA1b7r?JCsHEVNI3ZP1$ik)JPayMAR#NIE0Nl~b8G!e70qT~y!uoqsnS zc*I#E!)jlDwx)|j9V4dD=e>(wgc-`Yf{ycM*gfRU6-Q};mB!uGj0tz<&1UUI^&)g% zdFNjuW{2yvJUpXl7!CGko^)q-2z(Uc$1I zb*+%AlRCZ{M-NK2$bkA!Fv>_g=%UEkGo0dh!Snjrmx*Y7Gj3)|(==PF`Dt_X)O}K* z4|B;{JBpu(!L-l5_4;AN)C5vM72PxL!L~XYius4rJnwV2@9KLSV^QcO)!gF<{Vu8} zw^eK|R_kaqTr2m1&3u%ASTfT60sOAe%DY;%I6W+?FqBR+ry`M&Zfv88dF9SKlqxjC z$O_8ff1>5}&dL>`tqku6!r|(<Qb^7zi7Ii$e7+jD1wx? zN_>?qj?x7YZKom1Ou5>u?`(3aj4;vV4hzoK>swda!z5A&S3+8ivcGY*nlUafgYc^~?A3fTgGlk~ zXYu8F_0|zE;5Ru}@QlFNh}U7{b;FXKlA30I=_fws_w#}(?yY>kf3m_B49o%=k`@Ylt(fjqdkPRSLpeX zM=4{k9WSRIkXhuJrq*|hgh2$dmSyksjCPvPf_V*|>*0Nsp24o3;K?b5ZjT>)ojS=q9R%~mO0E8=w=R^Y$#7fGkAf5?@CkdPK zrtfDz11+DCm;C>e{tXeyoqMJq9_S0`@Egz`Sgwyu^;#uJ&{r5?^O5{;xce5|`KarK z$riIh?W%dWTPv!~j+=5cq+a&auX|Y1Te7>ePXD$N#e`^w*(3{VDmY=X^F)xbKi@G?*L{gLjT%NFzyA_Gp7)O5K zm7*N#dlL?U87*KEjP!@`eg1EMA73I~DPMTDZ*4oEP4%xlXEJsqdJ)4|YIkSpavf3J z={~I+c)5OHrGMGzfY~UY&wIqA*t&Rb7n)6Ch~XJ-wJN7^m8IS0vb;6wnoR3kv@7mE zr3(}|8ah0G_5`59-?LUB{FS8pxSq58Lu+~?7*szO(=#Bi1&thcEYv%H2wUb`Hb88s z>iV)U&?#Hr{nDH!#U+$Y;y{T?1h0e{1 zDbCk7&36Q@41;xH*vDcJMA8B8$daqhc2ff%l1c_0fp2Otsew$NyD5oU+^%$5_*&^U z-}H#H*ryZ)qDFVprNHSl!`IkXw?xa_>aD>k7pe-3&$i!cFhK7uL*i z*cz@1cv5Pf>4kqSxXIIi(aWuYc+HvhAD=>{l zN&AvwDQsMJoi}qtq7B(h!e^l1>ApDa1oE+X)p0C1(2q|CEH~X&kmn{&7|cSokQG;$ z->AS5<*--$4y0Sx-XhMpfj~ka9R5%SSGAxL%|?^=?#c3s)a3PV_a7NNy*XlCh^*zU z4t3q2(@7#$JTaO@5*qrfiwuj;4QL$S-P8$*eEjfz!R~f@?Z%3%?HNM9nsSPeBRrCA z1^m%lJ%yD=p=jU0euI%B;$C9gWER)6PJwle_4_IGtP`yP{$~c*@Q^?kVy>=q&;1Ju zQU*JOftiLXIoLbL&8jf5y@tv2bJrJ|+j}fbO3UFxd_<$}vS$IKnc@sPJ==5&+Ct1s zmb!sdE@u6*EsSVYgEWO!trpZR$xwFY2ip{Z5IzMNC7-2d_%_Zukhpwg+&(6vxThb+ zI~|YUQzmlARESHzKKH7Q?!ptdw1KEK!A7__`Y!P+MoGTMLftz?SDvJ8dxOA3G&2Xg&f*H8+WV)2|dr_b3^cHs7P>ydj}#= z(=P?6hYQ54y$71Bz4^z`*Ev<^EgL>%%=HUHu9P9W(53461Yv zGwayKnGcmejRmb3U39xCq;ADfu7LAU>wj!3A@oY}C}ofuw_aZwEA3OlW&Fn0I35IV zTsLII_TzB5n(FXmJ}@80;c4NVew%$oha`#mAZ2DX!CbCfzd#X(%Czfqgd%${-~^4# z@{G>+^M@2T!qH@J(jq-0*UoC~gr6B$0}Hp~`hI4ZN;m1U7b#&Md)G+eP`j+2)BN2O z9cMWUq&t45^Nm^5JTMcZ>=9;SD2lnM#7f5#{%O#Om370TbW`MDVDl38zpb(ZZ_{Vb z8g_rY$QlxN!EmJK;HS7-7^8GH6O*o}MMYr|=4-tX-@KnO=Bm3mP zXpP>GhfyCi(er4Lv+Ihz+JJ6fMZ0ePV1X}2i8o>w3~0wTkyEkm6$!5rX@>b{Z5B5Gz6oa$iu!lM2d#M2A>~l4?R)p@MBMZ%dk@=E8FY7*cfUZ zy>4Oxd81{mIX)hWn$AP=YkyR!EpJdB|SK2cjd|%RW$G@U%VsBc}ip zxhu&Zxf88c3?mtCvnaUoF8O9DFC{HDiwoY%?v*p_@}E*1Srhg6?y*qF(rfp!Yy3d4 z+1zobo=%!$c&1Ved#T!v3I=tYHA?ZwyLP}#@d)V6*ELKhQKUagTDl&g$Xw6@iABvL z#^aE_c@V*5q{*TYtD38YCSOnk8_Yzbi4}*!A>~JVWIW?4P3!1&v!1v`-A&t4F*g}Y z8lH4sYM_@{^kgIP`Rhqu-s>qya&(mq_*qXnhd9^hot^lVtnAIV3>s(Pz-CQI(2(Y< zRSR*QLdciB+DVi)Mz?h_jzAr~K|l9wIuvs2PG@-E0lx?cE-5!EuQM+xi+SwPS$? z``RcjRap|Q;UYW*yA+Ct+dNhe86{?4{ids?E*CV)As|rOHRcxH-!Cpv@@0lqYGtuO zbOsU0D@BUPb}H6vu)awEYD?UYPH|Ysgn4rp)7@ngVl%3usS)U}hV8SaV6SdS!|=rk zotqfEO?UH_)M`LbT{aNXB5m^VQeZgLY0(=z;nAWMkGI@hId$Tq6f|=1G z>M}8XbQjvLzK#3#xsmQ2S;s+cH@xDlYX#mA(c|WvV7@w2LzzdbpE1%u+gC;>Y->vx zv?K6C_z8sCWOz!t0UkoKSIXcTuMm>{Y-}21j~hn{M8?bYRu_K-EsBlgP*2+Rfx3Zx zq?C4eVonx+TRK!AX*{27MpZuJlqkSqGnF7)xak;YMVi-A?q^Nx=*Bvo4{lp791Oa; zYZwOR>ACOS<+p(ojIyjJruFIDvL!87ZG{+H)x3(D<Ka`n@sY zoQSQZMVZgQ0wc`+dgSx{czPw2xA?DJ{nzZ$jYYMz$aGEZhf?Pp`?k>E4qE4 z9WcWqU*bYPEj1U*iv%{%L0k=mwoGtB))B!BsO&D~un(6G3PWwW^kf)bc~BJ_7P=@qV2`bDC3 zKNNh#zE)>W0Ir>%RFBZMLkh8%#k>euQNOF4Y5|vvm&Ot2oJ_vBGm)(u$ME~=Dc-_? z1su19)GMA);?bF;6#l%qLGO~q5cMy|IBTt5Z(6@G(x8R&AQ!EN0kv+F!C?}~BOMdA zq!u?f8SuT=a4dPG{JW-(RW7bg+!$8#6J$DskYwPOkM*67u|)_Ry{YB?cKN58ZH`h$ zLh77jtN23gZ6qxmxbk5C8>NK9!soZ<4gXBL82=;flJ#fWh3BfWlM{!lqY1G# zBSs{X!K_JfSZ;7FC(3NB;)biA+r6r{X=_O^E3f2F35o<3NwjWq`^UxxX#-p7K!5~41vJ>ix|1Q%PB+H<~ z155Sk_aO?=%KY2$Qp8M4tnrls4gVs@wJ7llNv&zuf!y>dV2$IP5JxO5zTKD?#7Mhh zVzteRcV}T6?UrHJDLDwF?9qJYXc>%k_%h$OE{RRGpP-F1TY3SyT!xe7SEG^!CZT!F zB?Si^ae&Q-9LKM?N=a)r%I0+MZoB!P|KvXF6yuf~kg&q%AqA5WeI`~w9E+&-N~#}B z>B1gIYN|cE@h^fq|Mt_FKaz9*tJ7`nx@PQ$=FP}L+MyH&Rt9ISy7KC>5l1J08!5b|HLX7)M7(hY-M}?3^!EiS|t`&B+;ST+YQ75+(D5C<$R~XZ+MFzY{>* zT)-8lf;WZd(K;6R@jQJJiWSxQL6L1yF6*Zl_??MwW)0WN$@R1f>0o}5FRtVDmpZt? zw{>3cAjMqVv%J+GtE@ORe!4+6B(K+j6}U-3m9|ynN}DZ|pu~L@-^o8yTapIRZ8l-` zsWp%z=?jjNJmm2B-5;?vy0taj#59XDGyzT#k=u&sF+q&4wdyeA$97M}$o-3186d%a zU~p0FTLQNWQSt%HJ$P-8tLVm_{}`(;=w-rD4E=)imi)I;q_EK>K?+V_#o=(-D{zqf z@9@jR8R4AS6lj87_6g4r1sTnG{yX3N=KR;4sjtXsF>z;^TSQ zFj}03L(1|uFk%LBJ-Ml-CS9a!t{SZdzO`V>6LO9u#_W+dFz#X<)!g*H31xF9UI&^q@QV$G1CQEC>yv z%T){SoJ?sQXB6FM+5N{Xoa8nSg}|my5Ig(@S$;#quPifF<6b-BEh}O~l z-v3pl{gr5knXP<#wk+Y@wfZpzdv{(qQ`~_xhBcJMfYFE(8L}xjbMrV{we9%D{2hD6 zOLlVQkr0Ms&5_D$8(eQHs^a8{?tvo0%egwn!XOZ)FGw_fXjZ492vrrl%hUWfV0n;o z8e_qt-X;8%U>7DHXYhCG`ZXYy!OjN3YqTVfHFHmwKg%BLPO@Yfn%T%VL@!3`)kctDAH~@0vWh!>TwX<&oZY(Znz;s``97lK-D^{Q}21E@KNZl{cGr$7zI` z?#+pstu_Z;8T@}g+;jxOdve-N$S{oj}^QRTIzy`BbbNHyz-|$rf`B+()fgdp(<~qUe zFRrA=6%(F~ms|geb4(Y<5HSRpRN2P!RIfp}{J)jxZi}*DhHp-zm zHNe6b*k7A&;LFqS{dWldPob7k;w6?jYt-)6=u67-w~;p#SmHUNtzl;SZ(g=$yRfWXnn%E z5z0kkMWIk>*uP=(yJK`5MPG#W`EO$2`}(<#tk(Q%8}8ddwDOfV+*1b`3UJ8!bhkie z0;6#=CLaRXn5CzC-=g95mTR*S;S?LIXtX&6)O+yC(p1TlEvX<&z)JNz_O)tzNPFI+n6s)AdTVs0MBo5QKPcXPBlbzvw1d>pXV3LzCA0DdL#xLeceh0sn%O&*Q|Ux*ERw9eoBxn2jLy0@^5&QH=2P zthgyKUcn=HIqdNqS*sO$1%9|jzP0jlQ%2IuVN&+kl-u*PY-mDjgQc-PVaF%eMW&x0 z1B{NfYxo-t3jUNzKRp?#lrlmX0J|O15c^wVV;YFFp&tnZOl=_}vVI1bIyi(NVd6El zfzQy7$G&ri&(f;44&dub_E(e=od=OaHLNA6S4uZcuo(#0Pmv4$;okL0QM3g7q{dOA zuTte7ChCt>kQKv^*zJR441$vEEpge1Bll+Dz3XCkB$T4Ridov_In_ut4vY_GF)+dF zG2v3oZ;yw59I>li3zZ)Rt)vy}$8D7PlrpQIl~2(~q-=b<TkoCdxRCpT2~)#w^&g1qKraq8yN_mWcjmy9*@lKV&qkgt#X<9W(p+?wgp z*$*x~!nKD2lgD=lyq-qdcY9u{Yk!*FyQsDF>2?r9UhQH$Fyp2V+pbbg3{6<1_FId%jhUd4VQmza{UF3vB2zH<%~T1Rja` z>5vcDjt`sI(;-p@vRkB79e~M^qMZ-SpZ+BAw8jChcpj?9}oC6 z2XEBB&D)nTtzko~;l5XevME%_y6n!^7>bGeMO65+*l~5cd{8-Oc+{-2jMsZuR_m#{ zE`{zC=qJEgW*(U0`ko%of1l|UVq{{ic&Z?z9*}{o&w!Su2pP1wK_GJ$&;Iss1|a`u zWgoBm?*hIve_KZRyM*A65=xZJ-vJ<3J^}DN>slo&1?eq^D(O-VuljBP^AzuGrnc$J z)c~Kp=o*SX^N?2J%w#$D1aqNEAzN;&V_%tp;#f(&J+#NbbJi;_ccYoBay95}meAAFm?Y`dwGZReYN8vhuV_oPImi zUE8$UW<|BHtsc`x4fWU&!+pEnmkbiYBw`YMr=wE;sBt_S9M)w{-&nqpX(c#iqg2=& z`2oV;{J?-(vA`P42P`Kv>%ihp|H+5gq4wDba>@sxk4?qnOLZqB*|r0XhS?k~z%#RQ zcr-e?i@>6vf0`K4Tsb|kPN@j=l$5W3J=Be?<|Neka8-u$S|R(eq%$eX1sR6u(+VRO z>2&RWApUdx61nbKZ)jH`9ZtH;Rn*hgZ((x4A!6Lc%cn@9eS2sZm|H7<>Pnmdc*VM( z*5%@bdYLgiXIzai!3?KmphM?Aom+0WxW>4y1A9{q0zsAf`i^@v6(L|Y>cjgR4?>qi z+;n_`OuuBKBec_W@tD=>nH)%FjFFx-J_PURT>l>UZzR=H>#I6skL9@EzTK`@E^lEI z!R(mp!$Vk*27A|fB!Ro{>`tHP_^YYVHILDX{R-vOCtFU|F@S_l{gFAc0nsS>3_6HgKUn?-|kI7~-vbS=JRxCZd4fRxS1rNA=~e zLfIout?f@I0IcdMfBuqzc9rF%ziLpfOdirUlJpfL#Kyo#VMB(pz6LPw&(N&=tN*@` z|NQYUdG~M1e}B#WU2*ciQ(;VJy@1YZQCv`qQW6l^jt z!#6B|^FbqOb18$fTF=)c@2s0e+a0VXsO1d+NoIpZne*k+6H+fUs*2NRSV4QA-9t)d zV0<7(v|}hfG=nm}_`cIKxs~FD{n@N1vCiAB;%miulNsK0KUH$m-`VXZ#V_$-K;nhI z;-^oNoB)zOa?qRCi0Y{Bg>^{ATmOuameqHd6{n?S7ZB>(;DXjiF!J)G)DEeNdpKhq z38MQ<4c}u6zTCO&z2PhteCwN@#;`xV$akypH%K3gva+=WMJxHVxau*a6agiW)d|Cv z*S~WB%W2PD`mkd=YVM_HG%gB*L!MSWxvpH`r`}&vbFd;<6B8MNM(h zgEe=HYd^-&-WY#c!Qrcmu1{$x%VrLV6-d>ey@NRDnRmM2j9;-fws@|&a50Z>ju?z$ zptU+P?jeQ$pr~S@H92PiD%&U^T$_rP!W&+Th9$+smt}#pQE#5tNL9j{#?`fX|F8DW zGpea=-TS)T7DP}$LArE8l@NMy1EC}#^j-x*sG%bQqHd916G$MTNeM-I3q?ewm(Y7x zdKVB;Q1Q(<_nx!&xnuK=d){%!{q(L+bFO4%W~?>mT=RLJ-}9eh^D%E_#YRvHF_Gh= zj&km)w|UdUrms}3bXwR~`z(m0u9iK;E#4r?l17Ov-8#Z}m}n%if`g-?{;PCkRg6Ne zPEs4vSnOkMQ&`z+Z|yvajK)q1PtU!|?xV4jqXjWx$0=wKOeIVInxI%68EqqjIt_LB>orE+%a6Rp@4NfUkjCRTb;{ z-e_-xIm6dqdn>Is=tw;ac`6>cVgnDimgRP-0O`1sL9t2n(-c zIAVo(4tq{bA}+)f`&Ct&)&ztp(Yv0`XHL_sx|s5-CgdimKe@`=O{E&hX1cf3i)v1A z+|5snJCXznyRnW7hnP>iz8I0-s_v0(hYbpiM;dpF86f*2{g*wZZTnJ2dOo>|SGm}< zIrTUYE8}nZ)zSFEnc`lqa{G}SiDCA}piH^plH2}xU2X5SqE1{{M*A+7X9xeJ2aLun zg!PHnEz&a4FUJU)Keuoy;y5(51e=%CPp1iRs*qd_3soBoatwm$F%+XVGIXs3$_{J( zc`gF3cDW_<&Z-0}hld||QE3#yB;hvsa1O1M3UkEzt-H`N3k(Nrf5CIKuJboTO&nWv zP^d6RF%A2H%PDrWyzBQdqDj~s9V;2PVa#Fdf?-skHR^`p8@b-1BDAJp zqM#wn+=^7W{SE}5T2^~GCbpa{DYvnm@S%Jq5Xm5JAW?*tSnuI4Sye&dlmLKl8Y7pI z8Oj73qH2=D!3eNgL5`O-T$7av8{uY?JBWB#d;L(qDH@b2oF%->W9U0Tp7f&Tanhem%(F1&j$g+acV>*SrNEKGICGm;SIai!H)nw?K7s+v zqPG^Hi|*(Bnu;&*Pab24=Ya5-ysUURTX3f(Z>-u^Un;C5ufg_CnuV2W6vSNGnMXP% zxi_`FCG)c1K{*>SueF-I#F`R0Z#HKyorvr>O!sbBI8}dSgQO99Uj9u0^Wo^{Ia_OICx0`}R#ZD`D|1m5Q#d|1jarrYKkLRo&BwM!tDw}2e&h+ms^A`$J zrXJ|J1Y+LS_U0IL8ok_zt5|WX)vf<7Cvj_tkoGX$uTLY_LOU`hIVR-CwAu^3!#BLm z{kzrQQ{;MmWQXp+8k=R?w!D^n(pK3NCWyD? zE&dnayP_tu4>zFP3&Zub{nB$gk}m6o-lJSFlR3I=S5dNM;KwV6Q}MZoX`)(*1v<>e zagTe<>WO9vcTMm9ZTA@{6>X<4))Jth!7N~7juc@Z#8~Ve4Pv|b{!P4%CT`}z*a#OoiGIdk6hk%4qtAT!C?Bft zbM$m|tv+>8fwiqV&G`s_Nnf`>XK3Xz+Hzy->$fjAJC5r-t(NFnRH-@$r$B2*fU?28 zRcULN#cz6*PM@7XBMmp)CEOuqM@$LmcXHZ2T_rao@?etAachPgijf=q`ak?LXFP&q zCUm~KckFc?2RE45Cb?eh2%wYtLc)vS=DgP-C}(Yx85h#DBSyeV+2~H46ds?29GqOZ zWT6zdxB2tjwVj3#1vbpLB(^H7)J0ccnuCxAb*`E_QLE#)YEVD%B> z(CrAZ7hS-HDbzJKmd6kV@!rGJ$DFiP;}R)9&%pptM}UzrA7V|ON{36dBq9BqMlG=k z=9)22T&!2`&ogsMJ$({mLrzk*$jXfQfN@mHA(4@xX@^TDK7H~- zWWcO>BaUy_tJ*jD9bw0Tw#U`ol=~2@3*c{#@Le^uJP_>WM
52VT?L)hv^w5v@& z(-uqQk1FaMthU5oyr8GA&wmjz#V26{P2MUpV71inz#nbra4#UH`@C$4bW#%`*<}TU?#4!%xqT(6`BVeCdhp1p}7EeK(LxM)f z^!-hevciT4NW#Pl!;$fuB7rCZuz{VqGKFM(- zueq2J#fH%?l<)&f-jNLy4HHirs`yysIo{i;T(=X;fM6Q1lf9r-QZeO1N<7bq z$V!l8Ur#J0FQheLauZ&~tuGj5v&1+r{_a7hwTo{hQ1O*WWe<=QO0FfpUAf!Wq(E5~ zl4TqKB+Ll77woJWBq?TGk`U51EGz!D)LXkbNv}F~0@ToFOyyezgE0;{(CJMKtI29m zVk+XQAJQl>6$kySdlH<2S3g-CG>g{<499M6%o%o6r{wZ*m`I_3_(%pzJpYHtDhEjP zz#gt8?O_AEu250~dKUq`FMO2MtF- zI}y-?cHB6`&EHp$d`!|uvAk|A)hJx?n~s8KI?D1pYEqq$JiY6t)IPoq1lP8OT(4R3 zsO`sCXpJ6 zrW!f@>IP91G6mrl&sL3zYhk=>uJ+7$*zq`)TIA}f4vg_wnnuaNPTIgqyItFR6_)}z zY^t*9hM<}0;k=gAga6h3`p^2m5fJ|S6@M7>f67DTc0c3S|9<+SRDxG`E9%EZ$DNPY z32qLK4m5aQepuUk#qTMzDu7NaZ#WCCR*>@Ym82XJK7ZsZJ{=1B%g6=#{}b;2nGWz@ zZp7LS?ay;zWl{IdVJG;Pu_}GQ2Ez$tr2Y&3#%Hx-vL5t2zqym+u1JE2VgRib-tB4a z2)A=rv<2Ru=za_3{Ce; zg`vhrEh$9c0|38Of*7l-h+oJ4H3Fdf3sdJdA%cHZ|t5}tIL6T;8mx->JHAX-TjW@1u3m( zvy2AK=mD)qY0zYt8k{$_>)VykbuZdmIg6f=?l^YFc=a^yazKydgm+K+?%Fl9=>{n7 zH=_yu{6-7CEGW0?)CzE&-0LGYRJoaH*P)CvvY(W+z(!gvAR03)V@8+DM@ZN=hC(F6 zG9h<TO>qA5Og*p2@YjC<6pFN{F5t)^SK zKFIg}J?{jKp6YAon_b_2C9LRj;wN1gjvqwxbL^%-lS@Cr@`xcd82T)q*vm+G*Z$^A z^lVVaQL`v<0KPe?3sL2TuN%MuvfT&p{a^}N^>?pT1)8m(Jq#J*+m=9EE00Q6>)3Ua z!`&%eF-=|!A1I!ZF$uq!NAzIP8qRb8VUjfv2um8JQ?A;iTO-%rF!xENF^4AE2fc`8 zHusA~P2>$cUDhCS=?mp>wV>~Zh;xC!xMKq%{zvBE&RaI|=zZZfVp*D-wFl3oHOcOp zqha(*7o4ocmIuNVjw;|)Y)q1F)J*ftw{+&#)Ut}`t;DF8xA2$Sy7I+4sRWknOSTZL zWE^VkVjB_=QsJ9c8)g|-rcu=z>nfxz$D=wg>@FnL?^neb&dU1A&f<^4%hMx7?4$}3 z%EZlL!(+(5g?QA5=@tTgqVuKKMICWi{Ub#y9`^4V1||EMQnljxn-se}yKB4RSB0li ziOYYl4f7n;GW9J_a|io{djKSkJ$$OLy4vYf^xE^-80> z9+ltCcr>>ATgQ3j7K1y+MF0vXuIGkEKJQ%ulCgVDAgcT_g$&MnZY+?TW&L$3(rh1PW_D%f0qD zNq4c4)5lt>3L%&KE1=W3rNn7}4QqO9!Oc~#ZdYAy$gfr%_vQ005A57otcw)MUw}(e znaUi4lvL{@7(X(`5lMH8_cu545VQ59O*iy`9n^Wej*&DTs$XEQE3un8L3h&a9dB4#EPBjwXR1Ir znI_f@&pIRw+P#!wMvhV@MisD+39XP~qs>VXY&_5tP^n@xBy%|PbSRkx~=n-(HvEnKfwTVT5;O4_|eR->WxxG*D4|?NnJA(()D7& z+NEvVbg0ZCkcR(4lz0B%60Kjig;CN5J-j4#?8klf_Iuo&n>)gS+aHA&&E`bMx9r~V z(Y%&INqflR^qj~ah&4s7yy$I=q;d;APh<+Dyk=H+uUFWt)+XgHa)li2q8X<#7F=7c z1U{}Gfw~}dJE!`hpkh8CI((LL`^<}fOa6dUss4W@2R35Ji}Rjjvsi8OCy6nbs7%*x zaoR}iGYaMA4k-V7T5fMBJ{H|$D!5PXpMaonc_fhI^`#pVMp^VU(@u4xe zXs>2Cm$gG8t#CizL8L!@bW1Q^VSRvM#)kxTL9A|UTqooHzX}Lb4l=3xhH= z(FU8Sc6Rn=u?}T2Wl#;C$R12$!dPF12uY7GUf<>fb^~+)9_(%ZPhB~URblLuuc{{` zaI92pv?-Xevj*v3DN1RNQ7A z{q#x*Mq?xn+UYe8TDsjygnIb@B7pr-bJC{kTOjFunFVODc*B~pXH}}$mlva}qIvtI z7B}vE#27o=$ynX(x8L-Uud8rR=0MJ)s9S(Nm8@o0ccTXmZzR^&Fw@b~Yb2RYUw^Tv zT+aRC71r?*;o#Tfn+G9R+f#@2Ha~PN!!j7UA$U@=dbZWe$zzkG83(TR`T)9gtxa;N z;?P51>We+Qs|eTnp0&kzO&_?iMVfY?zA*COK*%%?2%yDFNQ45R$B_y4ci1n7Dz~O)z(kwEL<=Rl z+lXLfsB~AaTr4s>y`fa4`TVR+H3tdfZHn(s*g$V zuG^=>x|yUnn4d;(#usXiT9DVYs@7!sy|+k$|@XbN+F7Zu zwdR@7c4h5_)g^{o-~eHd#IVDI(J2yTNx?@e?niG(T4g7CqaD6}$>vZAwl&`Z#F?rd zc568eJ2t%0XnnM5wJYqfq8gyT9ee}L_N7DM=Q&UHd;1Buf1X?3BMR(Ox4G8PPsDwd zk1`nNvGwKZItgl&{Xu;gJC%Nm&o}l5m*mZ4(H4DXo-D-Sldj7FktgKvXPd^cLT)|B z2Y}_1TQL%d?6FpKSBksfYhOeMW=hKmzr}FudqlIO|FWX<_9Xq z>J+fmEb>YE7b1-LL#Lf{(AAjR|cIa0kA3zEK2CFyVv+QoXM`Zfk1&kDEI*%}m$u`FUoGC&#F6Kw5z6Qh1 zFZ66`D}m3g<$B!wNQ~#>^x#-y|Ac(EamI1ucBRSTzOn2sa#dJ%(lT#(R)!@7@V{)O zhqx(c3{p(E-T1qw@%^ED^nT1&9xH|q%#vnR2qsCV2a;TO1RwwUWIZtN&FK_HdiO~D zU+sZE42wV6n*Tk`n`q1bHu`06_$PdZ+UTP38MH<1Z^ma}q5pdq(fIOzPz9B2aSiy* zSV&!luOMl2G*qZLu($Nqv|xyZJYtcEJZ1L=KHdV>mDLpmDuTispW zMi5hF(i)J{y2VM{nn?0U-Z2~3u_Bya}l;?R9q&Dryr>FVeVGmd8Zz9Yi8 zq-yc1LUVeHc|bno#b_x813D;D;Q00_ILsI875w|MuV}g3!S|{dt}J|kLX__>U{9^m zYC-347dx@Ld8}6*e}$sj%6%o3?YCzf&Y<(0hrV*WBWT=ju*fPLDL`(OWH>tVkA=b2 zOD;pYNZO6sbTub(2G3Nr@Vra_X|PNN*PvjKEymUUs zy2Gsim!785DTRLHch%oT1~Z-oRo7apgA56oJ&9J<9MVSq^as_K<57{Q=2B<|`9tFv) zrp#BL&T~4vBV`KHwn}`r73tmYo&CkAdlc_3{HubO0Y7v4So2n2>GkALrCBJtheBHL zMn(qh_tWjk-!|LV?gefY2PyW+ZO0VL?Yst#kLJ9({hCi>LR=Lt?*c>dRq1j|-`n!l z`BP8(7c^7E6I!`#X($OdF2~!iko-AbD#eJ)hQ<%ym)>NSHqheZC1=^n0I?022P-=Z zw;m+gV^6{eoAMOOboPMMm_N>jm9EcBpaIfPasB#CpC#Jxd9$Nn0%9AoGf#a+PtSk< zh|&JG=sakxmR;>tdqq@%U97z{DNe!Mk((`C}ir{B0Uw#zN{?mPJ_>)UE|Cn;T5ofk%n zgeYb8zFi4Z2j)m|^QPy-ZgAPt1s*BsneeH4RFN}}fl(4D^Ya-q>coz$(#RuRu}C>t z{J^c7IfIWzDtT??E;)Qcf0zwY0>Gh%ARFv5H*@9`yLGyFcay$5%yQY&;Ixkl*6_Fj zc#kt%I|9viUH`pkr&Nuc1=rJD zq%^^cT==jzAZbxF(wkS^%8A*)B0kis?9N_t+I$q4MiWdlzhF^{U&`cT~PNX;$w8-Qr)r+T>_l#%Z_R@JyLw7BO zN4ojnYny`zruk`YCnI(0Bg8O8YwlE7UZ`n(dby)l|JOd-L34Xk$d~*6FLjJ!axpqu~{<%F{t{&vvp*bpyV=r-;nwh+RpMM3vtfdQ zQ$d5epR+lv(!S`K{T%*LeCv8w6jfqRLtMd4Fd}D#(Szq*u-9QPl&4(JN0CS0>l!58 zNBvY7fHB-Iy<(Ia&#)tU{jM%uDeH^aWN^;p7cR%of6bOl349fQo9mDoD`G0sC-FUt zIzzeVk{w5|?yMGCmb@lpu;7AH*3ImwDS90_oM`}IZ1o%LJKugtOABFQ@2XPhW=0Jk zP`lJs&7irCdmZ!VeDO3dV$!GAfHU?6W4%3@Mg^D8m{FjbR~kJtf9v(|+&wT>9bzyDKOdhX4Xuv@R& zQllPM%6IE+9wJJ>72{5egLl z$a)s;-frD8pEd4+Oy6K&V!m2*NYQsW%KqGSfB6mfg^n#iVvZYHONtp2MWuodj#J(* zZF%3x7?WsHw4>XPwDW6VcJ!~;j*>7nW^BteTwgI&*|^lhVP21e&l{M`A8emV=sJvM zh;-I!Rr)P6i17S82d*-c(-v3rzOupBCdKOPn{jondQI70S16&{@kSBb`zOEnSVcR! zUP5i}8vi_Zh@DeDte?*_norMTdGd_|y}o+PFn3<5dtipNfbeAhgUkJw#3)_a_ixVi z1GV$J1N*&U32PV@JkGvWT2#W+pNk4gGodwY*q0WmO1}Zxq-uc%?9BEX3VvXM0m)7> z?Q1=+~VFtC^RXZUbGo+FV;4Z4>fDmCj% z^0a<(Z@u7pK;Te9n=bh>BbCM;Y4jEDGxO0L(kecg5J@u4Q@T;g=f?)F_T6Z!IXj>&_{Jo3e8!Df$dirt_|6DLExF7M`W# z^gn$OOL+8tcQ&CN4I61--2FiK-BMzDYBFELIZlFp+MKkcU6PE_0$Lb>lSL+~0zkah zvj5N{WsAyQ*sC29p$0rLl+urs%QqDY+8(+J`gKCjq_Kde1?)_NM`vF`CKKBMYlhgXZ6eeK&Jnsd3tsee-Cd_bke?a!GE6frflyRMO8@^_ULalpTF1sO7rQT zF|mKwIDM@59Q3?qnEv^lC;${@&n@mvcm1rO?0+qwFLkG literal 0 HcmV?d00001 diff --git a/x/bep3/types/codec.go b/x/bep3/types/codec.go index ec9f2229..8ee32588 100644 --- a/x/bep3/types/codec.go +++ b/x/bep3/types/codec.go @@ -12,8 +12,6 @@ func init() { // 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) diff --git a/x/bep3/types/common_test.go b/x/bep3/types/common_test.go index 11fd749e..57601d1e 100644 --- a/x/bep3/types/common_test.go +++ b/x/bep3/types/common_test.go @@ -29,7 +29,7 @@ func atomicSwap(index int) types.AtomicSwap { randomNumberHash := types.CalculateRandomHash(randomNumber.Bytes(), timestamp) swap := types.NewAtomicSwap(cs(c("bnb", 50000)), randomNumberHash, expireOffset, timestamp, kavaAddrs[0], - kavaAddrs[1], binanceAddrs[0].String(), binanceAddrs[1].String(), 0, types.Open, true, types.Incoming) + kavaAddrs[1], binanceAddrs[0].String(), binanceAddrs[1].String(), 0, types.Open, true, types.Incoming) return swap } diff --git a/x/bep3/types/events.go b/x/bep3/types/events.go index cb320386..298f3be6 100644 --- a/x/bep3/types/events.go +++ b/x/bep3/types/events.go @@ -1,13 +1,13 @@ package types -// bep3 module event types +// Events for bep3 module const ( - EventTypeCreateAtomicSwap = "createAtomicSwap" - EventTypeDepositAtomicSwap = "depositAtomicSwap" - EventTypeClaimAtomicSwap = "claimAtomicSwap" - EventTypeRefundAtomicSwap = "refundAtomicSwap" + EventTypeCreateAtomicSwap = "create_atomic_swap" + EventTypeClaimAtomicSwap = "claim_atomic_swap" + EventTypeRefundAtomicSwap = "refund_atomic_swap" + EventTypeSwapsExpired = "swaps_expired" - // Common + AttributeValueCategory = ModuleName AttributeKeySender = "sender" AttributeKeyRecipient = "recipient" AttributeKeyAtomicSwapID = "atomic_swap_id" @@ -18,13 +18,9 @@ const ( AttributeKeyAmount = "amount" AttributeKeyExpectedIncome = "expected_income" AttributeKeyDirection = "direction" - - // Claim - AttributeKeyClaimSender = "claim_sender" - AttributeKeyRandomNumber = "random_number" - - // Refund - AttributeKeyRefundSender = "refund_sender" - - AttributeValueCategory = ModuleName + AttributeKeyClaimSender = "claim_sender" + AttributeKeyRandomNumber = "random_number" + AttributeKeyRefundSender = "refund_sender" + AttributeKeyAtomicSwapIDs = "atomic_swap_ids" + AttributeExpirationBlock = "expiration_block" ) diff --git a/x/bep3/types/swap.go b/x/bep3/types/swap.go index 6fee11dc..52a75f6c 100644 --- a/x/bep3/types/swap.go +++ b/x/bep3/types/swap.go @@ -10,16 +10,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Swap is an interface for handling common actions -type Swap interface { - GetSwapID() tmbytes.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 tmbytes.HexBytes `json:"random_number_hash" yaml:"random_number_hash"` ExpireHeight int64 `json:"expire_height" yaml:"expire_height"` @@ -59,8 +51,8 @@ func (a AtomicSwap) GetSwapID() tmbytes.HexBytes { return CalculateSwapID(a.RandomNumberHash, a.Sender, a.SenderOtherChain) } -// GetModuleAccountCoins returns the swap's amount as sdk.Coins -func (a AtomicSwap) GetModuleAccountCoins() sdk.Coins { +// GetCoins returns the swap's amount as sdk.Coins +func (a AtomicSwap) GetCoins() sdk.Coins { return sdk.NewCoins(a.Amount...) } diff --git a/x/bep3/types/swap_test.go b/x/bep3/types/swap_test.go index fb7fcf20..c9eae7f5 100644 --- a/x/bep3/types/swap_test.go +++ b/x/bep3/types/swap_test.go @@ -128,7 +128,7 @@ func (suite *AtomicSwapTestSuite) TestNewAtomicSwap() { if tc.expectPass { suite.Nil(swap.Validate()) - suite.Equal(tc.args.amount, swap.GetModuleAccountCoins()) + suite.Equal(tc.args.amount, swap.GetCoins()) expectedSwapID := types.CalculateSwapID(tc.args.randomNumberHash, tc.args.sender, tc.args.senderOtherChain) suite.Equal(tmbytes.HexBytes(expectedSwapID), swap.GetSwapID()) } else { From 23e23fdaaa7a155d2462cfe9e1418950d941be8d Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 23 Apr 2020 17:51:37 -0400 Subject: [PATCH 41/45] fix: restore auction sims operations (#465) --- x/auction/simulation/operations.go | 112 +++++++++++++++++++---------- 1 file changed, 75 insertions(+), 37 deletions(-) diff --git a/x/auction/simulation/operations.go b/x/auction/simulation/operations.go index 503c78b9..84f2297c 100644 --- a/x/auction/simulation/operations.go +++ b/x/auction/simulation/operations.go @@ -21,8 +21,8 @@ import ( ) var ( - noOpMsg = simulation.NoOpMsg(types.ModuleName) - ErrorNotEnoughCoins = errors.New("account doesn't have enough coins") + errorNotEnoughCoins = errors.New("account doesn't have enough coins") + errorCantReceiveBids = errors.New("auction can't receive bids (lot = 0 in reverse auction)") ) // Simulation operation weights constants @@ -72,10 +72,11 @@ func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation // search through auctions and an accounts to find a pair where a bid can be placed (ie account has enough coins to place bid on auction) blockTime := ctx.BlockHeader().Time + params := keeper.GetParams(ctx) bidder, openAuction, found := findValidAccountAuctionPair(accs, openAuctions, func(acc simulation.Account, auc types.Auction) bool { account := ak.GetAccount(ctx, acc.Address) - _, err := generateBidAmount(r, auc, account, blockTime) - if err == ErrorNotEnoughCoins { + _, err := generateBidAmount(r, params, auc, account, blockTime) + if err == errorNotEnoughCoins || err == errorCantReceiveBids { return false // keep searching } else if err != nil { panic(err) // raise errors @@ -88,27 +89,21 @@ func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation bidderAcc := ak.GetAccount(ctx, bidder.Address) if bidderAcc == nil { - return simulation.NoOpMsg(types.ModuleName), nil, nil + return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", bidder.Address) } // pick a bid amount for the chosen auction and bidder - amount, err := generateBidAmount(r, openAuction, bidderAcc, blockTime) - if err != nil { + amount, err := generateBidAmount(r, params, openAuction, bidderAcc, blockTime) + if err != nil { // shouldn't happen given the checks above return simulation.NoOpMsg(types.ModuleName), nil, err } - // create a msg + // create and deliver a tx msg := types.NewMsgPlaceBid(openAuction.GetID(), bidder.Address, amount) - spendable := bidderAcc.SpendableCoins(ctx.BlockTime()) - fees, err := simulation.RandomFees(r, ctx, spendable) - if err != nil { - return simulation.NoOpMsg(types.ModuleName), nil, err - } - tx := helpers.GenTx( []sdk.Msg{msg}, - fees, + sdk.NewCoins(), // TODO pick a random amount fees helpers.DefaultGenTxGas, chainID, []uint64{bidderAcc.GetAccountNumber()}, @@ -118,60 +113,103 @@ func SimulateMsgPlaceBid(ak auth.AccountKeeper, keeper keeper.Keeper) simulation _, result, err := app.Deliver(tx) if err != nil { - return simulation.NoOpMsg(types.ModuleName), nil, err + // to aid debugging, add the stack trace to the comment field of the returned opMsg + return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err } - - // Return an operationMsg indicating whether the msg was submitted successfully - // Using result.Log as the comment field as it contains any error message emitted by the keeper + // to aid debugging, add the result log to the comment field return simulation.NewOperationMsg(msg, true, result.Log), nil, nil } } -func generateBidAmount(r *rand.Rand, auc types.Auction, bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { +func generateBidAmount( + r *rand.Rand, params types.Params, auc types.Auction, + bidder authexported.Account, blockTime time.Time) (sdk.Coin, error) { bidderBalance := bidder.SpendableCoins(blockTime) switch a := auc.(type) { case types.DebtAuction: + // Check bidder has enough (stable coin) to pay in if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin - return sdk.Coin{}, ErrorNotEnoughCoins + return sdk.Coin{}, errorNotEnoughCoins } - amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount // TODO min bid increments + // Check auction can still receive new bids + if a.Lot.Amount.Equal(sdk.ZeroInt()) { + return sdk.Coin{}, errorCantReceiveBids + } + // Generate a new lot amount (gov coin) + maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementDebt).RoundInt(), + ), + ) + amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above if err != nil { panic(err) } return sdk.NewCoin(a.Lot.Denom, amt), nil // gov coin case types.SurplusAuction: - if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // gov coin // TODO account for bid increments - return sdk.Coin{}, ErrorNotEnoughCoins + // Check the bidder has enough (gov coin) to pay in + minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementSurplus).RoundInt(), + ), + ) + if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { // gov coin + return sdk.Coin{}, errorNotEnoughCoins } - amt, err := RandIntInclusive(r, a.Bid.Amount, bidderBalance.AmountOf(a.Bid.Denom)) + // Generate a new bid amount (gov coin) + amt, err := RandIntInclusive(r, minNewBidAmt, bidderBalance.AmountOf(a.Bid.Denom)) if err != nil { panic(err) } return sdk.NewCoin(a.Bid.Denom, amt), nil // gov coin case types.CollateralAuction: - if bidderBalance.AmountOf(a.Bid.Denom).LT(a.Bid.Amount) { // stable coin // TODO account for bid increments (in forward phase) - return sdk.Coin{}, ErrorNotEnoughCoins + // Check the bidder has enough (stable coin) to pay in + minNewBidAmt := a.Bid.Amount.Add( // new bids must be some % greater than old bid, and at least 1 larger to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Bid.Amount).Mul(params.IncrementCollateral).RoundInt(), + ), + ) + minNewBidAmt = sdk.MinInt(minNewBidAmt, a.MaxBid.Amount) // allow new bids to hit MaxBid even though it may be less than the increment % + if bidderBalance.AmountOf(a.Bid.Denom).LT(minNewBidAmt) { + return sdk.Coin{}, errorNotEnoughCoins } + // Check auction can still receive new bids + if a.IsReversePhase() && a.Lot.Amount.Equal(sdk.ZeroInt()) { + return sdk.Coin{}, errorCantReceiveBids + } + // Generate a new bid amount (collateral coin in reverse phase) if a.IsReversePhase() { - amt, err := RandIntInclusive(r, sdk.ZeroInt(), a.Lot.Amount) // pick amount less than current lot amount + maxNewLotAmt := a.Lot.Amount.Sub( // new lot must be some % less than old lot, and at least 1 smaller to avoid replacing an old bid at no cost + sdk.MaxInt( + sdk.NewInt(1), + sdk.NewDecFromInt(a.Lot.Amount).Mul(params.IncrementCollateral).RoundInt(), + ), + ) + amt, err := RandIntInclusive(r, sdk.ZeroInt(), maxNewLotAmt) // maxNewLotAmt shouldn't be < 0 given the check above if err != nil { panic(err) } return sdk.NewCoin(a.Lot.Denom, amt), nil // collateral coin + + // Generate a new bid amount (stable coin in forward phase) + } else { + amt, err := RandIntInclusive(r, minNewBidAmt, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) + if err != nil { + panic(err) + } + // when the bidder has enough coins, pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase + if r.Intn(2) == 0 && bidderBalance.AmountOf(a.Bid.Denom).GTE(a.MaxBid.Amount) { // 50% + amt = a.MaxBid.Amount + } + return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin } - amt, err := RandIntInclusive(r, a.Bid.Amount, sdk.MinInt(bidderBalance.AmountOf(a.Bid.Denom), a.MaxBid.Amount)) - if err != nil { - panic(err) - } - // pick the MaxBid amount more frequently to increase chance auctions phase get into reverse phase - if r.Intn(10) == 0 { // 10% - amt = a.MaxBid.Amount - } - return sdk.NewCoin(a.Bid.Denom, amt), nil // stable coin default: return sdk.Coin{}, fmt.Errorf("unknown auction type") From 5737f4fa199ebaa0daf59f00e5a51197c74ddc21 Mon Sep 17 00:00:00 2001 From: jmahess <7819619+jmahess@users.noreply.github.com> Date: Thu, 23 Apr 2020 19:25:44 -0400 Subject: [PATCH 42/45] [R4R] Update fees for all cdps (#449) * update cdp fees in begin block Co-authored-by: Federico Kunze Co-authored-by: Federico Kunze <31522760+fedekunze@users.noreply.github.com> Co-authored-by: Denali Marsh Co-authored-by: John Maheswaran Co-authored-by: Kevin Davis --- x/cdp/abci.go | 17 ++----- x/cdp/abci_test.go | 9 +--- x/cdp/alias.go | 2 - x/cdp/genesis.go | 10 +--- x/cdp/integration_test.go | 23 +++------ x/cdp/keeper/deposit.go | 10 +--- x/cdp/keeper/draw.go | 16 ++---- x/cdp/keeper/draw_test.go | 7 ++- x/cdp/keeper/fees.go | 86 +++++++++++++++----------------- x/cdp/keeper/fees_test.go | 44 +++++----------- x/cdp/keeper/integration_test.go | 3 -- x/cdp/keeper/seize.go | 28 ----------- x/cdp/keeper/seize_test.go | 8 --- x/cdp/simulation/decoder.go | 3 +- x/cdp/simulation/decoder_test.go | 2 +- x/cdp/simulation/genesis.go | 2 - x/cdp/types/genesis.go | 29 ++++------- x/cdp/types/keys.go | 6 +-- x/cdp/types/params.go | 1 - 19 files changed, 95 insertions(+), 211 deletions(-) diff --git a/x/cdp/abci.go b/x/cdp/abci.go index f31e81ee..8c48cbdf 100644 --- a/x/cdp/abci.go +++ b/x/cdp/abci.go @@ -11,23 +11,17 @@ import ( // BeginBlocker compounds the debt in outstanding cdps and liquidates cdps that are below the required collateralization ratio func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { params := k.GetParams(ctx) - previousBlockTime, found := k.GetPreviousBlockTime(ctx) - if !found { - previousBlockTime = ctx.BlockTime() - } + 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, blockTimeElapsed) - } - // call our update fees method for the risky cdps - err := k.UpdateFeesForRiskyCdps(ctx, cp.Denom, cp.MarketID) + for _, cp := range params.CollateralParams { + + err := k.UpdateFeesForAllCdps(ctx, cp.Denom) + // handle if an error is returned then propagate up if err != nil { ctx.EventManager().EmitEvent( @@ -76,6 +70,5 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k Keeper) { } k.SetPreviousSavingsDistribution(ctx, ctx.BlockTime()) } - k.SetPreviousBlockTime(ctx, ctx.BlockTime()) return } diff --git a/x/cdp/abci_test.go b/x/cdp/abci_test.go index b16ab7e5..b090f672 100644 --- a/x/cdp/abci_test.go +++ b/x/cdp/abci_test.go @@ -152,8 +152,6 @@ func (suite *ModuleTestSuite) TestBeginBlock() { func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() { err := suite.keeper.AddCdp(suite.ctx, suite.addrs[0], cs(c("xrp", 10000000000)), cs(c("usdx", 1000000000))) suite.NoError(err) - suite.keeper.SetPreviousBlockTime(suite.ctx, suite.ctx.BlockTime()) - previousBlockTime, _ := suite.keeper.GetPreviousBlockTime(suite.ctx) suite.Equal(i(1000000000), suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx")) sk := suite.app.GetSupplyKeeper() cdpMacc := sk.GetModuleAccount(suite.ctx, cdp.ModuleName) @@ -167,13 +165,10 @@ func (suite *ModuleTestSuite) TestSeizeSingleCdpWithFees() { suite.Equal(i(1000000900), (cdpMacc.GetCoins().AmountOf("debt"))) cdp, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 1) - timeElapsed := sdk.NewInt(suite.ctx.BlockTime().Unix() - previousBlockTime.Unix()) - - fees := suite.keeper.CalculateFees(suite.ctx, cdp.Principal, timeElapsed, "xrp") - suite.Equal(i(928), fees.AmountOf("usdx")) - err = suite.keeper.SeizeCollateral(suite.ctx, cdp) suite.NoError(err) + _, found := suite.keeper.GetCDP(suite.ctx, "xrp", 1) + suite.False(found) } func TestModuleTestSuite(t *testing.T) { diff --git a/x/cdp/alias.go b/x/cdp/alias.go index 9688a153..eeeb2c81 100644 --- a/x/cdp/alias.go +++ b/x/cdp/alias.go @@ -110,7 +110,6 @@ var ( GovDenomKey = types.GovDenomKey DepositKeyPrefix = types.DepositKeyPrefix PrincipalKeyPrefix = types.PrincipalKeyPrefix - PreviousBlockTimeKey = types.PreviousBlockTimeKey PreviousDistributionTimeKey = types.PreviousDistributionTimeKey KeyGlobalDebtLimit = types.KeyGlobalDebtLimit KeyCollateralParams = types.KeyCollateralParams @@ -128,7 +127,6 @@ var ( DefaultGovDenom = types.DefaultGovDenom DefaultSurplusThreshold = types.DefaultSurplusThreshold DefaultDebtThreshold = types.DefaultDebtThreshold - DefaultPreviousBlockTime = types.DefaultPreviousBlockTime DefaultPreviousDistributionTime = types.DefaultPreviousDistributionTime DefaultSavingsDistributionFrequency = types.DefaultSavingsDistributionFrequency MaxSortableDec = types.MaxSortableDec diff --git a/x/cdp/genesis.go b/x/cdp/genesis.go index 3fa365ef..9346ac9f 100644 --- a/x/cdp/genesis.go +++ b/x/cdp/genesis.go @@ -70,10 +70,6 @@ func InitGenesis(ctx sdk.Context, k Keeper, pk PricefeedKeeper, sk SupplyKeeper, for _, d := range gs.Deposits { k.SetDeposit(ctx, d) } - // only set the previous block time if it's different than default - if !gs.PreviousBlockTime.Equal(DefaultPreviousBlockTime) { - k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime) - } } // ExportGenesis export genesis state for cdp module @@ -95,14 +91,10 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { debtDenom := k.GetDebtDenom(ctx) govDenom := k.GetGovDenom(ctx) - previousBlockTime, found := k.GetPreviousBlockTime(ctx) - if !found { - previousBlockTime = DefaultPreviousBlockTime - } previousDistributionTime, found := k.GetPreviousSavingsDistribution(ctx) if !found { previousDistributionTime = DefaultPreviousDistributionTime } - return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousBlockTime, previousDistributionTime) + return NewGenesisState(params, cdps, deposits, cdpID, debtDenom, govDenom, previousDistributionTime) } diff --git a/x/cdp/integration_test.go b/x/cdp/integration_test.go index 68c644ee..ee47c829 100644 --- a/x/cdp/integration_test.go +++ b/x/cdp/integration_test.go @@ -70,7 +70,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { DebtDenom: cdp.DefaultDebtDenom, GovDenom: cdp.DefaultGovDenom, CDPs: cdp.CDPs{}, - PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, } return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} @@ -153,7 +152,6 @@ func NewCDPGenStateMulti() app.GenesisState { DebtDenom: cdp.DefaultDebtDenom, GovDenom: cdp.DefaultGovDenom, CDPs: cdp.CDPs{}, - PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, } return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} @@ -203,19 +201,16 @@ func badGenStates() []badGenState { g9.DebtDenom = "" g10 := baseGenState() - g10.PreviousBlockTime = time.Time{} + g10.Params.CollateralParams[0].AuctionSize = i(-10) g11 := baseGenState() - g11.Params.CollateralParams[0].AuctionSize = i(-10) + g11.Params.CollateralParams[0].LiquidationPenalty = d("5.0") g12 := baseGenState() - g12.Params.CollateralParams[0].LiquidationPenalty = d("5.0") + g12.GovDenom = "" g13 := baseGenState() - g13.GovDenom = "" - - g14 := baseGenState() - g14.Params.DebtParams[0].SavingsRate = d("4.0") + g13.Params.DebtParams[0].SavingsRate = d("4.0") return []badGenState{ badGenState{Genesis: g1, Reason: "duplicate collateral denom"}, @@ -226,11 +221,10 @@ func badGenStates() []badGenState { badGenState{Genesis: g6, Reason: "duplicate debt denom"}, badGenState{Genesis: g8, Reason: "debt param not found in global debt limit"}, badGenState{Genesis: g9, Reason: "debt denom not set"}, - badGenState{Genesis: g10, Reason: "previous block time not set"}, - 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"}, + badGenState{Genesis: g10, Reason: "negative auction size"}, + badGenState{Genesis: g11, Reason: "invalid liquidation penalty"}, + badGenState{Genesis: g12, Reason: "gov denom not set"}, + badGenState{Genesis: g13, Reason: "invalid savings rate"}, } } @@ -280,7 +274,6 @@ func baseGenState() cdp.GenesisState { DebtDenom: cdp.DefaultDebtDenom, GovDenom: cdp.DefaultGovDenom, CDPs: cdp.CDPs{}, - PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, } } diff --git a/x/cdp/keeper/deposit.go b/x/cdp/keeper/deposit.go index 6dcbf7ab..5dbee127 100644 --- a/x/cdp/keeper/deposit.go +++ b/x/cdp/keeper/deposit.go @@ -40,12 +40,9 @@ func (k Keeper) DepositCollateral(ctx sdk.Context, owner sdk.AccAddress, deposit k.SetDeposit(ctx, deposit) - periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() cdp.Collateral = cdp.Collateral.Add(collateral...) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) @@ -71,15 +68,13 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi return sdkerrors.Wrapf(types.ErrInvalidWithdrawAmount, "collateral %s, deposit %s", collateral, deposit.Amount) } - periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) - collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Principal, cdp.AccumulatedFees.Add(fees...)) + collateralizationRatio, err := k.CalculateCollateralizationRatio(ctx, cdp.Collateral.Sub(collateral), cdp.Principal, cdp.AccumulatedFees) if err != nil { return err } liquidationRatio := k.getLiquidationRatio(ctx, collateral[0].Denom) if collateralizationRatio.LT(liquidationRatio) { - return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "colateral %s, collateral ratio %s, liquidation ration %s", collateral[0].Denom, collateralizationRatio, liquidationRatio) + return sdkerrors.Wrapf(types.ErrInvalidCollateralRatio, "collateral %s, collateral ratio %s, liquidation ration %s", collateral[0].Denom, collateralizationRatio, liquidationRatio) } ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -96,7 +91,6 @@ func (k Keeper) WithdrawCollateral(ctx sdk.Context, owner sdk.AccAddress, deposi oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) k.RemoveCdpCollateralRatioIndex(ctx, cdp.Collateral[0].Denom, cdp.ID, oldCollateralToDebtRatio) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() cdp.Collateral = cdp.Collateral.Sub(collateral) collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) diff --git a/x/cdp/keeper/draw.go b/x/cdp/keeper/draw.go index 876795d1..4c3383fa 100644 --- a/x/cdp/keeper/draw.go +++ b/x/cdp/keeper/draw.go @@ -25,11 +25,7 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string return err } - // fee calculation - periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) - - err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal...), cdp.AccumulatedFees.Add(fees...)) + err = k.ValidateCollateralizationRatio(ctx, cdp.Collateral, cdp.Principal.Add(principal...), cdp.AccumulatedFees) if err != nil { return err } @@ -65,7 +61,6 @@ func (k Keeper) AddPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom string // update cdp state cdp.Principal = cdp.Principal.Add(principal...) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) cdp.FeesUpdated = ctx.BlockTime() // increment total principal for the input collateral type @@ -87,16 +82,13 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri return sdkerrors.Wrapf(types.ErrCdpNotFound, "owner %s, denom %s", owner, denom) } - // calculate fees - periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) - err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees...).Add(fees...)) + err := k.ValidatePaymentCoins(ctx, cdp, payment, cdp.Principal.Add(cdp.AccumulatedFees...)) if err != nil { return err } // calculate fee and principal payment - feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees...).Add(fees...), cdp.AccumulatedFees.Add(fees...), payment) + feePayment, principalPayment := k.calculatePayment(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), cdp.AccumulatedFees, payment) // send the payment from the sender to the cpd module err = k.supplyKeeper.SendCoinsFromAccountToModule(ctx, owner, types.ModuleName, feePayment.Add(principalPayment...)) @@ -142,7 +134,7 @@ func (k Keeper) RepayPrincipal(ctx sdk.Context, owner sdk.AccAddress, denom stri if !principalPayment.IsZero() { cdp.Principal = cdp.Principal.Sub(principalPayment) } - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...).Sub(feePayment) + cdp.AccumulatedFees = cdp.AccumulatedFees.Sub(feePayment) cdp.FeesUpdated = ctx.BlockTime() // decrement the total principal for the input collateral type diff --git a/x/cdp/keeper/draw_test.go b/x/cdp/keeper/draw_test.go index d353265e..e5b0993f 100644 --- a/x/cdp/keeper/draw_test.go +++ b/x/cdp/keeper/draw_test.go @@ -159,11 +159,12 @@ func (suite *DrawTestSuite) TestAddRepayPrincipalFees() { err := suite.keeper.AddCdp(suite.ctx, suite.addrs[2], cs(c("xrp", 1000000000000)), cs(c("usdx", 100000000000))) suite.NoError(err) suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Minute * 10)) + err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp") + suite.NoError(err) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 10000000))) suite.NoError(err) t, _ := suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) suite.Equal(cs(c("usdx", 92827)), t.AccumulatedFees) - _ = suite.keeper.MintDebtCoins(suite.ctx, types.ModuleName, "debt", cs(c("usdx", 92827))) err = suite.keeper.RepayPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100))) suite.NoError(err) t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(2)) @@ -176,7 +177,9 @@ func (suite *DrawTestSuite) TestAddRepayPrincipalFees() { err = suite.keeper.AddCdp(suite.ctx, suite.addrs[2], cs(c("xrp", 1000000000000)), cs(c("usdx", 100000000))) suite.NoError(err) - suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000)) + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 31536000)) // move forward one year in time + err = suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp") + suite.NoError(err) err = suite.keeper.AddPrincipal(suite.ctx, suite.addrs[2], "xrp", cs(c("usdx", 100000000))) suite.NoError(err) t, _ = suite.keeper.GetCDP(suite.ctx, "xrp", uint64(3)) diff --git a/x/cdp/keeper/fees.go b/x/cdp/keeper/fees.go index e1f3c94e..7140d4f3 100644 --- a/x/cdp/keeper/fees.go +++ b/x/cdp/keeper/fees.go @@ -1,8 +1,6 @@ package keeper import ( - "time" - "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/kava-labs/kava/x/cdp/types" @@ -27,38 +25,53 @@ 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) error { +// UpdateFeesForAllCdps updates the fees for each of the CDPs +func (k Keeper) UpdateFeesForAllCdps(ctx sdk.Context, collateralDenom string) error { - price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) - if err != nil { - return err - } + k.IterateCdpsByDenom(ctx, collateralDenom, func(cdp types.CDP) bool { - liquidationRatio := k.getLiquidationRatio(ctx, collateralDenom) - priceDivLiqRatio := price.Price.Quo(liquidationRatio) - if priceDivLiqRatio.IsZero() { - priceDivLiqRatio = sdk.SmallestDec() - } - // NOTE - we have a fixed cutoff at 110% - this may or may not be changed in the future - normalizedRatio := sdk.OneDec().Quo(priceDivLiqRatio).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 { oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) - // 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) + newFees := 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...) + // exit without updating fees if amount has rounded down to zero + // cdp will get updated next block when newFees, newFeesSavings, newFeesSurplus >0 + if newFees.IsZero() { + return false + } + + // note - only works if principal length is one + for _, dc := range cdp.Principal { + dp, found := k.GetDebtParam(ctx, dc.Denom) + if !found { + return false + } + savingsRate := dp.SavingsRate + + newFeesSavings := sdk.NewDecFromInt(newFees.AmountOf(dp.Denom)).Mul(savingsRate).RoundInt() + newFeesSurplus := newFees.AmountOf(dp.Denom).Sub(newFeesSavings) + + // similar to checking for rounding to zero of all fees, but in this case we + // need to handle cases where we expect surplus or savings fees to be zero, namely + // if newFeesSavings = 0, check if savings rate is not zero + // if newFeesSurplus = 0, check if savings rate is not one + if (newFeesSavings.IsZero() && !savingsRate.IsZero()) || (newFeesSurplus.IsZero() && !savingsRate.Equal(sdk.OneDec())) { + return false + } + // mint debt coins to the cdp account + k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), newFees) + previousDebt := k.GetTotalPrincipal(ctx, collateralDenom, dp.Denom) + feeCoins := sdk.NewCoins(sdk.NewCoin(dp.Denom, previousDebt)) + k.SetTotalPrincipal(ctx, collateralDenom, dp.Denom, feeCoins.Add(newFees...).AmountOf(dp.Denom)) + + // mint surplus coins divided between the liquidator and savings module accounts. + k.supplyKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus))) + k.supplyKeeper.MintCoins(ctx, types.SavingsRateMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSavings))) + } + + // now add the new fees fees to the accumulated fees for the cdp + cdp.AccumulatedFees = cdp.AccumulatedFees.Add(newFees...) // and set the fees updated time to the current block time since we just updated it cdp.FeesUpdated = ctx.BlockTime() @@ -109,20 +122,3 @@ func (k Keeper) SetTotalPrincipal(ctx sdk.Context, collateralDenom string, princ store := prefix.NewStore(ctx.KVStore(k.key), types.PrincipalKeyPrefix) store.Set([]byte(collateralDenom+principalDenom), k.cdc.MustMarshalBinaryLengthPrefixed(total)) } - -// 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)) -} diff --git a/x/cdp/keeper/fees_test.go b/x/cdp/keeper/fees_test.go index 1c6ca211..1b17554a 100644 --- a/x/cdp/keeper/fees_test.go +++ b/x/cdp/keeper/fees_test.go @@ -68,10 +68,6 @@ func (suite *FeeTestSuite) TestCalculateFeesPrecisionLoss() { absError := (sdk.OneDec().Sub(sdk.NewDecFromInt(bulkFees[0].Amount).Quo(sdk.NewDecFromInt(individualFees[0].Amount)))).Abs() - suite.T().Log(bulkFees) - suite.T().Log(individualFees) - suite.T().Log(absError) - suite.True(d("0.00001").GTE(absError)) } @@ -95,28 +91,26 @@ func (suite *FeeTestSuite) createCdps() { // 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 + // use the other account to create a cdp that SHOULD NOT have fees updated 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() { +// TestUpdateFees tests the functionality for updating the fees for CDPs +func (suite *FeeTestSuite) TestUpdateFees() { // 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") + // fees to accumulate, in this example 600 seconds + oldtime := suite.ctx.BlockTime() + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 600)) + err := suite.keeper.UpdateFeesForAllCdps(suite.ctx, "xrp") suite.NoError(err) // check that we don't have any error // cdp we expect fees to accumulate for @@ -124,29 +118,15 @@ func (suite *FeeTestSuite) TestUpdateFeesForRiskyCdps() { // 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 + // now check that we have the correct amount of fees overall (22 USDX for this scenario) + suite.Equal(sdk.NewInt(22), cdp1.AccumulatedFees.AmountOf("usdx")) + suite.Equal(suite.ctx.BlockTime(), cdp1.FeesUpdated) + // cdp we expect fees to not accumulate for because of rounding to zero cdp2, _ := suite.keeper.GetCDP(suite.ctx, "xrp", 2) // check fees are zero suite.True(cdp2.AccumulatedFees.Empty()) - -} - -func (suite *FeeTestSuite) TestGetSetPreviousBlockTime() { - now := tmtime.Now() - - _, f := suite.keeper.GetPreviousBlockTime(suite.ctx) - suite.False(f) - - suite.NotPanics(func() { suite.keeper.SetPreviousBlockTime(suite.ctx, now) }) - - bpt, f := suite.keeper.GetPreviousBlockTime(suite.ctx) - suite.True(f) - suite.Equal(now, bpt) - + suite.Equal(oldtime, cdp2.FeesUpdated) } func TestFeeTestSuite(t *testing.T) { diff --git a/x/cdp/keeper/integration_test.go b/x/cdp/keeper/integration_test.go index 0cfbbf1f..66bb4dc2 100644 --- a/x/cdp/keeper/integration_test.go +++ b/x/cdp/keeper/integration_test.go @@ -70,7 +70,6 @@ func NewCDPGenState(asset string, liquidationRatio sdk.Dec) app.GenesisState { DebtDenom: cdp.DefaultDebtDenom, GovDenom: cdp.DefaultGovDenom, CDPs: cdp.CDPs{}, - PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, } return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} @@ -153,7 +152,6 @@ func NewCDPGenStateMulti() app.GenesisState { DebtDenom: cdp.DefaultDebtDenom, GovDenom: cdp.DefaultGovDenom, CDPs: cdp.CDPs{}, - PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, } return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} @@ -211,7 +209,6 @@ func NewCDPGenStateHighDebtLimit() app.GenesisState { DebtDenom: cdp.DefaultDebtDenom, GovDenom: cdp.DefaultGovDenom, CDPs: cdp.CDPs{}, - PreviousBlockTime: cdp.DefaultPreviousBlockTime, PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, } return app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGenesis)} diff --git a/x/cdp/keeper/seize.go b/x/cdp/keeper/seize.go index 730c335b..daf6a813 100644 --- a/x/cdp/keeper/seize.go +++ b/x/cdp/keeper/seize.go @@ -18,11 +18,6 @@ import ( func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error { // Calculate the previous collateral ratio oldCollateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Principal.Add(cdp.AccumulatedFees...)) - // Update fees - periods := sdk.NewInt(ctx.BlockTime().Unix()).Sub(sdk.NewInt(cdp.FeesUpdated.Unix())) - fees := k.CalculateFees(ctx, cdp.Principal.Add(cdp.AccumulatedFees...), periods, cdp.Collateral[0].Denom) - cdp.AccumulatedFees = cdp.AccumulatedFees.Add(fees...) - cdp.FeesUpdated = ctx.BlockTime() // Move debt coins from cdp to liquidator account deposits := k.GetDeposits(ctx, cdp.ID) @@ -80,29 +75,6 @@ func (k Keeper) SeizeCollateral(ctx sdk.Context, cdp types.CDP) error { return nil } -// HandleNewDebt compounds the accumulated fees for the input collateral and principal coins. -// the following operations are performed: -// 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, 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)) -} - // LiquidateCdps seizes collateral from all CDPs below the input liquidation ratio func (k Keeper) LiquidateCdps(ctx sdk.Context, marketID string, denom string, liquidationRatio sdk.Dec) error { price, err := k.pricefeedKeeper.GetCurrentPrice(ctx, marketID) diff --git a/x/cdp/keeper/seize_test.go b/x/cdp/keeper/seize_test.go index 355333bd..579905d6 100644 --- a/x/cdp/keeper/seize_test.go +++ b/x/cdp/keeper/seize_test.go @@ -189,14 +189,6 @@ func (suite *SeizeTestSuite) TestLiquidateCdps() { suite.Equal(len(suite.liquidations.xrp), xrpLiquidations) } -func (suite *SeizeTestSuite) TestHandleNewDebt() { - suite.createCdps() - tpb := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") - suite.keeper.HandleNewDebt(suite.ctx, "xrp", "usdx", i(31536000)) - tpa := suite.keeper.GetTotalPrincipal(suite.ctx, "xrp", "usdx") - suite.Equal(sdk.NewDec(tpb.Int64()).Mul(d("1.05")).TruncateInt().Int64(), tpa.Int64()) -} - func (suite *SeizeTestSuite) TestApplyLiquidationPenalty() { penalty := suite.keeper.ApplyLiquidationPenalty(suite.ctx, "xrp", i(1000)) suite.Equal(i(50), penalty) diff --git a/x/cdp/simulation/decoder.go b/x/cdp/simulation/decoder.go index 1b0e347e..91c89c10 100644 --- a/x/cdp/simulation/decoder.go +++ b/x/cdp/simulation/decoder.go @@ -54,8 +54,7 @@ func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &totalB) return fmt.Sprintf("%s\n%s", totalA, totalB) - case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey), - bytes.Equal(kvA.Key[:1], types.PreviousDistributionTimeKey): + case bytes.Equal(kvA.Key[:1], types.PreviousDistributionTimeKey): var timeA, timeB time.Time cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA) cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB) diff --git a/x/cdp/simulation/decoder_test.go b/x/cdp/simulation/decoder_test.go index 7d092e6c..f1ab67cc 100644 --- a/x/cdp/simulation/decoder_test.go +++ b/x/cdp/simulation/decoder_test.go @@ -42,7 +42,7 @@ func TestDecodeDistributionStore(t *testing.T) { kv.Pair{Key: []byte(types.GovDenomKey), Value: cdc.MustMarshalBinaryLengthPrefixed(denom)}, kv.Pair{Key: []byte(types.DepositKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(deposit)}, kv.Pair{Key: []byte(types.PrincipalKeyPrefix), Value: cdc.MustMarshalBinaryLengthPrefixed(principal)}, - kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)}, + kv.Pair{Key: []byte(types.PreviousDistributionTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevDistTime)}, kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, } diff --git a/x/cdp/simulation/genesis.go b/x/cdp/simulation/genesis.go index 6fa7680e..65f568aa 100644 --- a/x/cdp/simulation/genesis.go +++ b/x/cdp/simulation/genesis.go @@ -121,7 +121,6 @@ func randomCdpGenState(selection int) types.GenesisState { DebtDenom: types.DefaultDebtDenom, GovDenom: types.DefaultGovDenom, CDPs: types.CDPs{}, - PreviousBlockTime: types.DefaultPreviousBlockTime, PreviousDistributionTime: types.DefaultPreviousDistributionTime, } case 1: @@ -158,7 +157,6 @@ func randomCdpGenState(selection int) types.GenesisState { DebtDenom: types.DefaultDebtDenom, GovDenom: types.DefaultGovDenom, CDPs: types.CDPs{}, - PreviousBlockTime: types.DefaultPreviousBlockTime, PreviousDistributionTime: types.DefaultPreviousDistributionTime, } default: diff --git a/x/cdp/types/genesis.go b/x/cdp/types/genesis.go index 1d1da37b..a1e73a3f 100644 --- a/x/cdp/types/genesis.go +++ b/x/cdp/types/genesis.go @@ -14,12 +14,11 @@ type GenesisState struct { 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"` + 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, previousDistTime time.Time) GenesisState { +func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID uint64, debtDenom, govDenom string, previousDistTime time.Time) GenesisState { return GenesisState{ Params: params, CDPs: cdps, @@ -27,23 +26,21 @@ func NewGenesisState(params Params, cdps CDPs, deposits Deposits, startingCdpID 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, - PreviousDistributionTime: DefaultPreviousDistributionTime, - } + return NewGenesisState( + DefaultParams(), + CDPs{}, + Deposits{}, + DefaultCdpStartingID, + DefaultDebtDenom, + DefaultGovDenom, + DefaultPreviousDistributionTime, + ) } // Validate performs basic validation of genesis data returning an @@ -54,10 +51,6 @@ func (gs GenesisState) Validate() error { return err } - if gs.PreviousBlockTime.Equal(time.Time{}) { - return fmt.Errorf("previous block time not set") - } - if gs.PreviousDistributionTime.Equal(time.Time{}) { return fmt.Errorf("previous distribution time not set") } diff --git a/x/cdp/types/keys.go b/x/cdp/types/keys.go index de0de209..8f4a8097 100644 --- a/x/cdp/types/keys.go +++ b/x/cdp/types/keys.go @@ -45,8 +45,7 @@ var sep = []byte(":") // - 0x05::: Deposit // - 0x06:totalPrincipal // - 0x07:feeRate -// - 0x08:previousBlockTime -// - 0x09:previousDistributionTime +// - 0x08:previousDistributionTime // KVStore key prefixes var ( @@ -58,8 +57,7 @@ var ( GovDenomKey = []byte{0x05} DepositKeyPrefix = []byte{0x06} PrincipalKeyPrefix = []byte{0x07} - PreviousBlockTimeKey = []byte{0x08} - PreviousDistributionTimeKey = []byte{0x09} + PreviousDistributionTimeKey = []byte{0x08} ) var lenPositiveDec = len(SortableDecBytes(sdk.OneDec())) diff --git a/x/cdp/types/params.go b/x/cdp/types/params.go index 5f1fd938..5246dba4 100644 --- a/x/cdp/types/params.go +++ b/x/cdp/types/params.go @@ -29,7 +29,6 @@ var ( 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 From 1ef9bd331baff50717b734997fc714013b3c6c3d Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 24 Apr 2020 11:20:34 -0400 Subject: [PATCH 43/45] USDX incentives implementation (#399) * USDX incentives implementation (#399) * feat: upgrade to cosmos-sdk v0.38 Co-authored-by: Denali Marsh Co-authored-by: John Maheswaran Co-authored-by: John Maheswaran --- app/app.go | 20 ++- app/test_common.go | 2 + go.sum | 1 + x/bep3/client/rest/query.go | 1 - x/incentive/abci.go | 13 ++ x/incentive/alias.go | 92 ++++++++++ x/incentive/client/cli/query.go | 98 +++++++++++ x/incentive/client/cli/tx.go | 63 +++++++ x/incentive/client/rest/query.go | 70 ++++++++ x/incentive/client/rest/rest.go | 13 ++ x/incentive/client/rest/tx.go | 36 ++++ x/incentive/doc.go | 40 +++++ x/incentive/genesis.go | 73 ++++++++ x/incentive/handler.go | 48 +++++ x/incentive/keeper/keeper.go | 243 ++++++++++++++++++++++++++ x/incentive/keeper/params.go | 18 ++ x/incentive/keeper/payout.go | 204 +++++++++++++++++++++ x/incentive/keeper/querier.go | 51 ++++++ x/incentive/keeper/rewards.go | 110 ++++++++++++ x/incentive/module.go | 164 +++++++++++++++++ x/incentive/simulation/decoder.go | 12 ++ x/incentive/simulation/params.go | 14 ++ x/incentive/simulation/simulation.go | 22 +++ x/incentive/types/account.go | 14 ++ x/incentive/types/account_test.go | 51 ++++++ x/incentive/types/codec.go | 22 +++ x/incentive/types/errors.go | 15 ++ x/incentive/types/events.go | 8 + x/incentive/types/expected_keepers.go | 27 +++ x/incentive/types/genesis.go | 75 ++++++++ x/incentive/types/keys.go | 61 +++++++ x/incentive/types/msg.go | 54 ++++++ x/incentive/types/msg_test.go | 59 +++++++ x/incentive/types/params.go | 165 +++++++++++++++++ x/incentive/types/params_test.go | 152 ++++++++++++++++ x/incentive/types/period.go | 11 ++ x/incentive/types/querier.go | 35 ++++ x/incentive/types/rewards.go | 97 ++++++++++ 38 files changed, 2250 insertions(+), 4 deletions(-) create mode 100644 x/incentive/abci.go create mode 100644 x/incentive/alias.go create mode 100644 x/incentive/client/cli/query.go create mode 100644 x/incentive/client/cli/tx.go create mode 100644 x/incentive/client/rest/query.go create mode 100644 x/incentive/client/rest/rest.go create mode 100644 x/incentive/client/rest/tx.go create mode 100644 x/incentive/doc.go create mode 100644 x/incentive/genesis.go create mode 100644 x/incentive/handler.go create mode 100644 x/incentive/keeper/keeper.go create mode 100644 x/incentive/keeper/params.go create mode 100644 x/incentive/keeper/payout.go create mode 100644 x/incentive/keeper/querier.go create mode 100644 x/incentive/keeper/rewards.go create mode 100644 x/incentive/module.go create mode 100644 x/incentive/simulation/decoder.go create mode 100644 x/incentive/simulation/params.go create mode 100644 x/incentive/simulation/simulation.go create mode 100644 x/incentive/types/account.go create mode 100644 x/incentive/types/account_test.go create mode 100644 x/incentive/types/codec.go create mode 100644 x/incentive/types/errors.go create mode 100644 x/incentive/types/events.go create mode 100644 x/incentive/types/expected_keepers.go create mode 100644 x/incentive/types/genesis.go create mode 100644 x/incentive/types/keys.go create mode 100644 x/incentive/types/msg.go create mode 100644 x/incentive/types/msg_test.go create mode 100644 x/incentive/types/params.go create mode 100644 x/incentive/types/params_test.go create mode 100644 x/incentive/types/period.go create mode 100644 x/incentive/types/querier.go create mode 100644 x/incentive/types/rewards.go diff --git a/app/app.go b/app/app.go index 151deeaa..da424542 100644 --- a/app/app.go +++ b/app/app.go @@ -7,6 +7,7 @@ import ( "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/incentive" "github.com/kava-labs/kava/x/kavadist" "github.com/kava-labs/kava/x/pricefeed" validatorvesting "github.com/kava-labs/kava/x/validator-vesting" @@ -69,6 +70,7 @@ var ( pricefeed.AppModuleBasic{}, bep3.AppModuleBasic{}, kavadist.AppModuleBasic{}, + incentive.AppModuleBasic{}, ) // module account permissions @@ -121,6 +123,7 @@ type App struct { pricefeedKeeper pricefeed.Keeper bep3Keeper bep3.Keeper kavadistKeeper kavadist.Keeper + incentiveKeeper incentive.Keeper // the module manager mm *module.Manager @@ -145,7 +148,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey, validatorvesting.StoreKey, auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey, bep3.StoreKey, - kavadist.StoreKey, + kavadist.StoreKey, incentive.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) @@ -173,6 +176,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace) bep3Subspace := app.paramsKeeper.Subspace(bep3.DefaultParamspace) kavadistSubspace := app.paramsKeeper.Subspace(kavadist.DefaultParamspace) + incentiveSubspace := app.paramsKeeper.Subspace(incentive.DefaultParamspace) // add keepers app.accountKeeper = auth.NewAccountKeeper( @@ -295,6 +299,14 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, kavadistSubspace, app.supplyKeeper, ) + app.incentiveKeeper = incentive.NewKeeper( + app.cdc, + keys[incentive.StoreKey], + incentiveSubspace, + app.supplyKeeper, + app.cdpKeeper, + app.accountKeeper, + ) // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks @@ -321,12 +333,13 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper), bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), + incentive.NewAppModule(app.incentiveKeeper, app.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. - app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName) + app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, kavadist.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, incentive.ModuleName) app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName) @@ -335,7 +348,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, validatorvesting.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, evidence.ModuleName, - pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, // TODO is this order ok? + pricefeed.ModuleName, cdp.ModuleName, auction.ModuleName, bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName, supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis crisis.ModuleName, // runs the invariants at genesis - should run after other modules genutil.ModuleName, // genutils must occur after staking so that pools are properly initialized with tokens from genesis accounts. @@ -363,6 +376,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper), bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), + incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), ) app.sm.RegisterStoreDecoders() diff --git a/app/test_common.go b/app/test_common.go index 42bc00a5..bb1ab7e7 100644 --- a/app/test_common.go +++ b/app/test_common.go @@ -30,6 +30,7 @@ import ( "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/incentive" "github.com/kava-labs/kava/x/kavadist" "github.com/kava-labs/kava/x/pricefeed" validatorvesting "github.com/kava-labs/kava/x/validator-vesting" @@ -75,6 +76,7 @@ func (tApp TestApp) GetCDPKeeper() cdp.Keeper { return tApp.cdpKeepe func (tApp TestApp) GetPriceFeedKeeper() pricefeed.Keeper { return tApp.pricefeedKeeper } func (tApp TestApp) GetBep3Keeper() bep3.Keeper { return tApp.bep3Keeper } func (tApp TestApp) GetKavadistKeeper() kavadist.Keeper { return tApp.kavadistKeeper } +func (tApp TestApp) GetIncentiveKeeper() incentive.Keeper { return tApp.incentiveKeeper } // 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 { diff --git a/go.sum b/go.sum index 27038216..d69e9d6c 100644 --- a/go.sum +++ b/go.sum @@ -416,6 +416,7 @@ github.com/tendermint/iavl v0.13.2/go.mod h1:vE1u0XAGXYjHykd4BLp8p/yivrw2PF1Tuol github.com/tendermint/tendermint v0.33.2/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= github.com/tendermint/tendermint v0.33.3 h1:6lMqjEoCGejCzAghbvfQgmw87snGSqEhDTo/jw+W8CI= github.com/tendermint/tendermint v0.33.3/go.mod h1:25DqB7YvV1tN3tHsjWoc2vFtlwICfrub9XO6UBO+4xk= +github.com/tendermint/tendermint v0.33.4 h1:NM3G9618yC5PaaxGrcAySc5ylc1PAANeIx42u2Re/jo= github.com/tendermint/tm-db v0.4.1/go.mod h1:JsJ6qzYkCGiGwm5GHl/H5GLI9XLb6qZX7PRe425dHAY= github.com/tendermint/tm-db v0.5.0 h1:qtM5UTr1dlRnHtDY6y7MZO5Di8XAE2j3lc/pCnKJ5hQ= github.com/tendermint/tm-db v0.5.0/go.mod h1:lSq7q5WRR/njf1LnhiZ/lIJHk2S8Y1Zyq5oP/3o9C2U= diff --git a/x/bep3/client/rest/query.go b/x/bep3/client/rest/query.go index 7170cf03..559919f5 100644 --- a/x/bep3/client/rest/query.go +++ b/x/bep3/client/rest/query.go @@ -37,7 +37,6 @@ func queryAtomicSwapHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) return } - swapID, err := types.HexToBytes(vars[restSwapID]) if err != nil { rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) diff --git a/x/incentive/abci.go b/x/incentive/abci.go new file mode 100644 index 00000000..e6c0b293 --- /dev/null +++ b/x/incentive/abci.go @@ -0,0 +1,13 @@ +package incentive + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/incentive/keeper" +) + +// BeginBlocker runs at the start of every block +func BeginBlocker(ctx sdk.Context, k keeper.Keeper) { + k.DeleteExpiredClaimsAndClaimPeriods(ctx) + k.ApplyRewardsToCdps(ctx) + k.CreateAndDeleteRewardPeriods(ctx) +} diff --git a/x/incentive/alias.go b/x/incentive/alias.go new file mode 100644 index 00000000..877ddba3 --- /dev/null +++ b/x/incentive/alias.go @@ -0,0 +1,92 @@ +// nolint +// autogenerated code using github.com/rigelrozanski/multitool +// aliases generated for the following subdirectories: +// ALIASGEN: github.com/kava-labs/kava/x/incentive/keeper +// ALIASGEN: github.com/kava-labs/kava/x/incentive/types +package incentive + +import ( + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" +) + +const ( + EventTypeClaim = types.EventTypeClaim + AttributeValueCategory = types.AttributeValueCategory + AttributeKeySender = types.AttributeKeySender + ModuleName = types.ModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + DefaultParamspace = types.DefaultParamspace + QuerierRoute = types.QuerierRoute + QueryGetClaims = types.QueryGetClaims + RestClaimOwner = types.RestClaimOwner + RestClaimDenom = types.RestClaimDenom + QueryGetParams = types.QueryGetParams +) + +var ( + // functions aliases + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + GetTotalVestingPeriodLength = types.GetTotalVestingPeriodLength + RegisterCodec = types.RegisterCodec + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + BytesToUint64 = types.BytesToUint64 + GetClaimPeriodPrefix = types.GetClaimPeriodPrefix + GetClaimPrefix = types.GetClaimPrefix + NewMsgClaimReward = types.NewMsgClaimReward + NewParams = types.NewParams + DefaultParams = types.DefaultParams + ParamKeyTable = types.ParamKeyTable + NewReward = types.NewReward + NewPeriod = types.NewPeriod + NewQueryClaimsParams = types.NewQueryClaimsParams + NewRewardPeriod = types.NewRewardPeriod + NewClaimPeriod = types.NewClaimPeriod + NewClaim = types.NewClaim + + // variable aliases + ModuleCdc = types.ModuleCdc + ErrClaimNotFound = types.ErrClaimNotFound + ErrClaimPeriodNotFound = types.ErrClaimPeriodNotFound + ErrInvalidAccountType = types.ErrInvalidAccountType + ErrNoClaimsFound = types.ErrNoClaimsFound + ErrInsufficientModAccountBalance = types.ErrInsufficientModAccountBalance + RewardPeriodKeyPrefix = types.RewardPeriodKeyPrefix + ClaimPeriodKeyPrefix = types.ClaimPeriodKeyPrefix + ClaimKeyPrefix = types.ClaimKeyPrefix + NextClaimPeriodIDPrefix = types.NextClaimPeriodIDPrefix + PreviousBlockTimeKey = types.PreviousBlockTimeKey + KeyActive = types.KeyActive + KeyRewards = types.KeyRewards + DefaultActive = types.DefaultActive + DefaultRewards = types.DefaultRewards + DefaultPreviousBlockTime = types.DefaultPreviousBlockTime + GovDenom = types.GovDenom + PrincipalDenom = types.PrincipalDenom + IncentiveMacc = types.IncentiveMacc +) + +type ( + Keeper = keeper.Keeper + SupplyKeeper = types.SupplyKeeper + CdpKeeper = types.CdpKeeper + AccountKeeper = types.AccountKeeper + GenesisClaimPeriodID = types.GenesisClaimPeriodID + GenesisClaimPeriodIDs = types.GenesisClaimPeriodIDs + GenesisState = types.GenesisState + MsgClaimReward = types.MsgClaimReward + Params = types.Params + Reward = types.Reward + Rewards = types.Rewards + QueryClaimsParams = types.QueryClaimsParams + PostClaimReq = types.PostClaimReq + RewardPeriod = types.RewardPeriod + RewardPeriods = types.RewardPeriods + ClaimPeriod = types.ClaimPeriod + ClaimPeriods = types.ClaimPeriods + Claim = types.Claim + Claims = types.Claims +) diff --git a/x/incentive/client/cli/query.go b/x/incentive/client/cli/query.go new file mode 100644 index 00000000..c9201c53 --- /dev/null +++ b/x/incentive/client/cli/query.go @@ -0,0 +1,98 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/spf13/cobra" +) + +// GetQueryCmd returns the cli query commands for the incentive module +func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + incentiveQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the incentive module", + } + + incentiveQueryCmd.AddCommand(flags.GetCommands( + queryParamsCmd(queryRoute, cdc), + queryClaimsCmd(queryRoute, cdc), + )...) + + return incentiveQueryCmd + +} + +func queryClaimsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "claims [owner-addr] [denom]", + Short: "get claims by onwer and denom", + Long: strings.TrimSpace( + fmt.Sprintf(`Get all claims owned by the owner address for the particular collateral type. + + Example: + $ %s query %s claims kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb`, version.ClientName, types.ModuleName)), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cliCtx := context.NewCLIContext().WithCodec(cdc) + // Prepare params for querier + ownerAddress, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + bz, err := cdc.MarshalJSON(types.QueryClaimsParams{ + Owner: ownerAddress, + Denom: args[1], + }) + if err != nil { + return err + } + + // Query + route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetClaims) + res, _, err := cliCtx.QueryWithData(route, bz) + if err != nil { + return err + } + + var claims types.Claims + if err := cdc.UnmarshalJSON(res, &claims); err != nil { + return fmt.Errorf("failed to unmarshal claims: %w", err) + } + return cliCtx.PrintOutput(claims) + + }, + } +} + +func queryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "params", + Short: "get the incentive module parameters", + Long: "Get the current global incentive module parameters.", + 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 params types.Params + if err := cdc.UnmarshalJSON(res, ¶ms); err != nil { + return fmt.Errorf("failed to unmarshal params: %w", err) + } + return cliCtx.PrintOutput(params) + }, + } +} diff --git a/x/incentive/client/cli/tx.go b/x/incentive/client/cli/tx.go new file mode 100644 index 00000000..559cbd37 --- /dev/null +++ b/x/incentive/client/cli/tx.go @@ -0,0 +1,63 @@ +package cli + +import ( + "bufio" + "fmt" + "strings" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/flags" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/spf13/cobra" +) + +// GetTxCmd returns the transaction cli commands for the incentive module +func GetTxCmd(cdc *codec.Codec) *cobra.Command { + incentiveTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "transaction commands for the incentive module", + } + + incentiveTxCmd.AddCommand(flags.PostCommands( + getCmdClaim(cdc), + )...) + + return incentiveTxCmd + +} + +func getCmdClaim(cdc *codec.Codec) *cobra.Command { + return &cobra.Command{ + Use: "claim [owner] [denom]", + Short: "claim rewards for owner and denom", + Long: strings.TrimSpace( + fmt.Sprintf(`Claim any outstanding rewards owned by owner for the input denom, + + Example: + $ %s tx %s claim kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw bnb + `, version.ClientName, types.ModuleName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + inBuf := bufio.NewReader(cmd.InOrStdin()) + cliCtx := context.NewCLIContext().WithCodec(cdc) + txBldr := auth.NewTxBuilderFromCLI(inBuf).WithTxEncoder(utils.GetTxEncoder(cdc)) + owner, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgClaimReward(owner, args[1]) + err = msg.ValidateBasic() + if err != nil { + return err + } + return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg}) + }, + } +} diff --git a/x/incentive/client/rest/query.go b/x/incentive/client/rest/query.go new file mode 100644 index 00000000..e8e0e5e1 --- /dev/null +++ b/x/incentive/client/rest/query.go @@ -0,0 +1,70 @@ +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/gorilla/mux" + "github.com/kava-labs/kava/x/incentive/types" +) + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc(fmt.Sprintf("/%s/claims", types.ModuleName), queryClaimsHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET") +} + +func queryClaimsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) + if !ok { + return + } + vars := mux.Vars(r) + ownerBech32 := vars[types.RestClaimOwner] + denom := vars[types.RestClaimDenom] + + owner, err := sdk.AccAddressFromBech32(ownerBech32) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + queryParams := types.NewQueryClaimsParams(owner, denom) + bz, err := cliCtx.Codec.MarshalJSON(queryParams) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/incentive/%s", types.QueryGetClaims), bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + + } +} + +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/parameters", types.QuerierRoute) + + 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) + } +} diff --git a/x/incentive/client/rest/rest.go b/x/incentive/client/rest/rest.go new file mode 100644 index 00000000..9c41f78f --- /dev/null +++ b/x/incentive/client/rest/rest.go @@ -0,0 +1,13 @@ +package rest + +import ( + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" +) + +// RegisterRoutes registers incentive-related REST handlers to a router +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + registerQueryRoutes(cliCtx, r) + registerTxRoutes(cliCtx, r) +} diff --git a/x/incentive/client/rest/tx.go b/x/incentive/client/rest/tx.go new file mode 100644 index 00000000..13ccd94a --- /dev/null +++ b/x/incentive/client/rest/tx.go @@ -0,0 +1,36 @@ +package rest + +import ( + "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/incentive/types" +) + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/incentive/claim", postClaimHandlerFn(cliCtx)).Methods("POST") + +} + +func postClaimHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var requestBody types.PostClaimReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &requestBody) { + return + } + requestBody.BaseReq = requestBody.BaseReq.Sanitize() + if !requestBody.BaseReq.ValidateBasic(w) { + return + } + msg := types.NewMsgClaimReward(requestBody.Sender, requestBody.Denom) + if err := msg.ValidateBasic(); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + utils.WriteGenerateStdTxResponse(w, cliCtx, requestBody.BaseReq, []sdk.Msg{msg}) + } +} diff --git a/x/incentive/doc.go b/x/incentive/doc.go new file mode 100644 index 00000000..f2b92511 --- /dev/null +++ b/x/incentive/doc.go @@ -0,0 +1,40 @@ +/* +Package incentive implements a Cosmos SDK module, per ADR 009, that provides governance-controlled, +on-chain incentives for users who open cdps and mint stablecoins (USDX). + +For the background and motivation of this module, see the governance proposal that was voted on by +KAVA token holders: https://ipfs.io/ipfs/QmSYedssC3nyQacDJmNcREtgmTPyaMx2JX7RNkMdAVkdkr/user-growth-fund-proposal.pdf + +The 'Reward' parameter is used to control how much incentives are given. +For example, the following reward: + + Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", 1000000000), + Duration: time.Hour*7*24, + TimeLock: time.Hour*24*365, + ClaimDuration: time.Hour*7*24, + } + +will distribute 1000 KAVA each week (Duration) to users who mint USDX using collateral bnb. +That KAVA can be claimed by the user for one week (ClaimDuration) after the reward period expires, +and all KAVA rewards will be timelocked for 1 year (TimeLock). If a user does not claim them during +the claim duration period, they are forgone. + +Rewards are accumulated by users continuously and proportionally - ie. if a user holds a CDP that has +minted 10% of all USDX backed by bnb for the entire reward period, they will be eligible to claim 10% +of rewards for that period. + +Once a reward period ends, but not before, users can claim the rewards they have accumulated. Users claim rewards +using a MsgClaimReward transaction. The following msg: + + MsgClaimReward { + "kava1..." + "bnb" + } + +will claim all outstanding rewards for minting USDX backed by bnb for the input user. + +*/ +package incentive diff --git a/x/incentive/genesis.go b/x/incentive/genesis.go new file mode 100644 index 00000000..5a38e146 --- /dev/null +++ b/x/incentive/genesis.go @@ -0,0 +1,73 @@ +package incentive + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" +) + +// InitGenesis initializes the store state from a genesis state. +func InitGenesis(ctx sdk.Context, k keeper.Keeper, supplyKeeper types.SupplyKeeper, gs types.GenesisState) { + + // check if the module account exists + moduleAcc := supplyKeeper.GetModuleAccount(ctx, types.IncentiveMacc) + if moduleAcc == nil { + panic(fmt.Sprintf("%s module account has not been set", types.IncentiveMacc)) + } + + if err := gs.Validate(); err != nil { + panic(fmt.Sprintf("failed to validate %s genesis state: %s", types.ModuleName, err)) + } + + k.SetParams(ctx, gs.Params) + + for _, r := range gs.Params.Rewards { + k.SetNextClaimPeriodID(ctx, r.Denom, 1) + } + + // only set the previous block time if it's different than default + if !gs.PreviousBlockTime.Equal(types.DefaultPreviousBlockTime) { + k.SetPreviousBlockTime(ctx, gs.PreviousBlockTime) + } + + // set store objects + for _, rp := range gs.RewardPeriods { + k.SetRewardPeriod(ctx, rp) + } + + for _, cp := range gs.ClaimPeriods { + k.SetClaimPeriod(ctx, cp) + } + + for _, c := range gs.Claims { + k.SetClaim(ctx, c) + } + + for _, id := range gs.NextClaimPeriodIDs { + k.SetNextClaimPeriodID(ctx, id.Denom, id.ID) + } + +} + +// ExportGenesis export genesis state for incentive module +func ExportGenesis(ctx sdk.Context, k keeper.Keeper) types.GenesisState { + // get all objects out of the store + params := k.GetParams(ctx) + previousBlockTime, found := k.GetPreviousBlockTime(ctx) + + // since it is not set in genesis, if somehow the chain got started and was exported + // immediately after InitGenesis, there would be no previousBlockTime value. + if !found { + previousBlockTime = types.DefaultPreviousBlockTime + } + + // Get all objects from the store + rewardPeriods := k.GetAllRewardPeriods(ctx) + claimPeriods := k.GetAllClaimPeriods(ctx) + claims := k.GetAllClaims(ctx) + claimPeriodIDs := k.GetAllClaimPeriodIDPairs(ctx) + + return types.NewGenesisState(params, previousBlockTime, rewardPeriods, claimPeriods, claims, claimPeriodIDs) +} diff --git a/x/incentive/handler.go b/x/incentive/handler.go new file mode 100644 index 00000000..1fdf8a58 --- /dev/null +++ b/x/incentive/handler.go @@ -0,0 +1,48 @@ +package incentive + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" +) + +// NewHandler creates an sdk.Handler for incentive module messages +func NewHandler(k keeper.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { + case types.MsgClaimReward: + return handleMsgClaimReward(ctx, k, msg) + default: + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) + + } + } +} + +func handleMsgClaimReward(ctx sdk.Context, k keeper.Keeper, msg types.MsgClaimReward) (*sdk.Result, error) { + + claims, found := k.GetClaimsByAddressAndDenom(ctx, msg.Sender, msg.Denom) + if !found { + return nil, sdkerrors.Wrapf(types.ErrNoClaimsFound, "address: %s, denom: %s", msg.Sender, msg.Denom) + } + + for _, claim := range claims { + err := k.PayoutClaim(ctx, claim.Owner, claim.Denom, claim.ClaimPeriodID) + if err != nil { + return nil, err + } + } + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + ), + ) + + return &sdk.Result{ + Events: ctx.EventManager().Events(), + }, nil +} diff --git a/x/incentive/keeper/keeper.go b/x/incentive/keeper/keeper.go new file mode 100644 index 00000000..3c2aa383 --- /dev/null +++ b/x/incentive/keeper/keeper.go @@ -0,0 +1,243 @@ +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/incentive/types" +) + +// Keeper keeper for the incentive module +type Keeper struct { + accountKeeper types.AccountKeeper + cdc *codec.Codec + cdpKeeper types.CdpKeeper + key sdk.StoreKey + paramSubspace subspace.Subspace + supplyKeeper types.SupplyKeeper +} + +// NewKeeper creates a new keeper +func NewKeeper( + cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, sk types.SupplyKeeper, + cdpk types.CdpKeeper, ak types.AccountKeeper, +) Keeper { + + return Keeper{ + accountKeeper: ak, + cdc: cdc, + cdpKeeper: cdpk, + key: key, + paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()), + supplyKeeper: sk, + } +} + +// GetRewardPeriod returns the reward period from the store for the input denom and a boolean for if it was found +func (k Keeper) GetRewardPeriod(ctx sdk.Context, denom string) (types.RewardPeriod, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix) + bz := store.Get([]byte(denom)) + if bz == nil { + return types.RewardPeriod{}, false + } + var rp types.RewardPeriod + k.cdc.MustUnmarshalBinaryBare(bz, &rp) + return rp, true +} + +// SetRewardPeriod sets the reward period in the store for the input deno, +func (k Keeper) SetRewardPeriod(ctx sdk.Context, rp types.RewardPeriod) { + store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(rp) + store.Set([]byte(rp.Denom), bz) +} + +// DeleteRewardPeriod deletes the reward period in the store for the input denom, +func (k Keeper) DeleteRewardPeriod(ctx sdk.Context, denom string) { + store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix) + store.Delete([]byte(denom)) +} + +// IterateRewardPeriods iterates over all reward period objects in the store and preforms a callback function +func (k Keeper) IterateRewardPeriods(ctx sdk.Context, cb func(rp types.RewardPeriod) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var rp types.RewardPeriod + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &rp) + if cb(rp) { + break + } + } +} + +// GetAllRewardPeriods returns all reward periods in the store +func (k Keeper) GetAllRewardPeriods(ctx sdk.Context) types.RewardPeriods { + rps := types.RewardPeriods{} + k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) (stop bool) { + rps = append(rps, rp) + return false + }) + return rps +} + +// GetNextClaimPeriodID returns the highest claim period id in the store for the input denom +func (k Keeper) GetNextClaimPeriodID(ctx sdk.Context, denom string) uint64 { + store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix) + bz := store.Get([]byte(denom)) + return types.BytesToUint64(bz) +} + +// SetNextClaimPeriodID sets the highest claim period id in the store for the input denom +func (k Keeper) SetNextClaimPeriodID(ctx sdk.Context, denom string, id uint64) { + store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix) + store.Set([]byte(denom), sdk.Uint64ToBigEndian(id)) +} + +// IterateClaimPeriodIDKeysAndValues iterates over the claim period id (value) and denom (key) of each claim period id in the store and performs a callback function +func (k Keeper) IterateClaimPeriodIDKeysAndValues(ctx sdk.Context, cb func(denom string, id uint64) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.NextClaimPeriodIDPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + id := types.BytesToUint64(iterator.Value()) + denom := string(iterator.Key()) + if cb(denom, id) { + break + } + } +} + +// GetAllClaimPeriodIDPairs returns all denom:nextClaimPeriodID pairs in the store +func (k Keeper) GetAllClaimPeriodIDPairs(ctx sdk.Context) types.GenesisClaimPeriodIDs { + ids := types.GenesisClaimPeriodIDs{} + k.IterateClaimPeriodIDKeysAndValues(ctx, func(denom string, id uint64) (stop bool) { + genID := types.GenesisClaimPeriodID{ + Denom: denom, + ID: id, + } + ids = append(ids, genID) + return false + }) + return ids +} + +// GetClaimPeriod returns claim period in the store for the input ID and denom and a boolean for if it was found +func (k Keeper) GetClaimPeriod(ctx sdk.Context, id uint64, denom string) (types.ClaimPeriod, bool) { + var cp types.ClaimPeriod + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix) + bz := store.Get(types.GetClaimPeriodPrefix(denom, id)) + if bz == nil { + return types.ClaimPeriod{}, false + } + k.cdc.MustUnmarshalBinaryBare(bz, &cp) + return cp, true +} + +// SetClaimPeriod sets the claim period in the store for the input ID and denom +func (k Keeper) SetClaimPeriod(ctx sdk.Context, cp types.ClaimPeriod) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(cp) + store.Set(types.GetClaimPeriodPrefix(cp.Denom, cp.ID), bz) +} + +// DeleteClaimPeriod deletes the claim period in the store for the input ID and denom +func (k Keeper) DeleteClaimPeriod(ctx sdk.Context, id uint64, denom string) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix) + store.Delete(types.GetClaimPeriodPrefix(denom, id)) +} + +// IterateClaimPeriods iterates over all claim period objects in the store and preforms a callback function +func (k Keeper) IterateClaimPeriods(ctx sdk.Context, cb func(cp types.ClaimPeriod) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimPeriodKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var cp types.ClaimPeriod + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &cp) + if cb(cp) { + break + } + } +} + +// GetAllClaimPeriods returns all ClaimPeriod objects in the store +func (k Keeper) GetAllClaimPeriods(ctx sdk.Context) types.ClaimPeriods { + cps := types.ClaimPeriods{} + k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) { + cps = append(cps, cp) + return false + }) + return cps +} + +// GetClaim returns the claim in the store corresponding the the input address denom and id and a boolean for if the claim was found +func (k Keeper) GetClaim(ctx sdk.Context, addr sdk.AccAddress, denom string, id uint64) (types.Claim, bool) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) + bz := store.Get(types.GetClaimPrefix(addr, denom, id)) + if bz == nil { + return types.Claim{}, false + } + var c types.Claim + k.cdc.MustUnmarshalBinaryBare(bz, &c) + return c, true +} + +// SetClaim sets the claim in the store corresponding to the input address, denom, and id +func (k Keeper) SetClaim(ctx sdk.Context, c types.Claim) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) + bz := k.cdc.MustMarshalBinaryBare(c) + store.Set(types.GetClaimPrefix(c.Owner, c.Denom, c.ClaimPeriodID), bz) + +} + +// DeleteClaim deletes the claim in the store corresponding to the input address, denom, and id +func (k Keeper) DeleteClaim(ctx sdk.Context, owner sdk.AccAddress, denom string, id uint64) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) + store.Delete(types.GetClaimPrefix(owner, denom, id)) +} + +// IterateClaims iterates over all claim objects in the store and preforms a callback function +func (k Keeper) IterateClaims(ctx sdk.Context, cb func(c types.Claim) (stop bool)) { + store := prefix.NewStore(ctx.KVStore(k.key), types.ClaimKeyPrefix) + iterator := sdk.KVStorePrefixIterator(store, []byte{}) + defer iterator.Close() + for ; iterator.Valid(); iterator.Next() { + var c types.Claim + k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &c) + if cb(c) { + break + } + } +} + +// GetAllClaims returns all Claim objects in the store +func (k Keeper) GetAllClaims(ctx sdk.Context) types.Claims { + cs := types.Claims{} + k.IterateClaims(ctx, func(c types.Claim) (stop bool) { + cs = append(cs, c) + return false + }) + return cs +} + +// 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.MustUnmarshalBinaryBare(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.MustMarshalBinaryBare(blockTime)) +} diff --git a/x/incentive/keeper/params.go b/x/incentive/keeper/params.go new file mode 100644 index 00000000..ad80a0e8 --- /dev/null +++ b/x/incentive/keeper/params.go @@ -0,0 +1,18 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/incentive/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, ¶ms) +} diff --git a/x/incentive/keeper/payout.go b/x/incentive/keeper/payout.go new file mode 100644 index 00000000..ac3142b8 --- /dev/null +++ b/x/incentive/keeper/payout.go @@ -0,0 +1,204 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + supplyExported "github.com/cosmos/cosmos-sdk/x/supply/exported" + "github.com/kava-labs/kava/x/incentive/types" + validatorvesting "github.com/kava-labs/kava/x/validator-vesting" +) + +// PayoutClaim sends the timelocked claim coins to the input address +func (k Keeper) PayoutClaim(ctx sdk.Context, addr sdk.AccAddress, denom string, id uint64) error { + claim, found := k.GetClaim(ctx, addr, denom, id) + if !found { + return sdkerrors.Wrapf(types.ErrClaimNotFound, "id: %d, denom %s, address: %s", id, denom, addr) + } + claimPeriod, found := k.GetClaimPeriod(ctx, id, denom) + if !found { + return sdkerrors.Wrapf(types.ErrClaimPeriodNotFound, "id: %d, denom: %s", id, denom) + } + err := k.SendTimeLockedCoinsToAccount(ctx, types.IncentiveMacc, addr, sdk.NewCoins(claim.Reward), int64(claimPeriod.TimeLock.Seconds())) + if err != nil { + return err + } + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeClaim, + sdk.NewAttribute(types.AttributeKeySender, fmt.Sprintf("%s", addr)), + ), + ) + return nil +} + +// SendTimeLockedCoinsToAccount sends time-locked coins from the input module account to the recipient. If the recipients account is not a vesting account, it is converted to a periodic vesting account and the coins are added to the vesting balance as a vesting period with the input length. +func (k Keeper) SendTimeLockedCoinsToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error { + macc := k.supplyKeeper.GetModuleAccount(ctx, senderModule) + if !macc.GetCoins().IsAllGTE(amt) { + return sdkerrors.Wrapf(types.ErrInsufficientModAccountBalance, "%s", senderModule) + } + + // 0. Get the account from the account keeper and do a type switch, error if it's a validator vesting account or module account (can make this work for validator vesting later if necessary) + acc := k.accountKeeper.GetAccount(ctx, recipientAddr) + + switch acc.(type) { + case *validatorvesting.ValidatorVestingAccount, supplyExported.ModuleAccountI: + return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", acc) + case *vesting.PeriodicVestingAccount: + return k.SendTimeLockedCoinsToPeriodicVestingAccount(ctx, senderModule, recipientAddr, amt, length) + case *auth.BaseAccount: + return k.SendTimeLockedCoinsToBaseAccount(ctx, senderModule, recipientAddr, amt, length) + default: + return sdkerrors.Wrapf(types.ErrInvalidAccountType, "%T", acc) + } +} + +// SendTimeLockedCoinsToPeriodicVestingAccount sends time-locked coins from the input module account to the recipient +func (k Keeper) SendTimeLockedCoinsToPeriodicVestingAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error { + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt) + if err != nil { + return err + } + k.addCoinsToVestingSchedule(ctx, recipientAddr, amt, length) + return nil +} + +// SendTimeLockedCoinsToBaseAccount sends time-locked coins from the input module account to the recipient, converting the recipient account to a vesting account +func (k Keeper) SendTimeLockedCoinsToBaseAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins, length int64) error { + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, senderModule, recipientAddr, amt) + if err != nil { + return err + } + acc := k.accountKeeper.GetAccount(ctx, recipientAddr) + // transition the account to a periodic vesting account: + bacc := authtypes.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence()) + newPeriods := vesting.Periods{types.NewPeriod(amt, length)} + bva, err := vesting.NewBaseVestingAccount(bacc, amt, ctx.BlockTime().Unix()+length) + if err != nil { + return err + } + pva := vesting.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), newPeriods) + k.accountKeeper.SetAccount(ctx, pva) + return nil +} + +// DeleteExpiredClaimsAndClaimPeriods deletes expired claim periods and their associated claims +func (k Keeper) DeleteExpiredClaimsAndClaimPeriods(ctx sdk.Context) { + k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) { + if !cp.End.Before(ctx.BlockTime()) { + return false + } + k.IterateClaims(ctx, func(c types.Claim) (stop bool) { + if !(c.Denom == cp.Denom && c.ClaimPeriodID == cp.ID) { + return false + } + k.DeleteClaim(ctx, c.Owner, c.Denom, c.ClaimPeriodID) + return false + }) + k.DeleteClaimPeriod(ctx, cp.ID, cp.Denom) + return false + }) +} + +// GetClaimsByAddressAndDenom returns all claims for a specific user and address and a bool for if any were found +func (k Keeper) GetClaimsByAddressAndDenom(ctx sdk.Context, addr sdk.AccAddress, denom string) (claims types.Claims, found bool) { + found = false + k.IterateClaimPeriods(ctx, func(cp types.ClaimPeriod) (stop bool) { + if cp.Denom != denom { + return false + } + c, hasClaim := k.GetClaim(ctx, addr, cp.Denom, cp.ID) + if !hasClaim { + return false + } + found = true + claims = append(claims, c) + return false + }) + return claims, found +} + +// addCoinsToVestingSchedule adds coins to the input account's vesting schedule where length is the amount of time (from the current block time), in seconds, that the coins will be vesting for +// the input address must be a periodic vesting account +func (k Keeper) addCoinsToVestingSchedule(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins, length int64) { + acc := k.accountKeeper.GetAccount(ctx, addr) + vacc := acc.(*vesting.PeriodicVestingAccount) + // Add the new vesting coins to OriginalVesting + vacc.OriginalVesting = vacc.OriginalVesting.Add(amt...) + // update vesting periods + if vacc.EndTime < ctx.BlockTime().Unix() { + // edge case one - the vesting account's end time is in the past (ie, all previous vesting periods have completed) + // append a new period to the vesting account, update the end time, update the account in the store and return + newPeriodLength := (ctx.BlockTime().Unix() - vacc.EndTime) + length + newPeriod := types.NewPeriod(amt, newPeriodLength) + vacc.VestingPeriods = append(vacc.VestingPeriods, newPeriod) + vacc.EndTime = ctx.BlockTime().Unix() + length + k.accountKeeper.SetAccount(ctx, vacc) + return + } + if vacc.StartTime > ctx.BlockTime().Unix() { + // edge case two - the vesting account's start time is in the future (all periods have not started) + // update the start time to now and adjust the period lengths in place - a new period will be inserted in the next code block + updatedPeriods := vesting.Periods{} + for i, period := range vacc.VestingPeriods { + updatedPeriod := period + if i == 0 { + updatedPeriod = types.NewPeriod(period.Amount, (vacc.StartTime-ctx.BlockTime().Unix())+period.Length) + } + updatedPeriods = append(updatedPeriods, updatedPeriod) + } + vacc.VestingPeriods = updatedPeriods + vacc.StartTime = ctx.BlockTime().Unix() + } + + // logic for inserting a new vesting period into the existing vesting schedule + totalPeriodLength := types.GetTotalVestingPeriodLength(vacc.VestingPeriods) + proposedEndTime := ctx.BlockTime().Unix() + length + if totalPeriodLength < length { + // in the case that the proposed length is longer than the sum of all previous period lengths, create a new period with length equal to the difference between the proposed length and the previous total length + newPeriodLength := length - totalPeriodLength + newPeriod := types.NewPeriod(amt, newPeriodLength) + vacc.VestingPeriods = append(vacc.VestingPeriods, newPeriod) + // update the end time so that the sum of all period lengths equals endTime - startTime + vacc.EndTime = proposedEndTime + } else { + // In the case that the proposed length is less than or equal to the sum of all previous period lengths, insert the period and update other periods as necessary. + // EXAMPLE (l is length, a is amount) + // Original Periods: {[l: 1 a: 1], [l: 2, a: 1], [l:8, a:3], [l: 5, a: 3]} + // Period we want to insert [l: 5, a: x] + // Expected result: + // {[l: 1, a: 1], [l:2, a: 1], [l:2, a:x], [l:6, a:3], [l:5, a:3]} + + newPeriods := vesting.Periods{} + lengthCounter := int64(0) + appendRemaining := false + for _, period := range vacc.VestingPeriods { + if appendRemaining { + newPeriods = append(newPeriods, period) + continue + } + lengthCounter += period.Length + if lengthCounter < length { + newPeriods = append(newPeriods, period) + } else if lengthCounter == length { + newPeriod := types.NewPeriod(period.Amount.Add(amt...), period.Length) + newPeriods = append(newPeriods, newPeriod) + appendRemaining = true + } else { + newPeriod := types.NewPeriod(amt, length-types.GetTotalVestingPeriodLength(newPeriods)) + previousPeriod := types.NewPeriod(period.Amount, period.Length-newPeriod.Length) + newPeriods = append(newPeriods, newPeriod, previousPeriod) + appendRemaining = true + } + } + vacc.VestingPeriods = newPeriods + } + k.accountKeeper.SetAccount(ctx, vacc) + return +} diff --git a/x/incentive/keeper/querier.go b/x/incentive/keeper/querier.go new file mode 100644 index 00000000..f5a3654c --- /dev/null +++ b/x/incentive/keeper/querier.go @@ -0,0 +1,51 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/kava-labs/kava/x/incentive/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +// NewQuerier is the module level router for state queries +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) { + switch path[0] { + case types.QueryGetParams: + return queryGetParams(ctx, req, k) + case types.QueryGetClaims: + return queryGetClaims(ctx, req, k) + default: + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName) + } + } +} + +// query params in the store +func queryGetParams(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { + // Get params + params := k.GetParams(ctx) + + // Encode results + bz, err := codec.MarshalJSONIndent(k.cdc, params) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return bz, nil +} + +func queryGetClaims(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, error) { + var requestParams types.QueryClaimsParams + err := k.cdc.UnmarshalJSON(req.Data, &requestParams) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + claims, _ := k.GetClaimsByAddressAndDenom(ctx, requestParams.Owner, requestParams.Denom) + + bz, err := codec.MarshalJSONIndent(k.cdc, claims) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + return bz, nil +} diff --git a/x/incentive/keeper/rewards.go b/x/incentive/keeper/rewards.go new file mode 100644 index 00000000..116c8755 --- /dev/null +++ b/x/incentive/keeper/rewards.go @@ -0,0 +1,110 @@ +package keeper + +import ( + "time" + + "github.com/cosmos/cosmos-sdk/store/prefix" + sdk "github.com/cosmos/cosmos-sdk/types" + cdptypes "github.com/kava-labs/kava/x/cdp/types" + "github.com/kava-labs/kava/x/incentive/types" +) + +// HandleRewardPeriodExpiry deletes expired RewardPeriods from the store and creates a ClaimPeriod in the store for each expired RewardPeriod +func (k Keeper) HandleRewardPeriodExpiry(ctx sdk.Context, rp types.RewardPeriod) { + k.CreateUniqueClaimPeriod(ctx, rp.Denom, rp.ClaimEnd, rp.ClaimTimeLock) + store := prefix.NewStore(ctx.KVStore(k.key), types.RewardPeriodKeyPrefix) + store.Delete([]byte(rp.Denom)) + return +} + +// CreateNewRewardPeriod creates a new reward period from the input reward +func (k Keeper) CreateNewRewardPeriod(ctx sdk.Context, reward types.Reward) { + // reward periods store the amount of rewards payed PER SECOND + rewardsPerSecond := sdk.NewDecFromInt(reward.AvailableRewards.Amount).Quo(sdk.NewDecFromInt(sdk.NewInt(int64(reward.Duration.Seconds())))).TruncateInt() + rewardCoinPerSecond := sdk.NewCoin(reward.AvailableRewards.Denom, rewardsPerSecond) + rp := types.RewardPeriod{ + Denom: reward.Denom, + Start: ctx.BlockTime(), + End: ctx.BlockTime().Add(reward.Duration), + Reward: rewardCoinPerSecond, + ClaimEnd: ctx.BlockTime().Add(reward.Duration).Add(reward.ClaimDuration), + ClaimTimeLock: reward.TimeLock, + } + k.SetRewardPeriod(ctx, rp) +} + +// CreateAndDeleteRewardPeriods creates reward periods for active rewards that don't already have a reward period and deletes reward periods for inactive rewards that currently have a reward period +func (k Keeper) CreateAndDeleteRewardPeriods(ctx sdk.Context) { + params := k.GetParams(ctx) + + for _, r := range params.Rewards { + _, found := k.GetRewardPeriod(ctx, r.Denom) + // if governance has made a reward inactive, delete the current period + if found && !r.Active { + k.DeleteRewardPeriod(ctx, r.Denom) + } + // if a reward period for an active reward is not found, create one + if !found && r.Active { + k.CreateNewRewardPeriod(ctx, r) + } + } +} + +// ApplyRewardsToCdps iterates over the reward periods and creates a claim for each cdp owner that created usdx with the collateral specified in the reward period +func (k Keeper) ApplyRewardsToCdps(ctx sdk.Context) { + previousBlockTime, found := k.GetPreviousBlockTime(ctx) + if !found { + previousBlockTime = ctx.BlockTime() + k.SetPreviousBlockTime(ctx, previousBlockTime) + return + } + k.IterateRewardPeriods(ctx, func(rp types.RewardPeriod) bool { + expired := false + // the total amount of usdx created with the collateral type being incentivized + totalPrincipal := k.cdpKeeper.GetTotalPrincipal(ctx, rp.Denom, types.PrincipalDenom) + // the number of seconds since last payout + timeElapsed := sdk.NewInt(ctx.BlockTime().Unix() - previousBlockTime.Unix()) + if rp.End.Before(ctx.BlockTime()) { + timeElapsed = sdk.NewInt(rp.End.Unix() - previousBlockTime.Unix()) + expired = true + } + // the amount of rewards to pay (rewardAmount * timeElapsed) + rewardsThisPeriod := rp.Reward.Amount.Mul(timeElapsed) + id := k.GetNextClaimPeriodID(ctx, rp.Denom) + k.cdpKeeper.IterateCdpsByDenom(ctx, rp.Denom, func(cdp cdptypes.CDP) bool { + rewardsShare := sdk.NewDecFromInt(cdp.Principal.AmountOf(types.PrincipalDenom).Add(cdp.AccumulatedFees.AmountOf(types.PrincipalDenom))).Quo(sdk.NewDecFromInt(totalPrincipal)) + // sanity check - don't create zero claims + if rewardsShare.IsZero() { + return false + } + rewardsEarned := rewardsShare.Mul(sdk.NewDecFromInt(rewardsThisPeriod)).RoundInt() + k.AddToClaim(ctx, cdp.Owner, rp.Denom, id, sdk.NewCoin(types.GovDenom, rewardsEarned)) + return false + }) + if !expired { + return false + } + k.HandleRewardPeriodExpiry(ctx, rp) + return false + }) + k.SetPreviousBlockTime(ctx, ctx.BlockTime()) +} + +// CreateUniqueClaimPeriod creates a new claim period in the store and updates the highest claim period id +func (k Keeper) CreateUniqueClaimPeriod(ctx sdk.Context, denom string, end time.Time, timeLock time.Duration) { + id := k.GetNextClaimPeriodID(ctx, denom) + claimPeriod := types.NewClaimPeriod(denom, id, end, timeLock) + k.SetClaimPeriod(ctx, claimPeriod) + k.SetNextClaimPeriodID(ctx, denom, id+1) +} + +// AddToClaim adds the amount to an existing claim or creates a new one for that amount +func (k Keeper) AddToClaim(ctx sdk.Context, addr sdk.AccAddress, denom string, id uint64, amount sdk.Coin) { + claim, found := k.GetClaim(ctx, addr, denom, id) + if found { + claim.Reward = claim.Reward.Add(amount) + } else { + claim = types.NewClaim(addr, amount, denom, id) + } + k.SetClaim(ctx, claim) +} diff --git a/x/incentive/module.go b/x/incentive/module.go new file mode 100644 index 00000000..623f883e --- /dev/null +++ b/x/incentive/module.go @@ -0,0 +1,164 @@ +package incentive + +import ( + "encoding/json" + "math/rand" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + sim "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/gorilla/mux" + "github.com/kava-labs/kava/x/incentive/client/cli" + "github.com/kava-labs/kava/x/incentive/client/rest" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/simulation" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} +) + +// AppModuleBasic defines the basic application module used by the incentive module. +type AppModuleBasic struct{} + +// Name returns the incentive module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterCodec registers the incentive module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + types.RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the incentive +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the incentive module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var gs types.GenesisState + err := types.ModuleCdc.UnmarshalJSON(bz, &gs) + if err != nil { + return err + } + return gs.Validate() +} + +// RegisterRESTRoutes registers the REST routes for the incentive module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + rest.RegisterRoutes(ctx, rtr) +} + +// GetTxCmd returns the root tx command for the incentive module. +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetTxCmd(cdc) +} + +// GetQueryCmd returns no root query command for the crisis module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + return cli.GetQueryCmd(types.StoreKey, cdc) +} + +// RegisterStoreDecoder registers a decoder for cdp module's types +func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// GenerateGenesisState creates a randomized GenState of the cdp module +func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RandomizedParams creates randomized cdp param changes for the simulator. +func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +// ProposalContents doesn't return any content functions for governance proposals. +func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent { + return nil +} + +// WeightedOperations returns the all the bep3 module operations with their respective weights. +func (am AppModule) WeightedOperations(_ module.SimulationState) []sim.WeightedOperation { + return nil +} + +// AppModule implements the sdk.AppModule interface. +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + supplyKeeper types.SupplyKeeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper keeper.Keeper, supplyKeeper types.SupplyKeeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + supplyKeeper: supplyKeeper, + } +} + +// Name returns the incentive module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants registers the incentive module invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route returns the message routing key for the incentive module. +func (AppModule) Route() string { + return types.RouterKey +} + +// NewHandler returns an sdk.Handler for the incentive module. +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} + +// QuerierRoute returns the incentive module's querier route name. +func (AppModule) QuerierRoute() string { + return types.QuerierRoute +} + +// NewQuerierHandler returns the incentive module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return keeper.NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the incentive module. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var gs types.GenesisState + types.ModuleCdc.MustUnmarshalJSON(data, &gs) + InitGenesis(ctx, am.keeper, am.supplyKeeper, gs) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the incentive module +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return types.ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the incentive module. +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, am.keeper) +} + +// EndBlock returns the end blocker for the incentive module. It returns no validator updates. +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/incentive/simulation/decoder.go b/x/incentive/simulation/decoder.go new file mode 100644 index 00000000..ace82b35 --- /dev/null +++ b/x/incentive/simulation/decoder.go @@ -0,0 +1,12 @@ +package simulation + +import ( + "github.com/cosmos/cosmos-sdk/codec" + "github.com/tendermint/tendermint/libs/kv" +) + +// DecodeStore unmarshals the KVPair's Value to the corresponding incentive type +func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { + // TODO implement this + return "" +} diff --git a/x/incentive/simulation/params.go b/x/incentive/simulation/params.go new file mode 100644 index 00000000..8c1f7aff --- /dev/null +++ b/x/incentive/simulation/params.go @@ -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{} +} diff --git a/x/incentive/simulation/simulation.go b/x/incentive/simulation/simulation.go new file mode 100644 index 00000000..3057550f --- /dev/null +++ b/x/incentive/simulation/simulation.go @@ -0,0 +1,22 @@ +package simulation + +import ( + "fmt" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/types/module" + + "github.com/kava-labs/kava/x/incentive/types" +) + +// RandomizedGenState generates a random GenesisState for cdp +func RandomizedGenState(simState *module.SimulationState) { + + // TODO implement this fully + // - randomly generating the genesis params + // - overwriting with genesis provided to simulation + genesis := types.DefaultGenesisState() + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) +} diff --git a/x/incentive/types/account.go b/x/incentive/types/account.go new file mode 100644 index 00000000..d651d7ee --- /dev/null +++ b/x/incentive/types/account.go @@ -0,0 +1,14 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/x/auth/vesting" +) + +// GetTotalVestingPeriodLength returns the summed length of all vesting periods +func GetTotalVestingPeriodLength(periods vesting.Periods) int64 { + length := int64(0) + for _, period := range periods { + length += period.Length + } + return length +} diff --git a/x/incentive/types/account_test.go b/x/incentive/types/account_test.go new file mode 100644 index 00000000..525a3d02 --- /dev/null +++ b/x/incentive/types/account_test.go @@ -0,0 +1,51 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/stretchr/testify/suite" +) + +type accountTest struct { + periods vesting.Periods + expectedVal int64 +} + +type AccountTestSuite struct { + suite.Suite + + tests []accountTest +} + +func (suite *AccountTestSuite) SetupTest() { + tests := []accountTest{ + accountTest{ + periods: vesting.Periods{ + vesting.Period{ + Length: int64(100), + Amount: sdk.Coins{}, + }, + vesting.Period{ + Length: int64(200), + Amount: sdk.Coins{}, + }, + }, + expectedVal: int64(300), + }, + } + suite.tests = tests +} + +func (suite *AccountTestSuite) TestGetTotalPeriodLength() { + for _, t := range suite.tests { + length := types.GetTotalVestingPeriodLength(t.periods) + suite.Equal(t.expectedVal, length) + } +} + +func TestAccountTestSuite(t *testing.T) { + suite.Run(t, new(AccountTestSuite)) +} diff --git a/x/incentive/types/codec.go b/x/incentive/types/codec.go new file mode 100644 index 00000000..31f066e0 --- /dev/null +++ b/x/incentive/types/codec.go @@ -0,0 +1,22 @@ +package types + +import "github.com/cosmos/cosmos-sdk/codec" + +// ModuleCdc generic sealed codec to be used throughout module +var ModuleCdc *codec.Codec + +func init() { + cdc := codec.New() + RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + ModuleCdc = cdc.Seal() +} + +// RegisterCodec registers the necessary types for incentive module +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgClaimReward{}, "incentive/MsgClaimReward", nil) + cdc.RegisterConcrete(GenesisClaimPeriodID{}, "incentive/GenesisClaimPeriodID", nil) + cdc.RegisterConcrete(RewardPeriod{}, "incentive/RewardPeriod", nil) + cdc.RegisterConcrete(ClaimPeriod{}, "incentive/ClaimPeriod", nil) + cdc.RegisterConcrete(Claim{}, "incentive/Claim", nil) +} diff --git a/x/incentive/types/errors.go b/x/incentive/types/errors.go new file mode 100644 index 00000000..f42cb5b6 --- /dev/null +++ b/x/incentive/types/errors.go @@ -0,0 +1,15 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// DONTCOVER + +var ( + ErrClaimNotFound = sdkerrors.Register(ModuleName, 1, "no claim with input id found for owner and denom") + ErrClaimPeriodNotFound = sdkerrors.Register(ModuleName, 2, "no claim period found for id and denom") + ErrInvalidAccountType = sdkerrors.Register(ModuleName, 3, "account type not supported") + ErrNoClaimsFound = sdkerrors.Register(ModuleName, 4, "no claims with denom found for address") + ErrInsufficientModAccountBalance = sdkerrors.Register(ModuleName, 5, "module account has insufficient balance to pay claim") +) diff --git a/x/incentive/types/events.go b/x/incentive/types/events.go new file mode 100644 index 00000000..09d0cd47 --- /dev/null +++ b/x/incentive/types/events.go @@ -0,0 +1,8 @@ +package types + +const ( + EventTypeClaim = "claim_reward" + + AttributeValueCategory = ModuleName + AttributeKeySender = "sender" +) diff --git a/x/incentive/types/expected_keepers.go b/x/incentive/types/expected_keepers.go new file mode 100644 index 00000000..ccb20830 --- /dev/null +++ b/x/incentive/types/expected_keepers.go @@ -0,0 +1,27 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" + cdptypes "github.com/kava-labs/kava/x/cdp/types" +) + +// SupplyKeeper defines the expected supply keeper for module accounts +type SupplyKeeper interface { + GetModuleAccount(ctx sdk.Context, name string) supplyexported.ModuleAccountI + + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error +} + +// CdpKeeper defines the expected cdp keeper for interacting with cdps +type CdpKeeper interface { + IterateCdpsByDenom(ctx sdk.Context, denom string, cb func(cdp cdptypes.CDP) (stop bool)) + GetTotalPrincipal(ctx sdk.Context, collateralDenom string, principalDenom string) (total sdk.Int) +} + +// AccountKeeper defines the expected keeper interface for interacting with account +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authexported.Account + SetAccount(ctx sdk.Context, acc authexported.Account) +} diff --git a/x/incentive/types/genesis.go b/x/incentive/types/genesis.go new file mode 100644 index 00000000..d73d1b5e --- /dev/null +++ b/x/incentive/types/genesis.go @@ -0,0 +1,75 @@ +package types + +import ( + "bytes" + "fmt" + "time" +) + +// GenesisClaimPeriodID stores the next claim id and its corresponding denom +type GenesisClaimPeriodID struct { + Denom string `json:"denom" yaml:"denom"` + ID uint64 `json:"id" yaml:"id"` +} + +// GenesisClaimPeriodIDs array of GenesisClaimPeriodID +type GenesisClaimPeriodIDs []GenesisClaimPeriodID + +// GenesisState is the state that must be provided at genesis. +type GenesisState struct { + Params Params `json:"params" yaml:"params"` + PreviousBlockTime time.Time `json:"previous_block_time" yaml:"previous_block_time"` + RewardPeriods RewardPeriods `json:"reward_periods" yaml:"reward_periods"` + ClaimPeriods ClaimPeriods `json:"claim_periods" yaml:"claim_periods"` + Claims Claims `json:"claims" yaml:"claims"` + NextClaimPeriodIDs GenesisClaimPeriodIDs `json:"next_claim_period_ids" yaml:"next_claim_period_ids"` +} + +// NewGenesisState returns a new genesis state +func NewGenesisState(params Params, previousBlockTime time.Time, rp RewardPeriods, cp ClaimPeriods, c Claims, ids GenesisClaimPeriodIDs) GenesisState { + return GenesisState{ + Params: params, + PreviousBlockTime: previousBlockTime, + RewardPeriods: rp, + ClaimPeriods: cp, + Claims: c, + NextClaimPeriodIDs: ids, + } +} + +// DefaultGenesisState returns a default genesis state +func DefaultGenesisState() GenesisState { + return GenesisState{ + Params: DefaultParams(), + PreviousBlockTime: DefaultPreviousBlockTime, + RewardPeriods: RewardPeriods{}, + ClaimPeriods: ClaimPeriods{}, + Claims: Claims{}, + NextClaimPeriodIDs: GenesisClaimPeriodIDs{}, + } +} + +// Validate performs basic validation of genesis data returning an +// error for any failed validation criteria. +func (gs GenesisState) Validate() error { + + if err := gs.Params.Validate(); err != nil { + return err + } + if gs.PreviousBlockTime.Equal(time.Time{}) { + return fmt.Errorf("previous block time not set") + } + return nil +} + +// Equal checks whether two gov 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{}) +} diff --git a/x/incentive/types/keys.go b/x/incentive/types/keys.go new file mode 100644 index 00000000..1185ff1d --- /dev/null +++ b/x/incentive/types/keys.go @@ -0,0 +1,61 @@ +package types + +import ( + "encoding/binary" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // ModuleName The name that will be used throughout the module + ModuleName = "incentive" + + // StoreKey Top level store key where all module items will be stored + StoreKey = ModuleName + + // RouterKey Top level router key + RouterKey = ModuleName + + // DefaultParamspace default name for parameter store + DefaultParamspace = ModuleName + + // QuerierRoute route used for abci queries + QuerierRoute = ModuleName +) + +// Key Prefixes +var ( + RewardPeriodKeyPrefix = []byte{0x01} // prefix for keys that store reward periods + ClaimPeriodKeyPrefix = []byte{0x02} // prefix for keys that store claim periods + ClaimKeyPrefix = []byte{0x03} // prefix for keys that store claims + NextClaimPeriodIDPrefix = []byte{0x04} // prefix for keys that store the next ID for claims periods + PreviousBlockTimeKey = []byte{0x05} // prefix for key that stores the previous blocktime +) + +// Keys +// 0x00:Denom <> RewardPeriod the current active reward period (max 1 reward period per denom) +// 0x01:Denom:ID <> ClaimPeriod object for that ID, indexed by denom and ID +// 0x02:Denom:ID:Owner <> Claim object, indexed by Denom, ID and owner +// 0x03:Denom <> NextClaimPeriodIDPrefix the ID of the next claim period, indexed by denom + +// BytesToUint64 returns uint64 format from a byte array +func BytesToUint64(bz []byte) uint64 { + return binary.BigEndian.Uint64(bz) +} + +// GetClaimPeriodPrefix returns the key (denom + id) for a claim prefix +func GetClaimPeriodPrefix(denom string, id uint64) []byte { + return createKey([]byte(denom), sdk.Uint64ToBigEndian(id)) +} + +// GetClaimPrefix returns the key (denom + id + address) for a claim +func GetClaimPrefix(addr sdk.AccAddress, denom string, id uint64) []byte { + return createKey([]byte(denom), sdk.Uint64ToBigEndian(id), addr) +} + +func createKey(bytes ...[]byte) (r []byte) { + for _, b := range bytes { + r = append(r, b...) + } + return +} diff --git a/x/incentive/types/msg.go b/x/incentive/types/msg.go new file mode 100644 index 00000000..446389d5 --- /dev/null +++ b/x/incentive/types/msg.go @@ -0,0 +1,54 @@ +package types + +import ( + "errors" + "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// ensure Msg interface compliance at compile time +var _ sdk.Msg = &MsgClaimReward{} + +// MsgClaimReward message type used to claim rewards +type MsgClaimReward struct { + Sender sdk.AccAddress `json:"sender" yaml:"sender"` + Denom string `json:"denom" yaml:"denom"` +} + +// NewMsgClaimReward returns a new MsgClaimReward. +func NewMsgClaimReward(sender sdk.AccAddress, denom string) MsgClaimReward { + return MsgClaimReward{ + Sender: sender, + Denom: denom, + } +} + +// Route return the message type used for routing the message. +func (msg MsgClaimReward) Route() string { return RouterKey } + +// Type returns a human-readable string for the message, intended for utilization within tags. +func (msg MsgClaimReward) Type() string { return "claim_reward" } + +// ValidateBasic does a simple validation check that doesn't require access to state. +func (msg MsgClaimReward) ValidateBasic() error { + if msg.Sender.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "sender address cannot be empty") + } + if strings.TrimSpace(msg.Denom) == "" { + return errors.New("invalid (empty) denom") + } + return nil +} + +// GetSignBytes gets the canonical byte representation of the Msg. +func (msg MsgClaimReward) GetSignBytes() []byte { + bz := ModuleCdc.MustMarshalJSON(msg) + return sdk.MustSortJSON(bz) +} + +// GetSigners returns the addresses of signers that must sign. +func (msg MsgClaimReward) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} diff --git a/x/incentive/types/msg_test.go b/x/incentive/types/msg_test.go new file mode 100644 index 00000000..0d77c94e --- /dev/null +++ b/x/incentive/types/msg_test.go @@ -0,0 +1,59 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/stretchr/testify/suite" + "github.com/tendermint/tendermint/crypto" +) + +type msgTest struct { + from sdk.AccAddress + denom string + expectPass bool +} + +type MsgTestSuite struct { + suite.Suite + + tests []msgTest +} + +func (suite *MsgTestSuite) SetupTest() { + tests := []msgTest{ + msgTest{ + from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), + denom: "bnb", + expectPass: true, + }, + msgTest{ + from: sdk.AccAddress(crypto.AddressHash([]byte("KavaTest1"))), + denom: "", + expectPass: false, + }, + msgTest{ + from: sdk.AccAddress{}, + denom: "bnb", + expectPass: false, + }, + } + suite.tests = tests +} + +func (suite *MsgTestSuite) TestMsgValidation() { + for _, t := range suite.tests { + msg := types.NewMsgClaimReward(t.from, t.denom) + err := msg.ValidateBasic() + if t.expectPass { + suite.NoError(err) + } else { + suite.Error(err) + } + } +} + +func TestMsgTestSuite(t *testing.T) { + suite.Run(t, new(MsgTestSuite)) +} diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go new file mode 100644 index 00000000..555beebc --- /dev/null +++ b/x/incentive/types/params.go @@ -0,0 +1,165 @@ +package types + +import ( + "fmt" + "strings" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + cdptypes "github.com/kava-labs/kava/x/cdp/types" + kavadistTypes "github.com/kava-labs/kava/x/kavadist/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// Parameter keys and default values +var ( + KeyActive = []byte("Active") + KeyRewards = []byte("Rewards") + DefaultActive = false + DefaultRewards = Rewards{} + DefaultPreviousBlockTime = tmtime.Canonical(time.Unix(0, 0)) + GovDenom = cdptypes.DefaultGovDenom + PrincipalDenom = "usdx" + IncentiveMacc = kavadistTypes.ModuleName +) + +// Params governance parameters for the incentive module +type Params struct { + Active bool `json:"active" yaml:"active"` // top level governance switch to disable all rewards + Rewards Rewards `json:"rewards" yaml:"rewards"` +} + +// NewParams returns a new params object +func NewParams(active bool, rewards Rewards) Params { + return Params{ + Active: active, + Rewards: rewards, + } +} + +// DefaultParams returns default params for kavadist module +func DefaultParams() Params { + return NewParams(DefaultActive, DefaultRewards) +} + +// String implements fmt.Stringer +func (p Params) String() string { + return fmt.Sprintf(`Params: + Active: %t + Rewards: %s`, p.Active, p.Rewards) +} + +// 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 +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{ + params.NewParamSetPair(KeyActive, &p.Active, validateActiveParam), + params.NewParamSetPair(KeyRewards, &p.Rewards, validateRewardsParam), + } +} + +// Validate checks that the parameters have valid values. +func (p Params) Validate() error { + if err := validateActiveParam(p.Active); err != nil { + return err + } + + if err := validateRewardsParam(p.Rewards); err != nil { + return err + } + + return nil +} + +func validateActiveParam(i interface{}) error { + _, ok := i.(bool) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + return nil +} + +func validateRewardsParam(i interface{}) error { + rewards, ok := i.(Rewards) + if !ok { + return fmt.Errorf("invalid parameter type: %T", i) + } + rewardDenoms := make(map[string]bool) + + for _, reward := range rewards { + if strings.TrimSpace(reward.Denom) == "" { + return fmt.Errorf("cannot have empty reward denom: %s", reward) + } + if rewardDenoms[reward.Denom] { + return fmt.Errorf("cannot have duplicate reward denoms: %s", reward.Denom) + } + rewardDenoms[reward.Denom] = true + if !reward.AvailableRewards.IsValid() { + return fmt.Errorf("invalid reward coins %s for %s", reward.AvailableRewards, reward.Denom) + } + if !reward.AvailableRewards.IsPositive() { + return fmt.Errorf("reward amount must be positive, is %s for %s", reward.AvailableRewards, reward.Denom) + } + if int(reward.Duration.Seconds()) <= 0 { + return fmt.Errorf("reward duration must be positive, is %s for %s", reward.Duration.String(), reward.Denom) + } + if int(reward.TimeLock.Seconds()) < 0 { + return fmt.Errorf("reward timelock must be non-negative, is %s for %s", reward.TimeLock.String(), reward.Denom) + } + if int(reward.ClaimDuration.Seconds()) <= 0 { + return fmt.Errorf("reward timelock must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom) + } + } + return nil +} + +// Reward stores the specified state for a single reward period. +type Reward struct { + Active bool `json:"active" yaml:"active"` // governance switch to disable a period + Denom string `json:"denom" yaml:"denom"` // the collateral denom rewards apply to, must be found in the cdp collaterals + AvailableRewards sdk.Coin `json:"available_rewards" yaml:"available_rewards"` // the total amount of coins distributed per period + Duration time.Duration `json:"duration" yaml:"duration"` // the duration of the period + TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` // how long rewards for this period are timelocked + ClaimDuration time.Duration `json:"claim_duration" yaml:"claim_duration"` // how long users have after the period ends to claim their rewards +} + +// NewReward returns a new Reward +func NewReward(active bool, denom string, reward sdk.Coin, duration time.Duration, timelock time.Duration, claimDuration time.Duration) Reward { + return Reward{ + Active: active, + Denom: denom, + AvailableRewards: reward, + Duration: duration, + TimeLock: timelock, + ClaimDuration: claimDuration, + } +} + +// String implements fmt.Stringer +func (r Reward) String() string { + return fmt.Sprintf(`Reward: + Active: %t, + Denom: %s, + Available Rewards: %s, + Duration: %s, + Time Lock: %s, + Claim Duration: %s`, + r.Active, r.Denom, r.AvailableRewards, r.Duration, r.TimeLock, r.ClaimDuration) +} + +// Rewards array of Reward +type Rewards []Reward + +// String implements fmt.Stringer +func (rs Rewards) String() string { + out := "Rewards\n" + for _, r := range rs { + out += fmt.Sprintf("%s\n", r) + } + return out +} diff --git a/x/incentive/types/params_test.go b/x/incentive/types/params_test.go new file mode 100644 index 00000000..745df28d --- /dev/null +++ b/x/incentive/types/params_test.go @@ -0,0 +1,152 @@ +package types_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/stretchr/testify/suite" +) + +type paramTest struct { + params types.Params + expectPass bool +} + +type ParamTestSuite struct { + suite.Suite + + tests []paramTest +} + +func (suite *ParamTestSuite) SetupTest() { + p1 := types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + } + p2 := types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + } + p3 := types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * -24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + } + p4 := types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * -8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + } + p5 := types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 0, + }, + }, + } + p6 := types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 0, + }, + }, + } + + suite.tests = []paramTest{ + paramTest{ + params: p1, + expectPass: true, + }, + paramTest{ + params: p2, + expectPass: false, + }, + paramTest{ + params: p3, + expectPass: false, + }, + paramTest{ + params: p4, + expectPass: false, + }, + paramTest{ + params: p5, + expectPass: false, + }, + paramTest{ + params: p6, + expectPass: false, + }, + } +} + +func (suite *ParamTestSuite) TestParamValidation() { + for _, t := range suite.tests { + err := t.params.Validate() + if t.expectPass { + suite.NoError(err) + } else { + suite.Error(err) + } + } +} + +func TestParamTestSuite(t *testing.T) { + suite.Run(t, new(ParamTestSuite)) +} diff --git a/x/incentive/types/period.go b/x/incentive/types/period.go new file mode 100644 index 00000000..c2894750 --- /dev/null +++ b/x/incentive/types/period.go @@ -0,0 +1,11 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" +) + +// NewPeriod returns a new vesting period +func NewPeriod(amount sdk.Coins, length int64) vesting.Period { + return vesting.Period{Amount: amount, Length: length} +} diff --git a/x/incentive/types/querier.go b/x/incentive/types/querier.go new file mode 100644 index 00000000..d6271048 --- /dev/null +++ b/x/incentive/types/querier.go @@ -0,0 +1,35 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" +) + +// Querier routes for the incentive module +const ( + QueryGetClaims = "claims" + RestClaimOwner = "owner" + RestClaimDenom = "denom" + QueryGetParams = "parameters" +) + +// QueryClaimsParams params for query /incentive/claims +type QueryClaimsParams struct { + Owner sdk.AccAddress + Denom string +} + +// NewQueryClaimsParams returns QueryClaimsParams +func NewQueryClaimsParams(owner sdk.AccAddress, denom string) QueryClaimsParams { + return QueryClaimsParams{ + Owner: owner, + Denom: denom, + } +} + +// PostClaimReq defines the properties of claim transaction's request body. +type PostClaimReq struct { + BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"` + Sender sdk.AccAddress `json:"sender" yaml:"sender"` + Denom string `json:"denom" yaml:"denom"` +} diff --git a/x/incentive/types/rewards.go b/x/incentive/types/rewards.go new file mode 100644 index 00000000..237b222a --- /dev/null +++ b/x/incentive/types/rewards.go @@ -0,0 +1,97 @@ +package types + +import ( + "fmt" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// RewardPeriod stores the state of an ongoing reward +type RewardPeriod struct { + Denom string `json:"denom" yaml:"denom"` + Start time.Time `json:"start" yaml:"start"` + End time.Time `json:"end" yaml:"end"` + Reward sdk.Coin `json:"reward" yaml:"reward"` // per second reward payouts + ClaimEnd time.Time `json:"claim_end" yaml:"claim_end"` + ClaimTimeLock time.Duration `json:"claim_time_lock" yaml:"claim_time_lock"` // the amount of time rewards are timelocked once they are sent to users +} + +// String implements fmt.Stringer +func (rp RewardPeriod) String() string { + return fmt.Sprintf(`Reward Period: + Denom: %s, + Start: %s, + End: %s, + Reward: %s, + Claim End: %s, + Claim Time Lock: %s`, + rp.Denom, rp.Start, rp.End, rp.Reward, rp.ClaimEnd, rp.ClaimTimeLock) +} + +// NewRewardPeriod returns a new RewardPeriod +func NewRewardPeriod(denom string, start time.Time, end time.Time, reward sdk.Coin, claimEnd time.Time, claimTimeLock time.Duration) RewardPeriod { + return RewardPeriod{ + Denom: denom, + Start: start, + End: end, + Reward: reward, + ClaimEnd: claimEnd, + ClaimTimeLock: claimTimeLock, + } +} + +// RewardPeriods array of RewardPeriod +type RewardPeriods []RewardPeriod + +// ClaimPeriod stores the state of an ongoing claim period +type ClaimPeriod struct { + Denom string `json:"denom" yaml:"denom"` + ID uint64 `json:"id" yaml:"id"` + End time.Time `json:"end" yaml:"end"` + TimeLock time.Duration `json:"time_lock" yaml:"time_lock"` +} + +// NewClaimPeriod returns a new ClaimPeriod +func NewClaimPeriod(denom string, id uint64, end time.Time, timeLock time.Duration) ClaimPeriod { + return ClaimPeriod{ + Denom: denom, + ID: id, + End: end, + TimeLock: timeLock, + } +} + +// ClaimPeriods array of ClaimPeriod +type ClaimPeriods []ClaimPeriod + +// Claim stores the rewards that can be claimed by owner +type Claim struct { + Owner sdk.AccAddress `json:"owner" yaml:"owner"` + Reward sdk.Coin `json:"reward" yaml:"reward"` + Denom string `json:"denom" yaml:"denom"` + ClaimPeriodID uint64 `json:"claim_period_id" yaml:"claim_period_id"` +} + +// NewClaim returns a new Claim +func NewClaim(owner sdk.AccAddress, reward sdk.Coin, denom string, claimPeriodID uint64) Claim { + return Claim{ + Owner: owner, + Reward: reward, + Denom: denom, + ClaimPeriodID: claimPeriodID, + } +} + +// String implements fmt.Stringer +func (c Claim) String() string { + return fmt.Sprintf(`Claim: + Owner: %s, + Denom: %s, + Reward: %s, + Claim Period ID: %d,`, + c.Owner, c.Denom, c.Reward, c.ClaimPeriodID) +} + +// Claims array of Claim +type Claims []Claim From e9a73b80ce91dc85cc1954b40e6e62ff6b9e8063 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 24 Apr 2020 11:44:44 -0400 Subject: [PATCH 44/45] Incentives tests (#429) * USDX Incentives tests (#429) Co-authored-by: Denali Marsh Co-authored-by: John Maheswaran Co-authored-by: John Maheswaran --- run_a_bunch_of_sims.sh | 5 + x/incentive/handler_test.go | 74 ++++++ x/incentive/keeper/keeper_test.go | 174 ++++++++++++++ x/incentive/keeper/payout_test.go | 357 +++++++++++++++++++++++++++++ x/incentive/keeper/querier_test.go | 32 +++ x/incentive/keeper/rewards_test.go | 258 +++++++++++++++++++++ x/incentive/types/msg_test.go | 4 +- x/incentive/types/params.go | 2 +- x/incentive/types/params_test.go | 279 +++++++++++++--------- 9 files changed, 1076 insertions(+), 109 deletions(-) create mode 100755 run_a_bunch_of_sims.sh create mode 100644 x/incentive/handler_test.go create mode 100644 x/incentive/keeper/keeper_test.go create mode 100644 x/incentive/keeper/payout_test.go create mode 100644 x/incentive/keeper/querier_test.go create mode 100644 x/incentive/keeper/rewards_test.go diff --git a/run_a_bunch_of_sims.sh b/run_a_bunch_of_sims.sh new file mode 100755 index 00000000..3fe1f5d7 --- /dev/null +++ b/run_a_bunch_of_sims.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +for i in {1..10}; do + go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed ${i} -v -timeout 24h +done \ No newline at end of file diff --git a/x/incentive/handler_test.go b/x/incentive/handler_test.go new file mode 100644 index 00000000..70253ef3 --- /dev/null +++ b/x/incentive/handler_test.go @@ -0,0 +1,74 @@ +package incentive_test + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/incentive" + "github.com/kava-labs/kava/x/kavadist" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } + +type HandlerTestSuite struct { + suite.Suite + + ctx sdk.Context + app app.TestApp + handler sdk.Handler + keeper incentive.Keeper + addrs []sdk.AccAddress +} + +func (suite *HandlerTestSuite) SetupTest() { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + keeper := tApp.GetIncentiveKeeper() + + // 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) + + suite.addrs = addrs + suite.handler = incentive.NewHandler(keeper) + suite.keeper = keeper + suite.app = tApp + suite.ctx = ctx +} + +func (suite *HandlerTestSuite) addClaim() { + supplyKeeper := suite.app.GetSupplyKeeper() + macc := supplyKeeper.GetModuleAccount(suite.ctx, kavadist.ModuleName) + err := supplyKeeper.MintCoins(suite.ctx, macc.GetName(), cs(c("ukava", 1000000))) + suite.Require().NoError(err) + cp := incentive.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + suite.NotPanics(func() { + suite.keeper.SetClaimPeriod(suite.ctx, cp) + }) + c1 := incentive.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + suite.NotPanics(func() { + suite.keeper.SetClaim(suite.ctx, c1) + }) +} + +func (suite *HandlerTestSuite) TestMsgClaimReward() { + suite.addClaim() + msg := incentive.NewMsgClaimReward(suite.addrs[0], "bnb") + res, err := suite.handler(suite.ctx, msg) + suite.NoError(err) + suite.Require().NotNil(res) +} +func TestHandlerTestSuite(t *testing.T) { + suite.Run(t, new(HandlerTestSuite)) +} diff --git a/x/incentive/keeper/keeper_test.go b/x/incentive/keeper/keeper_test.go new file mode 100644 index 00000000..0d56b2d5 --- /dev/null +++ b/x/incentive/keeper/keeper_test.go @@ -0,0 +1,174 @@ +package keeper_test + +import ( + "testing" + "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" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +// Test suite used for all keeper tests +type KeeperTestSuite struct { + suite.Suite + + keeper keeper.Keeper + app app.TestApp + ctx sdk.Context + addrs []sdk.AccAddress +} + +// The default state used by each test +func (suite *KeeperTestSuite) SetupTest() { + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + tApp.InitializeFromGenesisStates() + _, addrs := app.GeneratePrivKeyAddressPairs(1) + keeper := tApp.GetIncentiveKeeper() + suite.app = tApp + suite.ctx = ctx + suite.keeper = keeper + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) getAccount(addr sdk.AccAddress) authexported.Account { + ak := suite.app.GetAccountKeeper() + return ak.GetAccount(suite.ctx, addr) +} + +func (suite *KeeperTestSuite) getModuleAccount(name string) supplyexported.ModuleAccountI { + sk := suite.app.GetSupplyKeeper() + return sk.GetModuleAccount(suite.ctx, name) +} + +func (suite *KeeperTestSuite) TestGetSetDeleteRewardPeriod() { + rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + _, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.False(found) + suite.NotPanics(func() { + suite.keeper.SetRewardPeriod(suite.ctx, rp) + }) + testRP, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.True(found) + suite.Equal(rp, testRP) + suite.NotPanics(func() { + suite.keeper.DeleteRewardPeriod(suite.ctx, "bnb") + }) + _, found = suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.False(found) +} + +func (suite *KeeperTestSuite) TestGetSetDeleteClaimPeriod() { + cp := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) + suite.NotPanics(func() { + suite.keeper.SetClaimPeriod(suite.ctx, cp) + }) + testCP, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) + suite.Equal(cp, testCP) + suite.NotPanics(func() { + suite.keeper.DeleteClaimPeriod(suite.ctx, 1, "bnb") + }) + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) +} + +func (suite *KeeperTestSuite) TestGetSetClaimPeriodID() { + suite.Panics(func() { + suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb") + }) + suite.NotPanics(func() { + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + }) + testID := suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb") + suite.Equal(uint64(1), testID) +} + +func (suite *KeeperTestSuite) TestGetSetDeleteClaim() { + c := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + _, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.False(found) + suite.NotPanics(func() { + suite.keeper.SetClaim(suite.ctx, c) + }) + testC, found := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.True(found) + suite.Equal(c, testC) + suite.NotPanics(func() { + suite.keeper.DeleteClaim(suite.ctx, suite.addrs[0], "bnb", 1) + }) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.False(found) +} + +func (suite *KeeperTestSuite) TestIterateMethods() { + suite.addObjectsToStore() // adds 2 objects of each type to the store + + var rewardPeriods types.RewardPeriods + suite.keeper.IterateRewardPeriods(suite.ctx, func(rp types.RewardPeriod) (stop bool) { + rewardPeriods = append(rewardPeriods, rp) + return false + }) + suite.Equal(2, len(rewardPeriods)) + + var claimPeriods types.ClaimPeriods + suite.keeper.IterateClaimPeriods(suite.ctx, func(cp types.ClaimPeriod) (stop bool) { + claimPeriods = append(claimPeriods, cp) + return false + }) + suite.Equal(2, len(claimPeriods)) + + var claims types.Claims + suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) { + claims = append(claims, c) + return false + }) + suite.Equal(2, len(claims)) + + var genIDs types.GenesisClaimPeriodIDs + suite.keeper.IterateClaimPeriodIDKeysAndValues(suite.ctx, func(denom string, id uint64) (stop bool) { + genID := types.GenesisClaimPeriodID{Denom: denom, ID: id} + genIDs = append(genIDs, genID) + return false + }) + suite.Equal(2, len(genIDs)) +} + +func (suite *KeeperTestSuite) addObjectsToStore() { + rp1 := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + rp2 := types.NewRewardPeriod("xrp", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + suite.keeper.SetRewardPeriod(suite.ctx, rp1) + suite.keeper.SetRewardPeriod(suite.ctx, rp2) + + cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + cp2 := types.NewClaimPeriod("xrp", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + suite.keeper.SetClaimPeriod(suite.ctx, cp1) + suite.keeper.SetClaimPeriod(suite.ctx, cp2) + + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + suite.keeper.SetNextClaimPeriodID(suite.ctx, "xrp", 1) + + c1 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + c2 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "xrp", 1) + suite.keeper.SetClaim(suite.ctx, c1) + suite.keeper.SetClaim(suite.ctx, c2) + + params := types.NewParams( + true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24)}, + ) + suite.keeper.SetParams(suite.ctx, params) + +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} diff --git a/x/incentive/keeper/payout_test.go b/x/incentive/keeper/payout_test.go new file mode 100644 index 00000000..caefc6dd --- /dev/null +++ b/x/incentive/keeper/payout_test.go @@ -0,0 +1,357 @@ +package keeper_test + +import ( + "errors" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/kava-labs/kava/x/kavadist" + validatorvesting "github.com/kava-labs/kava/x/validator-vesting" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (suite *KeeperTestSuite) setupChain() { + // creates a new app state with 4 funded addresses and 1 module account + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)}) + _, addrs := app.GeneratePrivKeyAddressPairs(4) + authGS := app.NewAuthGenState( + addrs, + []sdk.Coins{ + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + }) + tApp.InitializeFromGenesisStates( + authGS, + ) + supplyKeeper := tApp.GetSupplyKeeper() + macc := supplyKeeper.GetModuleAccount(ctx, kavadist.ModuleName) + err := supplyKeeper.MintCoins(ctx, macc.GetName(), cs(c("ukava", 500))) + suite.Require().NoError(err) + + // sets addrs[0] to be a periodic vesting account + ak := tApp.GetAccountKeeper() + acc := ak.GetAccount(ctx, addrs[0]) + bacc := auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence()) + periods := vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + } + bva, err2 := vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16) + suite.Require().NoError(err2) + pva := vesting.NewPeriodicVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods) + ak.SetAccount(ctx, pva) + + // sets addrs[2] to be a validator vesting account + acc = ak.GetAccount(ctx, addrs[2]) + bacc = auth.NewBaseAccount(acc.GetAddress(), acc.GetCoins(), acc.GetPubKey(), acc.GetAccountNumber(), acc.GetSequence()) + bva, err2 = vesting.NewBaseVestingAccount(bacc, cs(c("ukava", 400)), ctx.BlockTime().Unix()+16) + suite.Require().NoError(err2) + vva := validatorvesting.NewValidatorVestingAccountRaw(bva, ctx.BlockTime().Unix(), periods, sdk.ConsAddress{}, nil, 90) + ak.SetAccount(ctx, vva) + suite.app = tApp + suite.keeper = tApp.GetIncentiveKeeper() + suite.ctx = ctx + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) setupExpiredClaims() { + // creates a new app state with 4 funded addresses + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: time.Unix(100, 0)}) + _, addrs := app.GeneratePrivKeyAddressPairs(4) + authGS := app.NewAuthGenState( + addrs, + []sdk.Coins{ + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + cs(c("ukava", 400)), + }) + tApp.InitializeFromGenesisStates( + authGS, + ) + + // creates two claim periods, one expired, and one that expires in the future + cp1 := types.NewClaimPeriod("bnb", 1, time.Unix(90, 0), time.Hour*8766) + cp2 := types.NewClaimPeriod("xrp", 1, time.Unix(110, 0), time.Hour*8766) + suite.keeper = tApp.GetIncentiveKeeper() + suite.keeper.SetClaimPeriod(ctx, cp1) + suite.keeper.SetClaimPeriod(ctx, cp2) + // creates one claim for the non-expired claim period and one claim for the expired claim period + c1 := types.NewClaim(addrs[0], c("ukava", 1000000), "bnb", 1) + c2 := types.NewClaim(addrs[0], c("ukava", 1000000), "xrp", 1) + suite.keeper.SetClaim(ctx, c1) + suite.keeper.SetClaim(ctx, c2) + suite.app = tApp + suite.ctx = ctx + suite.addrs = addrs +} + +func (suite *KeeperTestSuite) TestSendCoinsToPeriodicVestingAccount() { + suite.setupChain() + + type args struct { + coins sdk.Coins + length int64 + } + + type errArgs struct { + expectErr bool + errType error + } + + type vestingAccountTest struct { + name string + blockTime time.Time + args args + errArgs errArgs + expectedPeriods vesting.Periods + expectedOriginalVesting sdk.Coins + expectedCoins sdk.Coins + expectedStartTime int64 + expectedEndTime int64 + } + + type vestingAccountTests []vestingAccountTest + + testCases := vestingAccountTests{ + vestingAccountTest{ + name: "insert period into an existing vesting schedule", + blockTime: time.Unix(100, 0), + args: args{coins: cs(c("ukava", 100)), length: 5}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 500)), + expectedCoins: cs(c("ukava", 500)), + expectedStartTime: int64(100), + expectedEndTime: int64(116), + }, + vestingAccountTest{ + name: "append period to the end of an existing vesting schedule", + blockTime: time.Unix(100, 0), + args: args{coins: cs(c("ukava", 100)), length: 17}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 600)), + expectedCoins: cs(c("ukava", 600)), + expectedStartTime: int64(100), + expectedEndTime: int64(117), + }, + vestingAccountTest{ + name: "append period to the end of a completed vesting schedule", + blockTime: time.Unix(120, 0), + args: args{coins: cs(c("ukava", 100)), length: 5}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 700)), + expectedCoins: cs(c("ukava", 700)), + expectedStartTime: int64(100), + expectedEndTime: int64(125), + }, + vestingAccountTest{ + name: "prepend period to to an upcoming vesting schedule", + blockTime: time.Unix(90, 0), + args: args{coins: cs(c("ukava", 100)), length: 5}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 800)), + expectedCoins: cs(c("ukava", 800)), + expectedStartTime: int64(90), + expectedEndTime: int64(125), + }, + vestingAccountTest{ + name: "add period that coincides with an existing end time", + blockTime: time.Unix(90, 0), + args: args{coins: cs(c("ukava", 100)), length: 11}, + errArgs: errArgs{expectErr: false, errType: nil}, + expectedPeriods: vesting.Periods{ + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 200))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(2), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(6), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(1), Amount: cs(c("ukava", 100))}, + vesting.Period{Length: int64(8), Amount: cs(c("ukava", 100))}, + }, + expectedOriginalVesting: cs(c("ukava", 900)), + expectedCoins: cs(c("ukava", 900)), + expectedStartTime: int64(90), + expectedEndTime: int64(125), + }, + vestingAccountTest{ + name: "insufficient module account balance", + blockTime: time.Unix(90, 0), + args: args{coins: cs(c("ukava", 1000)), length: 11}, + errArgs: errArgs{expectErr: true, errType: types.ErrInsufficientModAccountBalance}, + expectedPeriods: vesting.Periods{}, + expectedOriginalVesting: sdk.Coins{}, + expectedCoins: sdk.Coins{}, + expectedStartTime: int64(0), + expectedEndTime: int64(0), + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.ctx = suite.ctx.WithBlockTime(tc.blockTime) + err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[0], tc.args.coins, tc.args.length) + if tc.errArgs.expectErr { + suite.Require().True(errors.Is(err, tc.errArgs.errType)) + } else { + suite.Require().NoError(err) + acc := suite.getAccount(suite.addrs[0]) + vacc, ok := acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + suite.Equal(tc.expectedPeriods, vacc.VestingPeriods) + suite.Equal(tc.expectedOriginalVesting, vacc.OriginalVesting) + suite.Equal(tc.expectedCoins, vacc.Coins) + suite.Equal(tc.expectedStartTime, vacc.StartTime) + suite.Equal(tc.expectedEndTime, vacc.EndTime) + } + }) + } +} + +func (suite *KeeperTestSuite) TestSendCoinsToBaseAccount() { + suite.setupChain() + // send coins to base account + err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[1], cs(c("ukava", 100)), 5) + suite.Require().NoError(err) + acc := suite.getAccount(suite.addrs[1]) + vacc, ok := acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + expectedPeriods := vesting.Periods{ + vesting.Period{Length: int64(5), Amount: cs(c("ukava", 100))}, + } + suite.Equal(expectedPeriods, vacc.VestingPeriods) + suite.Equal(cs(c("ukava", 100)), vacc.OriginalVesting) + suite.Equal(cs(c("ukava", 500)), vacc.Coins) + suite.Equal(int64(105), vacc.EndTime) + suite.Equal(int64(100), vacc.StartTime) + +} + +func (suite *KeeperTestSuite) TestSendCoinsToInvalidAccount() { + suite.setupChain() + err := suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, suite.addrs[2], cs(c("ukava", 100)), 5) + suite.Require().True(errors.Is(err, types.ErrInvalidAccountType)) + macc := suite.getModuleAccount(cdp.ModuleName) + err = suite.keeper.SendTimeLockedCoinsToAccount(suite.ctx, kavadist.ModuleName, macc.GetAddress(), cs(c("ukava", 100)), 5) + suite.Require().True(errors.Is(err, types.ErrInvalidAccountType)) +} + +func (suite *KeeperTestSuite) TestPayoutClaim() { + suite.setupChain() // adds 3 accounts - 1 periodic vesting account, 1 base account, and 1 validator vesting account + + // add 2 claims that correspond to an existing claim period and one claim that has no corresponding claim period + cp1 := types.NewClaimPeriod("bnb", 1, suite.ctx.BlockTime().Add(time.Hour*168), time.Hour*8766) + suite.keeper.SetClaimPeriod(suite.ctx, cp1) + // valid claim for addrs[0] + c1 := types.NewClaim(suite.addrs[0], c("ukava", 100), "bnb", 1) + // invalid claim for addrs[0] + c2 := types.NewClaim(suite.addrs[0], c("ukava", 100), "xrp", 1) + // valid claim for addrs[1] + c3 := types.NewClaim(suite.addrs[1], c("ukava", 100), "bnb", 1) + suite.keeper.SetClaim(suite.ctx, c1) + suite.keeper.SetClaim(suite.ctx, c2) + suite.keeper.SetClaim(suite.ctx, c3) + + // existing claim with corresponding claim period successfully claimed by existing periodic vesting account + err := suite.keeper.PayoutClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.Require().NoError(err) + acc := suite.getAccount(suite.addrs[0]) + // account is a periodic vesting account + vacc, ok := acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + // vesting balance is correct + suite.Equal(cs(c("ukava", 500)), vacc.OriginalVesting) + + // existing claim with corresponding claim period successfully claimed by base account + err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[1], "bnb", 1) + suite.Require().NoError(err) + acc = suite.getAccount(suite.addrs[1]) + // account has become a periodic vesting account + vacc, ok = acc.(*vesting.PeriodicVestingAccount) + suite.True(ok) + // vesting balance is correct + suite.Equal(cs(c("ukava", 100)), vacc.OriginalVesting) + + // addrs[3] has no claims + err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[3], "bnb", 1) + suite.Require().True(errors.Is(err, types.ErrClaimNotFound)) + // addrs[0] has an xrp claim, but there is not corresponding claim period + err = suite.keeper.PayoutClaim(suite.ctx, suite.addrs[0], "xrp", 1) + suite.Require().True(errors.Is(err, types.ErrClaimPeriodNotFound)) +} + +func (suite *KeeperTestSuite) TestDeleteExpiredClaimPeriods() { + suite.setupExpiredClaims() // creates new app state with one non-expired claim period (xrp) and one expired claim period (bnb) as well as a claim that corresponds to each claim period + + // both claim periods are present + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "xrp") + suite.True(found) + // both claims are present + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.True(found) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "xrp", 1) + suite.True(found) + + // expired claim period and associated claims should get deleted + suite.NotPanics(func() { + suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx) + }) + // expired claim period and claim are not found + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.False(found) + // non-expired claim period and claim are found + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "xrp") + suite.True(found) + _, found = suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "xrp", 1) + suite.True(found) + +} diff --git a/x/incentive/keeper/querier_test.go b/x/incentive/keeper/querier_test.go new file mode 100644 index 00000000..156d0e8d --- /dev/null +++ b/x/incentive/keeper/querier_test.go @@ -0,0 +1,32 @@ +package keeper_test + +import ( + "strings" + + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +func (suite *KeeperTestSuite) TestQuerier() { + suite.addObjectsToStore() + querier := keeper.NewQuerier(suite.keeper) + bz, err := querier(suite.ctx, []string{types.QueryGetParams}, abci.RequestQuery{}) + suite.Require().NoError(err) + suite.NotNil(bz) + + var p types.Params + suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &p)) + + claimQueryParams := types.NewQueryClaimsParams(suite.addrs[0], "bnb") + query := abci.RequestQuery{ + Path: strings.Join([]string{"custom", types.QuerierRoute, types.QueryGetClaims}, "/"), + Data: types.ModuleCdc.MustMarshalJSON(claimQueryParams), + } + bz, err = querier(suite.ctx, []string{types.QueryGetClaims}, query) + + var claims types.Claims + suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &claims)) + suite.Equal(1, len(claims)) + suite.Equal(types.Claims{types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1)}, claims) +} diff --git a/x/incentive/keeper/rewards_test.go b/x/incentive/keeper/rewards_test.go new file mode 100644 index 00000000..2bea860f --- /dev/null +++ b/x/incentive/keeper/rewards_test.go @@ -0,0 +1,258 @@ +package keeper_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/kava-labs/kava/app" + "github.com/kava-labs/kava/x/cdp" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/kava-labs/kava/x/pricefeed" + abci "github.com/tendermint/tendermint/abci/types" + tmtime "github.com/tendermint/tendermint/types/time" +) + +func (suite *KeeperTestSuite) TestExpireRewardPeriod() { + rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + suite.keeper.SetRewardPeriod(suite.ctx, rp) + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + suite.NotPanics(func() { + suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp) + }) + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) +} + +func (suite *KeeperTestSuite) TestAddToClaim() { + rp := types.NewRewardPeriod("bnb", suite.ctx.BlockTime(), suite.ctx.BlockTime().Add(time.Hour*168), c("ukava", 100000000), suite.ctx.BlockTime().Add(time.Hour*168*2), time.Hour*8766) + suite.keeper.SetRewardPeriod(suite.ctx, rp) + suite.keeper.SetNextClaimPeriodID(suite.ctx, "bnb", 1) + suite.keeper.HandleRewardPeriodExpiry(suite.ctx, rp) + c1 := types.NewClaim(suite.addrs[0], c("ukava", 1000000), "bnb", 1) + suite.keeper.SetClaim(suite.ctx, c1) + suite.NotPanics(func() { + suite.keeper.AddToClaim(suite.ctx, suite.addrs[0], "bnb", 1, c("ukava", 1000000)) + }) + testC, _ := suite.keeper.GetClaim(suite.ctx, suite.addrs[0], "bnb", 1) + suite.Equal(c("ukava", 2000000), testC.Reward) + + suite.NotPanics(func() { + suite.keeper.AddToClaim(suite.ctx, suite.addrs[0], "xpr", 1, c("ukava", 1000000)) + }) +} + +func (suite *KeeperTestSuite) TestCreateRewardPeriod() { + reward := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + suite.NotPanics(func() { + suite.keeper.CreateNewRewardPeriod(suite.ctx, reward) + }) + _, found := suite.keeper.GetRewardPeriod(suite.ctx, "bnb") + suite.True(found) +} + +func (suite *KeeperTestSuite) TestCreateAndDeleteRewardsPeriods() { + reward1 := types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + reward2 := types.NewReward(false, "xrp", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + reward3 := types.NewReward(false, "btc", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24) + // add a reward period to the store for a non-active reward + suite.NotPanics(func() { + suite.keeper.CreateNewRewardPeriod(suite.ctx, reward3) + }) + params := types.NewParams(true, types.Rewards{reward1, reward2, reward3}) + suite.keeper.SetParams(suite.ctx, params) + + suite.NotPanics(func() { + suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx) + }) + testCases := []struct { + name string + arg string + expectFound bool + }{ + { + "active reward period", + "bnb", + true, + }, + { + "attempt to add inactive reward period", + "xrp", + false, + }, + { + "remove inactive reward period", + "btc", + false, + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + _, found := suite.keeper.GetRewardPeriod(suite.ctx, tc.arg) + if tc.expectFound { + suite.True(found) + } else { + suite.False(found) + } + }) + } +} + +func (suite *KeeperTestSuite) TestApplyRewardsToCdps() { + suite.setupCdpChain() // creates a test app with 3 BNB cdps and usdx incentives for bnb - each reward period is one week + + // move the context forward by 100 periods + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100)) + // apply rewards to BNB cdps + suite.NotPanics(func() { + suite.keeper.ApplyRewardsToCdps(suite.ctx) + }) + // each cdp should have a claim + claims := types.Claims{} + suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) { + claims = append(claims, c) + return false + }) + suite.Equal(3, len(claims)) + // there should be no associated claim period, because the reward period has not ended yet + _, found := suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.False(found) + + // move ctx to the reward period expiry and check that the claim period has been created and the next claim period id has increased + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Hour * 24 * 7)) + + suite.NotPanics(func() { + // apply rewards to cdps + suite.keeper.ApplyRewardsToCdps(suite.ctx) + // delete the old reward period amd create a new one + suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx) + }) + _, found = suite.keeper.GetClaimPeriod(suite.ctx, 1, "bnb") + suite.True(found) + testID := suite.keeper.GetNextClaimPeriodID(suite.ctx, "bnb") + suite.Equal(uint64(2), testID) + + // move the context forward by 100 periods + suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 100)) + // run the begin blocker functions + suite.NotPanics(func() { + suite.keeper.DeleteExpiredClaimsAndClaimPeriods(suite.ctx) + suite.keeper.ApplyRewardsToCdps(suite.ctx) + suite.keeper.CreateAndDeleteRewardPeriods(suite.ctx) + }) + // each cdp should now have two claims + claims = types.Claims{} + suite.keeper.IterateClaims(suite.ctx, func(c types.Claim) (stop bool) { + claims = append(claims, c) + return false + }) + suite.Equal(6, len(claims)) +} + +func (suite *KeeperTestSuite) setupCdpChain() { + // creates a new test app with bnb as the only asset the pricefeed and cdp modules + // funds three addresses and creates 3 cdps, funded with 100 BNB, 1000 BNB, and 10000 BNB + // each CDP draws 10, 100, and 1000 USDX respectively + // adds usdx incentives for bnb - 1000 KAVA per week with a 1 year time lock + + tApp := app.NewTestApp() + ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()}) + // need pricefeed and cdp gen state with one collateral + pricefeedGS := pricefeed.GenesisState{ + Params: pricefeed.Params{ + Markets: []pricefeed.Market{ + pricefeed.Market{MarketID: "bnb:usd", BaseAsset: "bnb", QuoteAsset: "usd", Oracles: []sdk.AccAddress{}, Active: true}, + }, + }, + PostedPrices: []pricefeed.PostedPrice{ + pricefeed.PostedPrice{ + MarketID: "bnb:usd", + OracleAddress: sdk.AccAddress{}, + Price: d("12.29"), + Expiry: time.Now().Add(100000 * time.Hour), + }, + }, + } + // need incentive params for one collateral + cdpGS := cdp.GenesisState{ + Params: cdp.Params{ + GlobalDebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), + SurplusAuctionThreshold: cdp.DefaultSurplusThreshold, + DebtAuctionThreshold: cdp.DefaultDebtThreshold, + SavingsDistributionFrequency: cdp.DefaultSavingsDistributionFrequency, + CollateralParams: cdp.CollateralParams{ + { + Denom: "bnb", + LiquidationRatio: sdk.MustNewDecFromStr("2.0"), + DebtLimit: sdk.NewCoins(sdk.NewInt64Coin("usdx", 1000000000000)), + StabilityFee: sdk.MustNewDecFromStr("1.000000001547125958"), // %5 apr + LiquidationPenalty: d("0.05"), + AuctionSize: i(10000000000), + Prefix: 0x20, + MarketID: "bnb:usd", + ConversionFactor: i(8), + }, + }, + DebtParams: cdp.DebtParams{ + { + Denom: "usdx", + ReferenceAsset: "usd", + ConversionFactor: i(6), + DebtFloor: i(10000000), + SavingsRate: d("0.95"), + }, + }, + }, + StartingCdpID: cdp.DefaultCdpStartingID, + DebtDenom: cdp.DefaultDebtDenom, + GovDenom: cdp.DefaultGovDenom, + CDPs: cdp.CDPs{}, + PreviousDistributionTime: cdp.DefaultPreviousDistributionTime, + } + incentiveGS := types.NewGenesisState( + types.NewParams( + true, types.Rewards{types.NewReward(true, "bnb", c("ukava", 1000000000), time.Hour*7*24, time.Hour*24*365, time.Hour*7*24)}, + ), + types.DefaultPreviousBlockTime, + types.RewardPeriods{types.NewRewardPeriod("bnb", ctx.BlockTime(), ctx.BlockTime().Add(time.Hour*7*24), c("ukava", 1000), ctx.BlockTime().Add(time.Hour*7*24*2), time.Hour*365*24)}, + types.ClaimPeriods{}, + types.Claims{}, + types.GenesisClaimPeriodIDs{}) + pricefeedAppGs := app.GenesisState{pricefeed.ModuleName: pricefeed.ModuleCdc.MustMarshalJSON(pricefeedGS)} + cdpAppGs := app.GenesisState{cdp.ModuleName: cdp.ModuleCdc.MustMarshalJSON(cdpGS)} + incentiveAppGs := app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(incentiveGS)} + _, addrs := app.GeneratePrivKeyAddressPairs(3) + authGS := app.NewAuthGenState( + addrs[0:3], + []sdk.Coins{ + cs(c("bnb", 10000000000)), + cs(c("bnb", 100000000000)), + cs(c("bnb", 1000000000000)), + }) + tApp.InitializeFromGenesisStates( + authGS, + pricefeedAppGs, + incentiveAppGs, + cdpAppGs, + ) + suite.app = tApp + suite.keeper = tApp.GetIncentiveKeeper() + suite.ctx = ctx + // create 3 cdps + cdpKeeper := tApp.GetCDPKeeper() + err := cdpKeeper.AddCdp(suite.ctx, addrs[0], cs(c("bnb", 10000000000)), cs(c("usdx", 10000000))) + suite.Require().NoError(err) + err = cdpKeeper.AddCdp(suite.ctx, addrs[1], cs(c("bnb", 100000000000)), cs(c("usdx", 100000000))) + suite.Require().NoError(err) + err = cdpKeeper.AddCdp(suite.ctx, addrs[2], cs(c("bnb", 1000000000000)), cs(c("usdx", 1000000000))) + suite.Require().NoError(err) + // total usd is 1110 + + // set the previous block time + suite.keeper.SetPreviousBlockTime(suite.ctx, suite.ctx.BlockTime()) +} + +// Avoid cluttering test cases with long function names +func i(in int64) sdk.Int { return sdk.NewInt(in) } +func d(str string) sdk.Dec { return sdk.MustNewDecFromStr(str) } +func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) } +func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) } diff --git a/x/incentive/types/msg_test.go b/x/incentive/types/msg_test.go index 0d77c94e..dbe06fe7 100644 --- a/x/incentive/types/msg_test.go +++ b/x/incentive/types/msg_test.go @@ -47,9 +47,9 @@ func (suite *MsgTestSuite) TestMsgValidation() { msg := types.NewMsgClaimReward(t.from, t.denom) err := msg.ValidateBasic() if t.expectPass { - suite.NoError(err) + suite.Require().NoError(err) } else { - suite.Error(err) + suite.Require().Error(err) } } } diff --git a/x/incentive/types/params.go b/x/incentive/types/params.go index 555beebc..524cfb83 100644 --- a/x/incentive/types/params.go +++ b/x/incentive/types/params.go @@ -112,7 +112,7 @@ func validateRewardsParam(i interface{}) error { return fmt.Errorf("reward timelock must be non-negative, is %s for %s", reward.TimeLock.String(), reward.Denom) } if int(reward.ClaimDuration.Seconds()) <= 0 { - return fmt.Errorf("reward timelock must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom) + return fmt.Errorf("claim duration must be positive, is %s for %s", reward.ClaimDuration.String(), reward.Denom) } } return nil diff --git a/x/incentive/types/params_test.go b/x/incentive/types/params_test.go index 745df28d..a35a2508 100644 --- a/x/incentive/types/params_test.go +++ b/x/incentive/types/params_test.go @@ -1,6 +1,7 @@ package types_test import ( + "strings" "testing" "time" @@ -10,8 +11,14 @@ import ( ) type paramTest struct { - params types.Params + name string + params types.Params + errResult errResult +} + +type errResult struct { expectPass bool + contains string } type ParamTestSuite struct { @@ -21,129 +28,189 @@ type ParamTestSuite struct { } func (suite *ParamTestSuite) SetupTest() { - p1 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p2 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p3 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * -24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p4 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * -8766, - ClaimDuration: time.Hour * 24 * 14, - }, - }, - } - p5 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 0, - }, - }, - } - p6 := types.Params{ - Active: true, - Rewards: types.Rewards{ - types.Reward{ - Active: true, - Denom: "bnb", - AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), - Duration: time.Hour * 24 * 7, - TimeLock: time.Hour * 8766, - ClaimDuration: time.Hour * 0, - }, - }, - } - suite.tests = []paramTest{ paramTest{ - params: p1, - expectPass: true, + name: "valid - active", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: true, + contains: "", + }, }, paramTest{ - params: p2, - expectPass: false, + name: "valid - inactive", + params: types.Params{ + Active: false, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: true, + contains: "", + }, }, paramTest{ - params: p3, - expectPass: false, + name: "duplicate reward", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "cannot have duplicate reward denoms", + }, }, paramTest{ - params: p4, - expectPass: false, + name: "negative reward duration", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * -24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "reward duration must be positive", + }, }, paramTest{ - params: p5, - expectPass: false, + name: "negative time lock", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * -8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "reward timelock must be non-negative", + }, }, paramTest{ - params: p6, - expectPass: false, + name: "zero claim duration", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(10000000000)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 0, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "claim duration must be positive", + }, + }, + paramTest{ + name: "zero reward", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "bnb", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "reward amount must be positive", + }, + }, + paramTest{ + name: "empty reward denom", + params: types.Params{ + Active: true, + Rewards: types.Rewards{ + types.Reward{ + Active: true, + Denom: "", + AvailableRewards: sdk.NewCoin("ukava", sdk.NewInt(0)), + Duration: time.Hour * 24 * 7, + TimeLock: time.Hour * 8766, + ClaimDuration: time.Hour * 24 * 14, + }, + }, + }, + errResult: errResult{ + expectPass: false, + contains: "cannot have empty reward denom", + }, }, } } func (suite *ParamTestSuite) TestParamValidation() { for _, t := range suite.tests { - err := t.params.Validate() - if t.expectPass { - suite.NoError(err) - } else { - suite.Error(err) - } + suite.Run(t.name, func() { + err := t.params.Validate() + if t.errResult.expectPass { + suite.Require().NoError(err) + } else { + suite.Require().Error(err) + suite.Require().True(strings.Contains(err.Error(), t.errResult.contains)) + } + }) } } From b969a0ea33a5f4f1d8da25d9e4fb8f0093d87848 Mon Sep 17 00:00:00 2001 From: Denali Marsh Date: Fri, 24 Apr 2020 14:55:18 -0700 Subject: [PATCH 45/45] Incentive module simulations (#439) * Incentive module simulations (#439) Co-authored-by: John Maheswaran Co-authored-by: Kevin Davis Co-authored-by: Kevin Davis Co-authored-by: John Maheswaran --- app/app.go | 4 +- app/params/params.go | 1 + app/sim_test.go | 2 +- run_a_bunch_of_sims.sh | 5 - x/cdp/simulation/genesis.go | 1 + x/incentive/module.go | 13 ++- x/incentive/simulation/decoder.go | 44 +++++++- x/incentive/simulation/decoder_test.go | 65 +++++++++++ x/incentive/simulation/genesis.go | 140 ++++++++++++++++++++++++ x/incentive/simulation/operations.go | 146 +++++++++++++++++++++++++ x/incentive/simulation/params.go | 36 +++++- x/incentive/simulation/simulation.go | 22 ---- x/kavadist/simulation/genesis.go | 22 ++-- 13 files changed, 448 insertions(+), 53 deletions(-) delete mode 100755 run_a_bunch_of_sims.sh create mode 100644 x/incentive/simulation/decoder_test.go create mode 100644 x/incentive/simulation/genesis.go create mode 100644 x/incentive/simulation/operations.go delete mode 100644 x/incentive/simulation/simulation.go diff --git a/app/app.go b/app/app.go index da424542..37599d15 100644 --- a/app/app.go +++ b/app/app.go @@ -333,7 +333,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, pricefeed.NewAppModule(app.pricefeedKeeper, app.accountKeeper), bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), - incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), + incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -376,7 +376,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, auction.NewAppModule(app.auctionKeeper, app.accountKeeper, app.supplyKeeper), bep3.NewAppModule(app.bep3Keeper, app.accountKeeper, app.supplyKeeper), kavadist.NewAppModule(app.kavadistKeeper, app.supplyKeeper), - incentive.NewAppModule(app.incentiveKeeper, app.supplyKeeper), + incentive.NewAppModule(app.incentiveKeeper, app.accountKeeper, app.supplyKeeper), ) app.sm.RegisterStoreDecoders() diff --git a/app/params/params.go b/app/params/params.go index d74f7623..17138c3d 100644 --- a/app/params/params.go +++ b/app/params/params.go @@ -12,4 +12,5 @@ const ( DefaultWeightMsgCreateAtomicSwap int = 100 DefaultWeightMsgUpdatePrices int = 100 DefaultWeightMsgCdp int = 100 + DefaultWeightMsgClaimReward int = 100 ) diff --git a/app/sim_test.go b/app/sim_test.go index d7a48802..4081978a 100644 --- a/app/sim_test.go +++ b/app/sim_test.go @@ -284,4 +284,4 @@ func TestAppStateDeterminism(t *testing.T) { ) } } -} +} \ No newline at end of file diff --git a/run_a_bunch_of_sims.sh b/run_a_bunch_of_sims.sh deleted file mode 100755 index 3fe1f5d7..00000000 --- a/run_a_bunch_of_sims.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -for i in {1..10}; do - go test ./app -run TestAppStateDeterminism -Enabled -Commit -NumBlocks=100 -BlockSize=200 -Seed ${i} -v -timeout 24h -done \ No newline at end of file diff --git a/x/cdp/simulation/genesis.go b/x/cdp/simulation/genesis.go index 65f568aa..655e7018 100644 --- a/x/cdp/simulation/genesis.go +++ b/x/cdp/simulation/genesis.go @@ -32,6 +32,7 @@ func RandomizedGenState(simState *module.SimulationState) { sdk.NewCoin("xrp", sdk.NewInt(int64(simState.Rand.Intn(100000000000)))), sdk.NewCoin("btc", sdk.NewInt(int64(simState.Rand.Intn(500000000)))), sdk.NewCoin("usdx", sdk.NewInt(int64(simState.Rand.Intn(1000000000)))), + sdk.NewCoin("ukava", sdk.NewInt(int64(simState.Rand.Intn(500000000000)))), ) err := acc.SetCoins(acc.GetCoins().Add(coinsToAdd...)) if err != nil { diff --git a/x/incentive/module.go b/x/incentive/module.go index 623f883e..9c464e00 100644 --- a/x/incentive/module.go +++ b/x/incentive/module.go @@ -8,6 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth" sim "github.com/cosmos/cosmos-sdk/x/simulation" "github.com/gorilla/mux" "github.com/kava-labs/kava/x/incentive/client/cli" @@ -90,23 +91,25 @@ func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedP } // WeightedOperations returns the all the bep3 module operations with their respective weights. -func (am AppModule) WeightedOperations(_ module.SimulationState) []sim.WeightedOperation { - return nil +func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { + return simulation.WeightedOperations(simState.AppParams, simState.Cdc, am.accountKeeper, am.supplyKeeper, am.keeper) } // AppModule implements the sdk.AppModule interface. type AppModule struct { AppModuleBasic - keeper keeper.Keeper - supplyKeeper types.SupplyKeeper + keeper Keeper + accountKeeper auth.AccountKeeper + supplyKeeper SupplyKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper keeper.Keeper, supplyKeeper types.SupplyKeeper) AppModule { +func NewAppModule(keeper Keeper, accountKeeper auth.AccountKeeper, supplyKeeper SupplyKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, + accountKeeper: accountKeeper, supplyKeeper: supplyKeeper, } } diff --git a/x/incentive/simulation/decoder.go b/x/incentive/simulation/decoder.go index ace82b35..dda10310 100644 --- a/x/incentive/simulation/decoder.go +++ b/x/incentive/simulation/decoder.go @@ -1,12 +1,50 @@ package simulation import ( + "bytes" + "encoding/binary" + "fmt" + "time" + "github.com/cosmos/cosmos-sdk/codec" "github.com/tendermint/tendermint/libs/kv" + + "github.com/kava-labs/kava/x/incentive/types" ) -// DecodeStore unmarshals the KVPair's Value to the corresponding incentive type +// DecodeStore unmarshals the KVPair's Value to the module's corresponding type func DecodeStore(cdc *codec.Codec, kvA, kvB kv.Pair) string { - // TODO implement this - return "" + switch { + case bytes.Equal(kvA.Key[:1], types.RewardPeriodKeyPrefix): + var rewardPeriodA, rewardPeriodB types.RewardPeriod + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &rewardPeriodA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &rewardPeriodB) + return fmt.Sprintf("%v\n%v", rewardPeriodA, rewardPeriodB) + + case bytes.Equal(kvA.Key[:1], types.ClaimPeriodKeyPrefix): + var claimPeriodA, claimPeriodB types.ClaimPeriod + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimPeriodA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimPeriodB) + return fmt.Sprintf("%v\n%v", claimPeriodA, claimPeriodB) + + case bytes.Equal(kvA.Key[:1], types.ClaimKeyPrefix): + var claimA, claimB types.Claim + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &claimA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &claimB) + return fmt.Sprintf("%v\n%v", claimA, claimB) + + case bytes.Equal(kvA.Key[:1], types.NextClaimPeriodIDPrefix): + claimPeriodIDA := binary.BigEndian.Uint64(kvA.Value) + claimPeriodIDB := binary.BigEndian.Uint64(kvB.Value) + return fmt.Sprintf("%d\n%d", claimPeriodIDA, claimPeriodIDB) + + case bytes.Equal(kvA.Key[:1], types.PreviousBlockTimeKey): + var timeA, timeB time.Time + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &timeA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &timeB) + return fmt.Sprintf("%s\n%s", timeA, timeB) + + default: + panic(fmt.Sprintf("invalid %s key prefix %X", types.ModuleName, kvA.Key[:1])) + } } diff --git a/x/incentive/simulation/decoder_test.go b/x/incentive/simulation/decoder_test.go new file mode 100644 index 00000000..7eb08ebd --- /dev/null +++ b/x/incentive/simulation/decoder_test.go @@ -0,0 +1,65 @@ +package simulation + +import ( + "fmt" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/libs/kv" + + "github.com/kava-labs/kava/x/incentive/types" +) + +func makeTestCodec() (cdc *codec.Codec) { + cdc = codec.New() + sdk.RegisterCodec(cdc) + types.RegisterCodec(cdc) + return +} + +func TestDecodeDistributionStore(t *testing.T) { + cdc := makeTestCodec() + + // Set up RewardPeriod, ClaimPeriod, Claim, and previous block time + rewardPeriod := types.NewRewardPeriod("btc", time.Now().UTC(), time.Now().Add(time.Hour*1).UTC(), + sdk.NewCoin("ukava", sdk.NewInt(10000000000)), time.Now().Add(time.Hour*2).UTC(), time.Duration(time.Hour*2)) + claimPeriod := types.NewClaimPeriod("btc", 1, time.Now().Add(time.Hour*24).UTC(), time.Duration(time.Hour*24)) + addr, _ := sdk.AccAddressFromBech32("kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw") + claim := types.NewClaim(addr, sdk.NewCoin("ukava", sdk.NewInt(1000000)), "bnb", 1) + prevBlockTime := time.Now().Add(time.Hour * -1).UTC() + + kvPairs := kv.Pairs{ + kv.Pair{Key: types.RewardPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&rewardPeriod)}, + kv.Pair{Key: types.ClaimPeriodKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claimPeriod)}, + kv.Pair{Key: types.ClaimKeyPrefix, Value: cdc.MustMarshalBinaryLengthPrefixed(&claim)}, + kv.Pair{Key: types.NextClaimPeriodIDPrefix, Value: sdk.Uint64ToBigEndian(10)}, + kv.Pair{Key: []byte(types.PreviousBlockTimeKey), Value: cdc.MustMarshalBinaryLengthPrefixed(prevBlockTime)}, + kv.Pair{Key: []byte{0x99}, Value: []byte{0x99}}, + } + + tests := []struct { + name string + expectedLog string + }{ + {"RewardPeriod", fmt.Sprintf("%v\n%v", rewardPeriod, rewardPeriod)}, + {"ClaimPeriod", fmt.Sprintf("%v\n%v", claimPeriod, claimPeriod)}, + {"Claim", fmt.Sprintf("%v\n%v", claim, claim)}, + {"NextClaimPeriodID", "10\n10"}, + {"PreviousBlockTime", fmt.Sprintf("%v\n%v", prevBlockTime, prevBlockTime)}, + {"other", ""}, + } + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { DecodeStore(cdc, kvPairs[i], kvPairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, DecodeStore(cdc, kvPairs[i], kvPairs[i]), tt.name) + } + }) + } +} diff --git a/x/incentive/simulation/genesis.go b/x/incentive/simulation/genesis.go new file mode 100644 index 00000000..88a28bd6 --- /dev/null +++ b/x/incentive/simulation/genesis.go @@ -0,0 +1,140 @@ +package simulation + +import ( + "fmt" + "math/rand" + "time" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/kava-labs/kava/x/incentive/types" +) + +var ( + CollateralDenoms = [3]string{"bnb", "xrp", "btc"} + RewardDenom = "ukava" + MaxTotalAssetReward = sdk.NewInt(1000000000) +) + +// RandomizedGenState generates a random GenesisState for incentive module +func RandomizedGenState(simState *module.SimulationState) { + params := genParams(simState.Rand) + rewardPeriods := genRewardPeriods(simState.Rand, simState.GenTimestamp, params.Rewards) + claimPeriods := genClaimPeriods(rewardPeriods) + claimPeriodIDs := genNextClaimPeriodIds(claimPeriods) + + // New genesis state holds valid, linked reward periods, claim periods, and claim period IDs + incentiveGenesis := types.NewGenesisState(params, types.DefaultPreviousBlockTime, + rewardPeriods, claimPeriods, types.Claims{}, claimPeriodIDs) + if err := incentiveGenesis.Validate(); err != nil { + panic(err) + } + + fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, incentiveGenesis)) + simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(incentiveGenesis) +} + +// genParams generates random rewards and is active by default +func genParams(r *rand.Rand) types.Params { + params := types.NewParams(true, genRewards(r)) + if err := params.Validate(); err != nil { + panic(err) + } + return params +} + +// genRewards generates rewards for each specified collateral type +func genRewards(r *rand.Rand) types.Rewards { + var rewards types.Rewards + for _, denom := range CollateralDenoms { + active := true + // total reward is in range (half max total reward, max total reward) + amount := simulation.RandIntBetween(r, int(MaxTotalAssetReward.Int64()/2), int(MaxTotalAssetReward.Int64())) + totalRewards := sdk.NewInt64Coin(RewardDenom, int64(amount)) + // generate a random number of hours between 6-48 to use for reward's times + numbHours := simulation.RandIntBetween(r, 6, 48) + duration := time.Duration(time.Hour * time.Duration(numbHours)) + timeLock := time.Duration(time.Hour * time.Duration(numbHours/2)) // half as long as duration + claimDuration := time.Hour * time.Duration(numbHours*2) // twice as long as duration + reward := types.NewReward(active, denom, totalRewards, duration, timeLock, claimDuration) + rewards = append(rewards, reward) + } + return rewards +} + +// genRewardPeriods generates chronological reward periods for each given reward type +func genRewardPeriods(r *rand.Rand, timestamp time.Time, rewards types.Rewards) types.RewardPeriods { + var rewardPeriods types.RewardPeriods + for _, reward := range rewards { + rewardPeriodStart := timestamp + for i := 10; i >= simulation.RandIntBetween(r, 2, 9); i-- { + // Set up reward period parameters + start := rewardPeriodStart + end := start.Add(reward.Duration).UTC() + baseRewardAmount := reward.AvailableRewards.Amount.Quo(sdk.NewInt(100)) // base period reward is 1/100 total reward + // Earlier periods have larger rewards + amount := sdk.NewCoin(reward.Denom, baseRewardAmount.Mul(sdk.NewInt(int64(i)))) + claimEnd := end.Add(reward.ClaimDuration) + claimTimeLock := reward.TimeLock + // Create reward period and append to array + rewardPeriod := types.NewRewardPeriod(reward.Denom, start, end, amount, claimEnd, claimTimeLock) + rewardPeriods = append(rewardPeriods, rewardPeriod) + // Update start time of next reward period + rewardPeriodStart = end + } + } + return rewardPeriods +} + +// genClaimPeriods loads valid claim periods for an array of reward periods +func genClaimPeriods(rewardPeriods types.RewardPeriods) types.ClaimPeriods { + denomRewardPeriodsCount := make(map[string]uint64) + var claimPeriods types.ClaimPeriods + for _, rewardPeriod := range rewardPeriods { + // Increment reward period count for this denom (this is our claim period's ID) + denom := rewardPeriod.Denom + numbRewardPeriods := denomRewardPeriodsCount[denom] + 1 + denomRewardPeriodsCount[denom] = numbRewardPeriods + // Set end and timelock from the associated reward period + end := rewardPeriod.ClaimEnd + claimTimeLock := rewardPeriod.ClaimTimeLock + // Create the new claim period for this reward period + claimPeriod := types.NewClaimPeriod(denom, numbRewardPeriods, end, claimTimeLock) + claimPeriods = append(claimPeriods, claimPeriod) + } + return claimPeriods +} + +// genNextClaimPeriodIds returns an array of the most recent claim period IDs for each denom +func genNextClaimPeriodIds(cps types.ClaimPeriods) types.GenesisClaimPeriodIDs { + // Build a map of the most recent claim periods by denom + mostRecentClaimPeriodByDenom := make(map[string]uint64) + for _, cp := range cps { + if cp.ID > mostRecentClaimPeriodByDenom[cp.Denom] { + mostRecentClaimPeriodByDenom[cp.Denom] = cp.ID + } + } + // Write map contents to an array of GenesisClaimPeriodIDs + var claimPeriodIDs types.GenesisClaimPeriodIDs + for key, value := range mostRecentClaimPeriodByDenom { + claimPeriodID := types.GenesisClaimPeriodID{Denom: key, ID: value} + claimPeriodIDs = append(claimPeriodIDs, claimPeriodID) + } + return claimPeriodIDs +} + +// In a list of accounts, replace the first account found with the same address. If not found, append the account. +func replaceOrAppendAccount(accounts []authexported.GenesisAccount, acc authexported.GenesisAccount) []authexported.GenesisAccount { + newAccounts := accounts + for i, a := range accounts { + if a.GetAddress().Equals(acc.GetAddress()) { + newAccounts[i] = acc + return newAccounts + } + } + return append(newAccounts, acc) +} diff --git a/x/incentive/simulation/operations.go b/x/incentive/simulation/operations.go new file mode 100644 index 00000000..03e143d8 --- /dev/null +++ b/x/incentive/simulation/operations.go @@ -0,0 +1,146 @@ +package simulation + +import ( + "fmt" + "math/rand" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/simapp/helpers" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/auth/vesting" + "github.com/cosmos/cosmos-sdk/x/simulation" + + appparams "github.com/kava-labs/kava/app/params" + "github.com/kava-labs/kava/x/incentive/keeper" + "github.com/kava-labs/kava/x/incentive/types" + "github.com/kava-labs/kava/x/kavadist" +) + +// Simulation operation weights constants +const ( + OpWeightMsgClaimReward = "op_weight_msg_claim_reward" +) + +// WeightedOperations returns all the operations from the module with their respective weights +func WeightedOperations( + appParams simulation.AppParams, cdc *codec.Codec, ak auth.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper, +) simulation.WeightedOperations { + var weightMsgClaimReward int + + appParams.GetOrGenerate(cdc, OpWeightMsgClaimReward, &weightMsgClaimReward, nil, + func(_ *rand.Rand) { + weightMsgClaimReward = appparams.DefaultWeightMsgClaimReward + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgClaimReward, + SimulateMsgClaimReward(ak, sk, k), + ), + } +} + +// SimulateMsgClaimReward generates a MsgClaimReward +func SimulateMsgClaimReward(ak auth.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper) simulation.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, chainID string, + ) (simulation.OperationMsg, []simulation.FutureOperation, error) { + + // Load only account types that can claim rewards + var accounts []authexported.Account + validAccounts := make(map[string]bool) + for _, acc := range accs { + account := ak.GetAccount(ctx, acc.Address) + switch account.(type) { + case *vesting.PeriodicVestingAccount, *auth.BaseAccount: // Valid: BaseAccount, PeriodicVestingAccount + accounts = append(accounts, account) + validAccounts[account.GetAddress().String()] = true + break + default: // Invalid: ValidatorVestingAccount, DelayedVestingAccount, ContinuousVestingAccount + break + } + } + + // Load open claims and shuffle them to randomize + openClaims := types.Claims{} + k.IterateClaims(ctx, func(claim types.Claim) bool { + openClaims = append(openClaims, claim) + return false + }) + r.Shuffle(len(openClaims), func(i, j int) { + openClaims[i], openClaims[j] = openClaims[j], openClaims[i] + }) + + kavadistMacc := sk.GetModuleAccount(ctx, kavadist.KavaDistMacc) + kavadistBalance := kavadistMacc.SpendableCoins(ctx.BlockTime()) + + // Find address that has a claim of the same reward denom, then confirm it's distributable + claimer, claim, found := findValidAccountClaimPair(accs, openClaims, func(acc simulation.Account, claim types.Claim) bool { + if validAccounts[acc.Address.String()] { // Address must be valid type + if claim.Owner.Equals(acc.Address) { // Account must be claim owner + allClaims, found := k.GetClaimsByAddressAndDenom(ctx, claim.Owner, claim.Denom) + if found { // found should always be true + var rewards sdk.Coins + for _, individualClaim := range allClaims { + rewards = rewards.Add(individualClaim.Reward) + } + if rewards.AmountOf(claim.Reward.Denom).IsPositive() { // Can't distribute 0 coins + // Validate that kavadist module has enough coins to distribute rewards + if kavadistBalance.AmountOf(claim.Reward.Denom).GTE(rewards.AmountOf(claim.Reward.Denom)) { + return true + } + } + } + } + } + return false + }) + if !found { + return simulation.NewOperationMsgBasic(types.ModuleName, + "no-operation (no accounts currently have fulfillable claims)", "", false, nil), nil, nil + } + + claimerAcc := ak.GetAccount(ctx, claimer.Address) + if claimerAcc == nil { + return simulation.NoOpMsg(types.ModuleName), nil, fmt.Errorf("couldn't find account %s", claimer.Address) + } + + msg := types.NewMsgClaimReward(claimer.Address, claim.Denom) + + tx := helpers.GenTx( + []sdk.Msg{msg}, + sdk.NewCoins(), + helpers.DefaultGenTxGas, + chainID, + []uint64{claimerAcc.GetAccountNumber()}, + []uint64{claimerAcc.GetSequence()}, + claimer.PrivKey, + ) + + _, result, err := app.Deliver(tx) + if err != nil { + // to aid debugging, add the stack trace to the comment field of the returned opMsg + return simulation.NewOperationMsg(msg, false, fmt.Sprintf("%+v", err)), nil, err + } + + // to aid debugging, add the result log to the comment field + return simulation.NewOperationMsg(msg, true, result.Log), nil, nil + } +} + +// findValidAccountClaimPair finds an account and reward claim for which the callback func returns true +func findValidAccountClaimPair(accounts []simulation.Account, claims types.Claims, + cb func(simulation.Account, types.Claim) bool) (simulation.Account, types.Claim, bool) { + for _, claim := range claims { + for _, acc := range accounts { + if isValid := cb(acc, claim); isValid { + return acc, claim, true + } + } + } + return simulation.Account{}, types.Claim{}, false +} diff --git a/x/incentive/simulation/params.go b/x/incentive/simulation/params.go index 8c1f7aff..dd47e83f 100644 --- a/x/incentive/simulation/params.go +++ b/x/incentive/simulation/params.go @@ -1,14 +1,40 @@ package simulation import ( + "fmt" "math/rand" "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/kava-labs/kava/x/incentive/types" ) -// ParamChanges defines the parameters that can be modified by param change proposals -// on the simulation -func ParamChanges(r *rand.Rand) []simulation.ParamChange { - // TODO implement this - return []simulation.ParamChange{} +const ( + keyActive = "Active" + keyRewards = "Rewards" +) + +// genActive generates active bool with 80% chance of true +func genActive(r *rand.Rand) bool { + threshold := 80 + value := simulation.RandIntBetween(r, 1, 100) + if value > threshold { + return false + } + return true +} + +// ParamChanges defines the parameters that can be modified by param change proposals +func ParamChanges(r *rand.Rand) []simulation.ParamChange { + return []simulation.ParamChange{ + simulation.NewSimParamChange(types.ModuleName, keyActive, + func(r *rand.Rand) string { + return fmt.Sprintf("\"%t\"", genActive(r)) + }, + ), + simulation.NewSimParamChange(types.ModuleName, keyRewards, + func(r *rand.Rand) string { + return fmt.Sprintf("\"%v\"", genRewards(r)) + }, + ), + } } diff --git a/x/incentive/simulation/simulation.go b/x/incentive/simulation/simulation.go deleted file mode 100644 index 3057550f..00000000 --- a/x/incentive/simulation/simulation.go +++ /dev/null @@ -1,22 +0,0 @@ -package simulation - -import ( - "fmt" - - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/types/module" - - "github.com/kava-labs/kava/x/incentive/types" -) - -// RandomizedGenState generates a random GenesisState for cdp -func RandomizedGenState(simState *module.SimulationState) { - - // TODO implement this fully - // - randomly generating the genesis params - // - overwriting with genesis provided to simulation - genesis := types.DefaultGenesisState() - - fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, codec.MustMarshalJSONIndent(simState.Cdc, genesis)) - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesis) -} diff --git a/x/kavadist/simulation/genesis.go b/x/kavadist/simulation/genesis.go index d12a73d5..ebaa2ff7 100644 --- a/x/kavadist/simulation/genesis.go +++ b/x/kavadist/simulation/genesis.go @@ -14,10 +14,11 @@ import ( ) // SecondsPerYear is the number of seconds in a year -const SecondsPerYear = 31536000 - -// BaseAprPadding prevents the calculated SPR inflation rate from being 0.0 -const BaseAprPadding = "0.000000000100000000" +const ( + SecondsPerYear = 31536000 + // BaseAprPadding sets the minimum inflation to the calculated SPR inflation rate from being 0.0 + BaseAprPadding = "0.000000003022265980" +) // RandomizedGenState generates a random GenesisState for kavadist module func RandomizedGenState(simState *module.SimulationState) { @@ -46,8 +47,8 @@ func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods { numPeriods := simulation.RandIntBetween(r, 1, 10) periodStart := timestamp for i := 0; i < numPeriods; i++ { - // set periods to be between 2 weeks and 2 years - durationMultiplier := simulation.RandIntBetween(r, 14, 104) + // set periods to be between 1-3 days + durationMultiplier := simulation.RandIntBetween(r, 1, 3) duration := time.Duration(int64(24*durationMultiplier)) * time.Hour periodEnd := periodStart.Add(duration) inflation := genRandomInflation(r) @@ -59,13 +60,14 @@ func genRandomPeriods(r *rand.Rand, timestamp time.Time) types.Periods { } func genRandomInflation(r *rand.Rand) sdk.Dec { - // If sim.RandomDecAmount returns 0 (happens frequently by design), add BaseAprPadding + // If sim.RandomDecAmount is less than base apr padding, add base apr padding + aprPadding, _ := sdk.NewDecFromStr(BaseAprPadding) extraAprInflation := simulation.RandomDecAmount(r, sdk.MustNewDecFromStr("0.25")) - for extraAprInflation.Equal(sdk.ZeroDec()) { - extraAprInflation = extraAprInflation.Add(sdk.MustNewDecFromStr(BaseAprPadding)) + for extraAprInflation.LT(aprPadding) { + extraAprInflation = extraAprInflation.Add(aprPadding) } - aprInflation := sdk.OneDec().Add(extraAprInflation) + // convert APR inflation to SPR (inflation per second) inflationSpr, err := approxRoot(aprInflation, uint64(SecondsPerYear)) if err != nil {