Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(perp): use collections for state management #952

Merged
merged 24 commits into from
Sep 22, 2022
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#785](https://github.com/NibiruChain/nibiru/pull/785) - ci: create simulations job

### State Machine Breaking

* [#952](https://github.com/NibiruChain/nibiru/pull/952) - x/perp move state logic to collections
* [#872](https://github.com/NibiruChain/nibiru/pull/872) - x/perp remove module balances from genesis
* [#878](https://github.com/NibiruChain/nibiru/pull/878) - rename `PremiumFraction` to `FundingRate`
* [#900](https://github.com/NibiruChain/nibiru/pull/900) - refactor x/vpool snapshot state management
Expand Down
27 changes: 27 additions & 0 deletions collections/keys/bound.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package keys

// Bound defines a key bound.
type Bound[K Key] struct {
value K // value is the concrete key.
inclusive bool // inclusive defines if the key bound should include or not the provided value.
}

// Inclusive creates a key Bound which is inclusive,
// which means the provided key will be included
// in the key range (if present).
func Inclusive[K Key](k K) Bound[K] {
return Bound[K]{
value: k,
inclusive: true,
}
}

// Exclusive creates a key Bound which is exclusive,
// which means the provided key will be excluded from
// the key range.
func Exclusive[K Key](k K) Bound[K] {
return Bound[K]{
value: k,
inclusive: false,
}
}
5 changes: 5 additions & 0 deletions collections/keys/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import (
type Order uint8

const (
// OrderAscending defines an order going from the
// smallest key to the biggest key.
OrderAscending Order = iota
// OrderDescending defines an order going from the
// biggest key to the smallest. In the KVStore
// it equals to iterating in reverse.
OrderDescending
)

Expand Down
24 changes: 24 additions & 0 deletions collections/keys/pair.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ type Pair[K1 Key, K2 Key] struct {
p2 *K2
}

// K1 returns the first part of the key,
// if present. If the key is not present
// the zero value is returned.
func (t Pair[K1, K2]) K1() K1 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these used anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftovers from multi index... but we can keep this since it's going to be needed

if t.p1 != nil {
return *t.p1
} else {
var x K1
return x
}
}

// K2 returns the second part of the key,
// if present, If the key is not present
// the zero value is returned.
func (t Pair[K1, K2]) K2() K2 {
if t.p2 != nil {
return *t.p2
} else {
var x K2
return x
}
}

func (t Pair[K1, K2]) fkb1(b []byte) (int, K1) {
var k1 K1
i, p1 := k1.FromKeyBytes(b)
Expand Down
123 changes: 62 additions & 61 deletions collections/keys/range.go
Original file line number Diff line number Diff line change
@@ -1,104 +1,105 @@
package keys

import (
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
)

// IteratorFromRange returns a sdk.Iterator given the range
// and the sdk.KVStore to apply the iterator to.
// prefixBytes MUST not be mutated.
func IteratorFromRange[K Key](store sdk.KVStore, r Range[K]) (iter sdk.Iterator, prefixBytes []byte) {
pfx, start, end, order := r.RangeValues()
if pfx != nil {
prefixBytes = (*pfx).KeyBytes()
store = prefix.NewStore(store, prefixBytes)
}
var startBytes []byte // default is nil
if start != nil {
startBytes = start.value.KeyBytes()
// iterators are inclusive at start by default
// so if we want to make the iteration exclusive
// we extend by one byte.
if !start.inclusive {
startBytes = extendOneByte(startBytes)
}
}
var endBytes []byte // default is nil
if end != nil {
endBytes = end.value.KeyBytes()
// iterators are exclusive at end by default
// so if we want to make the iteration
// inclusive we need to extend by one byte.
if end.inclusive {
endBytes = extendOneByte(endBytes)
}
}

switch order {
case OrderAscending:
return store.Iterator(startBytes, endBytes), prefixBytes
case OrderDescending:
return store.ReverseIterator(startBytes, endBytes), prefixBytes
default:
panic("unrecognized order")
}
}

// Range defines an interface which instructs on how to iterate
// over keys.
type Range[K Key] interface {
// RangeValues returns the range instructions.
RangeValues() (prefix *K, start *Bound[K], end *Bound[K], order Order)
}

// NewRange returns a Range instance
// which iterates over all keys in
// ascending order.
func NewRange[K Key]() Range[K] {
return Range[K]{
func NewRange[K Key]() RawRange[K] {
return RawRange[K]{
prefix: nil,
start: nil,
end: nil,
order: OrderAscending,
}
}

// Range defines a range of keys.
type Range[K Key] struct {
// RawRange is a Range implementer.
type RawRange[K Key] struct {
prefix *K
start *Bound[K]
end *Bound[K]
order Order
}

// Prefix sets a fixed prefix for the key range.
func (r Range[K]) Prefix(key K) Range[K] {
func (r RawRange[K]) Prefix(key K) RawRange[K] {
r.prefix = &key
return r
}

// Start sets the start range of the key.
func (r Range[K]) Start(bound Bound[K]) Range[K] {
func (r RawRange[K]) Start(bound Bound[K]) RawRange[K] {
r.start = &bound
return r
}

// End sets the end range of the key.
func (r Range[K]) End(bound Bound[K]) Range[K] {
func (r RawRange[K]) End(bound Bound[K]) RawRange[K] {
r.end = &bound
return r
}

// Descending sets the key range to be inverse.
func (r Range[K]) Descending() Range[K] {
func (r RawRange[K]) Descending() RawRange[K] {
r.order = OrderDescending
return r
}

func (r Range[K]) Compile() (prefix []byte, start []byte, end []byte, order Order) {
order = r.order
if r.prefix != nil {
prefix = (*r.prefix).KeyBytes()
}
if r.start != nil {
start = r.compileStart()
}
if r.end != nil {
end = r.compileEnd()
}
return
}

func (r Range[K]) compileStart() []byte {
bytes := r.start.value.KeyBytes()
// iterator start is inclusive by default
if r.start.inclusive {
return bytes
} else {
return extendOneByte(bytes)
}
}

func (r Range[K]) compileEnd() []byte {
bytes := r.end.value.KeyBytes()
// iterator end is exclusive by default
if !r.end.inclusive {
return bytes
} else {
return extendOneByte(bytes)
}
func (r RawRange[K]) RangeValues() (prefix *K, start *Bound[K], end *Bound[K], order Order) {
return r.prefix, r.start, r.end, r.order
}

func extendOneByte(b []byte) []byte {
return append(b, 0)
}

type Bound[K Key] struct {
value K
inclusive bool
}

// Inclusive creates a key Bound which is inclusive.
func Inclusive[K Key](k K) Bound[K] {
return Bound[K]{
value: k,
inclusive: true,
}
}

// Exclusive creates a key Bound which is exclusive.
func Exclusive[K Key](k K) Bound[K] {
return Bound[K]{
value: k,
inclusive: false,
}
}
28 changes: 5 additions & 23 deletions collections/map.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package collections

import (
"fmt"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -87,27 +85,11 @@ func newMapIterator[K keys.Key, V any, PV interface {
*V
Object
}](cdc storeCodec, store sdk.KVStore, r keys.Range[K]) MapIterator[K, V, PV] {
pfx, start, end, order := r.Compile()

// if prefix is not nil then we replace the current store with a prefixed one
if pfx != nil {
store = prefix.NewStore(store, pfx)
}
switch order {
case keys.OrderAscending:
return MapIterator[K, V, PV]{
prefix: pfx,
cdc: cdc,
iter: store.Iterator(start, end),
}
case keys.OrderDescending:
return MapIterator[K, V, PV]{
prefix: pfx,
cdc: cdc,
iter: store.ReverseIterator(start, end),
}
default:
panic(fmt.Errorf("unrecognized order"))
iter, prefix := keys.IteratorFromRange(store, r)
return MapIterator[K, V, PV]{
prefix: prefix,
cdc: cdc,
iter: iter,
}
}

Expand Down
6 changes: 3 additions & 3 deletions proto/perp/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ option go_package = "github.com/NibiruChain/nibiru/x/perp/types";
message GenesisState {
Params params = 1 [ (gogoproto.nullable) = false ];

repeated PairMetadata pair_metadata = 2;
repeated PairMetadata pair_metadata = 2 [ (gogoproto.nullable) = false ];

repeated Position positions = 3;
repeated Position positions = 3 [ (gogoproto.nullable) = false ];

repeated PrepaidBadDebt prepaid_bad_debts = 4;
repeated PrepaidBadDebt prepaid_bad_debts = 4 [ (gogoproto.nullable) = false ];
}
8 changes: 5 additions & 3 deletions x/perp/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"testing"
"time"

"github.com/NibiruChain/nibiru/collections"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
Expand Down Expand Up @@ -105,7 +107,7 @@ func (s *IntegrationTestSuite) SetupSuite() {

// setup perp
perpGenesis := perptypes.DefaultGenesis()
perpGenesis.PairMetadata = []*perptypes.PairMetadata{
perpGenesis.PairMetadata = []perptypes.PairMetadata{
{
Pair: common.Pair_BTC_NUSD,
CumulativeFundingRates: []sdk.Dec{
Expand Down Expand Up @@ -362,7 +364,7 @@ func (s *IntegrationTestSuite) TestPositionEmptyAndClose() {
common.Pair_ETH_NUSD.String(),
}
out, _ := sdktestutilcli.ExecTestCLICmd(val.ClientCtx, cli.ClosePositionCmd(), append(args, commonArgs...))
s.Contains(out.String(), "no position found")
s.Contains(out.String(), collections.ErrNotFound.Error())
}

func (s *IntegrationTestSuite) TestGetPrices() {
Expand Down Expand Up @@ -439,7 +441,7 @@ func (s *IntegrationTestSuite) TestLiquidate() {

// liquidate a position that does not exist
out, err := sdktestutilcli.ExecTestCLICmd(val.ClientCtx, cli.LiquidateCmd(), append(args, commonArgs...))
s.Contains(out.String(), "no position found")
s.Contains(out.String(), collections.ErrNotFound.Error())
if err != nil {
s.T().Logf("user liquidate error: %+v", err)
}
Expand Down
Loading