update store methods

This commit is contained in:
rhuairahrighairigh 2019-12-21 01:04:04 +00:00
parent 5618e11990
commit 5363541de3
10 changed files with 332 additions and 291 deletions

View File

@ -151,7 +151,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace)
auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace) auctionSubspace := app.paramsKeeper.Subspace(auction.DefaultParamspace)
cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace) cdpSubspace := app.paramsKeeper.Subspace(cdp.DefaultParamspace)
liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace) //liquidatorSubspace := app.paramsKeeper.Subspace(liquidator.DefaultParamspace)
pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace) pricefeedSubspace := app.paramsKeeper.Subspace(pricefeed.DefaultParamspace)
// add keepers // add keepers
@ -237,16 +237,16 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
app.bankKeeper) app.bankKeeper)
app.auctionKeeper = auction.NewKeeper( app.auctionKeeper = auction.NewKeeper(
app.cdc, app.cdc,
app.cdpKeeper, // CDP keeper standing in for bank
keys[auction.StoreKey], keys[auction.StoreKey],
app.supplyKeeper, // CDP keeper standing in for bank
auctionSubspace) auctionSubspace)
app.liquidatorKeeper = liquidator.NewKeeper( // app.liquidatorKeeper = liquidator.NewKeeper(
app.cdc, // app.cdc,
keys[liquidator.StoreKey], // keys[liquidator.StoreKey],
liquidatorSubspace, // liquidatorSubspace,
app.cdpKeeper, // app.cdpKeeper,
app.auctionKeeper, // app.auctionKeeper,
app.cdpKeeper) // CDP keeper standing in for bank // app.cdpKeeper) // CDP keeper standing in for bank
// register the staking hooks // register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks

View File

@ -7,17 +7,16 @@ import (
// EndBlocker runs at the end of every block. // EndBlocker runs at the end of every block.
func EndBlocker(ctx sdk.Context, k Keeper) { func EndBlocker(ctx sdk.Context, k Keeper) {
// get an iterator of expired auctions var expiredAuctions []ID
expiredAuctions := k.GetQueueIterator(ctx, EndTime(ctx.BlockHeight())) k.IterateAuctionsByTime(ctx, ctx.BlockTime(), func(id ID) bool {
defer expiredAuctions.Close() expiredAuctions = append(expiredAuctions, id)
return false
// loop through and close them - distribute funds, delete from store (and queue) })
for ; expiredAuctions.Valid(); expiredAuctions.Next() { // 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 {
auctionID := k.DecodeAuctionID(ctx, expiredAuctions.Value()) err := k.CloseAuction(ctx, id)
err := k.CloseAuction(ctx, auctionID)
if err != nil { if err != nil {
panic(err) // TODO how should errors be handled here? panic(err)
} }
} }
} }

View File

@ -49,13 +49,9 @@ type (
Auction = types.Auction Auction = types.Auction
BaseAuction = types.BaseAuction BaseAuction = types.BaseAuction
ID = types.ID ID = types.ID
EndTime = types.EndTime
BankInput = types.BankInput
BankOutput = types.BankOutput
ForwardAuction = types.ForwardAuction ForwardAuction = types.ForwardAuction
ReverseAuction = types.ReverseAuction ReverseAuction = types.ReverseAuction
ForwardReverseAuction = types.ForwardReverseAuction ForwardReverseAuction = types.ForwardReverseAuction
BankKeeper = types.BankKeeper
GenesisAuctions = types.GenesisAuctions GenesisAuctions = types.GenesisAuctions
GenesisState = types.GenesisState GenesisState = types.GenesisState
MsgPlaceBid = types.MsgPlaceBid MsgPlaceBid = types.MsgPlaceBid

View File

@ -18,13 +18,10 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
params := keeper.GetParams(ctx) params := keeper.GetParams(ctx)
var genAuctions GenesisAuctions var genAuctions GenesisAuctions
iterator := keeper.GetAuctionIterator(ctx) keeper.IterateAuctions(ctx, func(a Auction) bool {
genAuctions = append(genAuctions, a)
return false
})
for ; iterator.Valid(); iterator.Next() {
auction := keeper.DecodeAuction(ctx, iterator.Value())
genAuctions = append(genAuctions, auction)
}
return NewGenesisState(params, genAuctions) return NewGenesisState(params, genAuctions)
} }

View File

@ -2,6 +2,7 @@ package keeper
import ( import (
"fmt" "fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/supply" "github.com/cosmos/cosmos-sdk/x/supply"
@ -295,3 +296,8 @@ func (k Keeper) PayoutAuctionLot(ctx sdk.Context, a types.Auction) sdk.Error {
} }
return nil return nil
} }
// FIXME stand in func for compiler
func earliestTime(t1, t2 time.Time) time.Time {
return t1
}

View File

@ -2,6 +2,7 @@ package keeper_test
import ( import (
"testing" "testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -12,118 +13,118 @@ import (
"github.com/kava-labs/kava/x/auction/types" "github.com/kava-labs/kava/x/auction/types"
) )
func TestKeeper_ForwardAuction(t *testing.T) { // func TestKeeper_ForwardAuction(t *testing.T) {
// Setup // // Setup
_, addrs := app.GeneratePrivKeyAddressPairs(2) // _, addrs := app.GeneratePrivKeyAddressPairs(2)
seller := addrs[0] // seller := addrs[0]
buyer := addrs[1] // buyer := addrs[1]
tApp := app.NewTestApp() // tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates( // tApp.InitializeFromGenesisStates(
app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), // app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}),
) // )
ctx := tApp.NewContext(false, abci.Header{}) // ctx := tApp.NewContext(false, abci.Header{})
keeper := tApp.GetAuctionKeeper() // keeper := tApp.GetAuctionKeeper()
// Create an auction (lot: 20 t1, initialBid: 0 t2) // // Create an auction (lot: 20 t1, initialBid: 0 t2)
auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) // lot, initialBid // auctionID, err := keeper.StartForwardAuction(ctx, seller, c("token1", 20), c("token2", 0)) // lot, initialBid
require.NoError(t, err) // require.NoError(t, err)
// Check seller's coins have decreased // // Check seller's coins have decreased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) // tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100)))
// PlaceBid (bid: 10 t2, lot: same as starting) // // PlaceBid (bid: 10 t2, lot: same as starting)
require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot // require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 10), c("token1", 20))) // bid, lot
// Check buyer's coins have decreased // // Check buyer's coins have decreased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90))) // tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 90)))
// Check seller's coins have increased // // Check seller's coins have increased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) // tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110)))
// Close auction at just after auction expiry // // Close auction at just after auction expiry
ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) // ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration))
require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // require.NoError(t, keeper.CloseAuction(ctx, auctionID))
// Check buyer's coins increased // // Check buyer's coins increased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) // tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90)))
} // }
func TestKeeper_ReverseAuction(t *testing.T) { // func TestKeeper_ReverseAuction(t *testing.T) {
// Setup // // Setup
_, addrs := app.GeneratePrivKeyAddressPairs(2) // _, addrs := app.GeneratePrivKeyAddressPairs(2)
seller := addrs[0] // seller := addrs[0]
buyer := addrs[1] // buyer := addrs[1]
tApp := app.NewTestApp() // tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates( // tApp.InitializeFromGenesisStates(
app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), // app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}),
) // )
ctx := tApp.NewContext(false, abci.Header{}) // ctx := tApp.NewContext(false, abci.Header{})
keeper := tApp.GetAuctionKeeper() // keeper := tApp.GetAuctionKeeper()
// Start auction // // Start auction
auctionID, err := keeper.StartReverseAuction(ctx, buyer, c("token1", 20), c("token2", 99)) // buyer, bid, initialLot // auctionID, err := keeper.StartReverseAuction(ctx, buyer, c("token1", 20), c("token2", 99)) // buyer, bid, initialLot
require.NoError(t, err) // require.NoError(t, err)
// Check buyer's coins have decreased // // Check buyer's coins have decreased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 1))) // tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 1)))
// Place a bid // // Place a bid
require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot // require.NoError(t, keeper.PlaceBid(ctx, 0, seller, c("token1", 20), c("token2", 10))) // bid, lot
// Check seller's coins have decreased // // Check seller's coins have decreased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) // tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100)))
// Check buyer's coins have increased // // Check buyer's coins have increased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90))) // tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 120), c("token2", 90)))
// Close auction at just after auction expiry // // Close auction at just after auction expiry
ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) // ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration))
require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // require.NoError(t, keeper.CloseAuction(ctx, auctionID))
// Check seller's coins increased // // Check seller's coins increased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110))) // tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 110)))
} // }
func TestKeeper_ForwardReverseAuction(t *testing.T) { // func TestKeeper_ForwardReverseAuction(t *testing.T) {
// Setup // // Setup
_, addrs := app.GeneratePrivKeyAddressPairs(3) // _, addrs := app.GeneratePrivKeyAddressPairs(3)
seller := addrs[0] // seller := addrs[0]
buyer := addrs[1] // buyer := addrs[1]
recipient := addrs[2] // recipient := addrs[2]
tApp := app.NewTestApp() // tApp := app.NewTestApp()
tApp.InitializeFromGenesisStates( // tApp.InitializeFromGenesisStates(
app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}), // app.NewAuthGenState(addrs, []sdk.Coins{cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100)), cs(c("token1", 100), c("token2", 100))}),
) // )
ctx := tApp.NewContext(false, abci.Header{}) // ctx := tApp.NewContext(false, abci.Header{})
keeper := tApp.GetAuctionKeeper() // keeper := tApp.GetAuctionKeeper()
// Start auction // // Start auction
auctionID, err := keeper.StartForwardReverseAuction(ctx, seller, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson // auctionID, err := keeper.StartForwardReverseAuction(ctx, seller, c("token1", 20), c("token2", 50), recipient) // seller, lot, maxBid, otherPerson
require.NoError(t, err) // require.NoError(t, err)
// Check seller's coins have decreased // // Check seller's coins have decreased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100))) // tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 100)))
// Place a bid // // Place a bid
require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot // require.NoError(t, keeper.PlaceBid(ctx, 0, buyer, c("token2", 50), c("token1", 15))) // bid, lot
// Check bidder's coins have decreased // // Check bidder's coins have decreased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50))) // tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 100), c("token2", 50)))
// Check seller's coins have increased // // Check seller's coins have increased
tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 150))) // tApp.CheckBalance(t, ctx, seller, cs(c("token1", 80), c("token2", 150)))
// Check "recipient" has received coins // // Check "recipient" has received coins
tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100))) // tApp.CheckBalance(t, ctx, recipient, cs(c("token1", 105), c("token2", 100)))
// Close auction at just after auction expiry // // Close auction at just after auction expiry
ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration)) // ctx = ctx.WithBlockHeight(int64(types.DefaultMaxBidDuration))
require.NoError(t, keeper.CloseAuction(ctx, auctionID)) // require.NoError(t, keeper.CloseAuction(ctx, auctionID))
// Check buyer's coins increased // // Check buyer's coins increased
tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50))) // tApp.CheckBalance(t, ctx, buyer, cs(c("token1", 115), c("token2", 50)))
} // }
func TestKeeper_SetGetDeleteAuction(t *testing.T) { func SetGetDeleteAuction(t *testing.T) {
// setup keeper, create auction // setup keeper, create auction
_, addrs := app.GeneratePrivKeyAddressPairs(1)
tApp := app.NewTestApp() tApp := app.NewTestApp()
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
ctx := tApp.NewContext(true, abci.Header{}) ctx := tApp.NewContext(true, abci.Header{})
auction, _ := types.NewForwardAuction(addrs[0], c("usdx", 100), c("kava", 0), types.EndTime(1000)) someTime := time.Date(43, time.January, 1, 0, 0, 0, 0, time.UTC) // need to specify UTC as tz info is lost on unmarshal
auction := types.NewForwardAuction("some_module", c("usdx", 100), "kava", someTime)
id := types.ID(5) id := types.ID(5)
auction.SetID(id) auction.SetID(id)
@ -135,9 +136,9 @@ func TestKeeper_SetGetDeleteAuction(t *testing.T) {
require.True(t, found) require.True(t, found)
require.Equal(t, &auction, readAuction) require.Equal(t, &auction, readAuction)
// check auction is in queue // check auction is in queue
iter := keeper.GetQueueIterator(ctx, 100000) // iter := keeper.GetQueueIterator(ctx, 100000)
require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter))) // require.Equal(t, 1, len(convertIteratorToSlice(keeper, iter)))
iter.Close() // iter.Close()
// delete auction // delete auction
keeper.DeleteAuction(ctx, id) keeper.DeleteAuction(ctx, id)
@ -146,41 +147,93 @@ func TestKeeper_SetGetDeleteAuction(t *testing.T) {
_, found = keeper.GetAuction(ctx, id) _, found = keeper.GetAuction(ctx, id)
require.False(t, found) require.False(t, found)
// check auction not in queue // check auction not in queue
iter = keeper.GetQueueIterator(ctx, 100000) // iter = keeper.GetQueueIterator(ctx, 100000)
require.Equal(t, 0, len(convertIteratorToSlice(keeper, iter))) // require.Equal(t, 0, len(convertIteratorToSlice(keeper, iter)))
iter.Close() // iter.Close()
} }
// TODO convert to table driven test with more test cases func TestIncrementNextAuctionID(t *testing.T) {
func TestKeeper_ExpiredAuctionQueue(t *testing.T) {
// setup keeper // setup keeper
tApp := app.NewTestApp() tApp := app.NewTestApp()
keeper := tApp.GetAuctionKeeper() keeper := tApp.GetAuctionKeeper()
ctx := tApp.NewContext(true, abci.Header{}) ctx := tApp.NewContext(true, abci.Header{})
// create an example queue // store id
type queue []struct { id := types.ID(123456)
endTime types.EndTime 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 keeper
// tApp := app.NewTestApp()
// keeper := tApp.GetAuctionKeeper()
// ctx := tApp.NewContext(true, abci.Header{})
// auctions := []types.Auction{
// &types.ForwardAuction{},
// }
// for _, a := range auctions {
// keeper.SetAuction(ctx, a)
// }
// var readAuctions []types.Auction
// keeper.IterateAuctions(ctx, func(a types.Auction) bool {
// readAuctions = append(readAuctions, a)
// return false
// })
// require.Equal(t, auctions, readAuctions)
// }
func TestIterateAuctionsByTime(t *testing.T) {
// setup keeper
tApp := app.NewTestApp()
keeper := tApp.GetAuctionKeeper()
ctx := tApp.NewContext(true, abci.Header{})
// create a list of times
queue := []struct {
endTime time.Time
auctionID types.ID auctionID types.ID
}{
{time.Date(84, time.January, 1, 0, 0, 0, 0, time.UTC), 34345345},
{time.Date(98, time.January, 2, 0, 0, 0, 0, time.UTC), 5},
{time.Date(98, time.January, 2, 13, 5, 0, 0, time.UTC), 6},
{time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 1},
{time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 3},
{time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC), 4},
{time.Date(98, time.January, 2, 16, 0, 0, 1, time.UTC), 0}, // TODO tidy up redundant entries
}
cutoffTime := time.Date(98, time.January, 2, 16, 0, 0, 0, time.UTC)
var expectedQueue []types.ID
for _, i := range queue {
if i.endTime.After(cutoffTime) { // only append items where endTime ≤ cutoffTime
break
}
expectedQueue = append(expectedQueue, i.auctionID)
} }
q := queue{{1000, 0}, {1300, 2}, {5200, 1}}
// write and read queue // write and read queue
for _, v := range q { for _, v := range queue {
keeper.InsertIntoQueue(ctx, v.endTime, v.auctionID) keeper.InsertIntoQueue(ctx, v.endTime, v.auctionID)
} }
iter := keeper.GetQueueIterator(ctx, 1000) var readQueue []types.ID
keeper.IterateAuctionsByTime(ctx, cutoffTime, func(id types.ID) bool {
// check before and after match readQueue = append(readQueue, id)
i := 0 return false
for ; iter.Valid(); iter.Next() { })
var auctionID types.ID
tApp.Codec().MustUnmarshalBinaryLengthPrefixed(iter.Value(), &auctionID)
require.Equal(t, q[i].auctionID, auctionID)
i++
}
require.Equal(t, expectedQueue, readQueue)
} }
func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types.ID { func convertIteratorToSlice(keeper keeper.Keeper, iterator sdk.Iterator) []types.ID {

View File

@ -1,6 +1,7 @@
package keeper package keeper
import ( import (
"fmt"
"github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/auction/types" "github.com/kava-labs/kava/x/auction/types"
@ -20,18 +21,14 @@ func NewQuerier(keeper Keeper) sdk.Querier {
} }
func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) { func queryAuctions(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) (res []byte, err sdk.Error) {
var AuctionsList types.QueryResAuctions var auctionsList types.QueryResAuctions
iterator := keeper.GetAuctionIterator(ctx) keeper.IterateAuctions(ctx, func(a types.Auction) bool {
auctionsList = append(auctionsList, fmt.Sprintf("%+v", a)) // TODO formatting
return false
})
for ; iterator.Valid(); iterator.Next() { bz, err2 := codec.MarshalJSONIndent(keeper.cdc, auctionsList)
var auction types.Auction
keeper.cdc.MustUnmarshalBinaryBare(iterator.Value(), &auction)
AuctionsList = append(AuctionsList, auction.String())
}
bz, err2 := codec.MarshalJSONIndent(keeper.cdc, AuctionsList)
if err2 != nil { if err2 != nil {
panic("could not marshal result to JSON") panic("could not marshal result to JSON")
} }

View File

@ -1,91 +1,84 @@
package keeper package keeper
import ( import (
"bytes"
"fmt"
"time" "time"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/auction/types" "github.com/kava-labs/kava/x/auction/types"
) )
// set an auction in the store, adding a new ID, and setting indexes // SetNextAuctionID stores an ID to be used for the next created auction
func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) { func (k Keeper) SetNextAuctionID(ctx sdk.Context, id types.ID) {
// get ID store := ctx.KVStore(k.storeKey)
newAuctionID, err := k.getNextAuctionID(ctx) store.Set(types.NextAuctionIDKey, id.Bytes())
if err != nil {
return 0, err
}
// set ID
auction.SetID(newAuctionID)
// store auction
k.SetAuction(ctx, auction)
k.incrementNextAuctionID(ctx)
return newAuctionID, nil
} }
// getNextAuctionID gets the next available global AuctionID // GetNextAuctionID reads the next available global ID from store
func (k Keeper) getNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) { func (k Keeper) GetNextAuctionID(ctx sdk.Context) (types.ID, sdk.Error) {
// get next ID from store
store := ctx.KVStore(k.storeKey) store := ctx.KVStore(k.storeKey)
bz := store.Get(k.getNextAuctionIDKey()) bz := store.Get(types.NextAuctionIDKey)
if bz == nil { if bz == nil {
// if not found, set the id at 0 //return 0, types.ErrInvalidGenesis(k.codespace, "initial auction ID hasn't been set") // TODO create error
bz = k.cdc.MustMarshalBinaryLengthPrefixed(types.ID(0)) return 0, sdk.ErrInternal("initial auction ID hasn't been set")
store.Set(k.getNextAuctionIDKey(), bz)
// TODO Set auction ID in genesis
//return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set")
} }
var auctionID types.ID return types.NewIDFromBytes(bz), nil
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID)
return auctionID, nil
} }
// incrementNextAuctionID increments the global ID in the store by 1 // incrementNextAuctionID increments the global ID in the store by 1
func (k Keeper) incrementNextAuctionID(ctx sdk.Context) sdk.Error { func (k Keeper) IncrementNextAuctionID(ctx sdk.Context) sdk.Error {
// get next ID from store id, err := k.GetNextAuctionID(ctx)
store := ctx.KVStore(k.storeKey) if err != nil {
bz := store.Get(k.getNextAuctionIDKey()) return err
if bz == nil {
panic("initial auctionID never set in genesis")
//return 0, ErrInvalidGenesis(keeper.codespace, "InitialProposalID never set") // TODO
} }
var auctionID types.ID k.SetNextAuctionID(ctx, id+1)
k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &auctionID)
// increment the stored next ID
bz = k.cdc.MustMarshalBinaryLengthPrefixed(auctionID + 1)
store.Set(k.getNextAuctionIDKey(), bz)
return nil return nil
} }
// storeNewAuction stores an auction, adding a new ID, and setting indexes
func (k Keeper) storeNewAuction(ctx sdk.Context, auction types.Auction) (types.ID, sdk.Error) {
newAuctionID, err := k.GetNextAuctionID(ctx)
if err != nil {
return 0, err
}
auction.SetID(newAuctionID)
k.SetAuction(ctx, auction)
err = k.IncrementNextAuctionID(ctx)
if err != nil {
return 0, err
}
return newAuctionID, nil
}
// TODO should get/set/delete be responsible for updating auctionByTime index?
// SetAuction puts the auction into the database and adds it to the queue // SetAuction puts the auction into the database and adds it to the queue
// it overwrites any pre-existing auction with same ID // it overwrites any pre-existing auction with same ID
func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) { func (k Keeper) SetAuction(ctx sdk.Context, auction types.Auction) {
// remove the auction from the queue if it is already in there // remove the auction from the queue if it is already in there
existingAuction, found := k.GetAuction(ctx, auction.GetID()) // existingAuction, found := k.GetAuction(ctx, auction.GetID())
if found { // if found {
k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID()) // k.removeFromQueue(ctx, existingAuction.GetEndTime(), existingAuction.GetID())
} // }
// store auction // store auction
store := ctx.KVStore(k.storeKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction) bz := k.cdc.MustMarshalBinaryLengthPrefixed(auction)
store.Set(k.getAuctionKey(auction.GetID()), bz) store.Set(types.GetAuctionKey(auction.GetID()), bz)
// add to the queue // add to the queue
k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID()) //k.InsertIntoQueue(ctx, auction.GetEndTime(), auction.GetID())
} }
// getAuction gets an auction from the store by auctionID // getAuction gets an auction from the store by auctionID
func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, bool) { func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction, bool) {
var auction types.Auction var auction types.Auction
store := ctx.KVStore(k.storeKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
bz := store.Get(k.getAuctionKey(auctionID)) bz := store.Get(types.GetAuctionKey(auctionID))
if bz == nil { if bz == nil {
return auction, false return auction, false
} }
@ -97,91 +90,59 @@ func (k Keeper) GetAuction(ctx sdk.Context, auctionID types.ID) (types.Auction,
// DeleteAuction removes an auction from the store without any validation // DeleteAuction removes an auction from the store without any validation
func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) { func (k Keeper) DeleteAuction(ctx sdk.Context, auctionID types.ID) {
// remove from queue // remove from queue
auction, found := k.GetAuction(ctx, auctionID) //auction, found := k.GetAuction(ctx, auctionID)
if found { // if found {
k.removeFromQueue(ctx, auction.GetEndTime(), auctionID) // k.removeFromQueue(ctx, auction.GetEndTime(), auctionID)
} // }
// delete auction // delete auction
store := ctx.KVStore(k.storeKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
store.Delete(k.getAuctionKey(auctionID)) store.Delete(types.GetAuctionKey(auctionID))
}
// ---------- Queue and key methods ----------
// These are lower level function used by the store methods above.
func (k Keeper) getNextAuctionIDKey() []byte {
return []byte("nextAuctionID")
}
func (k Keeper) getAuctionKey(auctionID types.ID) []byte {
return []byte(fmt.Sprintf("auctions:%d", auctionID))
} }
// Inserts a AuctionID into the queue at endTime // Inserts a AuctionID into the queue at endTime
func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { func (k Keeper) InsertIntoQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) {
// get the store store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
store := ctx.KVStore(k.storeKey) store.Set(types.GetAuctionByTimeKey(endTime, auctionID), auctionID.Bytes())
// marshal thing to be inserted
bz := k.cdc.MustMarshalBinaryLengthPrefixed(auctionID)
// store it
store.Set(
getQueueElementKey(endTime, auctionID),
bz,
)
} }
// removes an auctionID from the queue // removes an auctionID from the queue
func (k Keeper) removeFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) { func (k Keeper) RemoveFromQueue(ctx sdk.Context, endTime time.Time, auctionID types.ID) {
store := ctx.KVStore(k.storeKey) store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
store.Delete(getQueueElementKey(endTime, auctionID)) store.Delete(types.GetAuctionByTimeKey(endTime, auctionID))
} }
// Returns an iterator for all the auctions in the queue that expire by endTime func (k Keeper) IterateAuctionsByTime(ctx sdk.Context, inclusiveCutoffTime time.Time, cb func(auctionID types.ID) (stop bool)) {
func (k Keeper) GetQueueIterator(ctx sdk.Context, endTime time.Time) sdk.Iterator { // TODO rename to "getAuctionsByExpiry" ? store := prefix.NewStore(ctx.KVStore(k.storeKey), types.AuctionByTimeKeyPrefix)
// get store iterator := store.Iterator(
store := ctx.KVStore(k.storeKey) nil, // start at the very start of the prefix store
// get an interator sdk.PrefixEndBytes(sdk.FormatTimeBytes(inclusiveCutoffTime)), // include any keys with times equal to inclusiveCutoffTime
return store.Iterator(
queueKeyPrefix, // start key
sdk.PrefixEndBytes(getQueueElementKeyPrefix(endTime)), // end key (apparently exclusive but tests suggested otherwise)
) )
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
// TODO get the auction ID - either read from store, or extract from key
auctionID := types.NewIDFromBytes(iterator.Value())
if cb(auctionID) {
break
}
}
} }
// GetAuctionIterator returns an iterator over all auctions in the store // IterateAuctions provides an iterator over all stored auctions. For
func (k Keeper) GetAuctionIterator(ctx sdk.Context) sdk.Iterator { // each auction, cb will be called. If the cb returns true, the iterator
store := ctx.KVStore(k.storeKey) // will close and stop.
return sdk.KVStorePrefixIterator(store, nil) func (k Keeper) IterateAuctions(ctx sdk.Context, cb func(auction types.Auction) (stop bool)) {
} iterator := sdk.KVStorePrefixIterator(ctx.KVStore(k.storeKey), types.AuctionKeyPrefix)
var queueKeyPrefix = []byte("queue") defer iterator.Close()
var keyDelimiter = []byte(":") for ; iterator.Valid(); iterator.Next() {
// Returns half a key for an auctionID in the queue, it missed the id off the end
func getQueueElementKeyPrefix(endTime time.Time) []byte {
return bytes.Join([][]byte{
queueKeyPrefix,
sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering
}, keyDelimiter)
}
// Returns the key for an auctionID in the queue
func getQueueElementKey(endTime time.Time, auctionID types.ID) []byte {
return bytes.Join([][]byte{
queueKeyPrefix,
sdk.Uint64ToBigEndian(uint64(endTime)), // TODO check this gives correct ordering
sdk.Uint64ToBigEndian(uint64(auctionID)),
}, keyDelimiter)
}
// GetAuctionID returns the id from an input Auction
func (k Keeper) DecodeAuctionID(ctx sdk.Context, idBytes []byte) types.ID {
var auctionID types.ID
k.cdc.MustUnmarshalBinaryLengthPrefixed(idBytes, &auctionID)
return auctionID
}
func (k Keeper) DecodeAuction(ctx sdk.Context, auctionBytes []byte) types.Auction {
var auction types.Auction var auction types.Auction
k.cdc.MustUnmarshalBinaryBare(auctionBytes, &auction) k.cdc.MustUnmarshalBinaryLengthPrefixed(iterator.Value(), &auction)
return auction
if cb(auction) {
break
}
}
} }

View File

@ -1,6 +1,7 @@
package types package types
import ( import (
"encoding/binary"
"fmt" "fmt"
"strconv" "strconv"
"time" "time"
@ -9,6 +10,27 @@ import (
"github.com/cosmos/cosmos-sdk/x/supply" "github.com/cosmos/cosmos-sdk/x/supply"
) )
// ID type for auction IDs
type ID uint64
// NewIDFromString generate new auction ID from a string
func NewIDFromString(s string) (ID, error) {
n, err := strconv.ParseUint(s, 10, 64) // copied from how the gov module rest handler's parse proposal IDs
if err != nil {
return 0, err
}
return ID(n), nil
}
func NewIDFromBytes(bz []byte) ID {
return ID(binary.BigEndian.Uint64(bz))
}
func (id ID) Bytes() []byte {
bz := make([]byte, 8)
binary.BigEndian.PutUint64(bz, uint64(id))
return bz
}
// Auction is an interface to several types of auction. // Auction is an interface to several types of auction.
type Auction interface { type Auction interface {
GetID() ID GetID() ID
@ -29,23 +51,11 @@ type BaseAuction struct {
MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after. MaxEndTime time.Time // Maximum closing time. Auctions can close before this but never after.
} }
// ID type for auction IDs
type ID uint64
// NewIDFromString generate new auction ID from a string
func NewIDFromString(s string) (ID, error) {
n, err := strconv.ParseUint(s, 10, 64) // copied from how the gov module rest handler's parse proposal IDs
if err != nil {
return 0, err
}
return ID(n), nil
}
// GetID getter for auction ID // GetID getter for auction ID
func (a *BaseAuction) GetID() ID { return a.ID } func (a *BaseAuction) GetID() ID { return a.ID }
// SetID setter for auction ID // SetID setter for auction ID
func (a *BaseAuction) SetID(id ID) { a.ID = id } func (a *BaseAuction) SetID(id ID) { a.ID = id } // TODO if this returns a new auction with ID then no pointers are needed
// GetBid getter for auction bid // GetBid getter for auction bid
func (a *BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder } func (a *BaseAuction) GetBidder() sdk.AccAddress { return a.Bidder }
@ -76,15 +86,15 @@ type ForwardAuction struct {
} }
// NewForwardAuction creates a new forward auction // NewForwardAuction creates a new forward auction
func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, EndTime time.Time) ForwardAuction { func NewForwardAuction(seller string, lot sdk.Coin, bidDenom string, endTime time.Time) ForwardAuction {
auction := ForwardAuction{&BaseAuction{ auction := ForwardAuction{&BaseAuction{
// no ID // no ID
Initiator: seller, Initiator: seller,
Lot: lot, Lot: lot,
Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided Bidder: nil, // TODO on the first place bid, 0 coins will be sent to this address, check if this causes problems or can be avoided
Bid: sdk.NewInt64Coin(bidDenom, 0), Bid: sdk.NewInt64Coin(bidDenom, 0),
EndTime: EndTime, EndTime: endTime,
MaxEndTime: EndTime, MaxEndTime: endTime,
}} }}
// output := BankOutput{seller, lot} // output := BankOutput{seller, lot}
return auction return auction

View File

@ -1,5 +1,11 @@
package types package types
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const ( const (
// ModuleName The name that will be used throughout the module // ModuleName The name that will be used throughout the module
ModuleName = "auction" ModuleName = "auction"
@ -13,3 +19,19 @@ const (
// DefaultParamspace default name for parameter store // DefaultParamspace default name for parameter store
DefaultParamspace = ModuleName DefaultParamspace = ModuleName
) )
// TODO use cont to keep immutability?
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}
)
func GetAuctionKey(auctionID ID) []byte {
return auctionID.Bytes()
}
func GetAuctionByTimeKey(endTime time.Time, auctionID ID) []byte {
return append(sdk.FormatTimeBytes(endTime), auctionID.Bytes()...)
}