mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 07:45:18 +00:00
Merge pull request #375 from Kava-Labs/develop
feat: merge cdp modules to master
This commit is contained in:
commit
e3b1f7e24d
@ -96,6 +96,13 @@ jobs:
|
||||
target: start-remote-sims
|
||||
description: "Test multi-seed simulation (long)"
|
||||
|
||||
broken-link-check:
|
||||
executor: golang
|
||||
steps:
|
||||
- make:
|
||||
target: link-check
|
||||
description: "Check url links are not broken"
|
||||
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
@ -116,4 +123,7 @@ workflows:
|
||||
# These filters ensure that the long sim only runs during release
|
||||
filters:
|
||||
branches:
|
||||
only: "master"
|
||||
only: "master"
|
||||
- broken-link-check:
|
||||
requires:
|
||||
- setup-dependencies
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -8,9 +8,12 @@
|
||||
# Test binary, build with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Exclude build files
|
||||
vendor
|
||||
build
|
||||
|
||||
# IDE files
|
||||
*.vscode
|
9
Makefile
9
Makefile
@ -94,6 +94,15 @@ go.sum: go.mod
|
||||
clean:
|
||||
rm -rf build/
|
||||
|
||||
########################################
|
||||
### Linting
|
||||
|
||||
# Check url links in the repo are not broken.
|
||||
# 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*"
|
||||
|
||||
########################################
|
||||
### Testing
|
||||
|
||||
|
21
README.md
21
README.md
@ -23,19 +23,22 @@
|
||||
|
||||
</div>
|
||||
|
||||
Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [cosmos-sdk](https://github.com/cosmos/cosmos-sdk).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```sh
|
||||
make install
|
||||
```
|
||||
Reference implementation of Kava, a blockchain for cross-chain DeFi. Built using the [comsos-sdk](https://github.com/cosmos/cosmos-sdk).
|
||||
|
||||
## Mainnet
|
||||
|
||||
Note, the current recommended version of the software for mainnet is v0.3.2.
|
||||
Note, the current recommended version of the software for mainnet is v0.3.2. The master branch of this repository contains considerable development work since the last mainnet release and is __not__ runnable on mainnet.
|
||||
|
||||
To join the latest testnet, head over to the [testnet repo](https://github.com/Kava-Labs/kava-testnets).
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
git checkout v0.3.2
|
||||
make install
|
||||
```
|
||||
|
||||
## Testnet
|
||||
|
||||
The recommended version of the software for kava-testnet-4000 is v0.4.1. For further information on joining the testnet, head over to the [testnet repo](https://github.com/Kava-Labs/kava-testnets).
|
||||
|
||||
## License
|
||||
|
||||
|
65
app/app.go
65
app/app.go
@ -4,6 +4,9 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
@ -56,6 +59,9 @@ var (
|
||||
crisis.AppModuleBasic{},
|
||||
slashing.AppModuleBasic{},
|
||||
supply.AppModuleBasic{},
|
||||
auction.AppModuleBasic{},
|
||||
cdp.AppModuleBasic{},
|
||||
pricefeed.AppModuleBasic{},
|
||||
)
|
||||
|
||||
// module account permissions
|
||||
@ -67,6 +73,9 @@ var (
|
||||
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
|
||||
gov.ModuleName: {supply.Burner},
|
||||
validatorvesting.ModuleName: {supply.Burner},
|
||||
auction.ModuleName: nil,
|
||||
cdp.ModuleName: {supply.Minter, supply.Burner},
|
||||
cdp.LiquidatorMacc: {supply.Minter, supply.Burner},
|
||||
}
|
||||
)
|
||||
|
||||
@ -82,17 +91,20 @@ type App struct {
|
||||
tkeys map[string]*sdk.TransientStoreKey
|
||||
|
||||
// keepers from all the modules
|
||||
accountKeeper auth.AccountKeeper
|
||||
bankKeeper bank.Keeper
|
||||
supplyKeeper supply.Keeper
|
||||
stakingKeeper staking.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
mintKeeper mint.Keeper
|
||||
distrKeeper distr.Keeper
|
||||
govKeeper gov.Keeper
|
||||
crisisKeeper crisis.Keeper
|
||||
paramsKeeper params.Keeper
|
||||
vvKeeper validatorvesting.Keeper
|
||||
accountKeeper auth.AccountKeeper
|
||||
bankKeeper bank.Keeper
|
||||
supplyKeeper supply.Keeper
|
||||
stakingKeeper staking.Keeper
|
||||
slashingKeeper slashing.Keeper
|
||||
mintKeeper mint.Keeper
|
||||
distrKeeper distr.Keeper
|
||||
govKeeper gov.Keeper
|
||||
crisisKeeper crisis.Keeper
|
||||
paramsKeeper params.Keeper
|
||||
vvKeeper validatorvesting.Keeper
|
||||
auctionKeeper auction.Keeper
|
||||
cdpKeeper cdp.Keeper
|
||||
pricefeedKeeper pricefeed.Keeper
|
||||
|
||||
// the module manager
|
||||
mm *module.Manager
|
||||
@ -116,6 +128,7 @@ 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,
|
||||
)
|
||||
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
|
||||
|
||||
@ -137,6 +150,9 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
slashingSubspace := app.paramsKeeper.Subspace(slashing.DefaultParamspace)
|
||||
govSubspace := app.paramsKeeper.Subspace(gov.DefaultParamspace).WithKeyTable(gov.ParamKeyTable())
|
||||
crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace)
|
||||
auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace)
|
||||
cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace)
|
||||
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace)
|
||||
|
||||
// add keepers
|
||||
app.accountKeeper = auth.NewAccountKeeper(
|
||||
@ -208,6 +224,25 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
app.bankKeeper,
|
||||
app.supplyKeeper,
|
||||
&stakingKeeper)
|
||||
app.pricefeedKeeper = pricefeed.NewKeeper(
|
||||
app.cdc,
|
||||
keys[pricefeed.StoreKey],
|
||||
pricefeedSubspace,
|
||||
pricefeed.DefaultCodespace)
|
||||
// NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace, pfk types.PricefeedKeeper, sk types.SupplyKeeper, codespace sdk.CodespaceType)
|
||||
app.auctionKeeper = auction.NewKeeper(
|
||||
app.cdc,
|
||||
keys[auction.StoreKey],
|
||||
app.supplyKeeper,
|
||||
auctionSubspace)
|
||||
app.cdpKeeper = cdp.NewKeeper(
|
||||
app.cdc,
|
||||
keys[cdp.StoreKey],
|
||||
cdpSubspace,
|
||||
app.pricefeedKeeper,
|
||||
app.auctionKeeper,
|
||||
app.supplyKeeper,
|
||||
cdp.DefaultCodespace)
|
||||
|
||||
// register the staking hooks
|
||||
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
|
||||
@ -228,14 +263,17 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
|
||||
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
|
||||
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),
|
||||
pricefeed.NewAppModule(app.pricefeedKeeper),
|
||||
)
|
||||
|
||||
// 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)
|
||||
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName, cdp.ModuleName)
|
||||
|
||||
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName)
|
||||
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName, pricefeed.ModuleName, auction.ModuleName)
|
||||
|
||||
// Note: genutils must occur after staking so that pools are properly
|
||||
// initialized with tokens from genesis accounts.
|
||||
@ -246,6 +284,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?
|
||||
)
|
||||
|
||||
app.mm.RegisterInvariants(&app.crisisKeeper)
|
||||
|
135
app/test_common.go
Normal file
135
app/test_common.go
Normal file
@ -0,0 +1,135 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/crypto"
|
||||
"github.com/tendermint/tendermint/crypto/ed25519"
|
||||
"github.com/tendermint/tendermint/crypto/secp256k1"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
tmdb "github.com/tendermint/tm-db"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
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/bank"
|
||||
"github.com/cosmos/cosmos-sdk/x/crisis"
|
||||
"github.com/cosmos/cosmos-sdk/x/distribution"
|
||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||
"github.com/cosmos/cosmos-sdk/x/mint"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/cosmos/cosmos-sdk/x/staking"
|
||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
)
|
||||
|
||||
// TestApp is a simple wrapper around an App. It exposes internal keepers for use in integration tests.
|
||||
// This file also contains test helpers. Ideally they would be in separate package.
|
||||
// Basic Usage:
|
||||
// Create a test app with NewTestApp, then all keepers and their methods can be accessed for test setup and execution.
|
||||
// Advanced Usage:
|
||||
// Some tests call for an app to be initialized with some state. This can be achieved through keeper method calls (ie keeper.SetParams(...)).
|
||||
// However this leads to a lot of duplicated logic similar to InitGenesis methods.
|
||||
// So TestApp.InitializeFromGenesisStates() will call InitGenesis with the default genesis state.
|
||||
// and TestApp.InitializeFromGenesisStates(authState, cdpState) will do the same but overwrite the auth and cdp sections of the default genesis state
|
||||
// Creating the genesis states can be combersome, but helper methods can make it easier such as NewAuthGenStateFromAccounts below.
|
||||
type TestApp struct {
|
||||
App
|
||||
}
|
||||
|
||||
func NewTestApp() TestApp {
|
||||
db := tmdb.NewMemDB()
|
||||
app := NewApp(log.NewNopLogger(), db, nil, true, 0)
|
||||
return TestApp{App: *app}
|
||||
}
|
||||
|
||||
func (tApp TestApp) GetAccountKeeper() auth.AccountKeeper { return tApp.accountKeeper }
|
||||
func (tApp TestApp) GetBankKeeper() bank.Keeper { return tApp.bankKeeper }
|
||||
func (tApp TestApp) GetSupplyKeeper() supply.Keeper { return tApp.supplyKeeper }
|
||||
func (tApp TestApp) GetStakingKeeper() staking.Keeper { return tApp.stakingKeeper }
|
||||
func (tApp TestApp) GetSlashingKeeper() slashing.Keeper { return tApp.slashingKeeper }
|
||||
func (tApp TestApp) GetMintKeeper() mint.Keeper { return tApp.mintKeeper }
|
||||
func (tApp TestApp) GetDistrKeeper() distribution.Keeper { return tApp.distrKeeper }
|
||||
func (tApp TestApp) GetGovKeeper() gov.Keeper { return tApp.govKeeper }
|
||||
func (tApp TestApp) GetCrisisKeeper() crisis.Keeper { return tApp.crisisKeeper }
|
||||
func (tApp TestApp) GetParamsKeeper() params.Keeper { return tApp.paramsKeeper }
|
||||
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 }
|
||||
|
||||
// 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 {
|
||||
// Create a default genesis state and overwrite with provided values
|
||||
genesisState := NewDefaultGenesisState()
|
||||
for _, state := range genesisStates {
|
||||
for k, v := range state {
|
||||
genesisState[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the chain
|
||||
stateBytes, err := codec.MarshalJSONIndent(tApp.cdc, genesisState)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
tApp.InitChain(
|
||||
abci.RequestInitChain{
|
||||
Validators: []abci.ValidatorUpdate{},
|
||||
AppStateBytes: stateBytes,
|
||||
},
|
||||
)
|
||||
tApp.Commit()
|
||||
tApp.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{Height: tApp.LastBlockHeight() + 1}})
|
||||
return tApp
|
||||
}
|
||||
|
||||
func (tApp TestApp) CheckBalance(t *testing.T, ctx sdk.Context, owner sdk.AccAddress, expectedCoins sdk.Coins) {
|
||||
acc := tApp.GetAccountKeeper().GetAccount(ctx, owner)
|
||||
require.NotNilf(t, acc, "account with address '%s' doesn't exist", owner)
|
||||
require.Equal(t, expectedCoins, acc.GetCoins())
|
||||
}
|
||||
|
||||
// Create a new auth genesis state from some addresses and coins. The state is returned marshalled into a map.
|
||||
func NewAuthGenState(addresses []sdk.AccAddress, coins []sdk.Coins) GenesisState {
|
||||
// Create GenAccounts
|
||||
accounts := authexported.GenesisAccounts{}
|
||||
for i := range addresses {
|
||||
accounts = append(accounts, auth.NewBaseAccount(addresses[i], coins[i], nil, 0, 0))
|
||||
}
|
||||
// Create the auth genesis state
|
||||
authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts)
|
||||
return GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)}
|
||||
}
|
||||
|
||||
// GeneratePrivKeyAddressPairsFromRand generates (deterministically) a total of n private keys and addresses.
|
||||
// TODO only generate secp256 keys?
|
||||
func GeneratePrivKeyAddressPairs(n int) (keys []crypto.PrivKey, addrs []sdk.AccAddress) {
|
||||
r := rand.New(rand.NewSource(12345)) // make the generation deterministic
|
||||
keys = make([]crypto.PrivKey, n)
|
||||
addrs = make([]sdk.AccAddress, n)
|
||||
for i := 0; i < n; i++ {
|
||||
secret := make([]byte, 32)
|
||||
_, err := r.Read(secret)
|
||||
if err != nil {
|
||||
panic("Could not read randomness")
|
||||
}
|
||||
if r.Int63()%2 == 0 {
|
||||
keys[i] = secp256k1.GenPrivKeySecp256k1(secret)
|
||||
} else {
|
||||
keys[i] = ed25519.GenPrivKeyFromSecret(secret)
|
||||
}
|
||||
addrs[i] = sdk.AccAddress(keys[i].PubKey().Address())
|
||||
}
|
||||
return
|
||||
}
|
4
contrib/README.md
Normal file
4
contrib/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
# Contrib
|
||||
|
||||
Resources and examples for running and interacting with the kava blockchain.
|
@ -16,5 +16,3 @@
|
||||
<string>kava</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
QuantaFrontier.com
|
62
contrib/testnet-4000/README.md
Normal file
62
contrib/testnet-4000/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Testnet-4000
|
||||
|
||||
Resources and examples for running and interacting with kava-testnet-4000
|
||||
|
||||
## Rest server requests
|
||||
|
||||
### 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:
|
||||
|
||||
```bash
|
||||
kvcli q auth account $(kvcli keys show accB -a)
|
||||
```
|
||||
|
||||
If testing locally, start the Kava rest server:
|
||||
|
||||
```bash
|
||||
kvcli rest-server
|
||||
```
|
||||
|
||||
Now we'll create an unsigned request, sign it, and broadcast it to the Kava blockchain via the rest server. Note that if you're using the mainnet or testnet, the host IP address will need to be updated to point at an active rest server instead of http://127.0.0.1.
|
||||
|
||||
### 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`.
|
||||
|
||||
```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
|
||||
|
||||
# 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
|
||||
|
||||
# Broadcast the request
|
||||
kvcli tx broadcast ./contrib/requests/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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
# 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
|
||||
|
||||
# Broadcast the request
|
||||
kvcli tx broadcast ./contrib/requests/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`.
|
@ -0,0 +1,360 @@
|
||||
{
|
||||
"genesis_time": "2020-01-22T22:39:50.083273Z",
|
||||
"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": {
|
||||
"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": {}
|
||||
},
|
||||
"params": null,
|
||||
"bank": {
|
||||
"send_enabled": true
|
||||
},
|
||||
"crisis": {
|
||||
"constant_fee": {
|
||||
"amount": "1333000000",
|
||||
"denom": "ukava"
|
||||
}
|
||||
},
|
||||
"cdp": {
|
||||
"cdps": [],
|
||||
"debt_denom": "debt",
|
||||
"deposits": [],
|
||||
"gov_denom": "ukava",
|
||||
"params": {
|
||||
"circuit_breaker": false,
|
||||
"collateral_params": [
|
||||
{
|
||||
"auction_size": "10000000",
|
||||
"conversion_factor": "8",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "1000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"denom": "btc",
|
||||
"liquidation_penalty": "0.05",
|
||||
"liquidation_ratio": "1.5",
|
||||
"market_id": "btc:usd",
|
||||
"prefix": 0,
|
||||
"stability_fee": "1.0000000007829977"
|
||||
},
|
||||
{
|
||||
"auction_size": "10000000",
|
||||
"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": "1000000000",
|
||||
"debt_params": [
|
||||
{
|
||||
"conversion_factor": "6",
|
||||
"debt_floor": "10000000",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"denom": "usdx",
|
||||
"reference_asset": "usd"
|
||||
}
|
||||
],
|
||||
"global_debt_limit": [
|
||||
{
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"surplus_auction_threshold": "1000000000"
|
||||
},
|
||||
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||
"starting_cdp_id": "1"
|
||||
},
|
||||
"auction": {
|
||||
"auctions": [],
|
||||
"next_auction_id": "0",
|
||||
"params": {
|
||||
"bid_duration": "150000000000",
|
||||
"max_auction_duration": "400000000000"
|
||||
}
|
||||
},
|
||||
"genutil": {
|
||||
"gentxs": [
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgCreateValidator",
|
||||
"value": {
|
||||
"description": {
|
||||
"moniker": "validator",
|
||||
"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": "kavavalconspub1zcjduepq0ykwlzr3guncl5l8npqa5lmq8kc5y2aeqp8zc8e72t3stxujxvxsxqjqwt",
|
||||
"value": {
|
||||
"denom": "ukava",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "200000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
|
||||
},
|
||||
"signature": "llQmiLi+BQDKT2D2fOSr+eiDTXnce9xvZ7xls8EgoLIn+vz3HpyaTEoUQVes0kXbHfwJVSTz8jN6Z7HWYeEDlw=="
|
||||
}
|
||||
],
|
||||
"memo": "75917fc405a9af4ed44161a38456ce290a588100@10.21.104.217:26656"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"supply": {
|
||||
"supply": []
|
||||
},
|
||||
"validatorvesting": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"auth": {
|
||||
"accounts": [
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"pricefeed": {
|
||||
"params": {
|
||||
"markets": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "xrp",
|
||||
"market_id": "xrp:usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "btc",
|
||||
"market_id": "btc:usd",
|
||||
"oracles": [],
|
||||
"quote_asset": "usd"
|
||||
}
|
||||
]
|
||||
},
|
||||
"posted_prices": [
|
||||
{
|
||||
"expiry": "2050-01-01T00:00:00Z",
|
||||
"market_id": "btc:usd",
|
||||
"oracle_address": "",
|
||||
"price": "8700.0"
|
||||
},
|
||||
{
|
||||
"expiry": "2050-01-01T00:00:00Z",
|
||||
"market_id": "xrp:usd",
|
||||
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"price": "0.25"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gov": {
|
||||
"deposit_params": {
|
||||
"max_deposit_period": "300000000000",
|
||||
"min_deposit": [
|
||||
{
|
||||
"amount": "1000000",
|
||||
"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": "300000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,360 @@
|
||||
{
|
||||
"genesis_time": "2020-01-22T22:39:50.083273Z",
|
||||
"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": {
|
||||
"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": {}
|
||||
},
|
||||
"params": null,
|
||||
"bank": {
|
||||
"send_enabled": true
|
||||
},
|
||||
"crisis": {
|
||||
"constant_fee": {
|
||||
"amount": "1333000000",
|
||||
"denom": "ukava"
|
||||
}
|
||||
},
|
||||
"cdp": {
|
||||
"cdps": [],
|
||||
"debt_denom": "ukava",
|
||||
"deposits": [],
|
||||
"gov_denom": "ukava",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"global_debt_limit": [
|
||||
{
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"surplus_auction_threshold": "9000000"
|
||||
},
|
||||
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||
"starting_cdp_id": "1"
|
||||
},
|
||||
"auction": {
|
||||
"auctions": [],
|
||||
"next_auction_id": "0",
|
||||
"params": {
|
||||
"bid_duration": "100000000000",
|
||||
"max_auction_duration": "100000000000"
|
||||
}
|
||||
},
|
||||
"genutil": {
|
||||
"gentxs": [
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgCreateValidator",
|
||||
"value": {
|
||||
"description": {
|
||||
"moniker": "validator",
|
||||
"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": "kavavalconspub1zcjduepq0ykwlzr3guncl5l8npqa5lmq8kc5y2aeqp8zc8e72t3stxujxvxsxqjqwt",
|
||||
"value": {
|
||||
"denom": "ukava",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "200000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
|
||||
},
|
||||
"signature": "llQmiLi+BQDKT2D2fOSr+eiDTXnce9xvZ7xls8EgoLIn+vz3HpyaTEoUQVes0kXbHfwJVSTz8jN6Z7HWYeEDlw=="
|
||||
}
|
||||
],
|
||||
"memo": "75917fc405a9af4ed44161a38456ce290a588100@10.21.104.217:26656"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"supply": {
|
||||
"supply": []
|
||||
},
|
||||
"validatorvesting": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"auth": {
|
||||
"accounts": [
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"pricefeed": {
|
||||
"params": {
|
||||
"markets": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "xrp",
|
||||
"market_id": "xrp:usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "btc",
|
||||
"market_id": "btc:usd",
|
||||
"oracles": [],
|
||||
"quote_asset": "usd"
|
||||
}
|
||||
]
|
||||
},
|
||||
"posted_prices": [
|
||||
{
|
||||
"expiry": "2050-01-01T00:00:00Z",
|
||||
"market_id": "btc:usd",
|
||||
"oracle_address": "",
|
||||
"price": "8700.0"
|
||||
},
|
||||
{
|
||||
"expiry": "2050-01-01T00:00:00Z",
|
||||
"market_id": "xrp:usd",
|
||||
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"price": "0.25"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gov": {
|
||||
"deposit_params": {
|
||||
"max_deposit_period": "300000000000",
|
||||
"min_deposit": [
|
||||
{
|
||||
"amount": "1000000",
|
||||
"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": "300000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
{
|
||||
"title": "CDP Param Change",
|
||||
"description": "Add collateral XRP",
|
||||
"changes": [
|
||||
{
|
||||
"subspace": "cdp",
|
||||
"key": "CollateralParams",
|
||||
"value": [
|
||||
{
|
||||
"auction_size": "50000000",
|
||||
"conversion_factor": "6",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "1000000000",
|
||||
"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": "1000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"denom": "btc",
|
||||
"liquidation_penalty": "0.05",
|
||||
"liquidation_ratio": "1.5",
|
||||
"market_id": "btc:usd",
|
||||
"prefix": 1,
|
||||
"stability_fee": "1.0000000007829977"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"deposit": [
|
||||
{
|
||||
"denom": "ukava",
|
||||
"amount": "1000000"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
{
|
||||
"title": "CDP Param Change",
|
||||
"description": "Add collateral XRP",
|
||||
"changes": [
|
||||
{
|
||||
"subspace": "cdp",
|
||||
"key": "CollateralParams",
|
||||
"value": [
|
||||
{
|
||||
"auction_size": "50000000",
|
||||
"conversion_factor": "6",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "1000000000",
|
||||
"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": "1000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"denom": "btc",
|
||||
"liquidation_penalty": "0.05",
|
||||
"liquidation_ratio": "1.5",
|
||||
"market_id": "btc:usd",
|
||||
"prefix": 1,
|
||||
"stability_fee": "1.0000000007829977"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"subspace": "cdp",
|
||||
"key": "DebtParams",
|
||||
"value": [
|
||||
{
|
||||
"conversion_factor": "6",
|
||||
"debt_floor": "10000000",
|
||||
"denom": "usdx",
|
||||
"reference_asset": "usd"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"subspace": "cdp",
|
||||
"key": "GlobalDebtLimit",
|
||||
"value": [
|
||||
{
|
||||
"amount": "3000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"deposit": [
|
||||
{
|
||||
"denom": "ukava",
|
||||
"amount": "1000000"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,344 @@
|
||||
{
|
||||
"genesis_time": "2020-01-22T22:39:50.083273Z",
|
||||
"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": {
|
||||
"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": {}
|
||||
},
|
||||
"params": null,
|
||||
"bank": {
|
||||
"send_enabled": true
|
||||
},
|
||||
"crisis": {
|
||||
"constant_fee": {
|
||||
"amount": "1333000000",
|
||||
"denom": "ukava"
|
||||
}
|
||||
},
|
||||
"cdp": {
|
||||
"cdps": [],
|
||||
"debt_denom": "debt",
|
||||
"deposits": [],
|
||||
"gov_denom": "ukava",
|
||||
"params": {
|
||||
"circuit_breaker": false,
|
||||
"collateral_params": [
|
||||
{
|
||||
"auction_size": "10000000",
|
||||
"conversion_factor": "8",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "1000000000000",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"global_debt_limit": [
|
||||
{
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"surplus_auction_threshold": "1000000000"
|
||||
},
|
||||
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||
"starting_cdp_id": "1"
|
||||
},
|
||||
"auction": {
|
||||
"auctions": [],
|
||||
"next_auction_id": "0",
|
||||
"params": {
|
||||
"bid_duration": "600000000000",
|
||||
"max_auction_duration": "172800000000000"
|
||||
}
|
||||
},
|
||||
"genutil": {
|
||||
"gentxs": [
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgCreateValidator",
|
||||
"value": {
|
||||
"description": {
|
||||
"moniker": "validator",
|
||||
"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": "kavavalconspub1zcjduepq0ykwlzr3guncl5l8npqa5lmq8kc5y2aeqp8zc8e72t3stxujxvxsxqjqwt",
|
||||
"value": {
|
||||
"denom": "ukava",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "200000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
|
||||
},
|
||||
"signature": "llQmiLi+BQDKT2D2fOSr+eiDTXnce9xvZ7xls8EgoLIn+vz3HpyaTEoUQVes0kXbHfwJVSTz8jN6Z7HWYeEDlw=="
|
||||
}
|
||||
],
|
||||
"memo": "75917fc405a9af4ed44161a38456ce290a588100@10.21.104.217:26656"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"supply": {
|
||||
"supply": []
|
||||
},
|
||||
"validatorvesting": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"auth": {
|
||||
"accounts": [
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "90000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"pricefeed": {
|
||||
"params": {
|
||||
"markets": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "xrp",
|
||||
"market_id": "xrp:usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "btc",
|
||||
"market_id": "btc:usd",
|
||||
"oracles": [],
|
||||
"quote_asset": "usd"
|
||||
}
|
||||
]
|
||||
},
|
||||
"posted_prices": [
|
||||
{
|
||||
"expiry": "2050-01-01T00:00:00Z",
|
||||
"market_id": "btc:usd",
|
||||
"oracle_address": "",
|
||||
"price": "8700.0"
|
||||
},
|
||||
{
|
||||
"expiry": "2050-01-01T00:00:00Z",
|
||||
"market_id": "xrp:usd",
|
||||
"oracle_address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"price": "0.25"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gov": {
|
||||
"deposit_params": {
|
||||
"max_deposit_period": "300000000000",
|
||||
"min_deposit": [
|
||||
{
|
||||
"amount": "1000000",
|
||||
"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": "300000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
{
|
||||
"title": "Pricefeed Param Change",
|
||||
"description": "Add market bnb-usd",
|
||||
"changes": [
|
||||
{
|
||||
"subspace": "pricefeed",
|
||||
"key": "Markets",
|
||||
"value": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "btc",
|
||||
"market_id": "btc-usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "xrp",
|
||||
"market_id": "xrp-usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "bnb",
|
||||
"market_id": "bnb-usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"deposit": [
|
||||
{
|
||||
"denom": "ukava",
|
||||
"amount": "1000000"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,359 @@
|
||||
{
|
||||
"genesis_time": "2020-01-22T22:39:50.083273Z",
|
||||
"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": {
|
||||
"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": {}
|
||||
},
|
||||
"params": null,
|
||||
"bank": {
|
||||
"send_enabled": true
|
||||
},
|
||||
"crisis": {
|
||||
"constant_fee": {
|
||||
"amount": "1333000000",
|
||||
"denom": "ukava"
|
||||
}
|
||||
},
|
||||
"cdp": {
|
||||
"cdps": [],
|
||||
"debt_denom": "debt",
|
||||
"deposits": [],
|
||||
"gov_denom": "ukava",
|
||||
"params": {
|
||||
"circuit_breaker": false,
|
||||
"collateral_params": [
|
||||
{
|
||||
"auction_size": "5000000000",
|
||||
"conversion_factor": "6",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "1000000000000",
|
||||
"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": "1000000000000",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"global_debt_limit": [
|
||||
{
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"surplus_auction_threshold": "1000000000"
|
||||
},
|
||||
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||
"starting_cdp_id": "1"
|
||||
},
|
||||
"auction": {
|
||||
"auctions": [],
|
||||
"next_auction_id": "0",
|
||||
"params": {
|
||||
"bid_duration": "600000000000",
|
||||
"max_auction_duration": "172800000000000"
|
||||
}
|
||||
},
|
||||
"genutil": {
|
||||
"gentxs": [
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgCreateValidator",
|
||||
"value": {
|
||||
"description": {
|
||||
"moniker": "validator",
|
||||
"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": "kavavalconspub1zcjduepq0ykwlzr3guncl5l8npqa5lmq8kc5y2aeqp8zc8e72t3stxujxvxsxqjqwt",
|
||||
"value": {
|
||||
"denom": "ukava",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "200000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
|
||||
},
|
||||
"signature": "llQmiLi+BQDKT2D2fOSr+eiDTXnce9xvZ7xls8EgoLIn+vz3HpyaTEoUQVes0kXbHfwJVSTz8jN6Z7HWYeEDlw=="
|
||||
}
|
||||
],
|
||||
"memo": "75917fc405a9af4ed44161a38456ce290a588100@10.21.104.217:26656"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"supply": {
|
||||
"supply": []
|
||||
},
|
||||
"validatorvesting": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"auth": {
|
||||
"accounts": [
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "100000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "100000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "100000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"pricefeed": {
|
||||
"params": {
|
||||
"markets": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "xrp",
|
||||
"market_id": "xrp:usd",
|
||||
"oracles": [],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "btc",
|
||||
"market_id": "btc:usd",
|
||||
"oracles": [],
|
||||
"quote_asset": "usd"
|
||||
}
|
||||
]
|
||||
},
|
||||
"posted_prices": [
|
||||
{
|
||||
"expiry": "5000-01-01T00:00:00Z",
|
||||
"market_id": "xrp:usd",
|
||||
"oracle_address": "",
|
||||
"price": "0.25"
|
||||
},
|
||||
{
|
||||
"expiry": "5000-01-01T00:00:00Z",
|
||||
"market_id": "btc:usd",
|
||||
"oracle_address": "",
|
||||
"price": "8700.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gov": {
|
||||
"deposit_params": {
|
||||
"max_deposit_period": "300000000000",
|
||||
"min_deposit": [
|
||||
{
|
||||
"amount": "1000000",
|
||||
"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": "300000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "Auction Param Change",
|
||||
"description": "Update auction bid duration",
|
||||
"changes": [
|
||||
{
|
||||
"subspace": "auction",
|
||||
"key": "BidDuration",
|
||||
"value": "912345000000"
|
||||
}
|
||||
],
|
||||
"deposit": [
|
||||
{
|
||||
"denom": "ukava",
|
||||
"amount": "1000000"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,361 @@
|
||||
{
|
||||
"genesis_time": "2020-01-22T22:39:50.083273Z",
|
||||
"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": {
|
||||
"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": {}
|
||||
},
|
||||
"params": null,
|
||||
"bank": {
|
||||
"send_enabled": true
|
||||
},
|
||||
"crisis": {
|
||||
"constant_fee": {
|
||||
"amount": "1333000000",
|
||||
"denom": "ukava"
|
||||
}
|
||||
},
|
||||
"cdp": {
|
||||
"cdps": [],
|
||||
"debt_denom": "debt",
|
||||
"deposits": [],
|
||||
"gov_denom": "ukava",
|
||||
"params": {
|
||||
"circuit_breaker": false,
|
||||
"collateral_params": [
|
||||
{
|
||||
"auction_size": "5000000000",
|
||||
"conversion_factor": "6",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "1000000000000",
|
||||
"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": "1000000000000",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"global_debt_limit": [
|
||||
{
|
||||
"amount": "2000000000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"surplus_auction_threshold": "1000000000"
|
||||
},
|
||||
"previous_block_time": "1970-01-01T00:00:00Z",
|
||||
"starting_cdp_id": "1"
|
||||
},
|
||||
"auction": {
|
||||
"auctions": [],
|
||||
"next_auction_id": "0",
|
||||
"params": {
|
||||
"bid_duration": "600000000000",
|
||||
"max_auction_duration": "172800000000000"
|
||||
}
|
||||
},
|
||||
"genutil": {
|
||||
"gentxs": [
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cosmos-sdk/MsgCreateValidator",
|
||||
"value": {
|
||||
"description": {
|
||||
"moniker": "validator",
|
||||
"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": "kavavalconspub1zcjduepq0ykwlzr3guncl5l8npqa5lmq8kc5y2aeqp8zc8e72t3stxujxvxsxqjqwt",
|
||||
"value": {
|
||||
"denom": "ukava",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "200000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
|
||||
},
|
||||
"signature": "llQmiLi+BQDKT2D2fOSr+eiDTXnce9xvZ7xls8EgoLIn+vz3HpyaTEoUQVes0kXbHfwJVSTz8jN6Z7HWYeEDlw=="
|
||||
}
|
||||
],
|
||||
"memo": "75917fc405a9af4ed44161a38456ce290a588100@10.21.104.217:26656"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"supply": {
|
||||
"supply": []
|
||||
},
|
||||
"validatorvesting": {
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"auth": {
|
||||
"accounts": [
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "cosmos-sdk/Account",
|
||||
"value": {
|
||||
"account_number": "0",
|
||||
"address": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"coins": [
|
||||
{
|
||||
"amount": "10000000000",
|
||||
"denom": "btc"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000000",
|
||||
"denom": "ukava"
|
||||
},
|
||||
{
|
||||
"amount": "9000000000000",
|
||||
"denom": "xrp"
|
||||
}
|
||||
],
|
||||
"public_key": null,
|
||||
"sequence": "0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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
|
||||
},
|
||||
"pricefeed": {
|
||||
"params": {
|
||||
"markets": [
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "xrp",
|
||||
"market_id": "xrp:usd",
|
||||
"oracles": [
|
||||
"kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw"
|
||||
],
|
||||
"quote_asset": "usd"
|
||||
},
|
||||
{
|
||||
"active": true,
|
||||
"base_asset": "btc",
|
||||
"market_id": "btc:usd",
|
||||
"oracles": [],
|
||||
"quote_asset": "usd"
|
||||
}
|
||||
]
|
||||
},
|
||||
"posted_prices": [
|
||||
{
|
||||
"expiry": "5000-01-01T00:00:00Z",
|
||||
"market_id": "xrp:usd",
|
||||
"oracle_address": "",
|
||||
"price": "0.25"
|
||||
},
|
||||
{
|
||||
"expiry": "5000-01-01T00:00:00Z",
|
||||
"market_id": "btc:usd",
|
||||
"oracle_address": "",
|
||||
"price": "8700.0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"gov": {
|
||||
"deposit_params": {
|
||||
"max_deposit_period": "300000000000",
|
||||
"min_deposit": [
|
||||
{
|
||||
"amount": "1000000",
|
||||
"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": "300000000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
{
|
||||
"title": "CDP Param Change",
|
||||
"description": "Update usdx debt limit for collateral type XRP",
|
||||
"changes": [
|
||||
{
|
||||
"subspace": "cdp",
|
||||
"key": "CollateralParams",
|
||||
"value": [
|
||||
{
|
||||
"auction_size": "10000000",
|
||||
"conversion_factor": "6",
|
||||
"debt_limit": [
|
||||
{
|
||||
"amount": "750000000",
|
||||
"denom": "usdx"
|
||||
}
|
||||
],
|
||||
"denom": "xrp",
|
||||
"liquidation_penalty": "0.05",
|
||||
"liquidation_ratio": "2.0",
|
||||
"market_id": "xrp:usd",
|
||||
"prefix": 1,
|
||||
"stability_fee": "1.0000000007829977"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"deposit": [
|
||||
{
|
||||
"denom": "ukava",
|
||||
"amount": "1000000"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
{
|
||||
"title": "Auction Param Change",
|
||||
"description": "Update max auction duration",
|
||||
"changes": [
|
||||
{
|
||||
"subspace": "auction",
|
||||
"key": "MaxAuctionDuration",
|
||||
"value": "987654321000"
|
||||
}
|
||||
],
|
||||
"deposit": [
|
||||
{
|
||||
"denom": "ukava",
|
||||
"amount": "1000000"
|
||||
}
|
||||
]
|
||||
}
|
39
contrib/testnet-4000/requests/broadcast-create-cdp.json
Normal file
39
contrib/testnet-4000/requests/broadcast-create-cdp.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cdp/MsgCreateCDP",
|
||||
"value": {
|
||||
"sender": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"collateral": [
|
||||
{
|
||||
"denom": "xrp",
|
||||
"amount": "110000000"
|
||||
}
|
||||
],
|
||||
"principal": [
|
||||
{
|
||||
"denom": "usdx",
|
||||
"amount": "10000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "AxYhgA8CL/ZmTZCQDS2dGcccu+dv7zOBT0Wz0+grIufW"
|
||||
},
|
||||
"signature": "KMGQekAgj9qSqnu38swM/jTnIkrSskL4m9L3QQlBIbVZdzDQVh9Rkb0UL7TPeXTaswlSTiwFic3hvZZOcjM4SA=="
|
||||
}
|
||||
],
|
||||
"memo": ""
|
||||
}
|
||||
}
|
34
contrib/testnet-4000/requests/broadcast-deposit-cdp.json
Normal file
34
contrib/testnet-4000/requests/broadcast-deposit-cdp.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"tx": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cdp/MsgDeposit",
|
||||
"value": {
|
||||
"depositor": "kava1rjnv8ns2wvz9calje5pmcnpxm055pegprywvjj",
|
||||
"owner": "kava1mzqz3jfs9nzfp6v7qp647rv0afxlu2csl0txmq",
|
||||
"collateral": [
|
||||
{
|
||||
"denom": "xrp",
|
||||
"amount": "110000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "A2SB6wV+X9cVl6URZ28mmfxNQwFJD1u3oo5G332jMa2L"
|
||||
},
|
||||
"signature": "mJCjAhEVS/2KCvmGpcnvmeHCBo/UEu3/SbdhoyWkp8sDOLe5FO6016HyjUMJ2jJh80aSBJpPFQyyh2gOFQluXw=="
|
||||
}
|
||||
],
|
||||
"memo": ""
|
||||
},
|
||||
"mode": "sync"
|
||||
}
|
30
contrib/testnet-4000/requests/broadcast-post-price.json
Normal file
30
contrib/testnet-4000/requests/broadcast-post-price.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "pricefeed/MsgPostPrice",
|
||||
"value": {
|
||||
"from": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"market_id": "xrp:usd",
|
||||
"price": "0.290000000000000000",
|
||||
"expiry": "2100-03-29T10:40:00Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "Az740XKIPCJtnZLmJfktTfhsEStEJE3n2iRVyJ3wko43"
|
||||
},
|
||||
"signature": "1CMkPnD0UYSpIgPTIGUjroM/16DizLgrR+NSNlsjNs1b5cvD2j3VMfYM0nyBa4IC305RkBOUdthTPc6RPZWDGg=="
|
||||
}
|
||||
],
|
||||
"memo": ""
|
||||
}
|
||||
}
|
31
contrib/testnet-4000/requests/create-cdp-unsigned.json
Normal file
31
contrib/testnet-4000/requests/create-cdp-unsigned.json
Normal file
@ -0,0 +1,31 @@
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "cdp/MsgCreateCDP",
|
||||
"value": {
|
||||
"sender": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"collateral": [
|
||||
{
|
||||
"denom": "xrp",
|
||||
"amount": "110000000"
|
||||
}
|
||||
],
|
||||
"principal": [
|
||||
{
|
||||
"denom": "usdx",
|
||||
"amount": "10000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": null,
|
||||
"memo": ""
|
||||
}
|
||||
}
|
25
contrib/testnet-4000/requests/create-cdp.json
Normal file
25
contrib/testnet-4000/requests/create-cdp.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"base_req": {
|
||||
"from": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"memo": "",
|
||||
"chain_id": "testing",
|
||||
"account_number": "2",
|
||||
"sequence": "0",
|
||||
"gas": "500000",
|
||||
"gas_adjustment": "1.0",
|
||||
"simulate": false
|
||||
},
|
||||
"owner": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"collateral": [
|
||||
{
|
||||
"denom": "xrp",
|
||||
"amount": "110000000"
|
||||
}
|
||||
],
|
||||
"principal": [
|
||||
{
|
||||
"denom": "usdx",
|
||||
"amount": "10000000"
|
||||
}
|
||||
]
|
||||
}
|
20
contrib/testnet-4000/requests/deposit-cdp.json
Normal file
20
contrib/testnet-4000/requests/deposit-cdp.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"base_req": {
|
||||
"from": "kava1rjnv8ns2wvz9calje5pmcnpxm055pegprywvjj",
|
||||
"memo": "",
|
||||
"chain_id": "kava-internal",
|
||||
"account_number": "12",
|
||||
"sequence": "0",
|
||||
"gas": "500000",
|
||||
"gas_adjustment": "1.0",
|
||||
"simulate": false
|
||||
},
|
||||
"owner": "kava1mzqz3jfs9nzfp6v7qp647rv0afxlu2csl0txmq",
|
||||
"depositor": "kava1rjnv8ns2wvz9calje5pmcnpxm055pegprywvjj",
|
||||
"collateral": [
|
||||
{
|
||||
"denom": "xrp",
|
||||
"amount": "110000000"
|
||||
}
|
||||
]
|
||||
}
|
25
contrib/testnet-4000/requests/example-create-cdp.json
Normal file
25
contrib/testnet-4000/requests/example-create-cdp.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"base_req": {
|
||||
"from": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"memo": "",
|
||||
"chain_id": "testing",
|
||||
"account_number": "2",
|
||||
"sequence": "0",
|
||||
"gas": "500000",
|
||||
"gas_adjustment": "1.0",
|
||||
"simulate": false
|
||||
},
|
||||
"owner": "kava12jk3szk45afmvjc3xc6kvj4e40tuy2m8ckgs03",
|
||||
"collateral": [
|
||||
{
|
||||
"denom": "xrp",
|
||||
"amount": "110000000"
|
||||
}
|
||||
],
|
||||
"principal": [
|
||||
{
|
||||
"denom": "usdx",
|
||||
"amount": "10000000"
|
||||
}
|
||||
]
|
||||
}
|
16
contrib/testnet-4000/requests/example-post-price.json
Normal file
16
contrib/testnet-4000/requests/example-post-price.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"base_req": {
|
||||
"from": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"memo": "",
|
||||
"chain_id": "testing",
|
||||
"account_number": "0",
|
||||
"sequence": "96",
|
||||
"gas": "500000",
|
||||
"gas_adjustment": "1.0",
|
||||
"simulate": false
|
||||
},
|
||||
"from": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"market_id": "xrp:usd",
|
||||
"price": "0.29",
|
||||
"expiry": "4110000000"
|
||||
}
|
22
contrib/testnet-4000/requests/post-price-unsigned.json
Normal file
22
contrib/testnet-4000/requests/post-price-unsigned.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "pricefeed/MsgPostPrice",
|
||||
"value": {
|
||||
"from": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"market_id": "xrp:usd",
|
||||
"price": "0.290000000000000000",
|
||||
"expiry": "2100-03-29T10:40:00Z"
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": null,
|
||||
"memo": ""
|
||||
}
|
||||
}
|
16
contrib/testnet-4000/requests/post-price.json
Normal file
16
contrib/testnet-4000/requests/post-price.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"base_req": {
|
||||
"from": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"memo": "",
|
||||
"chain_id": "testing",
|
||||
"account_number": "0",
|
||||
"sequence": "96",
|
||||
"gas": "500000",
|
||||
"gas_adjustment": "1.0",
|
||||
"simulate": false
|
||||
},
|
||||
"from": "kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw",
|
||||
"market_id": "xrp:usd",
|
||||
"price": "0.29",
|
||||
"expiry": "4110000000"
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
{
|
||||
"base_req": {
|
||||
"from": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"memo": "arbitrary text",
|
||||
"chain_id": "testing",
|
||||
"account_number": "0",
|
||||
"sequence": "2",
|
||||
"gas": "500000",
|
||||
"gas_adjustment": "1.0",
|
||||
"simulate": false
|
||||
},
|
||||
"amount": {
|
||||
"denom": "usdx",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "auction/MsgPlaceBid",
|
||||
"value": {
|
||||
"auction_id": "1",
|
||||
"bidder": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"amount": {
|
||||
"denom": "usdx",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "AsBYqm9S7cjIBdG6N/xzAGuEizTt2S2CJuM1FCsgta7u"
|
||||
},
|
||||
"signature": "O3zsB8P61SN7/5C+TNfO9hMaFYWV6hjr2aVigN0GrckR2PQGZhpyEZ03P71A9uK7DFLawzvAHDZRqQu833lAnA=="
|
||||
}
|
||||
],
|
||||
"memo": "arbitrary text"
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
{
|
||||
"type": "cosmos-sdk/StdTx",
|
||||
"value": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "auction/MsgPlaceBid",
|
||||
"value": {
|
||||
"auction_id": "1",
|
||||
"bidder": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"amount": {
|
||||
"denom": "usdx",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": null,
|
||||
"memo": "arbitrary text"
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
{
|
||||
"tx": {
|
||||
"msg": [
|
||||
{
|
||||
"type": "auction/MsgPlaceBid",
|
||||
"value": {
|
||||
"auction_id": "1",
|
||||
"bidder": "kava1xy7hrjy9r0algz9w3gzm8u6mrpq97kwta747gj",
|
||||
"amount": {
|
||||
"denom": "usdx",
|
||||
"amount": "100000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"fee": {
|
||||
"amount": [],
|
||||
"gas": "500000"
|
||||
},
|
||||
"signatures": [
|
||||
{
|
||||
"pub_key": {
|
||||
"type": "tendermint/PubKeySecp256k1",
|
||||
"value": "AsBYqm9S7cjIBdG6N/xzAGuEizTt2S2CJuM1FCsgta7u"
|
||||
},
|
||||
"signature": "O3zsB8P61SN7/5C+TNfO9hMaFYWV6hjr2aVigN0GrckR2PQGZhpyEZ03P71A9uK7DFLawzvAHDZRqQu833lAnA=="
|
||||
}
|
||||
],
|
||||
"memo": "arbitrary text"
|
||||
},
|
||||
"mode": "block"
|
||||
}
|
1
go.mod
1
go.mod
@ -5,6 +5,7 @@ go 1.13
|
||||
require (
|
||||
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/raviqqe/liche v0.0.0-20191208214012-e144e0808a75 // indirect
|
||||
github.com/spf13/cobra v0.0.5
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
|
30
go.sum
30
go.sum
@ -43,6 +43,7 @@ github.com/cosmos/cosmos-sdk v0.34.4-0.20191010155330-64a27412505c/go.mod h1:Fxj
|
||||
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1 h1:yb+E8HGzFnO0YwLS6OCBIAVWtN8KfCYoKeO9mgAmQN0=
|
||||
github.com/cosmos/cosmos-sdk v0.34.4-0.20191010193331-18de630d0ae1/go.mod h1:IGBhkbOK1ebLqMWjtgo99zUxWHsA5IOb6N9CI8nHs0Y=
|
||||
github.com/cosmos/cosmos-sdk v0.37.1 h1:mz5W3Au32VIPPtrY65dheVYeVDSFfS3eSSmuIj+cXsI=
|
||||
github.com/cosmos/cosmos-sdk v0.37.4 h1:1ioXxkpiS+wOgaUbROeDIyuF7hciU5nti0TSyBmV2Ok=
|
||||
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI=
|
||||
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
|
||||
github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc=
|
||||
@ -58,6 +59,8 @@ 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=
|
||||
@ -69,6 +72,8 @@ 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=
|
||||
@ -140,6 +145,11 @@ 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=
|
||||
@ -156,6 +166,9 @@ github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDe
|
||||
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=
|
||||
@ -199,6 +212,8 @@ 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=
|
||||
@ -206,7 +221,12 @@ 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=
|
||||
@ -267,6 +287,11 @@ github.com/tendermint/tm-db v0.2.0/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6
|
||||
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=
|
||||
@ -297,6 +322,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
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=
|
||||
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=
|
||||
@ -310,10 +338,12 @@ 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=
|
||||
|
BIN
swagger-ui/testnet-4000/favicon-16x16.png
Normal file
BIN
swagger-ui/testnet-4000/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 665 B |
BIN
swagger-ui/testnet-4000/favicon-32x32.png
Normal file
BIN
swagger-ui/testnet-4000/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 628 B |
60
swagger-ui/testnet-4000/index.html
Normal file
60
swagger-ui/testnet-4000/index.html
Normal file
@ -0,0 +1,60 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" >
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "./swagger-testnet-4000.yaml",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
})
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
68
swagger-ui/testnet-4000/oauth2-redirect.html
Normal file
68
swagger-ui/testnet-4000/oauth2-redirect.html
Normal file
@ -0,0 +1,68 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
3309
swagger-ui/testnet-4000/swagger-testnet-4000.yaml
Normal file
3309
swagger-ui/testnet-4000/swagger-testnet-4000.yaml
Normal file
File diff suppressed because it is too large
Load Diff
134
swagger-ui/testnet-4000/swagger-ui-bundle.js
Normal file
134
swagger-ui/testnet-4000/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
swagger-ui/testnet-4000/swagger-ui-bundle.js.map
Normal file
1
swagger-ui/testnet-4000/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
22
swagger-ui/testnet-4000/swagger-ui-standalone-preset.js
Normal file
22
swagger-ui/testnet-4000/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4
swagger-ui/testnet-4000/swagger-ui.css
Normal file
4
swagger-ui/testnet-4000/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
swagger-ui/testnet-4000/swagger-ui.css.map
Normal file
1
swagger-ui/testnet-4000/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
9
swagger-ui/testnet-4000/swagger-ui.js
Normal file
9
swagger-ui/testnet-4000/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
swagger-ui/testnet-4000/swagger-ui.js.map
Normal file
1
swagger-ui/testnet-4000/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
13
x/auction/abci.go
Normal file
13
x/auction/abci.go
Normal file
@ -0,0 +1,13 @@
|
||||
package auction
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// EndBlocker runs at the end of every block.
|
||||
func EndBlocker(ctx sdk.Context, k Keeper) {
|
||||
err := k.CloseExpiredAuctions(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
66
x/auction/abci_test.go
Normal file
66
x/auction/abci_test.go
Normal file
@ -0,0 +1,66 @@
|
||||
package auction_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
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/supply"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
)
|
||||
|
||||
func TestKeeper_EndBlocker(t *testing.T) {
|
||||
// Setup
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(2)
|
||||
buyer := addrs[0]
|
||||
returnAddrs := addrs[1:]
|
||||
returnWeights := []sdk.Int{sdk.NewInt(1)}
|
||||
sellerModName := cdp.LiquidatorMacc
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
sellerAcc := supply.NewEmptyModuleAccount(sellerModName)
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 30)))
|
||||
|
||||
// Run the endblocker, simulating a block time 1ns before auction expiry
|
||||
preExpiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration - 1)
|
||||
auction.EndBlocker(ctx.WithBlockTime(preExpiryTime), keeper)
|
||||
|
||||
// Check auction has not been closed yet
|
||||
_, found := keeper.GetAuction(ctx, auctionID)
|
||||
require.True(t, found)
|
||||
|
||||
// Run the endblocker, simulating a block time equal to auction expiry
|
||||
expiryTime := ctx.BlockTime().Add(auction.DefaultBidDuration)
|
||||
auction.EndBlocker(ctx.WithBlockTime(expiryTime), keeper)
|
||||
|
||||
// Check auction has been closed
|
||||
_, found = keeper.GetAuction(ctx, auctionID)
|
||||
require.False(t, found)
|
||||
}
|
||||
|
||||
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 NewAuthGenStateFromAccs(accounts authexported.GenesisAccounts) app.GenesisState {
|
||||
authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts)
|
||||
return app.GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)}
|
||||
}
|
80
x/auction/alias.go
Normal file
80
x/auction/alias.go
Normal file
@ -0,0 +1,80 @@
|
||||
// nolint
|
||||
// autogenerated code using github.com/rigelrozanski/multitool
|
||||
// aliases generated for the following subdirectories:
|
||||
// ALIASGEN: github.com/kava-labs/kava/x/auction/types/
|
||||
// ALIASGEN: github.com/kava-labs/kava/x/auction/keeper/
|
||||
package auction
|
||||
|
||||
import (
|
||||
"github.com/kava-labs/kava/x/auction/keeper"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultCodespace = types.DefaultCodespace
|
||||
CodeInvalidInitialAuctionID = types.CodeInvalidInitialAuctionID
|
||||
CodeUnrecognizedAuctionType = types.CodeUnrecognizedAuctionType
|
||||
CodeAuctionNotFound = types.CodeAuctionNotFound
|
||||
CodeAuctionHasNotExpired = types.CodeAuctionHasNotExpired
|
||||
CodeAuctionHasExpired = types.CodeAuctionHasExpired
|
||||
CodeInvalidBidDenom = types.CodeInvalidBidDenom
|
||||
CodeInvalidLotDenom = types.CodeInvalidLotDenom
|
||||
CodeBidTooSmall = types.CodeBidTooSmall
|
||||
CodeBidTooLarge = types.CodeBidTooLarge
|
||||
CodeLotTooLarge = types.CodeLotTooLarge
|
||||
CodeCollateralAuctionIsInReversePhase = types.CodeCollateralAuctionIsInReversePhase
|
||||
CodeCollateralAuctionIsInForwardPhase = types.CodeCollateralAuctionIsInForwardPhase
|
||||
ModuleName = types.ModuleName
|
||||
StoreKey = types.StoreKey
|
||||
RouterKey = types.RouterKey
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
DefaultMaxAuctionDuration = types.DefaultMaxAuctionDuration
|
||||
DefaultBidDuration = types.DefaultBidDuration
|
||||
QueryGetAuction = types.QueryGetAuction
|
||||
DefaultNextAuctionID = types.DefaultNextAuctionID
|
||||
)
|
||||
|
||||
var (
|
||||
// functions aliases
|
||||
NewSurplusAuction = types.NewSurplusAuction
|
||||
NewDebtAuction = types.NewDebtAuction
|
||||
NewCollateralAuction = types.NewCollateralAuction
|
||||
NewWeightedAddresses = types.NewWeightedAddresses
|
||||
RegisterCodec = types.RegisterCodec
|
||||
NewGenesisState = types.NewGenesisState
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
GetAuctionKey = types.GetAuctionKey
|
||||
GetAuctionByTimeKey = types.GetAuctionByTimeKey
|
||||
Uint64FromBytes = types.Uint64FromBytes
|
||||
Uint64ToBytes = types.Uint64ToBytes
|
||||
NewMsgPlaceBid = types.NewMsgPlaceBid
|
||||
NewParams = types.NewParams
|
||||
DefaultParams = types.DefaultParams
|
||||
ParamKeyTable = types.ParamKeyTable
|
||||
NewKeeper = keeper.NewKeeper
|
||||
NewQuerier = keeper.NewQuerier
|
||||
|
||||
// variable aliases
|
||||
ModuleCdc = types.ModuleCdc
|
||||
AuctionKeyPrefix = types.AuctionKeyPrefix
|
||||
AuctionByTimeKeyPrefix = types.AuctionByTimeKeyPrefix
|
||||
NextAuctionIDKey = types.NextAuctionIDKey
|
||||
KeyAuctionBidDuration = types.KeyAuctionBidDuration
|
||||
KeyAuctionDuration = types.KeyAuctionDuration
|
||||
)
|
||||
|
||||
type (
|
||||
Auction = types.Auction
|
||||
BaseAuction = types.BaseAuction
|
||||
SurplusAuction = types.SurplusAuction
|
||||
DebtAuction = types.DebtAuction
|
||||
CollateralAuction = types.CollateralAuction
|
||||
WeightedAddresses = types.WeightedAddresses
|
||||
SupplyKeeper = types.SupplyKeeper
|
||||
GenesisAuctions = types.GenesisAuctions
|
||||
GenesisAuction = types.GenesisAuction
|
||||
GenesisState = types.GenesisState
|
||||
MsgPlaceBid = types.MsgPlaceBid
|
||||
Params = types.Params
|
||||
Keeper = keeper.Keeper
|
||||
)
|
120
x/auction/client/cli/query.go
Normal file
120
x/auction/client/cli/query.go
Normal file
@ -0,0 +1,120 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
// GetQueryCmd returns the cli query commands for this module
|
||||
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
// Group nameservice queries under a subcommand
|
||||
auctionQueryCmd := &cobra.Command{
|
||||
Use: "auction",
|
||||
Short: "Querying commands for the auction module",
|
||||
}
|
||||
|
||||
auctionQueryCmd.AddCommand(client.GetCommands(
|
||||
QueryGetAuctionCmd(queryRoute, cdc),
|
||||
QueryGetAuctionsCmd(queryRoute, cdc),
|
||||
QueryParamsCmd(queryRoute, cdc),
|
||||
)...)
|
||||
|
||||
return auctionQueryCmd
|
||||
}
|
||||
|
||||
// QueryGetAuctionCmd queries one auction in the store
|
||||
func QueryGetAuctionCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "auction [auction-id]",
|
||||
Short: "get a info about an auction",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Prepare params for querier
|
||||
id, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auction-id '%s' not a valid uint", args[0])
|
||||
}
|
||||
bz, err := cdc.MarshalJSON(types.QueryAuctionParams{
|
||||
AuctionID: id,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query
|
||||
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuction), bz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode and print results
|
||||
var auction types.Auction
|
||||
cdc.MustUnmarshalJSON(res, &auction)
|
||||
auctionWithPhase := types.NewAuctionWithPhase(auction)
|
||||
return cliCtx.PrintOutput(auctionWithPhase)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryGetAuctionsCmd queries the auctions in the store
|
||||
func QueryGetAuctionsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "auctions",
|
||||
Short: "get a list of active auctions",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Query
|
||||
res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetAuctions), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode and print results
|
||||
var auctions types.Auctions
|
||||
cdc.MustUnmarshalJSON(res, &auctions)
|
||||
|
||||
auctionsWithPhase := []types.AuctionWithPhase{} // using empty slice so json returns [] instead of null when there's no auctions
|
||||
for _, a := range auctions {
|
||||
auctionsWithPhase = append(auctionsWithPhase, types.NewAuctionWithPhase(a))
|
||||
}
|
||||
return cliCtx.PrintOutput(auctionsWithPhase)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryParamsCmd queries the auction module parameters
|
||||
func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "params",
|
||||
Short: "get the auction module parameters",
|
||||
Long: "Get the current global auction 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 out types.Params
|
||||
cdc.MustUnmarshalJSON(res, &out)
|
||||
return cliCtx.PrintOutput(out)
|
||||
},
|
||||
}
|
||||
}
|
69
x/auction/client/cli/tx.go
Normal file
69
x/auction/client/cli/tx.go
Normal file
@ -0,0 +1,69 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"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/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/auction/types"
|
||||
)
|
||||
|
||||
// GetTxCmd returns the transaction commands for this module
|
||||
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
auctionTxCmd := &cobra.Command{
|
||||
Use: "auction",
|
||||
Short: "auction transactions subcommands",
|
||||
}
|
||||
|
||||
auctionTxCmd.AddCommand(client.PostCommands(
|
||||
GetCmdPlaceBid(cdc),
|
||||
)...)
|
||||
|
||||
return auctionTxCmd
|
||||
}
|
||||
|
||||
// GetCmdPlaceBid cli command for placing bids on auctions
|
||||
func GetCmdPlaceBid(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "bid [auction-id] [amount]",
|
||||
Short: "place a bid on an auction",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Place a bid on any type of auction, updating the latest bid amount to [amount]. Collateral auctions must be bid up to their maxbid before entering reverse phase.
|
||||
|
||||
Example:
|
||||
$ %s tx %s bid 34 1000usdx --from myKeyName
|
||||
`, 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))
|
||||
|
||||
id, err := strconv.ParseUint(args[0], 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auction-id '%s' not a valid uint", args[0])
|
||||
}
|
||||
|
||||
amt, err := sdk.ParseCoin(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msg := types.NewMsgPlaceBid(id, cliCtx.GetFromAddress(), amt)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
119
x/auction/client/rest/query.go
Normal file
119
x/auction/client/rest/query.go
Normal file
@ -0,0 +1,119 @@
|
||||
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/auction/types"
|
||||
)
|
||||
|
||||
const restAuctionID = "auction-id"
|
||||
|
||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc(fmt.Sprintf("/%s/auctions", types.ModuleName), queryAuctionsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/auctions/{%s}", types.ModuleName, restAuctionID), queryAuctionHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), getParamsHandlerFn(cliCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
func queryAuctionHandlerFn(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[restAuctionID]) == 0 {
|
||||
err := fmt.Errorf("%s required but not specified", restAuctionID)
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
auctionID, ok := rest.ParseUint64OrReturnBadRequest(w, vars[restAuctionID])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
bz, err := cliCtx.Codec.MarshalJSON(types.QueryAuctionParams{AuctionID: auctionID})
|
||||
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.QueryGetAuction), bz)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Decode and return results
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
var auction types.Auction
|
||||
err = cliCtx.Codec.UnmarshalJSON(res, &auction)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
auctionWithPhase := types.NewAuctionWithPhase(auction)
|
||||
rest.PostProcessResponse(w, cliCtx, cliCtx.Codec.MustMarshalJSON(auctionWithPhase))
|
||||
}
|
||||
}
|
||||
|
||||
func queryAuctionsHandlerFn(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
|
||||
}
|
||||
|
||||
// Get all auctions
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("/custom/%s/%s", types.ModuleName, types.QueryGetAuctions), nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Decode and return results
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
var auctions types.Auctions
|
||||
err = cliCtx.Codec.UnmarshalJSON(res, &auctions)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
auctionsWithPhase := []types.AuctionWithPhase{} // using empty slice so json returns [] instead of null when there's no auctions
|
||||
for _, a := range auctions {
|
||||
auctionsWithPhase = append(auctionsWithPhase, types.NewAuctionWithPhase(a))
|
||||
}
|
||||
rest.PostProcessResponse(w, cliCtx, cliCtx.Codec.MustMarshalJSON(auctionsWithPhase))
|
||||
}
|
||||
}
|
||||
|
||||
func getParamsHandlerFn(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
|
||||
}
|
||||
// Get the params
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.ModuleName, types.QueryGetParams), nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// Decode and return results
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
13
x/auction/client/rest/rest.go
Normal file
13
x/auction/client/rest/rest.go
Normal file
@ -0,0 +1,13 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
)
|
||||
|
||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
registerQueryRoutes(cliCtx, r)
|
||||
registerTxRoutes(cliCtx, r)
|
||||
}
|
58
x/auction/client/rest/tx.go
Normal file
58
x/auction/client/rest/tx.go
Normal file
@ -0,0 +1,58 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"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"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/client/utils"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc(fmt.Sprintf("/%s/auctions/{%s}/bids", types.ModuleName, restAuctionID), bidHandlerFn(cliCtx)).Methods("POST")
|
||||
}
|
||||
|
||||
type placeBidReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req"`
|
||||
Amount sdk.Coin `json:"amount"`
|
||||
}
|
||||
|
||||
func bidHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Get auction ID from url
|
||||
auctionID, ok := rest.ParseUint64OrReturnBadRequest(w, mux.Vars(r)[restAuctionID])
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Get info from the http request body
|
||||
var req placeBidReq
|
||||
if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) {
|
||||
return
|
||||
}
|
||||
req.BaseReq = req.BaseReq.Sanitize()
|
||||
if !req.BaseReq.ValidateBasic(w) {
|
||||
return
|
||||
}
|
||||
bidderAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Create and return a StdTx
|
||||
msg := types.NewMsgPlaceBid(auctionID, bidderAddr, req.Amount)
|
||||
if err := msg.ValidateBasic(); err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseReq, []sdk.Msg{msg})
|
||||
}
|
||||
}
|
60
x/auction/genesis.go
Normal file
60
x/auction/genesis.go
Normal file
@ -0,0 +1,60 @@
|
||||
package auction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
// InitGenesis initializes the store state from a genesis state.
|
||||
func InitGenesis(ctx sdk.Context, keeper Keeper, supplyKeeper types.SupplyKeeper, gs GenesisState) {
|
||||
if err := gs.Validate(); err != nil {
|
||||
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
|
||||
}
|
||||
|
||||
keeper.SetNextAuctionID(ctx, gs.NextAuctionID)
|
||||
|
||||
keeper.SetParams(ctx, gs.Params)
|
||||
|
||||
totalAuctionCoins := sdk.NewCoins()
|
||||
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())
|
||||
}
|
||||
|
||||
// check if the module account exists
|
||||
moduleAcc := supplyKeeper.GetModuleAccount(ctx, ModuleName)
|
||||
if moduleAcc == nil {
|
||||
panic(fmt.Sprintf("%s module account has not been set", ModuleName))
|
||||
}
|
||||
// check module coins match auction coins
|
||||
// Note: Other sdk modules do not check this, instead just using the existing module account coins, or if zero, setting them.
|
||||
if !moduleAcc.GetCoins().IsEqual(totalAuctionCoins) {
|
||||
panic(fmt.Sprintf("total auction coins (%s) do not equal (%s) module account (%s) ", moduleAcc.GetCoins(), ModuleName, totalAuctionCoins))
|
||||
}
|
||||
}
|
||||
|
||||
// ExportGenesis returns a GenesisState for a given context and keeper.
|
||||
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
|
||||
nextAuctionID, err := keeper.GetNextAuctionID(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
params := keeper.GetParams(ctx)
|
||||
|
||||
genAuctions := GenesisAuctions{} // return empty list instead of nil if no auctions
|
||||
keeper.IterateAuctions(ctx, func(a Auction) bool {
|
||||
ga, ok := a.(types.GenesisAuction)
|
||||
if !ok {
|
||||
panic("could not convert stored auction to GenesisAuction type")
|
||||
}
|
||||
genAuctions = append(genAuctions, ga)
|
||||
return false
|
||||
})
|
||||
|
||||
return NewGenesisState(nextAuctionID, params, genAuctions)
|
||||
}
|
109
x/auction/genesis_test.go
Normal file
109
x/auction/genesis_test.go
Normal file
@ -0,0 +1,109 @@
|
||||
package auction_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
)
|
||||
|
||||
var testTime = time.Date(1998, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
var testAuction = auction.NewCollateralAuction(
|
||||
"seller",
|
||||
c("lotdenom", 10),
|
||||
testTime,
|
||||
c("biddenom", 1000),
|
||||
auction.WeightedAddresses{},
|
||||
c("debt", 1000),
|
||||
).WithID(3).(auction.GenesisAuction)
|
||||
|
||||
func TestInitGenesis(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
// setup keepers
|
||||
tApp := app.NewTestApp()
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
// create genesis
|
||||
gs := auction.NewGenesisState(
|
||||
10,
|
||||
auction.DefaultParams(),
|
||||
auction.GenesisAuctions{testAuction},
|
||||
)
|
||||
|
||||
// run init
|
||||
require.NotPanics(t, func() {
|
||||
auction.InitGenesis(ctx, keeper, tApp.GetSupplyKeeper(), gs)
|
||||
})
|
||||
|
||||
// check state is as expected
|
||||
actualID, err := keeper.GetNextAuctionID(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, gs.NextAuctionID, actualID)
|
||||
|
||||
require.Equal(t, gs.Params, keeper.GetParams(ctx))
|
||||
|
||||
// TODO is there a nicer way of comparing state?
|
||||
sort.Slice(gs.Auctions, func(i, j int) bool {
|
||||
return gs.Auctions[i].GetID() > gs.Auctions[j].GetID()
|
||||
})
|
||||
i := 0
|
||||
keeper.IterateAuctions(ctx, func(a auction.Auction) bool {
|
||||
require.Equal(t, gs.Auctions[i], a)
|
||||
i++
|
||||
return false
|
||||
})
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
// setup keepers
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
|
||||
// create invalid genesis
|
||||
gs := auction.NewGenesisState(
|
||||
0, // next id < testAuction ID
|
||||
auction.DefaultParams(),
|
||||
auction.GenesisAuctions{testAuction},
|
||||
)
|
||||
|
||||
// check init fails
|
||||
require.Panics(t, func() {
|
||||
auction.InitGenesis(ctx, tApp.GetAuctionKeeper(), tApp.GetSupplyKeeper(), gs)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestExportGenesis(t *testing.T) {
|
||||
t.Run("default", func(t *testing.T) {
|
||||
// setup state
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
tApp.InitializeFromGenesisStates()
|
||||
|
||||
// export
|
||||
gs := auction.ExportGenesis(ctx, tApp.GetAuctionKeeper())
|
||||
|
||||
// check state matches
|
||||
require.Equal(t, auction.DefaultGenesisState(), gs)
|
||||
})
|
||||
t.Run("one auction", func(t *testing.T) {
|
||||
// setup state
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
tApp.InitializeFromGenesisStates()
|
||||
tApp.GetAuctionKeeper().SetAuction(ctx, testAuction)
|
||||
|
||||
// export
|
||||
gs := auction.ExportGenesis(ctx, tApp.GetAuctionKeeper())
|
||||
|
||||
// check state matches
|
||||
expectedGenesisState := auction.DefaultGenesisState()
|
||||
expectedGenesisState.Auctions = append(expectedGenesisState.Auctions, testAuction)
|
||||
require.Equal(t, expectedGenesisState, gs)
|
||||
})
|
||||
}
|
43
x/auction/handler.go
Normal file
43
x/auction/handler.go
Normal file
@ -0,0 +1,43 @@
|
||||
package auction
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"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 {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgPlaceBid(ctx sdk.Context, keeper Keeper, msg MsgPlaceBid) sdk.Result {
|
||||
|
||||
err := keeper.PlaceBid(ctx, msg.AuctionID, msg.Bidder, msg.Amount)
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
sdk.EventTypeMessage,
|
||||
sdk.NewAttribute(sdk.AttributeKeyModule, types.AttributeValueCategory),
|
||||
sdk.NewAttribute(sdk.AttributeKeySender, msg.Bidder.String()),
|
||||
),
|
||||
)
|
||||
|
||||
return sdk.Result{
|
||||
Events: ctx.EventManager().Events(),
|
||||
}
|
||||
}
|
526
x/auction/keeper/auctions.go
Normal file
526
x/auction/keeper/auctions.go
Normal file
@ -0,0 +1,526 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"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) {
|
||||
|
||||
auction := types.NewSurplusAuction(
|
||||
seller,
|
||||
lot,
|
||||
bidDenom,
|
||||
types.DistantFuture)
|
||||
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
auctionID, err := k.StoreNewAuction(ctx, auction)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionStart,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionType, auction.GetType()),
|
||||
sdk.NewAttribute(types.AttributeKeyBidDenom, auction.Bid.Denom),
|
||||
sdk.NewAttribute(types.AttributeKeyLotDenom, auction.Lot.Denom),
|
||||
),
|
||||
)
|
||||
return auctionID, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
auction := types.NewDebtAuction(
|
||||
buyer,
|
||||
bid,
|
||||
initialLot,
|
||||
types.DistantFuture,
|
||||
debt)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, buyer, types.ModuleName, sdk.NewCoins(debt))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
auctionID, err := k.StoreNewAuction(ctx, auction)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionStart,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionType, auction.GetType()),
|
||||
sdk.NewAttribute(types.AttributeKeyBidDenom, auction.Bid.Denom),
|
||||
sdk.NewAttribute(types.AttributeKeyLotDenom, auction.Lot.Denom),
|
||||
),
|
||||
)
|
||||
return auctionID, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
weightedAddresses, err := types.NewWeightedAddresses(lotReturnAddrs, lotReturnWeights)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
auction := types.NewCollateralAuction(
|
||||
seller,
|
||||
lot,
|
||||
types.DistantFuture,
|
||||
maxBid,
|
||||
weightedAddresses,
|
||||
debt)
|
||||
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(lot))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, seller, types.ModuleName, sdk.NewCoins(debt))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
auctionID, err := k.StoreNewAuction(ctx, auction)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionStart,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionType, auction.GetType()),
|
||||
sdk.NewAttribute(types.AttributeKeyBidDenom, auction.Bid.Denom),
|
||||
sdk.NewAttribute(types.AttributeKeyLotDenom, auction.Lot.Denom),
|
||||
),
|
||||
)
|
||||
return auctionID, nil
|
||||
}
|
||||
|
||||
// PlaceBid places a bid on any auction.
|
||||
func (k Keeper) PlaceBid(ctx sdk.Context, auctionID uint64, bidder sdk.AccAddress, newAmount sdk.Coin) sdk.Error {
|
||||
|
||||
auction, found := k.GetAuction(ctx, auctionID)
|
||||
if !found {
|
||||
return types.ErrAuctionNotFound(k.codespace, auctionID)
|
||||
}
|
||||
|
||||
// validation common to all auctions
|
||||
if ctx.BlockTime().After(auction.GetEndTime()) {
|
||||
return types.ErrAuctionHasExpired(k.codespace, auctionID)
|
||||
}
|
||||
|
||||
// move coins and return updated auction
|
||||
var err sdk.Error
|
||||
var updatedAuction types.Auction
|
||||
switch a := auction.(type) {
|
||||
case types.SurplusAuction:
|
||||
if updatedAuction, err = k.PlaceBidSurplus(ctx, a, bidder, newAmount); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.DebtAuction:
|
||||
if updatedAuction, err = k.PlaceBidDebt(ctx, a, bidder, newAmount); err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
k.SetAuction(ctx, updatedAuction)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Validate new bid
|
||||
if bid.Denom != a.Bid.Denom {
|
||||
return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom)
|
||||
}
|
||||
if !a.Bid.IsLT(bid) {
|
||||
return a, types.ErrBidTooSmall(k.codespace, bid, a.Bid)
|
||||
}
|
||||
|
||||
// New bidder pays back old bidder
|
||||
// Catch edge cases of a bidder replacing their own bid, or the amount being zero (sending zero coins produces meaningless send events).
|
||||
if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() {
|
||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
}
|
||||
// Increase in bid is burned
|
||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid)))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
err = k.supplyKeeper.BurnCoins(ctx, a.Initiator, sdk.NewCoins(bid.Sub(a.Bid)))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
|
||||
// Update Auction
|
||||
a.Bidder = bidder
|
||||
a.Bid = bid
|
||||
if !a.HasReceivedBids {
|
||||
a.MaxEndTime = ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration) // set maximum ending time on receipt of first bid
|
||||
}
|
||||
a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout, up to MaxEndTime
|
||||
a.HasReceivedBids = true
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionBid,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", a.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyBidder, a.Bidder.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyBidAmount, a.Bid.Amount.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyEndTime, fmt.Sprintf("%d", a.EndTime.Unix())),
|
||||
),
|
||||
)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Validate new bid
|
||||
if bid.Denom != a.Bid.Denom {
|
||||
return a, types.ErrInvalidBidDenom(k.codespace, bid.Denom, a.Bid.Denom)
|
||||
}
|
||||
if a.IsReversePhase() {
|
||||
return a, types.ErrCollateralAuctionIsInReversePhase(k.codespace, a.ID)
|
||||
}
|
||||
if !a.Bid.IsLT(bid) {
|
||||
return a, types.ErrBidTooSmall(k.codespace, bid, a.Bid)
|
||||
}
|
||||
if a.MaxBid.IsLT(bid) {
|
||||
return a, types.ErrBidTooLarge(k.codespace, bid, a.MaxBid)
|
||||
}
|
||||
|
||||
// New bidder pays back old bidder
|
||||
// Catch edge cases of a bidder replacing their own bid, and the amount being zero (sending zero coins produces meaningless send events).
|
||||
if !bidder.Equals(a.Bidder) && !a.Bid.IsZero() {
|
||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
}
|
||||
// Increase in bid sent to auction initiator
|
||||
bidIncrement := bid.Sub(a.Bid)
|
||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, a.Initiator, sdk.NewCoins(bidIncrement))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
// Debt coins are sent to liquidator (until there is no CorrespondingDebt left). Amount sent is equal to bidIncrement (or whatever is left if < bidIncrement).
|
||||
if a.CorrespondingDebt.IsPositive() {
|
||||
|
||||
debtAmountToReturn := sdk.MinInt(bidIncrement.Amount, a.CorrespondingDebt.Amount)
|
||||
debtToReturn := sdk.NewCoin(a.CorrespondingDebt.Denom, debtAmountToReturn)
|
||||
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(debtToReturn))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
|
||||
}
|
||||
|
||||
// Update Auction
|
||||
a.Bidder = bidder
|
||||
a.Bid = bid
|
||||
if !a.HasReceivedBids {
|
||||
a.MaxEndTime = ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration) // set maximum ending time on receipt of first bid
|
||||
}
|
||||
a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout, up to MaxEndTime
|
||||
a.HasReceivedBids = true
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionBid,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", a.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyBidder, a.Bidder.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyBidAmount, a.Bid.Amount.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyEndTime, fmt.Sprintf("%d", a.EndTime.Unix())),
|
||||
),
|
||||
)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Validate new bid
|
||||
if lot.Denom != a.Lot.Denom {
|
||||
return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom)
|
||||
}
|
||||
if !a.IsReversePhase() {
|
||||
return a, types.ErrCollateralAuctionIsInForwardPhase(k.codespace, a.ID)
|
||||
}
|
||||
if !lot.IsLT(a.Lot) {
|
||||
return a, types.ErrLotTooLarge(k.codespace, lot, a.Lot)
|
||||
}
|
||||
|
||||
// New bidder pays back old bidder
|
||||
// Catch edge cases of a bidder replacing their own bid
|
||||
if !bidder.Equals(a.Bidder) {
|
||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
}
|
||||
// Decrease in lot is sent to weighted addresses (normally the CDP depositors)
|
||||
// TODO paying out rateably to cdp depositors is vulnerable to errors compounding over multiple bids - check this can't be gamed.
|
||||
lotPayouts, err := splitCoinIntoWeightedBuckets(a.Lot.Sub(lot), a.LotReturns.Weights)
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
for i, payout := range lotPayouts {
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.LotReturns.Addresses[i], sdk.NewCoins(payout))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
}
|
||||
|
||||
// Update Auction
|
||||
a.Bidder = bidder
|
||||
a.Lot = lot
|
||||
if !a.HasReceivedBids {
|
||||
a.MaxEndTime = ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration) // set maximum ending time on receipt of first bid
|
||||
}
|
||||
a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout, up to MaxEndTime
|
||||
a.HasReceivedBids = true
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionBid,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", a.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyBidder, a.Bidder.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyLotAmount, a.Lot.Amount.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyEndTime, fmt.Sprintf("%d", a.EndTime.Unix())),
|
||||
),
|
||||
)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Validate new bid
|
||||
if lot.Denom != a.Lot.Denom {
|
||||
return a, types.ErrInvalidLotDenom(k.codespace, lot.Denom, a.Lot.Denom)
|
||||
}
|
||||
if !lot.IsLT(a.Lot) {
|
||||
return a, types.ErrLotTooLarge(k.codespace, lot, a.Lot)
|
||||
}
|
||||
|
||||
// New bidder pays back old bidder
|
||||
// Catch edge cases of a bidder replacing their own bid
|
||||
if !bidder.Equals(a.Bidder) {
|
||||
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Bid))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
}
|
||||
// Debt coins are sent to liquidator the first time a bid is placed. Amount sent is equal to min of Bid and amount of debt.
|
||||
if a.Bidder.Equals(supply.NewModuleAddress(a.Initiator)) {
|
||||
|
||||
debtAmountToReturn := sdk.MinInt(a.Bid.Amount, a.CorrespondingDebt.Amount)
|
||||
debtToReturn := sdk.NewCoin(a.CorrespondingDebt.Denom, debtAmountToReturn)
|
||||
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(debtToReturn))
|
||||
if err != nil {
|
||||
return a, err
|
||||
}
|
||||
a.CorrespondingDebt = a.CorrespondingDebt.Sub(debtToReturn) // debtToReturn will always be ≤ a.CorrespondingDebt from the MinInt above
|
||||
}
|
||||
|
||||
// Update Auction
|
||||
a.Bidder = bidder
|
||||
a.Lot = lot
|
||||
if !a.HasReceivedBids {
|
||||
a.MaxEndTime = ctx.BlockTime().Add(k.GetParams(ctx).MaxAuctionDuration) // set maximum ending time on receipt of first bid
|
||||
}
|
||||
a.EndTime = earliestTime(ctx.BlockTime().Add(k.GetParams(ctx).BidDuration), a.MaxEndTime) // increment timeout, up to MaxEndTime
|
||||
a.HasReceivedBids = true
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionBid,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", a.ID)),
|
||||
sdk.NewAttribute(types.AttributeKeyBidder, a.Bidder.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyLotAmount, a.Lot.Amount.String()),
|
||||
sdk.NewAttribute(types.AttributeKeyEndTime, fmt.Sprintf("%d", a.EndTime.Unix())),
|
||||
),
|
||||
)
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
// CloseAuction closes an auction and distributes funds to the highest bidder.
|
||||
func (k Keeper) CloseAuction(ctx sdk.Context, auctionID uint64) sdk.Error {
|
||||
|
||||
auction, found := k.GetAuction(ctx, auctionID)
|
||||
if !found {
|
||||
return types.ErrAuctionNotFound(k.codespace, auctionID)
|
||||
}
|
||||
|
||||
if ctx.BlockTime().Before(auction.GetEndTime()) {
|
||||
return types.ErrAuctionHasNotExpired(k.codespace, ctx.BlockTime(), auction.GetEndTime())
|
||||
}
|
||||
|
||||
// payout to the last bidder
|
||||
switch auc := auction.(type) {
|
||||
case types.SurplusAuction:
|
||||
if err := k.PayoutSurplusAuction(ctx, auc); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.DebtAuction:
|
||||
if err := k.PayoutDebtAuction(ctx, auc); err != nil {
|
||||
return err
|
||||
}
|
||||
case types.CollateralAuction:
|
||||
if err := k.PayoutCollateralAuction(ctx, auc); err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
return types.ErrUnrecognizedAuctionType(k.codespace)
|
||||
}
|
||||
|
||||
k.DeleteAuction(ctx, auctionID)
|
||||
|
||||
ctx.EventManager().EmitEvent(
|
||||
sdk.NewEvent(
|
||||
types.EventTypeAuctionClose,
|
||||
sdk.NewAttribute(types.AttributeKeyAuctionID, fmt.Sprintf("%d", auction.GetID())),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
err := k.supplyKeeper.MintCoins(ctx, a.Initiator, sdk.NewCoins(a.Lot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, a.Initiator, a.Bidder, sdk.NewCoins(a.Lot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.CorrespondingDebt.IsPositive() {
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(a.CorrespondingDebt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PayoutSurplusAuction pays out the proceeds for a surplus auction.
|
||||
func (k Keeper) PayoutSurplusAuction(ctx sdk.Context, a types.SurplusAuction) sdk.Error {
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PayoutCollateralAuction pays out the proceeds for a collateral auction.
|
||||
func (k Keeper) PayoutCollateralAuction(ctx sdk.Context, a types.CollateralAuction) sdk.Error {
|
||||
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, a.Bidder, sdk.NewCoins(a.Lot))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if a.CorrespondingDebt.IsPositive() {
|
||||
err = k.supplyKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, a.Initiator, sdk.NewCoins(a.CorrespondingDebt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var expiredAuctions []uint64
|
||||
k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool {
|
||||
expiredAuctions = append(expiredAuctions, id)
|
||||
return false
|
||||
})
|
||||
// Note: iteration and auction closing are in separate loops as db should not be modified during iteration // TODO is this correct? gov modifies during iteration
|
||||
for _, id := range expiredAuctions {
|
||||
if err := k.CloseAuction(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// earliestTime returns the earliest of two times.
|
||||
func earliestTime(t1, t2 time.Time) time.Time {
|
||||
if t1.Before(t2) {
|
||||
return t1
|
||||
}
|
||||
return t2 // also returned if times are equal
|
||||
}
|
||||
|
||||
// splitCoinIntoWeightedBuckets divides up some amount of coins according to some weights.
|
||||
func splitCoinIntoWeightedBuckets(coin sdk.Coin, buckets []sdk.Int) ([]sdk.Coin, sdk.Error) {
|
||||
for _, bucket := range buckets {
|
||||
if bucket.IsNegative() {
|
||||
return nil, sdk.ErrInternal("cannot split coin into bucket with negative weight")
|
||||
}
|
||||
}
|
||||
amounts := splitIntIntoWeightedBuckets(coin.Amount, buckets)
|
||||
result := make([]sdk.Coin, len(amounts))
|
||||
for i, a := range amounts {
|
||||
result[i] = sdk.NewCoin(coin.Denom, a)
|
||||
}
|
||||
return result, nil
|
||||
}
|
409
x/auction/keeper/auctions_test.go
Normal file
409
x/auction/keeper/auctions_test.go
Normal file
@ -0,0 +1,409 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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/supply"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
)
|
||||
|
||||
func TestSurplusAuctionBasic(t *testing.T) {
|
||||
// Setup
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
buyer := addrs[0]
|
||||
sellerModName := cdp.LiquidatorMacc
|
||||
sellerAddr := supply.NewModuleAddress(sellerModName)
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
|
||||
sellerAcc := supply.NewEmptyModuleAccount(sellerModName, supply.Burner) // forward auctions burn proceeds
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Create an auction (lot: 20 token1, initialBid: 0 token2)
|
||||
auctionID, err := keeper.StartSurplusAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom
|
||||
require.NoError(t, err)
|
||||
// Check seller's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100)))
|
||||
|
||||
// PlaceBid (bid: 10 token, lot: same as starting)
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 10)))
|
||||
// Check buyer's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90)))
|
||||
// Check seller's coins have not increased (because proceeds are burned)
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100)))
|
||||
|
||||
// increment bid same bidder
|
||||
err = keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 20))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Close auction at just at auction expiry time
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration))
|
||||
require.NoError(t, keeper.CloseAuction(ctx, auctionID))
|
||||
// Check buyer's coins increased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 80)))
|
||||
}
|
||||
|
||||
func TestDebtAuctionBasic(t *testing.T) {
|
||||
// Setup
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
seller := addrs[0]
|
||||
buyerModName := cdp.LiquidatorMacc
|
||||
buyerAddr := supply.NewModuleAddress(buyerModName)
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
|
||||
buyerAcc := supply.NewEmptyModuleAccount(buyerModName, supply.Minter) // reverse auctions mint payout
|
||||
require.NoError(t, buyerAcc.SetCoins(cs(c("debt", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(seller, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
buyerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Start auction
|
||||
auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 20), c("token2", 99999), c("debt", 20))
|
||||
require.NoError(t, err)
|
||||
// Check buyer's coins have not decreased (except for debt), as lot is minted at the end
|
||||
tApp.CheckBalance(t, ctx, buyerAddr, cs(c("debt", 80)))
|
||||
|
||||
// Place a bid
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, seller, c("token2", 10)))
|
||||
// Check seller's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100)))
|
||||
// Check buyer's coins have increased
|
||||
tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 20), c("debt", 100)))
|
||||
|
||||
// Close auction at just after auction expiry
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration))
|
||||
require.NoError(t, keeper.CloseAuction(ctx, auctionID))
|
||||
// Check seller's coins increased
|
||||
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110)))
|
||||
}
|
||||
|
||||
func TestDebtAuctionDebtRemaining(t *testing.T) {
|
||||
// Setup
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
seller := addrs[0]
|
||||
buyerModName := cdp.LiquidatorMacc
|
||||
buyerAddr := supply.NewModuleAddress(buyerModName)
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
|
||||
buyerAcc := supply.NewEmptyModuleAccount(buyerModName, supply.Minter) // reverse auctions mint payout
|
||||
require.NoError(t, buyerAcc.SetCoins(cs(c("debt", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(seller, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
buyerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Start auction
|
||||
auctionID, err := keeper.StartDebtAuction(ctx, buyerModName, c("token1", 10), c("token2", 99999), c("debt", 20))
|
||||
require.NoError(t, err)
|
||||
// Check buyer's coins have not decreased (except for debt), as lot is minted at the end
|
||||
tApp.CheckBalance(t, ctx, buyerAddr, cs(c("debt", 80)))
|
||||
|
||||
// Place a bid
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, seller, c("token2", 10)))
|
||||
// Check seller's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 90), c("token2", 100)))
|
||||
// Check buyer's coins have increased
|
||||
tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 10), c("debt", 90)))
|
||||
|
||||
// Close auction at just after auction expiry
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration))
|
||||
require.NoError(t, keeper.CloseAuction(ctx, auctionID))
|
||||
// Check seller's coins increased
|
||||
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 90), c("token2", 110)))
|
||||
// check that debt has increased due to corresponding debt being greater than bid
|
||||
tApp.CheckBalance(t, ctx, buyerAddr, cs(c("token1", 10), c("debt", 100)))
|
||||
}
|
||||
|
||||
func TestCollateralAuctionBasic(t *testing.T) {
|
||||
// Setup
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
||||
buyer := addrs[0]
|
||||
returnAddrs := addrs[1:]
|
||||
returnWeights := is(30, 20, 10)
|
||||
sellerModName := cdp.LiquidatorMacc
|
||||
sellerAddr := supply.NewModuleAddress(sellerModName)
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
sellerAcc := supply.NewEmptyModuleAccount(sellerModName)
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
auth.NewBaseAccount(returnAddrs[0], cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
auth.NewBaseAccount(returnAddrs[1], cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
auth.NewBaseAccount(returnAddrs[2], cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Start auction
|
||||
auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40))
|
||||
require.NoError(t, err)
|
||||
// Check seller's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100), c("debt", 60)))
|
||||
|
||||
// Place a forward bid
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 10)))
|
||||
// Check bidder's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90)))
|
||||
// Check seller's coins have increased
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110), c("debt", 70)))
|
||||
// Check return addresses have not received coins
|
||||
for _, ra := range returnAddrs {
|
||||
tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100)))
|
||||
}
|
||||
|
||||
// Place a reverse bid
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 50))) // first bid up to max bid to switch phases
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token1", 15)))
|
||||
// Check bidder's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50)))
|
||||
// Check seller's coins have increased
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 150), c("debt", 100)))
|
||||
// Check return addresses have received coins
|
||||
tApp.CheckBalance(t, ctx, returnAddrs[0], cs(c("token1", 102), c("token2", 100)))
|
||||
tApp.CheckBalance(t, ctx, returnAddrs[1], cs(c("token1", 102), c("token2", 100)))
|
||||
tApp.CheckBalance(t, ctx, returnAddrs[2], cs(c("token1", 101), c("token2", 100)))
|
||||
|
||||
// Close auction at just after auction expiry
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration))
|
||||
require.NoError(t, keeper.CloseAuction(ctx, auctionID))
|
||||
// Check buyer's coins increased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50)))
|
||||
}
|
||||
|
||||
func TestCollateralAuctionDebtRemaining(t *testing.T) {
|
||||
// Setup
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(4)
|
||||
buyer := addrs[0]
|
||||
returnAddrs := addrs[1:]
|
||||
returnWeights := is(30, 20, 10)
|
||||
sellerModName := cdp.LiquidatorMacc
|
||||
sellerAddr := supply.NewModuleAddress(sellerModName)
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
sellerAcc := supply.NewEmptyModuleAccount(sellerModName)
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100), c("debt", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
auth.NewBaseAccount(returnAddrs[0], cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
auth.NewBaseAccount(returnAddrs[1], cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
auth.NewBaseAccount(returnAddrs[2], cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Start auction
|
||||
auctionID, err := keeper.StartCollateralAuction(ctx, sellerModName, c("token1", 20), c("token2", 50), returnAddrs, returnWeights, c("debt", 40))
|
||||
require.NoError(t, err)
|
||||
// Check seller's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 100), c("debt", 60)))
|
||||
|
||||
// Place a forward bid
|
||||
require.NoError(t, keeper.PlaceBid(ctx, auctionID, buyer, c("token2", 10)))
|
||||
// Check bidder's coins have decreased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90)))
|
||||
// Check seller's coins have increased
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110), c("debt", 70)))
|
||||
// Check return addresses have not received coins
|
||||
for _, ra := range returnAddrs {
|
||||
tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100)))
|
||||
}
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultBidDuration))
|
||||
require.NoError(t, keeper.CloseAuction(ctx, auctionID))
|
||||
|
||||
// check that buyers coins have increased
|
||||
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90)))
|
||||
// Check return addresses have not received coins
|
||||
for _, ra := range returnAddrs {
|
||||
tApp.CheckBalance(t, ctx, ra, cs(c("token1", 100), c("token2", 100)))
|
||||
}
|
||||
// check that token2 has increased by 10, debt by 40, for a net debt increase of 30 debt
|
||||
tApp.CheckBalance(t, ctx, sellerAddr, cs(c("token1", 80), c("token2", 110), c("debt", 100)))
|
||||
}
|
||||
|
||||
func TestStartSurplusAuction(t *testing.T) {
|
||||
someTime := time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
type args struct {
|
||||
seller string
|
||||
lot sdk.Coin
|
||||
bidDenom string
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
blockTime time.Time
|
||||
args args
|
||||
expectPass bool
|
||||
}{
|
||||
{
|
||||
"normal",
|
||||
someTime,
|
||||
args{cdp.LiquidatorMacc, c("stable", 10), "gov"},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"no module account",
|
||||
someTime,
|
||||
args{"nonExistentModule", c("stable", 10), "gov"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"not enough coins",
|
||||
someTime,
|
||||
args{cdp.LiquidatorMacc, c("stable", 101), "gov"},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"incorrect denom",
|
||||
someTime,
|
||||
args{cdp.LiquidatorMacc, c("notacoin", 10), "gov"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// setup
|
||||
initialLiquidatorCoins := cs(c("stable", 100))
|
||||
tApp := app.NewTestApp()
|
||||
|
||||
liqAcc := supply.NewEmptyModuleAccount(cdp.LiquidatorMacc, supply.Burner)
|
||||
require.NoError(t, liqAcc.SetCoins(initialLiquidatorCoins))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{liqAcc}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{}).WithBlockTime(tc.blockTime)
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// run function under test
|
||||
id, err := keeper.StartSurplusAuction(ctx, tc.args.seller, tc.args.lot, tc.args.bidDenom)
|
||||
|
||||
// check
|
||||
sk := tApp.GetSupplyKeeper()
|
||||
liquidatorCoins := sk.GetModuleAccount(ctx, cdp.LiquidatorMacc).GetCoins()
|
||||
actualAuc, found := keeper.GetAuction(ctx, id)
|
||||
|
||||
if tc.expectPass {
|
||||
require.NoError(t, err)
|
||||
// check coins moved
|
||||
require.Equal(t, initialLiquidatorCoins.Sub(cs(tc.args.lot)), liquidatorCoins)
|
||||
// check auction in store and is correct
|
||||
require.True(t, found)
|
||||
expectedAuction := types.Auction(types.SurplusAuction{BaseAuction: types.BaseAuction{
|
||||
ID: id,
|
||||
Initiator: tc.args.seller,
|
||||
Lot: tc.args.lot,
|
||||
Bidder: nil,
|
||||
Bid: c(tc.args.bidDenom, 0),
|
||||
HasReceivedBids: false,
|
||||
EndTime: types.DistantFuture,
|
||||
MaxEndTime: types.DistantFuture,
|
||||
}})
|
||||
require.Equal(t, expectedAuction, actualAuc)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
// check coins not moved
|
||||
require.Equal(t, initialLiquidatorCoins, liquidatorCoins)
|
||||
// check auction not in store
|
||||
require.False(t, found)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseAuction(t *testing.T) {
|
||||
// Set up
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
buyer := addrs[0]
|
||||
sellerModName := cdp.LiquidatorMacc
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
|
||||
sellerAcc := supply.NewEmptyModuleAccount(sellerModName, supply.Burner) // forward auctions burn proceeds
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Create an auction (lot: 20 token1, initialBid: 0 token2)
|
||||
id, err := keeper.StartSurplusAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom
|
||||
require.NoError(t, err)
|
||||
|
||||
// Attempt to close the auction before EndTime
|
||||
require.Error(t, keeper.CloseAuction(ctx, id))
|
||||
|
||||
// Attempt to close auction that does not exist
|
||||
require.Error(t, keeper.CloseAuction(ctx, 999))
|
||||
}
|
||||
|
||||
func TestCloseExpiredAuctions(t *testing.T) {
|
||||
// Set up
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
buyer := addrs[0]
|
||||
sellerModName := "liquidator"
|
||||
|
||||
tApp := app.NewTestApp()
|
||||
|
||||
sellerAcc := supply.NewEmptyModuleAccount(sellerModName, supply.Burner) // forward auctions burn proceeds
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 100), c("token2", 100))))
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 100), c("token2", 100)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Start auction 1
|
||||
_, err := keeper.StartSurplusAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom
|
||||
require.NoError(t, err)
|
||||
|
||||
// Start auction 2
|
||||
_, err = keeper.StartSurplusAuction(ctx, sellerModName, c("token1", 20), "token2") // lot, bid denom
|
||||
require.NoError(t, err)
|
||||
|
||||
// Fast forward the block time
|
||||
ctx = ctx.WithBlockTime(ctx.BlockTime().Add(types.DefaultMaxAuctionDuration).Add(1))
|
||||
|
||||
// Close expired auctions
|
||||
err = keeper.CloseExpiredAuctions(ctx)
|
||||
require.NoError(t, err)
|
||||
}
|
348
x/auction/keeper/bidding_test.go
Normal file
348
x/auction/keeper/bidding_test.go
Normal file
@ -0,0 +1,348 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
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/supply"
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
type AuctionType int
|
||||
|
||||
const (
|
||||
Invalid AuctionType = 0
|
||||
Surplus AuctionType = 1
|
||||
Debt AuctionType = 2
|
||||
CollateralPhase1 AuctionType = 3
|
||||
CollateralPhase2 AuctionType = 4
|
||||
)
|
||||
|
||||
func TestAuctionBidding(t *testing.T) {
|
||||
someTime := time.Date(0001, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(5)
|
||||
buyer := addrs[0]
|
||||
secondBuyer := addrs[1]
|
||||
modName := "liquidator"
|
||||
collateralAddrs := addrs[2:]
|
||||
collateralWeights := is(30, 20, 10)
|
||||
|
||||
type auctionArgs struct {
|
||||
auctionType AuctionType
|
||||
seller string
|
||||
lot sdk.Coin
|
||||
bid sdk.Coin
|
||||
debt sdk.Coin
|
||||
addresses []sdk.AccAddress
|
||||
weights []sdk.Int
|
||||
}
|
||||
|
||||
type bidArgs struct {
|
||||
bidder sdk.AccAddress
|
||||
amount sdk.Coin
|
||||
secondBidder sdk.AccAddress
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
auctionArgs auctionArgs
|
||||
bidArgs bidArgs
|
||||
expectedError sdk.CodeType
|
||||
expectedEndTime time.Time
|
||||
expectedBidder sdk.AccAddress
|
||||
expectedBid sdk.Coin
|
||||
expectpass bool
|
||||
}{
|
||||
{
|
||||
"basic: auction doesn't exist",
|
||||
auctionArgs{Surplus, "", c("token1", 1), c("token2", 1), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("token2", 10), nil},
|
||||
types.CodeAuctionNotFound,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"surplus: normal",
|
||||
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("token2", 10), nil},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"surplus: second bidder",
|
||||
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("token2", 10), secondBuyer},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
secondBuyer,
|
||||
c("token2", 11),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"surplus: invalid bid denom",
|
||||
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("badtoken", 10), nil},
|
||||
types.CodeInvalidBidDenom,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"surplus: invalid bid (equal)",
|
||||
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 0), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("token2", 0), nil},
|
||||
types.CodeBidTooSmall,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"debt: normal",
|
||||
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
|
||||
bidArgs{buyer, c("token1", 10), nil},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 100),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"debt: second bidder",
|
||||
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
|
||||
bidArgs{buyer, c("token1", 10), secondBuyer},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
secondBuyer,
|
||||
c("token2", 100),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"debt: invalid lot denom",
|
||||
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}}, // initial bid, lot
|
||||
bidArgs{buyer, c("badtoken", 10), nil},
|
||||
types.CodeInvalidLotDenom,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token1", 20),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"debt: invalid lot size (larger)",
|
||||
auctionArgs{Debt, modName, c("token1", 20), c("token2", 100), c("debt", 20), []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("token1", 21), nil},
|
||||
types.CodeLotTooLarge,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token1", 20),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"collateral [forward]: normal",
|
||||
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token2", 10), nil},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"collateral [forward]: second bidder",
|
||||
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token2", 10), secondBuyer},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
secondBuyer,
|
||||
c("token2", 11),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"collateral [forward]: invalid bid denom",
|
||||
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("badtoken", 10), nil},
|
||||
types.CodeInvalidBidDenom,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"collateral [forward]: invalid bid size (smaller)",
|
||||
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token2", 0), nil}, // lot, bid
|
||||
types.CodeBidTooSmall,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"collateral [forward]: invalid bid size (greater than max)",
|
||||
auctionArgs{CollateralPhase1, modName, c("token1", 20), c("token2", 100), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token2", 101), nil}, // lot, bid
|
||||
types.CodeBidTooLarge,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"collateral [reverse]: normal",
|
||||
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token1", 15), nil},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 50),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"collateral [reverse]: second bidder",
|
||||
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token1", 15), secondBuyer},
|
||||
sdk.CodeType(0),
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
secondBuyer,
|
||||
c("token2", 50),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"collateral [reverse]: invalid lot denom",
|
||||
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("badtoken", 15), nil},
|
||||
types.CodeInvalidLotDenom,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 50),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"collateral [reverse]: invalid lot size (equal)",
|
||||
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token1", 20), nil},
|
||||
types.CodeLotTooLarge,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 50),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"collateral [reverse]: invalid lot size (greater)",
|
||||
auctionArgs{CollateralPhase2, modName, c("token1", 20), c("token2", 50), c("debt", 50), collateralAddrs, collateralWeights}, // lot, max bid
|
||||
bidArgs{buyer, c("token1", 21), nil},
|
||||
types.CodeLotTooLarge,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 50),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"basic: closed auction",
|
||||
auctionArgs{Surplus, modName, c("token1", 100), c("token2", 10), sdk.Coin{}, []sdk.AccAddress{}, []sdk.Int{}},
|
||||
bidArgs{buyer, c("token2", 10), nil},
|
||||
types.CodeAuctionHasExpired,
|
||||
someTime.Add(types.DefaultBidDuration),
|
||||
buyer,
|
||||
c("token2", 10),
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Setup test
|
||||
tApp := app.NewTestApp()
|
||||
// Set up seller account
|
||||
sellerAcc := supply.NewEmptyModuleAccount(modName, supply.Minter, supply.Burner)
|
||||
require.NoError(t, sellerAcc.SetCoins(cs(c("token1", 1000), c("token2", 1000), c("debt", 1000))))
|
||||
// Initialize genesis accounts
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 1000), c("token2", 1000)), nil, 0, 0),
|
||||
auth.NewBaseAccount(secondBuyer, cs(c("token1", 1000), c("token2", 1000)), nil, 0, 0),
|
||||
auth.NewBaseAccount(collateralAddrs[0], cs(c("token1", 1000), c("token2", 1000)), nil, 0, 0),
|
||||
auth.NewBaseAccount(collateralAddrs[1], cs(c("token1", 1000), c("token2", 1000)), nil, 0, 0),
|
||||
auth.NewBaseAccount(collateralAddrs[2], cs(c("token1", 1000), c("token2", 1000)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
ctx := tApp.NewContext(false, abci.Header{})
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
|
||||
// Start Auction
|
||||
var id uint64
|
||||
var err sdk.Error
|
||||
switch tc.auctionArgs.auctionType {
|
||||
case Surplus:
|
||||
id, _ = 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)
|
||||
case CollateralPhase1, CollateralPhase2:
|
||||
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
|
||||
// Move CollateralAuction to debt phase by placing max bid
|
||||
if tc.auctionArgs.auctionType == CollateralPhase2 {
|
||||
err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.auctionArgs.bid)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
// Close the auction early to test late bidding (if applicable)
|
||||
if strings.Contains(tc.name, "closed") {
|
||||
ctx = ctx.WithBlockTime(types.DistantFuture.Add(1))
|
||||
}
|
||||
|
||||
// Place bid on auction
|
||||
err = keeper.PlaceBid(ctx, id, tc.bidArgs.bidder, tc.bidArgs.amount)
|
||||
|
||||
// Place second bid from new bidder
|
||||
if tc.bidArgs.secondBidder != nil {
|
||||
// Set bid increase/decrease based on auction type, phase
|
||||
var secondBid sdk.Coin
|
||||
switch tc.auctionArgs.auctionType {
|
||||
case Surplus, CollateralPhase1:
|
||||
secondBid = tc.bidArgs.amount.Add(c(tc.bidArgs.amount.Denom, 1))
|
||||
case Debt, CollateralPhase2:
|
||||
secondBid = tc.bidArgs.amount.Sub(c(tc.bidArgs.amount.Denom, 1))
|
||||
default:
|
||||
t.Fail()
|
||||
}
|
||||
// Place the second bid
|
||||
err2 := keeper.PlaceBid(ctx, id, tc.bidArgs.secondBidder, secondBid)
|
||||
require.NoError(t, err2)
|
||||
}
|
||||
|
||||
// Check success/failure
|
||||
if tc.expectpass {
|
||||
require.Nil(t, err)
|
||||
// Get auction from store
|
||||
auction, found := keeper.GetAuction(ctx, id)
|
||||
require.True(t, found)
|
||||
// Check auction values
|
||||
require.Equal(t, modName, auction.GetInitiator())
|
||||
require.Equal(t, tc.expectedBidder, auction.GetBidder())
|
||||
require.Equal(t, tc.expectedBid, auction.GetBid())
|
||||
require.Equal(t, tc.expectedEndTime, auction.GetEndTime())
|
||||
} else {
|
||||
// Check expected error code type
|
||||
require.Equal(t, tc.expectedError, err.Result().Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
24
x/auction/keeper/integration_test.go
Normal file
24
x/auction/keeper/integration_test.go
Normal file
@ -0,0 +1,24 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
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/kava-labs/kava/app"
|
||||
)
|
||||
|
||||
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 i(n int64) sdk.Int { return sdk.NewInt(n) }
|
||||
func is(ns ...int64) (is []sdk.Int) {
|
||||
for _, n := range ns {
|
||||
is = append(is, sdk.NewInt(n))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewAuthGenStateFromAccs(accounts authexported.GenesisAccounts) app.GenesisState {
|
||||
authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts)
|
||||
return app.GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)}
|
||||
}
|
171
x/auction/keeper/keeper.go
Normal file
171
x/auction/keeper/keeper.go
Normal file
@ -0,0 +1,171 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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/tendermint/tendermint/libs/log"
|
||||
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
type Keeper struct {
|
||||
supplyKeeper types.SupplyKeeper
|
||||
storeKey sdk.StoreKey
|
||||
cdc *codec.Codec
|
||||
paramSubspace subspace.Subspace
|
||||
codespace sdk.CodespaceType
|
||||
}
|
||||
|
||||
// NewKeeper returns a new auction keeper.
|
||||
func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, supplyKeeper types.SupplyKeeper, paramstore subspace.Subspace) Keeper {
|
||||
if addr := supplyKeeper.GetModuleAddress(types.ModuleName); addr == nil {
|
||||
panic(fmt.Sprintf("%s module account has not been set", types.ModuleName))
|
||||
}
|
||||
return Keeper{
|
||||
supplyKeeper: supplyKeeper,
|
||||
storeKey: storeKey,
|
||||
cdc: cdc,
|
||||
paramSubspace: paramstore.WithKeyTable(types.ParamKeyTable()),
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// SetNextAuctionID stores an ID to be used for the next created auction
|
||||
func (k Keeper) SetNextAuctionID(ctx sdk.Context, id uint64) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
store.Set(types.NextAuctionIDKey, types.Uint64ToBytes(id))
|
||||
}
|
||||
|
||||
// GetNextAuctionID reads the next available global ID from store
|
||||
func (k Keeper) GetNextAuctionID(ctx sdk.Context) (uint64, sdk.Error) {
|
||||
store := ctx.KVStore(k.storeKey)
|
||||
bz := store.Get(types.NextAuctionIDKey)
|
||||
if bz == nil {
|
||||
return 0, types.ErrInvalidInitialAuctionID(k.codespace)
|
||||
}
|
||||
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 {
|
||||
id, err := k.GetNextAuctionID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.SetNextAuctionID(ctx, id+1)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StoreNewAuction stores an auction, adding a new ID
|
||||
func (k Keeper) StoreNewAuction(ctx sdk.Context, auction types.Auction) (uint64, sdk.Error) {
|
||||
newAuctionID, err := k.GetNextAuctionID(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
auction = auction.WithID(newAuctionID)
|
||||
|
||||
k.SetAuction(ctx, auction)
|
||||
|
||||
err = k.IncrementNextAuctionID(ctx)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return newAuctionID, nil
|
||||
}
|
||||
|
||||
// SetAuction puts the auction into the store, and updates any indexes.
|
||||
func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) {
|
||||
// remove the auction from the byTime index if it is already in there
|
||||
existingAuction, found := k.GetAuction(ctx, auction.GetID())
|
||||
if found {
|
||||
k.removeFromByTimeIndex(ctx, existingAuction.GetEndTime(), existingAuction.GetID())
|
||||
}
|
||||
|
||||
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
|
||||
bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction)
|
||||
store.Set(types.GetAuctionKey(auction.GetID()), bz)
|
||||
|
||||
k.InsertIntoByTimeIndex(ctx, auction.GetEndTime(), auction.GetID())
|
||||
}
|
||||
|
||||
// GetAuction gets an auction from the store.
|
||||
func (k Keeper) GetAuction(ctx sdk.Context, auctionID uint64) (types.Auction, bool) {
|
||||
var auction types.Auction
|
||||
|
||||
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
|
||||
bz := store.Get(types.GetAuctionKey(auctionID))
|
||||
if bz == nil {
|
||||
return auction, false
|
||||
}
|
||||
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auction)
|
||||
return auction, true
|
||||
}
|
||||
|
||||
// DeleteAuction removes an auction from the store, and any indexes.
|
||||
func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID uint64) {
|
||||
auction, found := k.GetAuction(ctx, auctionID)
|
||||
if found {
|
||||
k.removeFromByTimeIndex(ctx, auction.GetEndTime(), auctionID)
|
||||
}
|
||||
|
||||
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
|
||||
store.Delete(types.GetAuctionKey(auctionID))
|
||||
}
|
||||
|
||||
// InsertIntoByTimeIndex adds an auction ID and end time into the byTime index.
|
||||
func (k Keeper) InsertIntoByTimeIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) { // TODO make private, and find way to make tests work
|
||||
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
|
||||
store.Set(types.GetAuctionByTimeKey(endTime, auctionID), types.Uint64ToBytes(auctionID))
|
||||
}
|
||||
|
||||
// removeFromByTimeIndex removes an auction ID and end time from the byTime index.
|
||||
func (k Keeper) removeFromByTimeIndex(ctx sdk.Context, endTime time.Time, auctionID uint64) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
|
||||
store.Delete(types.GetAuctionByTimeKey(endTime, auctionID))
|
||||
}
|
||||
|
||||
// IterateAuctionByTime provides an iterator over auctions ordered by auction.EndTime.
|
||||
// For each auction cb will be callled. If cb returns true the iterator will close and stop.
|
||||
func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time.Time, cb func(auctionID uint64) (stop bool)) {
|
||||
store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
|
||||
iterator := store.Iterator(
|
||||
nil, // start at the very start of the prefix store
|
||||
sdk.PrefixEndBytes(sdk.FormatTimeBytes(inclusiveCutoffTime)), // include any keys with times equal to inclusiveCutoffTime
|
||||
)
|
||||
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
|
||||
auctionID := types.Uint64FromBytes(iterator.Value())
|
||||
|
||||
if cb(auctionID) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IterateAuctions provides an iterator over all stored auctions.
|
||||
// For each auction, cb will be called. If cb returns true, the iterator will close and stop.
|
||||
func (k Keeper) IterateAuctions(ctx sdk.Context, cb func(auction types.Auction) (stop bool)) {
|
||||
iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
|
||||
|
||||
defer iterator.Close()
|
||||
for ; iterator.Valid(); iterator.Next() {
|
||||
var auction types.Auction
|
||||
k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auction)
|
||||
|
||||
if cb(auction) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
135
x/auction/keeper/keeper_test.go
Normal file
135
x/auction/keeper/keeper_test.go
Normal file
@ -0,0 +1,135 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
func SetGetDeleteAuction(t *testing.T) {
|
||||
// setup keeper, create auction
|
||||
tApp := app.NewTestApp()
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
someTime := time.Date(43, time.January, 1, 0, 0, 0, 0, time.UTC) // need to specify UTC as tz info is lost on unmarshal
|
||||
var id uint64 = 5
|
||||
auction := types.NewSurplusAuction("some_module", c("usdx", 100), "kava", someTime).WithID(id)
|
||||
|
||||
// write and read from store
|
||||
keeper.SetAuction(ctx, auction)
|
||||
readAuction, found := keeper.GetAuction(ctx, id)
|
||||
|
||||
// check before and after match
|
||||
require.True(t, found)
|
||||
require.Equal(t, auction, readAuction)
|
||||
// check auction is in the index
|
||||
keeper.IterateAuctionsByTime(ctx, auction.GetEndTime(), func(readID uint64) bool {
|
||||
require.Equal(t, auction.GetID(), readID)
|
||||
return false
|
||||
})
|
||||
|
||||
// delete auction
|
||||
keeper.DeleteAuction(ctx, id)
|
||||
|
||||
// check auction does not exist
|
||||
_, found = keeper.GetAuction(ctx, id)
|
||||
require.False(t, found)
|
||||
// check auction not in index
|
||||
keeper.IterateAuctionsByTime(ctx, time.Unix(999999999, 0), func(readID uint64) bool {
|
||||
require.Fail(t, "index should be empty", " found auction ID '%s", readID)
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
func TestIncrementNextAuctionID(t *testing.T) {
|
||||
// setup keeper
|
||||
tApp := app.NewTestApp()
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
|
||||
// store id
|
||||
var id uint64 = 123456
|
||||
keeper.SetNextAuctionID(ctx, id)
|
||||
|
||||
require.NoError(t, keeper.IncrementNextAuctionID(ctx))
|
||||
|
||||
// check id was incremented
|
||||
readID, err := keeper.GetNextAuctionID(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, id+1, readID)
|
||||
|
||||
}
|
||||
|
||||
func TestIterateAuctions(t *testing.T) {
|
||||
// setup
|
||||
tApp := app.NewTestApp()
|
||||
tApp.InitializeFromGenesisStates()
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
|
||||
auctions := []types.Auction{
|
||||
types.NewSurplusAuction("sellerMod", c("denom", 12345678), "anotherdenom", time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC)).WithID(0),
|
||||
types.NewDebtAuction("buyerMod", c("denom", 12345678), c("anotherdenom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("debt", 12345678)).WithID(1),
|
||||
types.NewCollateralAuction("sellerMod", c("denom", 12345678), time.Date(1998, time.January, 1, 0, 0, 0, 0, time.UTC), c("anotherdenom", 12345678), types.WeightedAddresses{}, c("debt", 12345678)).WithID(2),
|
||||
}
|
||||
for _, a := range auctions {
|
||||
keeper.SetAuction(ctx, a)
|
||||
}
|
||||
|
||||
// run
|
||||
var readAuctions []types.Auction
|
||||
keeper.IterateAuctions(ctx, func(a types.Auction) bool {
|
||||
readAuctions = append(readAuctions, a)
|
||||
return false
|
||||
})
|
||||
|
||||
// check
|
||||
require.Equal(t, auctions, readAuctions)
|
||||
}
|
||||
|
||||
func TestIterateAuctionsByTime(t *testing.T) {
|
||||
// setup keeper
|
||||
tApp := app.NewTestApp()
|
||||
keeper := tApp.GetAuctionKeeper()
|
||||
ctx := tApp.NewContext(true, abci.Header{})
|
||||
|
||||
// setup byTime index
|
||||
byTimeIndex := []struct {
|
||||
endTime time.Time
|
||||
auctionID uint64
|
||||
}{
|
||||
{time.Date(0, time.January, 1, 0, 0, 0, 0, time.UTC), 9999}, // distant past
|
||||
{time.Date(1998, time.January, 1, 11, 59, 59, 999999999, time.UTC), 1}, // just before cutoff
|
||||
{time.Date(1998, time.January, 1, 11, 59, 59, 999999999, time.UTC), 2}, //
|
||||
{time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC), 3}, // equal to cutoff
|
||||
{time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC), 4}, //
|
||||
{time.Date(1998, time.January, 1, 12, 0, 0, 1, time.UTC), 5}, // just after cutoff
|
||||
{time.Date(1998, time.January, 1, 12, 0, 0, 1, time.UTC), 6}, //
|
||||
{time.Date(9999, time.January, 1, 0, 0, 0, 0, time.UTC), 0}, // distant future
|
||||
}
|
||||
for _, v := range byTimeIndex {
|
||||
keeper.InsertIntoByTimeIndex(ctx, v.endTime, v.auctionID)
|
||||
}
|
||||
|
||||
// read out values from index up to a cutoff time and check they are as expected
|
||||
cutoffTime := time.Date(1998, time.January, 1, 12, 0, 0, 0, time.UTC)
|
||||
var expectedIndex []uint64
|
||||
for _, v := range byTimeIndex {
|
||||
if v.endTime.Before(cutoffTime) || v.endTime.Equal(cutoffTime) { // endTime ≤ cutoffTime
|
||||
expectedIndex = append(expectedIndex, v.auctionID)
|
||||
}
|
||||
|
||||
}
|
||||
var readIndex []uint64
|
||||
keeper.IterateAuctionsByTime(ctx, cutoffTime, func(id uint64) bool {
|
||||
readIndex = append(readIndex, id)
|
||||
return false
|
||||
})
|
||||
|
||||
require.Equal(t, expectedIndex, readIndex)
|
||||
}
|
69
x/auction/keeper/math.go
Normal file
69
x/auction/keeper/math.go
Normal file
@ -0,0 +1,69 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// splitIntIntoWeightedBuckets divides an initial +ve integer among several buckets in proportion to the buckets' weights
|
||||
// It uses the largest remainder method:
|
||||
// https://en.wikipedia.org/wiki/Largest_remainder_method
|
||||
// see also: https://stackoverflow.com/questions/13483430/how-to-make-rounded-percentages-add-up-to-100
|
||||
func splitIntIntoWeightedBuckets(amount sdk.Int, buckets []sdk.Int) []sdk.Int {
|
||||
// TODO ideally change algorithm to work with -ve numbers. Limiting to +ve numbers until them
|
||||
if amount.IsNegative() {
|
||||
panic("negative amount")
|
||||
}
|
||||
for _, bucket := range buckets {
|
||||
if bucket.IsNegative() {
|
||||
panic("negative bucket")
|
||||
}
|
||||
}
|
||||
|
||||
totalWeights := totalInts(buckets...)
|
||||
|
||||
// split amount by weights, recording whole number part and remainder
|
||||
quotients := make([]quoRem, len(buckets))
|
||||
for i := range buckets {
|
||||
q := amount.Mul(buckets[i]).Quo(totalWeights)
|
||||
r := amount.Mul(buckets[i]).Mod(totalWeights)
|
||||
quotients[i] = quoRem{index: i, quo: q, rem: r}
|
||||
}
|
||||
|
||||
// apportion left over to buckets with the highest remainder (to minimize error)
|
||||
sort.Slice(quotients, func(i, j int) bool {
|
||||
return quotients[i].rem.GT(quotients[j].rem) // decreasing remainder order
|
||||
})
|
||||
|
||||
allocated := sdk.ZeroInt()
|
||||
for _, qr := range quotients {
|
||||
allocated = allocated.Add(qr.quo)
|
||||
}
|
||||
leftToAllocate := amount.Sub(allocated)
|
||||
|
||||
results := make([]sdk.Int, len(quotients))
|
||||
for _, qr := range quotients {
|
||||
results[qr.index] = qr.quo
|
||||
if !leftToAllocate.IsZero() {
|
||||
results[qr.index] = results[qr.index].Add(sdk.OneInt())
|
||||
leftToAllocate = leftToAllocate.Sub(sdk.OneInt())
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
type quoRem struct {
|
||||
index int
|
||||
quo sdk.Int
|
||||
rem sdk.Int
|
||||
}
|
||||
|
||||
// totalInts adds together sdk.Ints
|
||||
func totalInts(is ...sdk.Int) sdk.Int {
|
||||
total := sdk.ZeroInt()
|
||||
for _, i := range is {
|
||||
total = total.Add(i)
|
||||
}
|
||||
return total
|
||||
}
|
36
x/auction/keeper/math_test.go
Normal file
36
x/auction/keeper/math_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSplitIntIntoWeightedBuckets(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
amount sdk.Int
|
||||
buckets []sdk.Int
|
||||
want []sdk.Int
|
||||
}{
|
||||
{"2split1,1", i(2), is(1, 1), is(1, 1)},
|
||||
{"100split1,9", i(100), is(1, 9), is(10, 90)},
|
||||
{"7split1,2", i(7), is(1, 2), is(2, 5)},
|
||||
{"17split1,1,1", i(17), is(1, 1, 1), is(6, 6, 5)},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := splitIntIntoWeightedBuckets(tc.amount, tc.buckets)
|
||||
require.Equal(t, tc.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func i(n int64) sdk.Int { return sdk.NewInt(n) }
|
||||
func is(ns ...int64) (is []sdk.Int) {
|
||||
for _, n := range ns {
|
||||
is = append(is, sdk.NewInt(n))
|
||||
}
|
||||
return
|
||||
}
|
15
x/auction/keeper/params.go
Normal file
15
x/auction/keeper/params.go
Normal file
@ -0,0 +1,15 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
||||
}
|
||||
|
||||
func (k Keeper) GetParams(ctx sdk.Context) (params types.Params) {
|
||||
k.paramSubspace.GetParamSet(ctx, ¶ms)
|
||||
return
|
||||
}
|
79
x/auction/keeper/querier.go
Normal file
79
x/auction/keeper/querier.go
Normal file
@ -0,0 +1,79 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"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) {
|
||||
switch path[0] {
|
||||
case types.QueryGetAuction:
|
||||
return queryAuction(ctx, req, keeper)
|
||||
case types.QueryGetAuctions:
|
||||
return queryAuctions(ctx, req, keeper)
|
||||
case types.QueryGetParams:
|
||||
return queryGetParams(ctx, req, keeper)
|
||||
default:
|
||||
return nil, sdk.ErrUnknownRequest("unknown auction query endpoint")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func queryAuction(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
|
||||
// Decode request
|
||||
var requestParams types.QueryAuctionParams
|
||||
err := keeper.cdc.UnmarshalJSON(req.Data, &requestParams)
|
||||
if err != nil {
|
||||
return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error()))
|
||||
}
|
||||
|
||||
// Lookup auction
|
||||
auction, found := keeper.GetAuction(ctx, requestParams.AuctionID)
|
||||
if !found {
|
||||
return nil, types.ErrAuctionNotFound(types.DefaultCodespace, 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 bz, nil
|
||||
}
|
||||
|
||||
func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) {
|
||||
// Get all auctions
|
||||
auctionsList := types.Auctions{}
|
||||
keeper.IterateAuctions(ctx, func(a types.Auction) bool {
|
||||
auctionsList = append(auctionsList, a)
|
||||
return false
|
||||
})
|
||||
|
||||
// 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 bz, nil
|
||||
}
|
||||
|
||||
// query params in the auction 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
|
||||
}
|
134
x/auction/keeper/querier_test.go
Normal file
134
x/auction/keeper/querier_test.go
Normal file
@ -0,0 +1,134 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
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/cosmos/cosmos-sdk/x/supply"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction/keeper"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/stretchr/testify/suite"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
const (
|
||||
custom = "custom"
|
||||
TestAuctionCount = 10
|
||||
)
|
||||
|
||||
type QuerierTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
keeper keeper.Keeper
|
||||
app app.TestApp
|
||||
auctions types.Auctions
|
||||
ctx sdk.Context
|
||||
querier sdk.Querier
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(1)
|
||||
buyer := addrs[0]
|
||||
modName := cdp.LiquidatorMacc
|
||||
|
||||
// Set up seller account
|
||||
sellerAcc := supply.NewEmptyModuleAccount(modName, supply.Minter, supply.Burner)
|
||||
sellerAcc.SetCoins(cs(c("token1", 1000), c("token2", 1000), c("debt", 1000)))
|
||||
|
||||
// Initialize genesis accounts
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewAuthGenStateFromAccs(authexported.GenesisAccounts{
|
||||
auth.NewBaseAccount(buyer, cs(c("token1", 1000), c("token2", 1000)), nil, 0, 0),
|
||||
sellerAcc,
|
||||
}),
|
||||
)
|
||||
|
||||
suite.ctx = ctx
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetAuctionKeeper()
|
||||
|
||||
// Populate with auctions
|
||||
randSrc := rand.New(rand.NewSource(int64(1234)))
|
||||
for j := 0; j < TestAuctionCount; j++ {
|
||||
lotAmount := simulation.RandIntBetween(randSrc, 10, 100)
|
||||
id, err := suite.keeper.StartSurplusAuction(suite.ctx, modName, c("token1", int64(lotAmount)), "token2")
|
||||
suite.NoError(err)
|
||||
|
||||
auc, found := suite.keeper.GetAuction(suite.ctx, id)
|
||||
suite.True(found)
|
||||
suite.auctions = append(suite.auctions, auc)
|
||||
}
|
||||
|
||||
suite.querier = keeper.NewQuerier(suite.keeper)
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) TestQueryAuction() {
|
||||
ctx := suite.ctx.WithIsCheckTx(false)
|
||||
// Set up request query
|
||||
query := abci.RequestQuery{
|
||||
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAuction}, "/"),
|
||||
Data: types.ModuleCdc.MustMarshalJSON(types.QueryAuctionParams{AuctionID: types.DefaultNextAuctionID}), // get the first auction
|
||||
}
|
||||
|
||||
// Execute query and check the []byte result
|
||||
bz, err := suite.querier(ctx, []string{types.QueryGetAuction}, query)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(bz)
|
||||
|
||||
// Unmarshal the bytes into type Auction
|
||||
var auction types.Auction
|
||||
suite.NoError(types.ModuleCdc.UnmarshalJSON(bz, &auction))
|
||||
|
||||
// Check the returned auction
|
||||
suite.Equal(suite.auctions[0].GetID(), auction.GetID())
|
||||
suite.Equal(suite.auctions[0].GetInitiator(), auction.GetInitiator())
|
||||
suite.Equal(suite.auctions[0].GetLot(), auction.GetLot())
|
||||
suite.Equal(suite.auctions[0].GetBid(), auction.GetBid())
|
||||
suite.Equal(suite.auctions[0].GetEndTime(), auction.GetEndTime())
|
||||
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) TestQueryAuctions() {
|
||||
ctx := suite.ctx.WithIsCheckTx(false)
|
||||
// Set up request query
|
||||
query := abci.RequestQuery{
|
||||
Path: strings.Join([]string{custom, types.QuerierRoute, types.QueryGetAuctions}, "/"),
|
||||
Data: types.ModuleCdc.MustMarshalJSON(types.NewQueryAllAuctionParams(1, TestAuctionCount)),
|
||||
}
|
||||
|
||||
// Execute query and check the []byte result
|
||||
bz, err := suite.querier(ctx, []string{types.QueryGetAuctions}, query)
|
||||
suite.NoError(err)
|
||||
suite.NotNil(bz)
|
||||
|
||||
// Unmarshal the bytes into type Auctions
|
||||
var auctions types.Auctions
|
||||
suite.NoError(types.ModuleCdc.UnmarshalJSON(bz, &auctions))
|
||||
|
||||
// Check that each Auction has correct values
|
||||
if len(auctions) == 0 && len(suite.auctions) != 0 {
|
||||
suite.FailNow("no auctions returned") // skip the panic from indexing empty slice below
|
||||
}
|
||||
for i := 0; i < TestAuctionCount; i++ {
|
||||
suite.Equal(suite.auctions[i].GetID(), auctions[i].GetID())
|
||||
suite.Equal(suite.auctions[i].GetInitiator(), auctions[i].GetInitiator())
|
||||
suite.Equal(suite.auctions[i].GetLot(), auctions[i].GetLot())
|
||||
suite.Equal(suite.auctions[i].GetBid(), auctions[i].GetBid())
|
||||
suite.Equal(suite.auctions[i].GetEndTime(), auctions[i].GetEndTime())
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuerierTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(QuerierTestSuite))
|
||||
}
|
130
x/auction/module.go
Normal file
130
x/auction/module.go
Normal file
@ -0,0 +1,130 @@
|
||||
package auction
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"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/auction/client/cli"
|
||||
"github.com/kava-labs/kava/x/auction/client/rest"
|
||||
"github.com/kava-labs/kava/x/auction/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ module.AppModule = AppModule{}
|
||||
_ module.AppModuleBasic = AppModuleBasic{}
|
||||
)
|
||||
|
||||
// AppModuleBasic implements the sdk.AppModuleBasic interface.
|
||||
type AppModuleBasic struct{}
|
||||
|
||||
// Name returns the module name.
|
||||
func (AppModuleBasic) Name() string {
|
||||
return ModuleName
|
||||
}
|
||||
|
||||
// RegisterCodec registers the module codec.
|
||||
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
|
||||
RegisterCodec(cdc)
|
||||
}
|
||||
|
||||
// DefaultGenesis returns the default genesis state.
|
||||
func (AppModuleBasic) DefaultGenesis() json.RawMessage {
|
||||
return ModuleCdc.MustMarshalJSON(DefaultGenesisState())
|
||||
}
|
||||
|
||||
// ValidateGenesis performs genesis state validation for the auction module.
|
||||
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
|
||||
var gs GenesisState
|
||||
err := ModuleCdc.UnmarshalJSON(bz, &gs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to unmarshal %s genesis state: %w", ModuleName, err)
|
||||
}
|
||||
return gs.Validate()
|
||||
}
|
||||
|
||||
// RegisterRESTRoutes registers the REST routes for the auction module.
|
||||
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
|
||||
rest.RegisterRoutes(ctx, rtr)
|
||||
}
|
||||
|
||||
// GetTxCmd returns the root tx command for the auction module.
|
||||
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return cli.GetTxCmd(cdc)
|
||||
}
|
||||
|
||||
// GetQueryCmd returns the root query command for the auction 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 types.SupplyKeeper
|
||||
}
|
||||
|
||||
// NewAppModule creates a new AppModule object
|
||||
func NewAppModule(keeper Keeper, supplyKeeper types.SupplyKeeper) AppModule {
|
||||
return AppModule{
|
||||
AppModuleBasic: AppModuleBasic{},
|
||||
keeper: keeper,
|
||||
supplyKeeper: supplyKeeper,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterInvariants performs a no-op.
|
||||
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 module querier
|
||||
func (am AppModule) NewQuerierHandler() sdk.Querier {
|
||||
return NewQuerier(am.keeper)
|
||||
}
|
||||
|
||||
// InitGenesis performs genesis initialization for the auction 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 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 (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}
|
||||
|
||||
// EndBlock module end-block
|
||||
func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
|
||||
EndBlocker(ctx, am.keeper)
|
||||
return []abci.ValidatorUpdate{}
|
||||
}
|
9
x/auction/spec/01_concepts.md
Normal file
9
x/auction/spec/01_concepts.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Concepts
|
||||
|
||||
Auctions are broken down into three distinct types, which correspond to three specific functionalities within the CDP system.
|
||||
|
||||
* **Surplus Auction:** An auction in which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 they are willing to pay for the lot of c1. After the completion of a surplus auction, the winning bid of c2 is burned, and the bidder receives the lot of c1. As a concrete example, surplus auction are used to sell a fixed amount of USDX stable coins in exchange for increasing bids of KAVA governance tokens. The governance tokens are then burned and the winner receives USDX.
|
||||
* **Debt Auction:** An auction in which a fixed amount of coins (c1) is bid for a decreasing lot of other coins (c2). Bidders decrement the lot of c2 they are willing to receive for the fixed amount of c1. As a concrete example, debt auctions are used to raise a certain amount of USDX stable coins in exchange for decreasing lots of KAVA governance tokens. The USDX tokens are used to recapitalize the cdp system and the winner receives KAVA.
|
||||
* **Surplus Reverse Auction:** Are two phase auction is which a fixed lot of coins (c1) is sold for increasing amounts of other coins (c2). Bidders increment the amount of c2 until a specific `maxBid` is reached. Once `maxBid` is reached, a fixed amount of c2 is bid for a decreasing lot of c1. In the second phase, bidders decrement the lot of c1 they are willing to receive for a fixed amount of c2. As a concrete example, collateral auctions are used to sell collateral (ATOM, for example) for up to a `maxBid` amount of USDX. The USDX tokens are used to recapitalize the cdp system and the winner receives the specified lot of ATOM. In the event that the winning lot is smaller than the total lot, the excess ATOM is ratably returned to the original owners of the liquidated CDPs that were collateralized with that ATOM.
|
||||
|
||||
Auctions are always initiated by another module, and not directly by users. Auctions start with an expiry, the time at which the auction is guaranteed to end, even if there have been no bidders. After each bid, the auction is extended by a specific amount of time, `BidDuration`. In the case that increasing the auction time by `BidDuration` would cause the auction to go past its expiry, the expiry is chosen as the ending time.
|
75
x/auction/spec/02_state.md
Normal file
75
x/auction/spec/02_state.md
Normal file
@ -0,0 +1,75 @@
|
||||
# State
|
||||
|
||||
## Parameters and genesis state
|
||||
|
||||
`Paramaters` define the rules according to which auctions are run. There is only one active parameter set at any given time. Updates to the parameter set can be made via on-chain parameter update proposals.
|
||||
|
||||
```go
|
||||
// Params governance parameters for auction module
|
||||
type Params struct {
|
||||
MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction
|
||||
MaxBidDuration time.Duration `json:"max_bid_duration" yaml:"max_bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry.
|
||||
}
|
||||
```
|
||||
|
||||
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for normal function of the auction module to resume.
|
||||
|
||||
```go
|
||||
// GenesisState - auction state that must be provided at genesis
|
||||
type GenesisState struct {
|
||||
NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"` // auctionID that will be used for the next created auction
|
||||
Params Params `json:"auction_params" yaml:"auction_params"` // auction params
|
||||
Auctions Auctions `json:"genesis_auctions" yaml:"genesis_auctions"` // auctions currently in the store
|
||||
}
|
||||
```
|
||||
|
||||
## Base types
|
||||
|
||||
```go
|
||||
// Auction is an interface to several types of auction.
|
||||
type Auction interface {
|
||||
GetID() uint64
|
||||
WithID(uint64) Auction
|
||||
GetEndTime() time.Time
|
||||
}
|
||||
|
||||
// BaseAuction is a common type shared by all Auctions.
|
||||
type BaseAuction struct {
|
||||
ID uint64
|
||||
Initiator string // Module name that starts the auction. Pays out Lot.
|
||||
Lot sdk.Coin // Coins that will paid out by Initiator to the winning bidder.
|
||||
Bidder sdk.AccAddress // Latest bidder. Receiver of Lot.
|
||||
Bid sdk.Coin // Coins paid into the auction the bidder.
|
||||
EndTime time.Time // Current auction closing time. Triggers at the end of the block with time ≥ EndTime.
|
||||
MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after.
|
||||
}
|
||||
|
||||
// SurplusAuction is a forward auction that burns what it receives from bids.
|
||||
// It is normally used to sell off excess pegged asset acquired by the CDP system.
|
||||
type SurplusAuction struct {
|
||||
BaseAuction
|
||||
}
|
||||
|
||||
// DebtAuction is a reverse auction that mints what it pays out.
|
||||
// It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral.
|
||||
type DebtAuction struct {
|
||||
BaseAuction
|
||||
}
|
||||
|
||||
// WeightedAddresses is a type for storing some addresses and associated weights.
|
||||
type WeightedAddresses struct {
|
||||
Addresses []sdk.AccAddress
|
||||
Weights []sdk.Int
|
||||
}
|
||||
|
||||
// CollateralAuction is a two phase auction.
|
||||
// Initially, in forward auction phase, bids can be placed up to a max bid.
|
||||
// Then it switches to a reverse auction phase, where the initial amount up for auction is bid down.
|
||||
// Unsold Lot is sent to LotReturns, being divided among the addresses by weight.
|
||||
// Collateral auctions are normally used to sell off collateral seized from CDPs.
|
||||
type CollateralAuction struct {
|
||||
BaseAuction
|
||||
MaxBid sdk.Coin
|
||||
LotReturns WeightedAddresses
|
||||
}
|
||||
```
|
32
x/auction/spec/03_messages.md
Normal file
32
x/auction/spec/03_messages.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Messages
|
||||
|
||||
## Bidding
|
||||
|
||||
Users can bid on auctions using the `MsgPlaceBid` message type. All auction types can be bid on using the same message type.
|
||||
|
||||
```go
|
||||
// MsgPlaceBid is the message type used to place a bid on any type of auction.
|
||||
type MsgPlaceBid struct {
|
||||
AuctionID uint64
|
||||
Bidder sdk.AccAddress
|
||||
Amount sdk.Coin
|
||||
}
|
||||
```
|
||||
|
||||
**State Modifications:**
|
||||
|
||||
* Update bidder if different than previous bidder
|
||||
* For Surplus auctions:
|
||||
* Update Bid to msg.Amount
|
||||
* Return bid coins to previous bidder
|
||||
* Burn coins equal to the increment in the bid (CurrentBid - PreviousBid)
|
||||
* For Debt auctions:
|
||||
* Update Lot amount to msg.Amount
|
||||
* Return bid coins to previous bidder
|
||||
* For Collateral auctions:
|
||||
* Return bid coins to previous bidder
|
||||
* If in forward phase:
|
||||
* Update Bid amount to msg.Amount
|
||||
* If in reverse phase:
|
||||
* Update Lot amount to msg.Amount
|
||||
* Extend auction by `BidDuration`, up to `MaxEndTime`
|
32
x/auction/spec/04_events.md
Normal file
32
x/auction/spec/04_events.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Events
|
||||
|
||||
The `x/auction` module emits the following events:
|
||||
|
||||
## Triggered By Other Modules
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------------|---------------|---------------------|
|
||||
| auction_start | auction_id | {auction ID} |
|
||||
| auction_start | auction_type | {auction type} |
|
||||
| auction_start | lot_denom | {auction lot denom} |
|
||||
| auction_start | bid_denom | {auction bid denom} |
|
||||
|
||||
## Handlers
|
||||
|
||||
### MsgPlaceBid
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|-------------|---------------|--------------------|
|
||||
| auction_bid | auction_id | {auction ID} |
|
||||
| auction_bid | bidder | {latest bidder} |
|
||||
| auction_bid | bid_amount | {coin amount} |
|
||||
| auction_bid | lot_amount | {coin amount} |
|
||||
| auction_bid | end_time | {auction end time} |
|
||||
| message | module | auction |
|
||||
| message | sender | {sender address} |
|
||||
|
||||
## EndBlock
|
||||
|
||||
| Type | Attribute Key | Attribute Value |
|
||||
|---------------|---------------|-----------------|
|
||||
| auction_close | auction_id | {auction ID} |
|
8
x/auction/spec/05_params.md
Normal file
8
x/auction/spec/05_params.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Parameters
|
||||
|
||||
The auction module contains the following parameters:
|
||||
|
||||
| Key | Type | Example |
|
||||
| ------------------ | ---------------------- | -----------|
|
||||
| MaxAuctionDuration | string (time.Duration) | "48h0m0s" |
|
||||
| BidDuration | string (time.Duration) | "3h0m0s" |
|
18
x/auction/spec/06_end_block.md
Normal file
18
x/auction/spec/06_end_block.md
Normal file
@ -0,0 +1,18 @@
|
||||
# End Block
|
||||
|
||||
At the end of each block, auctions that have reached `EndTime` are closed. The logic to close auctions is as follows:
|
||||
|
||||
```go
|
||||
var expiredAuctions []uint64
|
||||
k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id uint64) bool {
|
||||
expiredAuctions = append(expiredAuctions, id)
|
||||
return false
|
||||
})
|
||||
|
||||
for _, id := range expiredAuctions {
|
||||
err := k.CloseAuction(ctx, id)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
13
x/auction/spec/README.md
Normal file
13
x/auction/spec/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# `auction`
|
||||
|
||||
<!-- TOC -->
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[Messages](03_messages.md)**
|
||||
4. **[Events](04_events.md)**
|
||||
5. **[Params](05_params.md)**
|
||||
6. **[EndBlock](06_end_block.md)**
|
||||
|
||||
## Abstract
|
||||
|
||||
`x/auction` is an implementation of a Cosmos SDK Module that handles the creation, bidding, and payout of 3 distinct auction types. All auction types implement the `Auction` interface. Each auction type is used at different points during the normal functioning of the CDP system.
|
266
x/auction/types/auctions.go
Normal file
266
x/auction/types/auctions.go
Normal file
@ -0,0 +1,266 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/supply"
|
||||
)
|
||||
|
||||
// DistantFuture is a very large time value to use as initial the ending time for auctions.
|
||||
// It is not set to the max time supported. This can cause problems with time comparisons, see https://stackoverflow.com/a/32620397.
|
||||
// Also amino panics when encoding times ≥ the start of year 10000.
|
||||
var DistantFuture = time.Date(9000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
// Auction is an interface for handling common actions on auctions.
|
||||
type Auction interface {
|
||||
GetID() uint64
|
||||
WithID(uint64) Auction
|
||||
|
||||
GetInitiator() string
|
||||
GetLot() sdk.Coin
|
||||
GetBidder() sdk.AccAddress
|
||||
GetBid() sdk.Coin
|
||||
GetEndTime() time.Time
|
||||
|
||||
GetType() string
|
||||
GetPhase() string
|
||||
}
|
||||
|
||||
// Auctions is a slice of auctions.
|
||||
type Auctions []Auction
|
||||
|
||||
// BaseAuction is a common type shared by all Auctions.
|
||||
type BaseAuction struct {
|
||||
ID uint64 `json:"id" yaml:"id"`
|
||||
Initiator string `json:"initiator" yaml:"initiator"` // Module name that starts the auction. Pays out Lot.
|
||||
Lot sdk.Coin `json:"lot" yaml:"lot"` // Coins that will paid out by Initiator to the winning bidder.
|
||||
Bidder sdk.AccAddress `json:"bidder" yaml:"bidder"` // Latest bidder. Receiver of Lot.
|
||||
Bid sdk.Coin `json:"bid" yaml:"bid"` // Coins paid into the auction the bidder.
|
||||
HasReceivedBids bool `json:"has_received_bids" yaml:"has_received_bids"` // Whether the auction has received any bids or not.
|
||||
EndTime time.Time `json:"end_time" yaml:"end_time"` // Current auction closing time. Triggers at the end of the block with time ≥ EndTime.
|
||||
MaxEndTime time.Time `json:"max_end_time" yaml:"max_end_time"` // Maximum closing time. Auctions can close before this but never after.
|
||||
}
|
||||
|
||||
// GetID is a getter for auction ID.
|
||||
func (a BaseAuction) GetID() uint64 { return a.ID }
|
||||
|
||||
// GetInitiator is a getter for auction Initiator.
|
||||
func (a BaseAuction) GetInitiator() string { return a.Initiator }
|
||||
|
||||
// GetLot is a getter for auction Lot.
|
||||
func (a BaseAuction) GetLot() sdk.Coin { return a.Lot }
|
||||
|
||||
// GetBidder is a getter for auction Bidder.
|
||||
func (a BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder }
|
||||
|
||||
// GetBid is a getter for auction Bid.
|
||||
func (a BaseAuction) GetBid() sdk.Coin { return a.Bid }
|
||||
|
||||
// GetEndTime is a getter for auction end time.
|
||||
func (a BaseAuction) GetEndTime() time.Time { return a.EndTime }
|
||||
|
||||
// GetType returns theauction type. Used to identify auctions in event attributes.
|
||||
func (a BaseAuction) GetType() string { return "base" }
|
||||
|
||||
// Validate verifies that the auction end time is before max end time
|
||||
func (a BaseAuction) Validate() error {
|
||||
if a.EndTime.After(a.MaxEndTime) {
|
||||
return fmt.Errorf("MaxEndTime < EndTime (%s < %s)", a.MaxEndTime, a.EndTime)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a BaseAuction) String() string {
|
||||
return fmt.Sprintf(`Auction %d:
|
||||
Initiator: %s
|
||||
Lot: %s
|
||||
Bidder: %s
|
||||
Bid: %s
|
||||
End Time: %s
|
||||
Max End Time: %s`,
|
||||
a.GetID(), a.Initiator, a.Lot,
|
||||
a.Bidder, a.Bid, a.GetEndTime().String(),
|
||||
a.MaxEndTime.String(),
|
||||
)
|
||||
}
|
||||
|
||||
// SurplusAuction is a forward auction that burns what it receives from bids.
|
||||
// It is normally used to sell off excess pegged asset acquired by the CDP system.
|
||||
type SurplusAuction struct {
|
||||
BaseAuction `json:"base_auction" yaml:"base_auction"`
|
||||
}
|
||||
|
||||
// WithID returns an auction with the ID set.
|
||||
func (a SurplusAuction) WithID(id uint64) Auction { a.ID = id; return a }
|
||||
|
||||
// GetType returns the auction type. Used to identify auctions in event attributes.
|
||||
func (a SurplusAuction) GetType() string { return "surplus" }
|
||||
|
||||
// GetModuleAccountCoins returns the total number of coins held in the module account for this auction.
|
||||
// It is used in genesis initialize the module account correctly.
|
||||
func (a SurplusAuction) GetModuleAccountCoins() sdk.Coins {
|
||||
// a.Bid is paid out on bids, so is never stored in the module account
|
||||
return sdk.NewCoins(a.Lot)
|
||||
}
|
||||
|
||||
// GetPhase returns the direction of a surplus auction, which never changes.
|
||||
func (a SurplusAuction) GetPhase() string { return "forward" }
|
||||
|
||||
// NewSurplusAuction returns a new surplus auction.
|
||||
func NewSurplusAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) SurplusAuction {
|
||||
auction := SurplusAuction{BaseAuction{
|
||||
// no ID
|
||||
Initiator: seller,
|
||||
Lot: lot,
|
||||
Bidder: nil,
|
||||
Bid: sdk.NewInt64Coin(bidDenom, 0),
|
||||
HasReceivedBids: false, // new auctions don't have any bids
|
||||
EndTime: endTime,
|
||||
MaxEndTime: endTime,
|
||||
}}
|
||||
return auction
|
||||
}
|
||||
|
||||
// DebtAuction is a reverse auction that mints what it pays out.
|
||||
// It is normally used to acquire pegged asset to cover the CDP system's debts that were not covered by selling collateral.
|
||||
type DebtAuction struct {
|
||||
BaseAuction `json:"base_auction" yaml:"base_auction"`
|
||||
|
||||
CorrespondingDebt sdk.Coin `json:"corresponding_debt" yaml:"corresponding_debt"`
|
||||
}
|
||||
|
||||
// WithID returns an auction with the ID set.
|
||||
func (a DebtAuction) WithID(id uint64) Auction { a.ID = id; return a }
|
||||
|
||||
// GetType returns the auction type. Used to identify auctions in event attributes.
|
||||
func (a DebtAuction) GetType() string { return "debt" }
|
||||
|
||||
// GetModuleAccountCoins returns the total number of coins held in the module account for this auction.
|
||||
// It is used in genesis initialize the module account correctly.
|
||||
func (a DebtAuction) GetModuleAccountCoins() sdk.Coins {
|
||||
// a.Lot is minted at auction close, so is never stored in the module account
|
||||
// a.Bid is paid out on bids, so is never stored in the module account
|
||||
return sdk.NewCoins(a.CorrespondingDebt)
|
||||
}
|
||||
|
||||
// GetPhase returns the direction of a debt auction, which never changes.
|
||||
func (a DebtAuction) GetPhase() string { return "reverse" }
|
||||
|
||||
// NewDebtAuction returns a new debt auction.
|
||||
func NewDebtAuction(buyerModAccName string, bid sdk.Coin, initialLot sdk.Coin, endTime time.Time, debt sdk.Coin) DebtAuction {
|
||||
// Note: Bidder is set to the initiator's module account address instead of module name. (when the first bid is placed, it is paid out to the initiator)
|
||||
// Setting to the module account address bypasses calling supply.SendCoinsFromModuleToModule, instead calls SendCoinsFromModuleToAccount.
|
||||
// This isn't a problem currently, but if additional logic/validation was added for sending to coins to Module Accounts, it would be bypassed.
|
||||
auction := DebtAuction{
|
||||
BaseAuction: BaseAuction{
|
||||
// no ID
|
||||
Initiator: buyerModAccName,
|
||||
Lot: initialLot,
|
||||
Bidder: supply.NewModuleAddress(buyerModAccName), // send proceeds from the first bid to the buyer.
|
||||
Bid: bid, // amount that the buyer is buying - doesn't change over course of auction
|
||||
HasReceivedBids: false, // new auctions don't have any bids
|
||||
EndTime: endTime,
|
||||
MaxEndTime: endTime},
|
||||
CorrespondingDebt: debt,
|
||||
}
|
||||
return auction
|
||||
}
|
||||
|
||||
// CollateralAuction is a two phase auction.
|
||||
// Initially, in forward auction phase, bids can be placed up to a max bid.
|
||||
// Then it switches to a reverse auction phase, where the initial amount up for auction is bid down.
|
||||
// Unsold Lot is sent to LotReturns, being divided among the addresses by weight.
|
||||
// Collateral auctions are normally used to sell off collateral seized from CDPs.
|
||||
type CollateralAuction struct {
|
||||
BaseAuction `json:"base_auction" yaml:"base_auction"`
|
||||
|
||||
CorrespondingDebt sdk.Coin `json:"corresponding_debt" yaml:"corresponding_debt"`
|
||||
MaxBid sdk.Coin `json:"max_bid" yaml:"max_bid"`
|
||||
LotReturns WeightedAddresses `json:"lot_returns" yaml:"lot_returns"`
|
||||
}
|
||||
|
||||
// WithID returns an auction with the ID set.
|
||||
func (a CollateralAuction) WithID(id uint64) Auction { a.ID = id; return a }
|
||||
|
||||
// GetType returns the auction type. Used to identify auctions in event attributes.
|
||||
func (a CollateralAuction) GetType() string { return "collateral" }
|
||||
|
||||
// GetModuleAccountCoins returns the total number of coins held in the module account for this auction.
|
||||
// It is used in genesis initialize the module account correctly.
|
||||
func (a CollateralAuction) GetModuleAccountCoins() sdk.Coins {
|
||||
// a.Bid is paid out on bids, so is never stored in the module account
|
||||
return sdk.NewCoins(a.Lot).Add(sdk.NewCoins(a.CorrespondingDebt))
|
||||
}
|
||||
|
||||
// IsReversePhase returns whether the auction has switched over to reverse phase or not.
|
||||
// CollateralAuctions initially start in forward phase.
|
||||
func (a CollateralAuction) IsReversePhase() bool {
|
||||
return a.Bid.IsEqual(a.MaxBid)
|
||||
}
|
||||
|
||||
// GetPhase returns the direction of a collateral auction.
|
||||
func (a CollateralAuction) GetPhase() string {
|
||||
if a.IsReversePhase() {
|
||||
return "reverse"
|
||||
}
|
||||
return "forward"
|
||||
}
|
||||
|
||||
func (a CollateralAuction) String() string {
|
||||
return fmt.Sprintf(`Auction %d:
|
||||
Initiator: %s
|
||||
Lot: %s
|
||||
Bidder: %s
|
||||
Bid: %s
|
||||
End Time: %s
|
||||
Max End Time: %s
|
||||
Max Bid %s
|
||||
LotReturns %s`,
|
||||
a.GetID(), a.Initiator, a.Lot,
|
||||
a.Bidder, a.Bid, a.GetEndTime().String(),
|
||||
a.MaxEndTime.String(), a.MaxBid, a.LotReturns,
|
||||
)
|
||||
}
|
||||
|
||||
// NewCollateralAuction returns a new collateral auction.
|
||||
func NewCollateralAuction(seller string, lot sdk.Coin, endTime time.Time, maxBid sdk.Coin, lotReturns WeightedAddresses, debt sdk.Coin) CollateralAuction {
|
||||
auction := CollateralAuction{
|
||||
BaseAuction: BaseAuction{
|
||||
// no ID
|
||||
Initiator: seller,
|
||||
Lot: lot,
|
||||
Bidder: nil,
|
||||
Bid: sdk.NewInt64Coin(maxBid.Denom, 0),
|
||||
HasReceivedBids: false, // new auctions don't have any bids
|
||||
EndTime: endTime,
|
||||
MaxEndTime: endTime},
|
||||
CorrespondingDebt: debt,
|
||||
MaxBid: maxBid,
|
||||
LotReturns: lotReturns,
|
||||
}
|
||||
return auction
|
||||
}
|
||||
|
||||
// WeightedAddresses is a type for storing some addresses and associated weights.
|
||||
type WeightedAddresses struct {
|
||||
Addresses []sdk.AccAddress `json:"addresses" yaml:"addresses"`
|
||||
Weights []sdk.Int `json:"weights" yaml:"weights"`
|
||||
}
|
||||
|
||||
// NewWeightedAddresses returns a new list addresses with weights.
|
||||
func NewWeightedAddresses(addrs []sdk.AccAddress, weights []sdk.Int) (WeightedAddresses, 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{
|
||||
Addresses: addrs,
|
||||
Weights: weights,
|
||||
}, nil
|
||||
}
|
195
x/auction/types/auctions_test.go
Normal file
195
x/auction/types/auctions_test.go
Normal file
@ -0,0 +1,195 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
TestInitiatorModuleName = "liquidator"
|
||||
TestLotDenom = "usdx"
|
||||
TestLotAmount = 100
|
||||
TestBidDenom = "kava"
|
||||
TestBidAmount = 20
|
||||
TestDebtDenom = "debt"
|
||||
TestDebtAmount1 = 20
|
||||
TestDebtAmount2 = 15
|
||||
TestExtraEndTime = 10000
|
||||
TestAuctionID = 9999123
|
||||
TestAccAddress1 = "kava1qcfdf69js922qrdr4yaww3ax7gjml6pd39p8lj"
|
||||
TestAccAddress2 = "kava1pdfav2cjhry9k79nu6r8kgknnjtq6a7rcr0qlr"
|
||||
)
|
||||
|
||||
func TestNewWeightedAddresses(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
addresses []sdk.AccAddress
|
||||
weights []sdk.Int
|
||||
expectpass bool
|
||||
}{
|
||||
{
|
||||
"normal",
|
||||
[]sdk.AccAddress{
|
||||
sdk.AccAddress([]byte(TestAccAddress1)),
|
||||
sdk.AccAddress([]byte(TestAccAddress2)),
|
||||
},
|
||||
[]sdk.Int{
|
||||
sdk.NewInt(6),
|
||||
sdk.NewInt(8),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"mismatched",
|
||||
[]sdk.AccAddress{
|
||||
sdk.AccAddress([]byte(TestAccAddress1)),
|
||||
sdk.AccAddress([]byte(TestAccAddress2)),
|
||||
},
|
||||
[]sdk.Int{
|
||||
sdk.NewInt(6),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"negativeWeight",
|
||||
[]sdk.AccAddress{
|
||||
sdk.AccAddress([]byte(TestAccAddress1)),
|
||||
sdk.AccAddress([]byte(TestAccAddress2)),
|
||||
},
|
||||
[]sdk.Int{
|
||||
sdk.NewInt(6),
|
||||
sdk.NewInt(-8),
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
// Run NewWeightedAdresses tests
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Attempt to instantiate new WeightedAddresses
|
||||
weightedAddresses, err := NewWeightedAddresses(tc.addresses, tc.weights)
|
||||
|
||||
if tc.expectpass {
|
||||
// Confirm there is no error
|
||||
require.Nil(t, err)
|
||||
|
||||
// Check addresses, weights
|
||||
require.Equal(t, tc.addresses, weightedAddresses.Addresses)
|
||||
require.Equal(t, tc.weights, weightedAddresses.Weights)
|
||||
} else {
|
||||
// Confirm that there is an error
|
||||
require.NotNil(t, err)
|
||||
|
||||
switch tc.name {
|
||||
case "mismatched":
|
||||
require.Contains(t, err.Error(), "number of addresses doesn't match number of weights")
|
||||
case "negativeWeight":
|
||||
require.Contains(t, err.Error(), "weights contain a negative amount")
|
||||
default:
|
||||
// Unexpected error state
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAuctionGetters(t *testing.T) {
|
||||
endTime := time.Now().Add(TestExtraEndTime)
|
||||
|
||||
// Create a new BaseAuction (via SurplusAuction)
|
||||
auction := NewSurplusAuction(
|
||||
TestInitiatorModuleName,
|
||||
c(TestLotDenom, TestLotAmount),
|
||||
TestBidDenom, endTime,
|
||||
)
|
||||
|
||||
auctionID := auction.GetID()
|
||||
auctionBid := auction.GetBid()
|
||||
auctionLot := auction.GetLot()
|
||||
auctionEndTime := auction.GetEndTime()
|
||||
auctionString := auction.String()
|
||||
|
||||
require.Equal(t, auction.ID, auctionID)
|
||||
require.Equal(t, auction.Bid, auctionBid)
|
||||
require.Equal(t, auction.Lot, auctionLot)
|
||||
require.Equal(t, auction.EndTime, auctionEndTime)
|
||||
require.NotNil(t, auctionString)
|
||||
}
|
||||
|
||||
func TestNewSurplusAuction(t *testing.T) {
|
||||
endTime := time.Now().Add(TestExtraEndTime)
|
||||
|
||||
// Create a new SurplusAuction
|
||||
surplusAuction := NewSurplusAuction(
|
||||
TestInitiatorModuleName,
|
||||
c(TestLotDenom, TestLotAmount),
|
||||
TestBidDenom, endTime,
|
||||
)
|
||||
|
||||
require.Equal(t, surplusAuction.Initiator, TestInitiatorModuleName)
|
||||
require.Equal(t, surplusAuction.Lot, c(TestLotDenom, TestLotAmount))
|
||||
require.Equal(t, surplusAuction.Bid, c(TestBidDenom, 0))
|
||||
require.Equal(t, surplusAuction.EndTime, endTime)
|
||||
require.Equal(t, surplusAuction.MaxEndTime, endTime)
|
||||
}
|
||||
|
||||
func TestNewDebtAuction(t *testing.T) {
|
||||
endTime := time.Now().Add(TestExtraEndTime)
|
||||
|
||||
// Create a new DebtAuction
|
||||
debtAuction := NewDebtAuction(
|
||||
TestInitiatorModuleName,
|
||||
c(TestBidDenom, TestBidAmount),
|
||||
c(TestLotDenom, TestLotAmount),
|
||||
endTime,
|
||||
c(TestDebtDenom, TestDebtAmount1),
|
||||
)
|
||||
|
||||
require.Equal(t, debtAuction.Initiator, TestInitiatorModuleName)
|
||||
require.Equal(t, debtAuction.Lot, c(TestLotDenom, TestLotAmount))
|
||||
require.Equal(t, debtAuction.Bid, c(TestBidDenom, TestBidAmount))
|
||||
require.Equal(t, debtAuction.EndTime, endTime)
|
||||
require.Equal(t, debtAuction.MaxEndTime, endTime)
|
||||
require.Equal(t, debtAuction.CorrespondingDebt, c(TestDebtDenom, TestDebtAmount1))
|
||||
}
|
||||
|
||||
func TestNewCollateralAuction(t *testing.T) {
|
||||
// Set up WeightedAddresses
|
||||
addresses := []sdk.AccAddress{
|
||||
sdk.AccAddress([]byte(TestAccAddress1)),
|
||||
sdk.AccAddress([]byte(TestAccAddress2)),
|
||||
}
|
||||
|
||||
weights := []sdk.Int{
|
||||
sdk.NewInt(6),
|
||||
sdk.NewInt(8),
|
||||
}
|
||||
|
||||
weightedAddresses, _ := NewWeightedAddresses(addresses, weights)
|
||||
|
||||
endTime := time.Now().Add(TestExtraEndTime)
|
||||
|
||||
collateralAuction := NewCollateralAuction(
|
||||
TestInitiatorModuleName,
|
||||
c(TestLotDenom, TestLotAmount),
|
||||
endTime,
|
||||
c(TestBidDenom, TestBidAmount),
|
||||
weightedAddresses,
|
||||
c(TestDebtDenom, TestDebtAmount2),
|
||||
)
|
||||
|
||||
require.Equal(t, collateralAuction.BaseAuction.Initiator, TestInitiatorModuleName)
|
||||
require.Equal(t, collateralAuction.BaseAuction.Lot, c(TestLotDenom, TestLotAmount))
|
||||
require.Equal(t, collateralAuction.BaseAuction.Bid, c(TestBidDenom, 0))
|
||||
require.Equal(t, collateralAuction.BaseAuction.EndTime, endTime)
|
||||
require.Equal(t, collateralAuction.BaseAuction.MaxEndTime, endTime)
|
||||
require.Equal(t, collateralAuction.MaxBid, c(TestBidDenom, TestBidAmount))
|
||||
require.Equal(t, collateralAuction.LotReturns, weightedAddresses)
|
||||
require.Equal(t, collateralAuction.CorrespondingDebt, c(TestDebtDenom, TestDebtAmount2))
|
||||
}
|
23
x/auction/types/codec.go
Normal file
23
x/auction/types/codec.go
Normal file
@ -0,0 +1,23 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
)
|
||||
|
||||
// ModuleCdc module level codec
|
||||
var ModuleCdc = codec.New()
|
||||
|
||||
func init() {
|
||||
RegisterCodec(ModuleCdc)
|
||||
}
|
||||
|
||||
// RegisterCodec registers concrete types on the codec.
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(MsgPlaceBid{}, "auction/MsgPlaceBid", nil)
|
||||
|
||||
cdc.RegisterInterface((*GenesisAuction)(nil), nil)
|
||||
cdc.RegisterInterface((*Auction)(nil), nil)
|
||||
cdc.RegisterConcrete(SurplusAuction{}, "auction/SurplusAuction", nil)
|
||||
cdc.RegisterConcrete(DebtAuction{}, "auction/DebtAuction", nil)
|
||||
cdc.RegisterConcrete(CollateralAuction{}, "auction/CollateralAuction", nil)
|
||||
}
|
92
x/auction/types/errors.go
Normal file
92
x/auction/types/errors.go
Normal file
@ -0,0 +1,92 @@
|
||||
// DONTCOVER
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// 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
|
||||
CodeLotTooLarge sdk.CodeType = 11
|
||||
CodeCollateralAuctionIsInReversePhase sdk.CodeType = 12
|
||||
CodeCollateralAuctionIsInForwardPhase sdk.CodeType = 13
|
||||
)
|
||||
|
||||
// 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 last bid
|
||||
func ErrBidTooSmall(codespace sdk.CodespaceType, bid sdk.Coin, lastBid sdk.Coin) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeBidTooSmall, fmt.Sprintf("bid %s is not greater than auction's last bid %s", bid.String(), lastBid.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()))
|
||||
}
|
||||
|
||||
// ErrLotTooLarge error for when lot is not smaller than auction's last lot
|
||||
func ErrLotTooLarge(codespace sdk.CodespaceType, lot sdk.Coin, lastLot sdk.Coin) sdk.Error {
|
||||
return sdk.NewError(codespace, CodeLotTooLarge, fmt.Sprintf("lot %s is not less than auction's last lot %s", lot.String(), lastLot.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))
|
||||
}
|
18
x/auction/types/events.go
Normal file
18
x/auction/types/events.go
Normal file
@ -0,0 +1,18 @@
|
||||
package types
|
||||
|
||||
// Events for auction module
|
||||
const (
|
||||
EventTypeAuctionStart = "auction_start"
|
||||
EventTypeAuctionBid = "auction_bid"
|
||||
EventTypeAuctionClose = "auction_close"
|
||||
|
||||
AttributeValueCategory = ModuleName
|
||||
AttributeKeyAuctionID = "auction_id"
|
||||
AttributeKeyAuctionType = "auction_type"
|
||||
AttributeKeyBidder = "bidder"
|
||||
AttributeKeyBidDenom = "bid_denom"
|
||||
AttributeKeyLotDenom = "lot_denom"
|
||||
AttributeKeyBidAmount = "bid_amount"
|
||||
AttributeKeyLotAmount = "lot_amount"
|
||||
AttributeKeyEndTime = "end_time"
|
||||
)
|
19
x/auction/types/expected_keepers.go
Normal file
19
x/auction/types/expected_keepers.go
Normal file
@ -0,0 +1,19 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
)
|
||||
|
||||
// SupplyKeeper defines the expected supply Keeper
|
||||
type SupplyKeeper interface {
|
||||
GetModuleAddress(name string) sdk.AccAddress
|
||||
GetModuleAccount(ctx sdk.Context, moduleName string) supplyexported.ModuleAccountI
|
||||
|
||||
SendCoinsFromModuleToModule(ctx sdk.Context, sender, recipient string, amt sdk.Coins) sdk.Error
|
||||
SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error
|
||||
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error
|
||||
|
||||
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
|
||||
MintCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
|
||||
}
|
83
x/auction/types/genesis.go
Normal file
83
x/auction/types/genesis.go
Normal file
@ -0,0 +1,83 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// DefaultNextAuctionID is the starting poiint for auction IDs.
|
||||
const DefaultNextAuctionID uint64 = 1
|
||||
|
||||
// GenesisAuction is an interface that extends the auction interface to add functionality needed for initializing auctions from genesis.
|
||||
type GenesisAuction interface {
|
||||
Auction
|
||||
GetModuleAccountCoins() sdk.Coins
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// GenesisAuctions is a slice of genesis auctions.
|
||||
type GenesisAuctions []GenesisAuction
|
||||
|
||||
// GenesisState is auction state that must be provided at chain genesis.
|
||||
type GenesisState struct {
|
||||
NextAuctionID uint64 `json:"next_auction_id" yaml:"next_auction_id"`
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
Auctions GenesisAuctions `json:"auctions" yaml:"auctions"`
|
||||
}
|
||||
|
||||
// NewGenesisState returns a new genesis state object for auctions module.
|
||||
func NewGenesisState(nextID uint64, ap Params, ga GenesisAuctions) GenesisState {
|
||||
return GenesisState{
|
||||
NextAuctionID: nextID,
|
||||
Params: ap,
|
||||
Auctions: ga,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultGenesisState returns the default genesis state for auction module.
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return NewGenesisState(
|
||||
DefaultNextAuctionID,
|
||||
DefaultParams(),
|
||||
GenesisAuctions{},
|
||||
)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
ids := map[uint64]bool{}
|
||||
for _, a := range gs.Auctions {
|
||||
|
||||
if err := a.Validate(); err != nil {
|
||||
return fmt.Errorf("found invalid auction: %w", err)
|
||||
}
|
||||
|
||||
if ids[a.GetID()] {
|
||||
return fmt.Errorf("found duplicate auction ID (%d)", a.GetID())
|
||||
}
|
||||
ids[a.GetID()] = true
|
||||
|
||||
if a.GetID() >= gs.NextAuctionID {
|
||||
return fmt.Errorf("found auction ID >= the nextAuctionID (%d >= %d)", a.GetID(), gs.NextAuctionID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
46
x/auction/types/genesis_test.go
Normal file
46
x/auction/types/genesis_test.go
Normal file
@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var testCoin = sdk.NewInt64Coin("test", 20)
|
||||
|
||||
func TestGenesisState_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
nextID uint64
|
||||
auctions GenesisAuctions
|
||||
expectPass bool
|
||||
}{
|
||||
{"default", DefaultGenesisState().NextAuctionID, DefaultGenesisState().Auctions, true},
|
||||
{"invalid next ID", 54, GenesisAuctions{SurplusAuction{BaseAuction{ID: 105}}}, false},
|
||||
{
|
||||
"repeated ID",
|
||||
1000,
|
||||
GenesisAuctions{
|
||||
SurplusAuction{BaseAuction{ID: 105}},
|
||||
DebtAuction{BaseAuction{ID: 105}, testCoin},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gs := NewGenesisState(tc.nextID, DefaultParams(), tc.auctions)
|
||||
|
||||
err := gs.Validate()
|
||||
|
||||
if tc.expectPass {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
55
x/auction/types/keys.go
Normal file
55
x/auction/types/keys.go
Normal file
@ -0,0 +1,55 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// ModuleName The name that will be used throughout the module
|
||||
ModuleName = "auction"
|
||||
|
||||
// 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 (
|
||||
AuctionKeyPrefix = []byte{0x00} // prefix for keys that store auctions
|
||||
AuctionByTimeKeyPrefix = []byte{0x01} // prefix for keys that are part of the auctionsByTime index
|
||||
|
||||
NextAuctionIDKey = []byte{0x02} // key for the next auction id
|
||||
)
|
||||
|
||||
// GetAuctionKey returns the bytes of an auction key
|
||||
func GetAuctionKey(auctionID uint64) []byte {
|
||||
return Uint64ToBytes(auctionID)
|
||||
}
|
||||
|
||||
// GetAuctionByTimeKey returns the key for iterating auctions by time
|
||||
func GetAuctionByTimeKey(endTime time.Time, auctionID uint64) []byte {
|
||||
return append(sdk.FormatTimeBytes(endTime), Uint64ToBytes(auctionID)...)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
53
x/auction/types/msg.go
Normal file
53
x/auction/types/msg.go
Normal file
@ -0,0 +1,53 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// ensure Msg interface compliance at compile time
|
||||
var _ sdk.Msg = &MsgPlaceBid{}
|
||||
|
||||
// MsgPlaceBid is the message type used to place a bid on any type of auction.
|
||||
type MsgPlaceBid struct {
|
||||
AuctionID uint64 `json:"auction_id" yaml:"auction_id"`
|
||||
Bidder sdk.AccAddress `json:"bidder" yaml:"bidder"`
|
||||
Amount sdk.Coin `json:"amount" yaml:"amount"` // The new bid or lot to be set on the auction.
|
||||
}
|
||||
|
||||
// NewMsgPlaceBid returns a new MsgPlaceBid.
|
||||
func NewMsgPlaceBid(auctionID uint64, bidder sdk.AccAddress, amt sdk.Coin) MsgPlaceBid {
|
||||
return MsgPlaceBid{
|
||||
AuctionID: auctionID,
|
||||
Bidder: bidder,
|
||||
Amount: amt,
|
||||
}
|
||||
}
|
||||
|
||||
// Route return the message type used for routing the message.
|
||||
func (msg MsgPlaceBid) Route() string { return RouterKey }
|
||||
|
||||
// Type returns a human-readable string for the message, intended for utilization within tags.
|
||||
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 {
|
||||
if msg.Bidder.Empty() {
|
||||
return sdk.ErrInvalidAddress("invalid (empty) bidder address")
|
||||
}
|
||||
if !msg.Amount.IsValid() {
|
||||
return sdk.ErrInvalidCoins(fmt.Sprintf("invalid bid amount: %s", msg.Amount))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSignBytes gets the canonical byte representation of the Msg.
|
||||
func (msg MsgPlaceBid) GetSignBytes() []byte {
|
||||
bz := ModuleCdc.MustMarshalJSON(msg)
|
||||
return sdk.MustSortJSON(bz)
|
||||
}
|
||||
|
||||
// GetSigners returns the addresses of signers that must sign.
|
||||
func (msg MsgPlaceBid) GetSigners() []sdk.AccAddress {
|
||||
return []sdk.AccAddress{msg.Bidder}
|
||||
}
|
42
x/auction/types/msg_test.go
Normal file
42
x/auction/types/msg_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestMsgPlaceBid_ValidateBasic(t *testing.T) {
|
||||
addr := sdk.AccAddress([]byte("someName"))
|
||||
tests := []struct {
|
||||
name string
|
||||
msg MsgPlaceBid
|
||||
expectPass bool
|
||||
}{
|
||||
{"normal",
|
||||
NewMsgPlaceBid(0, addr, c("token", 10)),
|
||||
true},
|
||||
{"emptyAddr",
|
||||
NewMsgPlaceBid(0, sdk.AccAddress{}, c("token", 10)),
|
||||
false},
|
||||
{"negativeAmount",
|
||||
NewMsgPlaceBid(0, addr, sdk.Coin{Denom: "token", Amount: sdk.NewInt(-10)}),
|
||||
false},
|
||||
{"zeroAmount",
|
||||
NewMsgPlaceBid(0, addr, c("token", 0)),
|
||||
true},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.expectPass {
|
||||
require.NoError(t, tc.msg.ValidateBasic())
|
||||
} else {
|
||||
require.Error(t, tc.msg.ValidateBasic())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
90
x/auction/types/params.go
Normal file
90
x/auction/types/params.go
Normal file
@ -0,0 +1,90 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
||||
)
|
||||
|
||||
// Defaults for auction params
|
||||
const (
|
||||
// DefaultMaxAuctionDuration max length of auction
|
||||
DefaultMaxAuctionDuration time.Duration = 2 * 24 * time.Hour
|
||||
// DefaultBidDuration how long an auction gets extended when someone bids
|
||||
DefaultBidDuration time.Duration = 1 * time.Hour
|
||||
)
|
||||
|
||||
// Parameter keys
|
||||
var (
|
||||
// ParamStoreKeyParams Param store key for auction params
|
||||
KeyAuctionBidDuration = []byte("BidDuration")
|
||||
KeyAuctionDuration = []byte("MaxAuctionDuration")
|
||||
)
|
||||
|
||||
var _ subspace.ParamSet = &Params{}
|
||||
|
||||
// Params is the governance parameters for the auction module.
|
||||
type Params struct {
|
||||
MaxAuctionDuration time.Duration `json:"max_auction_duration" yaml:"max_auction_duration"` // max length of auction
|
||||
BidDuration time.Duration `json:"bid_duration" yaml:"bid_duration"` // additional time added to the auction end time after each bid, capped by the expiry.
|
||||
}
|
||||
|
||||
// NewParams returns a new Params object.
|
||||
func NewParams(maxAuctionDuration time.Duration, bidDuration time.Duration) Params {
|
||||
return Params{
|
||||
MaxAuctionDuration: maxAuctionDuration,
|
||||
BidDuration: bidDuration,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultParams returns the default parameters for auctions.
|
||||
func DefaultParams() Params {
|
||||
return NewParams(
|
||||
DefaultMaxAuctionDuration,
|
||||
DefaultBidDuration,
|
||||
)
|
||||
}
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
func ParamKeyTable() subspace.KeyTable {
|
||||
return subspace.NewKeyTable().RegisterParamSet(&Params{})
|
||||
}
|
||||
|
||||
// ParamSetPairs implements the ParamSet interface and returns all the key/value pairs.
|
||||
func (p *Params) ParamSetPairs() subspace.ParamSetPairs {
|
||||
return subspace.ParamSetPairs{
|
||||
{Key: KeyAuctionBidDuration, Value: &p.BidDuration},
|
||||
{Key: KeyAuctionDuration, Value: &p.MaxAuctionDuration},
|
||||
}
|
||||
}
|
||||
|
||||
// Equal returns a boolean determining if two Params types are identical.
|
||||
func (p Params) Equal(p2 Params) bool {
|
||||
bz1 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&p)
|
||||
bz2 := ModuleCdc.MustMarshalBinaryLengthPrefixed(&p2)
|
||||
return bytes.Equal(bz1, bz2)
|
||||
}
|
||||
|
||||
// String implements stringer interface
|
||||
func (p Params) String() string {
|
||||
return fmt.Sprintf(`Auction Params:
|
||||
Max Auction Duration: %s
|
||||
Bid Duration: %s`, p.MaxAuctionDuration, p.BidDuration)
|
||||
}
|
||||
|
||||
// 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 p.MaxAuctionDuration < 0 {
|
||||
return sdk.ErrInternal("max auction duration cannot be negative")
|
||||
}
|
||||
if p.BidDuration > p.MaxAuctionDuration {
|
||||
return sdk.ErrInternal("bid duration param cannot be larger than max auction duration")
|
||||
}
|
||||
return nil
|
||||
}
|
38
x/auction/types/params_test.go
Normal file
38
x/auction/types/params_test.go
Normal file
@ -0,0 +1,38 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParams_Validate(t *testing.T) {
|
||||
type fields struct {
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
MaxAuctionDuration time.Duration
|
||||
BidDuration time.Duration
|
||||
expectErr bool
|
||||
}{
|
||||
{"normal", 24 * time.Hour, 1 * time.Hour, false},
|
||||
{"negativeBid", 24 * time.Hour, -1 * time.Hour, true},
|
||||
{"negativeAuction", -24 * time.Hour, 1 * time.Hour, true},
|
||||
{"bid>auction", 1 * time.Hour, 24 * time.Hour, true},
|
||||
{"zeros", 0, 0, false},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := Params{
|
||||
MaxAuctionDuration: tc.MaxAuctionDuration,
|
||||
BidDuration: tc.BidDuration,
|
||||
}
|
||||
err := p.Validate()
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
46
x/auction/types/querier.go
Normal file
46
x/auction/types/querier.go
Normal file
@ -0,0 +1,46 @@
|
||||
package types
|
||||
|
||||
const (
|
||||
// QueryGetAuction is the query path for querying one auction
|
||||
QueryGetAuction = "auction"
|
||||
// QueryGetAuctions is the query path for querying all auctions
|
||||
QueryGetAuctions = "auctions"
|
||||
// QueryGetParams is the query path for querying the global auction params
|
||||
QueryGetParams = "params"
|
||||
)
|
||||
|
||||
// QueryAuctionParams params for query /auction/auction
|
||||
type QueryAuctionParams struct {
|
||||
AuctionID uint64
|
||||
}
|
||||
|
||||
// QueryAllAuctionParams is the params for an auctions query
|
||||
type QueryAllAuctionParams struct {
|
||||
Page int `json:"page" yaml:"page"`
|
||||
Limit int `json:"limit" yaml:"limit"`
|
||||
}
|
||||
|
||||
// NewQueryAllAuctionParams creates a new QueryAllAuctionParams
|
||||
func NewQueryAllAuctionParams(page int, limit int) QueryAllAuctionParams {
|
||||
return QueryAllAuctionParams{
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
// AuctionWithPhase augmented type for collateral auctions which includes auction phase for querying
|
||||
type AuctionWithPhase struct {
|
||||
Auction Auction `json:"auction" yaml:"auction"`
|
||||
|
||||
Type string `json:"type" yaml:"type"`
|
||||
Phase string `json:"phase" yaml:"phase"`
|
||||
}
|
||||
|
||||
// NewAuctionWithPhase returns new AuctionWithPhase
|
||||
func NewAuctionWithPhase(a Auction) AuctionWithPhase {
|
||||
return AuctionWithPhase{
|
||||
Auction: a,
|
||||
Type: a.GetType(),
|
||||
Phase: a.GetPhase(),
|
||||
}
|
||||
}
|
47
x/cdp/abci.go
Normal file
47
x/cdp/abci.go
Normal file
@ -0,0 +1,47 @@
|
||||
package cdp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
// 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()
|
||||
}
|
||||
timeElapsed := 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)
|
||||
}
|
||||
|
||||
err := k.LiquidateCdps(ctx, cp.MarketID, cp.Denom, cp.LiquidationRatio)
|
||||
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.RunSurplusAndDebtAuctions(ctx)
|
||||
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.SetPreviousBlockTime(ctx, ctx.BlockTime())
|
||||
return
|
||||
}
|
181
x/cdp/abci_test.go
Normal file
181
x/cdp/abci_test.go
Normal file
@ -0,0 +1,181 @@
|
||||
package cdp_test
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/simulation"
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/auction"
|
||||
"github.com/kava-labs/kava/x/cdp"
|
||||
"github.com/stretchr/testify/suite"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
type ModuleTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
keeper cdp.Keeper
|
||||
addrs []sdk.AccAddress
|
||||
app app.TestApp
|
||||
cdps cdp.CDPs
|
||||
ctx sdk.Context
|
||||
liquidations liquidationTracker
|
||||
}
|
||||
|
||||
type liquidationTracker struct {
|
||||
xrp []uint64
|
||||
btc []uint64
|
||||
debt int64
|
||||
}
|
||||
|
||||
func (suite *ModuleTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
coins := []sdk.Coins{}
|
||||
tracker := liquidationTracker{}
|
||||
|
||||
for j := 0; j < 100; j++ {
|
||||
coins = append(coins, cs(c("btc", 100000000), c("xrp", 10000000000)))
|
||||
}
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(100)
|
||||
|
||||
authGS := app.NewAuthGenState(
|
||||
addrs, coins)
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
)
|
||||
suite.ctx = ctx
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetCDPKeeper()
|
||||
suite.cdps = cdp.CDPs{}
|
||||
suite.addrs = addrs
|
||||
suite.liquidations = tracker
|
||||
}
|
||||
|
||||
func (suite *ModuleTestSuite) createCdps() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
cdps := make(cdp.CDPs, 100)
|
||||
_, addrs := app.GeneratePrivKeyAddressPairs(100)
|
||||
coins := []sdk.Coins{}
|
||||
tracker := liquidationTracker{}
|
||||
|
||||
for j := 0; j < 100; j++ {
|
||||
coins = append(coins, cs(c("btc", 100000000), c("xrp", 10000000000)))
|
||||
}
|
||||
|
||||
authGS := app.NewAuthGenState(
|
||||
addrs, coins)
|
||||
tApp.InitializeFromGenesisStates(
|
||||
authGS,
|
||||
NewPricefeedGenStateMulti(),
|
||||
NewCDPGenStateMulti(),
|
||||
)
|
||||
|
||||
suite.ctx = ctx
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetCDPKeeper()
|
||||
|
||||
for j := 0; j < 100; j++ {
|
||||
collateral := "xrp"
|
||||
amount := 10000000000
|
||||
debt := simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 750000000, 1249000000)
|
||||
if j%2 == 0 {
|
||||
collateral = "btc"
|
||||
amount = 100000000
|
||||
debt = simulation.RandIntBetween(rand.New(rand.NewSource(int64(j))), 2700000000, 5332000000)
|
||||
if debt >= 4000000000 {
|
||||
tracker.btc = append(tracker.btc, uint64(j+1))
|
||||
tracker.debt += int64(debt)
|
||||
}
|
||||
} else {
|
||||
if debt >= 1000000000 {
|
||||
tracker.xrp = append(tracker.xrp, uint64(j+1))
|
||||
tracker.debt += int64(debt)
|
||||
}
|
||||
}
|
||||
suite.Nil(suite.keeper.AddCdp(suite.ctx, addrs[j], cs(c(collateral, int64(amount))), cs(c("usdx", int64(debt)))))
|
||||
c, f := suite.keeper.GetCDP(suite.ctx, collateral, uint64(j+1))
|
||||
suite.True(f)
|
||||
cdps[j] = c
|
||||
}
|
||||
|
||||
suite.cdps = cdps
|
||||
suite.addrs = addrs
|
||||
suite.liquidations = tracker
|
||||
}
|
||||
|
||||
func (suite *ModuleTestSuite) setPrice(price sdk.Dec, market string) {
|
||||
pfKeeper := suite.app.GetPriceFeedKeeper()
|
||||
|
||||
pfKeeper.SetPrice(suite.ctx, sdk.AccAddress{}, market, price, suite.ctx.BlockTime().Add(time.Hour*3))
|
||||
err := pfKeeper.SetCurrentPrices(suite.ctx, market)
|
||||
suite.NoError(err)
|
||||
pp, err := pfKeeper.GetCurrentPrice(suite.ctx, market)
|
||||
suite.NoError(err)
|
||||
suite.Equal(price, pp.Price)
|
||||
}
|
||||
func (suite *ModuleTestSuite) TestBeginBlock() {
|
||||
suite.createCdps()
|
||||
sk := suite.app.GetSupplyKeeper()
|
||||
acc := sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
|
||||
originalXrpCollateral := acc.GetCoins().AmountOf("xrp")
|
||||
suite.setPrice(d("0.2"), "xrp:usd")
|
||||
cdp.BeginBlocker(suite.ctx, abci.RequestBeginBlock{Header: suite.ctx.BlockHeader()}, suite.keeper)
|
||||
acc = sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
|
||||
finalXrpCollateral := acc.GetCoins().AmountOf("xrp")
|
||||
seizedXrpCollateral := originalXrpCollateral.Sub(finalXrpCollateral)
|
||||
xrpLiquidations := int(seizedXrpCollateral.Quo(i(10000000000)).Int64())
|
||||
suite.Equal(len(suite.liquidations.xrp), xrpLiquidations)
|
||||
|
||||
acc = sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
|
||||
originalBtcCollateral := acc.GetCoins().AmountOf("btc")
|
||||
suite.setPrice(d("6000"), "btc:usd")
|
||||
cdp.BeginBlocker(suite.ctx, abci.RequestBeginBlock{Header: suite.ctx.BlockHeader()}, suite.keeper)
|
||||
acc = sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
|
||||
finalBtcCollateral := acc.GetCoins().AmountOf("btc")
|
||||
seizedBtcCollateral := originalBtcCollateral.Sub(finalBtcCollateral)
|
||||
btcLiquidations := int(seizedBtcCollateral.Quo(i(100000000)).Int64())
|
||||
suite.Equal(len(suite.liquidations.btc), btcLiquidations)
|
||||
|
||||
acc = sk.GetModuleAccount(suite.ctx, auction.ModuleName)
|
||||
suite.Equal(suite.liquidations.debt, acc.GetCoins().AmountOf("debt").Int64())
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
suite.Equal(i(1000000000), cdpMacc.GetCoins().AmountOf("debt"))
|
||||
for i := 0; i < 100; i++ {
|
||||
suite.ctx = suite.ctx.WithBlockTime(suite.ctx.BlockTime().Add(time.Second * 6))
|
||||
cdp.BeginBlocker(suite.ctx, abci.RequestBeginBlock{Header: suite.ctx.BlockHeader()}, suite.keeper)
|
||||
}
|
||||
|
||||
cdpMacc = sk.GetModuleAccount(suite.ctx, cdp.ModuleName)
|
||||
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)
|
||||
}
|
||||
|
||||
func TestModuleTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(ModuleTestSuite))
|
||||
}
|
169
x/cdp/alias.go
Normal file
169
x/cdp/alias.go
Normal file
@ -0,0 +1,169 @@
|
||||
// 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/
|
||||
package cdp
|
||||
|
||||
import (
|
||||
"github.com/kava-labs/kava/x/cdp/keeper"
|
||||
"github.com/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
const (
|
||||
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
|
||||
EventTypeCdpRepay = types.EventTypeCdpRepay
|
||||
EventTypeCdpClose = types.EventTypeCdpClose
|
||||
EventTypeCdpWithdrawal = types.EventTypeCdpWithdrawal
|
||||
EventTypeCdpLiquidation = types.EventTypeCdpLiquidation
|
||||
EventTypeBeginBlockerFatal = types.EventTypeBeginBlockerFatal
|
||||
AttributeKeyCdpID = types.AttributeKeyCdpID
|
||||
AttributeKeyDepositor = types.AttributeKeyDepositor
|
||||
AttributeValueCategory = types.AttributeValueCategory
|
||||
AttributeKeyError = types.AttributeKeyError
|
||||
ModuleName = types.ModuleName
|
||||
StoreKey = types.StoreKey
|
||||
RouterKey = types.RouterKey
|
||||
QuerierRoute = types.QuerierRoute
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
LiquidatorMacc = types.LiquidatorMacc
|
||||
QueryGetCdp = types.QueryGetCdp
|
||||
QueryGetCdps = types.QueryGetCdps
|
||||
QueryGetCdpsByCollateralization = types.QueryGetCdpsByCollateralization
|
||||
QueryGetParams = types.QueryGetParams
|
||||
RestOwner = types.RestOwner
|
||||
RestCollateralDenom = types.RestCollateralDenom
|
||||
RestRatio = types.RestRatio
|
||||
)
|
||||
|
||||
var (
|
||||
// functions aliases
|
||||
NewCDP = types.NewCDP
|
||||
RegisterCodec = types.RegisterCodec
|
||||
NewDeposit = types.NewDeposit
|
||||
ErrCdpAlreadyExists = types.ErrCdpAlreadyExists
|
||||
ErrInvalidCollateralLength = types.ErrInvalidCollateralLength
|
||||
ErrCollateralNotSupported = types.ErrCollateralNotSupported
|
||||
ErrDebtNotSupported = types.ErrDebtNotSupported
|
||||
ErrExceedsDebtLimit = types.ErrExceedsDebtLimit
|
||||
ErrInvalidCollateralRatio = types.ErrInvalidCollateralRatio
|
||||
ErrCdpNotFound = types.ErrCdpNotFound
|
||||
ErrDepositNotFound = types.ErrDepositNotFound
|
||||
ErrInvalidDepositDenom = types.ErrInvalidDepositDenom
|
||||
ErrInvalidPaymentDenom = types.ErrInvalidPaymentDenom
|
||||
ErrDepositNotAvailable = types.ErrDepositNotAvailable
|
||||
ErrInvalidCollateralDenom = types.ErrInvalidCollateralDenom
|
||||
ErrInvalidWithdrawAmount = types.ErrInvalidWithdrawAmount
|
||||
ErrCdpNotAvailable = types.ErrCdpNotAvailable
|
||||
ErrBelowDebtFloor = types.ErrBelowDebtFloor
|
||||
ErrPaymentExceedsDebt = types.ErrPaymentExceedsDebt
|
||||
ErrLoadingAugmentedCDP = types.ErrLoadingAugmentedCDP
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
GetCdpIDBytes = types.GetCdpIDBytes
|
||||
GetCdpIDFromBytes = types.GetCdpIDFromBytes
|
||||
CdpKey = types.CdpKey
|
||||
SplitCdpKey = types.SplitCdpKey
|
||||
DenomIterKey = types.DenomIterKey
|
||||
SplitDenomIterKey = types.SplitDenomIterKey
|
||||
DepositKey = types.DepositKey
|
||||
SplitDepositKey = types.SplitDepositKey
|
||||
DepositIterKey = types.DepositIterKey
|
||||
SplitDepositIterKey = types.SplitDepositIterKey
|
||||
CollateralRatioBytes = types.CollateralRatioBytes
|
||||
CollateralRatioKey = types.CollateralRatioKey
|
||||
SplitCollateralRatioKey = types.SplitCollateralRatioKey
|
||||
CollateralRatioIterKey = types.CollateralRatioIterKey
|
||||
SplitCollateralRatioIterKey = types.SplitCollateralRatioIterKey
|
||||
NewMsgCreateCDP = types.NewMsgCreateCDP
|
||||
NewMsgDeposit = types.NewMsgDeposit
|
||||
NewMsgWithdraw = types.NewMsgWithdraw
|
||||
NewMsgDrawDebt = types.NewMsgDrawDebt
|
||||
NewMsgRepayDebt = types.NewMsgRepayDebt
|
||||
NewParams = types.NewParams
|
||||
DefaultParams = types.DefaultParams
|
||||
ParamKeyTable = types.ParamKeyTable
|
||||
NewQueryCdpsParams = types.NewQueryCdpsParams
|
||||
NewQueryCdpParams = types.NewQueryCdpParams
|
||||
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
|
||||
)
|
||||
|
||||
type (
|
||||
CDP = types.CDP
|
||||
CDPs = types.CDPs
|
||||
AugmentedCDP = types.AugmentedCDP
|
||||
AugmentedCDPs = types.AugmentedCDPs
|
||||
Deposit = types.Deposit
|
||||
Deposits = types.Deposits
|
||||
SupplyKeeper = types.SupplyKeeper
|
||||
PricefeedKeeper = types.PricefeedKeeper
|
||||
GenesisState = types.GenesisState
|
||||
MsgCreateCDP = types.MsgCreateCDP
|
||||
MsgDeposit = types.MsgDeposit
|
||||
MsgWithdraw = types.MsgWithdraw
|
||||
MsgDrawDebt = types.MsgDrawDebt
|
||||
MsgRepayDebt = types.MsgRepayDebt
|
||||
Params = types.Params
|
||||
CollateralParam = types.CollateralParam
|
||||
CollateralParams = types.CollateralParams
|
||||
DebtParam = types.DebtParam
|
||||
DebtParams = types.DebtParams
|
||||
QueryCdpsParams = types.QueryCdpsParams
|
||||
QueryCdpParams = types.QueryCdpParams
|
||||
QueryCdpsByRatioParams = types.QueryCdpsByRatioParams
|
||||
Keeper = keeper.Keeper
|
||||
)
|
227
x/cdp/client/cli/query.go
Normal file
227
x/cdp/client/cli/query.go
Normal file
@ -0,0 +1,227 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/kava-labs/kava/x/cdp/types"
|
||||
)
|
||||
|
||||
// GetQueryCmd returns the cli query commands for this module
|
||||
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
// Group nameservice queries under a subcommand
|
||||
cdpQueryCmd := &cobra.Command{
|
||||
Use: "cdp",
|
||||
Short: "Querying commands for the cdp module",
|
||||
}
|
||||
|
||||
cdpQueryCmd.AddCommand(client.GetCommands(
|
||||
QueryCdpCmd(queryRoute, cdc),
|
||||
QueryCdpsByDenomCmd(queryRoute, cdc),
|
||||
QueryCdpsByDenomAndRatioCmd(queryRoute, cdc),
|
||||
QueryCdpDepositsCmd(queryRoute, cdc),
|
||||
QueryParamsCmd(queryRoute, cdc),
|
||||
)...)
|
||||
|
||||
return cdpQueryCmd
|
||||
}
|
||||
|
||||
// QueryCdpCmd returns the command handler for querying a particular cdp
|
||||
func QueryCdpCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "cdp [owner-addr] [collateral-name]",
|
||||
Short: "get info about a cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Get a CDP by the owner address and the collateral name.
|
||||
|
||||
Example:
|
||||
$ %s query %s cdp kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw uatom
|
||||
`, 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.QueryCdpParams{
|
||||
CollateralDenom: args[1],
|
||||
Owner: ownerAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdp)
|
||||
res, _, err := cliCtx.QueryWithData(route, bz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode and print results
|
||||
var cdp types.AugmentedCDP
|
||||
cdc.MustUnmarshalJSON(res, &cdp)
|
||||
return cliCtx.PrintOutput(cdp)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryCdpsByDenomCmd returns the command handler for querying cdps for a collateral type
|
||||
func QueryCdpsByDenomCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "cdps [collateral-name]",
|
||||
Short: "query CDPs by collateral",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`List all CDPs collateralized with the specified asset.
|
||||
|
||||
Example:
|
||||
$ %s query %s cdps uatom
|
||||
`, version.ClientName, types.ModuleName)),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Prepare params for querier
|
||||
bz, err := cdc.MarshalJSON(types.QueryCdpsParams{CollateralDenom: args[0]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdps)
|
||||
res, _, err := cliCtx.QueryWithData(route, bz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode and print results
|
||||
var cdps types.AugmentedCDPs
|
||||
cdc.MustUnmarshalJSON(res, &cdps)
|
||||
return cliCtx.PrintOutput(cdps)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryCdpsByDenomAndRatioCmd returns the command handler for querying cdps
|
||||
// that are under the specified collateral ratio
|
||||
func QueryCdpsByDenomAndRatioCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "cdps-by-ratio [collateral-name] [collateralization-ratio]",
|
||||
Short: "get cdps under a collateralization ratio",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`List all CDPs under a specified collateralization ratio.
|
||||
Collateralization ratio is: collateral * price / debt.
|
||||
|
||||
Example:
|
||||
$ %s query %s cdps-by-ratio uatom 1.5
|
||||
`, 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
|
||||
ratio, errSdk := sdk.NewDecFromStr(args[1])
|
||||
if errSdk != nil {
|
||||
return fmt.Errorf(errSdk.Error())
|
||||
}
|
||||
bz, err := cdc.MarshalJSON(types.QueryCdpsByRatioParams{
|
||||
CollateralDenom: args[0],
|
||||
Ratio: ratio,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdpsByCollateralization)
|
||||
res, _, err := cliCtx.QueryWithData(route, bz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode and print results
|
||||
var cdps types.AugmentedCDPs
|
||||
cdc.MustUnmarshalJSON(res, &cdps)
|
||||
return cliCtx.PrintOutput(cdps)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryCdpDepositsCmd returns the command handler for querying the deposits of a particular cdp
|
||||
func QueryCdpDepositsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "deposits [owner-addr] [collateral-name]",
|
||||
Short: "get deposits for a cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Get the deposits of a CDP.
|
||||
|
||||
Example:
|
||||
$ %s query %s deposits kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw uatom
|
||||
`, 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.QueryCdpParams{
|
||||
CollateralDenom: args[1],
|
||||
Owner: ownerAddress,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetCdpDeposits)
|
||||
res, _, err := cliCtx.QueryWithData(route, bz)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Decode and print results
|
||||
var deposits types.Deposits
|
||||
cdc.MustUnmarshalJSON(res, &deposits)
|
||||
return cliCtx.PrintOutput(deposits)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// QueryParamsCmd returns the command handler for cdp parameter querying
|
||||
func QueryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "params",
|
||||
Short: "get the cdp module parameters",
|
||||
Long: "Get the current global cdp 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 out types.Params
|
||||
cdc.MustUnmarshalJSON(res, &out)
|
||||
return cliCtx.PrintOutput(out)
|
||||
},
|
||||
}
|
||||
}
|
198
x/cdp/client/cli/tx.go
Normal file
198
x/cdp/client/cli/tx.go
Normal file
@ -0,0 +1,198 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"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/cdp/types"
|
||||
)
|
||||
|
||||
// GetTxCmd returns the transaction commands for this module
|
||||
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
cdpTxCmd := &cobra.Command{
|
||||
Use: "cdp",
|
||||
Short: "cdp transactions subcommands",
|
||||
}
|
||||
|
||||
cdpTxCmd.AddCommand(client.PostCommands(
|
||||
GetCmdCreateCdp(cdc),
|
||||
GetCmdDeposit(cdc),
|
||||
GetCmdWithdraw(cdc),
|
||||
GetCmdDraw(cdc),
|
||||
GetCmdRepay(cdc),
|
||||
)...)
|
||||
|
||||
return cdpTxCmd
|
||||
}
|
||||
|
||||
// GetCmdCreateCdp returns the command handler for creating a cdp
|
||||
func GetCmdCreateCdp(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "create [collateral] [debt]",
|
||||
Short: "create a new cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Create a new cdp, depositing some collateral and drawing some debt.
|
||||
|
||||
Example:
|
||||
$ %s tx %s create 10000000uatom 1000usdx --from myKeyName
|
||||
`, 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))
|
||||
|
||||
collateral, err := sdk.ParseCoins(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
debt, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := types.NewMsgCreateCDP(cliCtx.GetFromAddress(), collateral, debt)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCmdDeposit cli command for depositing to a cdp.
|
||||
func GetCmdDeposit(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "deposit [owner-addr] [collateral]",
|
||||
Short: "deposit collateral to an existing cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Add collateral to an existing cdp.
|
||||
|
||||
Example:
|
||||
$ %s tx %s deposit kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw 10000000uatom --from myKeyName
|
||||
`, 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))
|
||||
|
||||
collateral, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
owner, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := types.NewMsgDeposit(owner, cliCtx.GetFromAddress(), collateral)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCmdWithdraw cli command for withdrawing from a cdp.
|
||||
func GetCmdWithdraw(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "withdraw [owner-addr] [collateral]",
|
||||
Short: "withdraw collateral from an existing cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Remove collateral from an existing cdp.
|
||||
|
||||
Example:
|
||||
$ %s tx %s withdraw kava15qdefkmwswysgg4qxgqpqr35k3m49pkx2jdfnw 10000000uatom --from myKeyName
|
||||
`, 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))
|
||||
|
||||
collateral, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
owner, err := sdk.AccAddressFromBech32(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := types.NewMsgWithdraw(owner, cliCtx.GetFromAddress(), collateral)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCmdDraw cli command for depositing to a cdp.
|
||||
func GetCmdDraw(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "draw [collateral-name] [debt]",
|
||||
Short: "draw debt off an existing cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Create debt in an existing cdp and send the newly minted asset to your account.
|
||||
|
||||
Example:
|
||||
$ %s tx %s draw uatom 1000usdx --from myKeyName
|
||||
`, 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))
|
||||
|
||||
debt, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := types.NewMsgDrawDebt(cliCtx.GetFromAddress(), args[0], debt)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// GetCmdRepay cli command for depositing to a cdp.
|
||||
func GetCmdRepay(cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "repay [collateral-name] [debt]",
|
||||
Short: "repay debt to an existing cdp",
|
||||
Long: strings.TrimSpace(
|
||||
fmt.Sprintf(`Cancel out debt in an existing cdp.
|
||||
|
||||
Example:
|
||||
$ %s tx %s repay uatom 1000usdx --from myKeyName
|
||||
`, 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))
|
||||
|
||||
payment, err := sdk.ParseCoins(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := types.NewMsgRepayDebt(cliCtx.GetFromAddress(), args[0], payment)
|
||||
err = msg.ValidateBasic()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.GenerateOrBroadcastMsgs(cliCtx, txBldr, []sdk.Msg{msg})
|
||||
},
|
||||
}
|
||||
}
|
183
x/cdp/client/rest/query.go
Normal file
183
x/cdp/client/rest/query.go
Normal file
@ -0,0 +1,183 @@
|
||||
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/cdp/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// define routes that get registered by the main application
|
||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc("/cdp/parameters", getParamsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/{%s}/{%s}", types.RestOwner, types.RestCollateralDenom), queryCdpHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/denom/{%s}", types.RestCollateralDenom), queryCdpsHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/ratio/{%s}/{%s}", types.RestCollateralDenom, types.RestRatio), queryCdpsByRatioHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc(fmt.Sprintf("/cdp/cdps/cdp/deposits/{%s}/{%s}", types.RestOwner, types.RestCollateralDenom), queryCdpDepositsHandlerFn(cliCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
func queryCdpHandlerFn(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
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
ownerBech32 := vars[types.RestOwner]
|
||||
collateralDenom := vars[types.RestCollateralDenom]
|
||||
|
||||
owner, err := sdk.AccAddressFromBech32(ownerBech32)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryCdpParams(owner, collateralDenom)
|
||||
|
||||
bz, err := cliCtx.Codec.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetCdp), bz)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func queryCdpsHandlerFn(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
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
collateralDenom := vars[types.RestCollateralDenom]
|
||||
|
||||
params := types.NewQueryCdpsParams(collateralDenom)
|
||||
|
||||
bz, err := cliCtx.Codec.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetCdps), bz)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func queryCdpsByRatioHandlerFn(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
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
collateralDenom := vars[types.RestCollateralDenom]
|
||||
ratioStr := vars[types.RestRatio]
|
||||
|
||||
ratioDec, sdkError := sdk.NewDecFromStr(ratioStr)
|
||||
if sdkError != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, sdkError.Error())
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryCdpsByRatioParams(collateralDenom, ratioDec)
|
||||
bz, err := cliCtx.Codec.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetCdpsByCollateralization), bz)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func queryCdpDepositsHandlerFn(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
|
||||
}
|
||||
vars := mux.Vars(r)
|
||||
ownerBech32 := vars[types.RestOwner]
|
||||
collateralDenom := vars[types.RestCollateralDenom]
|
||||
|
||||
owner, err := sdk.AccAddressFromBech32(ownerBech32)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
params := types.NewQueryCdpDeposits(owner, collateralDenom)
|
||||
|
||||
bz, err := cliCtx.Codec.MarshalJSON(params)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetCdpDeposits), bz)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func getParamsHandlerFn(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
|
||||
}
|
||||
// Get the params
|
||||
res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/cdp/%s", types.QueryGetParams), nil)
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
// Return the params
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
55
x/cdp/client/rest/rest.go
Normal file
55
x/cdp/client/rest/rest.go
Normal file
@ -0,0 +1,55 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// RegisterRoutes - Central function to define routes that get registered by the main application
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
registerQueryRoutes(cliCtx, r)
|
||||
registerTxRoutes(cliCtx, r)
|
||||
}
|
||||
|
||||
// PostCdpReq defines the properties of cdp request's body.
|
||||
type PostCdpReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Sender sdk.AccAddress `json:"sender" yaml:"sender"`
|
||||
Collateral sdk.Coins `json:"collateral" yaml:"collateral"`
|
||||
Principal sdk.Coins `json:"principal" yaml:"principal"`
|
||||
}
|
||||
|
||||
// PostDepositReq defines the properties of cdp request's body.
|
||||
type PostDepositReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
|
||||
Collateral sdk.Coins `json:"collateral" yaml:"collateral"`
|
||||
}
|
||||
|
||||
// PostWithdrawalReq defines the properties of cdp request's body.
|
||||
type PostWithdrawalReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||
Depositor sdk.AccAddress `json:"depositor" yaml:"depositor"`
|
||||
Collateral sdk.Coins `json:"collateral" yaml:"collateral"`
|
||||
}
|
||||
|
||||
// PostDrawReq defines the properties of cdp request's body.
|
||||
type PostDrawReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
Principal sdk.Coins `json:"principal" yaml:"principal"`
|
||||
}
|
||||
|
||||
// PostRepayReq defines the properties of cdp request's body.
|
||||
type PostRepayReq struct {
|
||||
BaseReq rest.BaseReq `json:"base_req" yaml:"base_req"`
|
||||
Owner sdk.AccAddress `json:"owner" yaml:"owner"`
|
||||
Denom string `json:"denom" yaml:"denom"`
|
||||
Payment sdk.Coins `json:"payment" yaml:"payment"`
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user