Skip to content

Commit

Permalink
refactor(perp): use collections for state management (#952)
Browse files Browse the repository at this point in the history
* add: indexed map

* change: use IndexedMap for positions

* add: query trader positions + lint

* add: query trader pos tests

* chore: lint

* chore: docs

* chore: CHANGELOG.md

* add: move pairs metadata to collections

* change: move prepaid bad debt to collections

* chore: lint

* chore: update CHANGELOG.md

* chore: docs

* change: refactor collections

* fix: remove double iteration

* change: rename search to match + docs

* change: simplify pr

* change: rename BadDebt->PrepaidBadDebt

* change: make genesis fields be non-nullable

* change: move state functions to appropriate files and add bad debt tests

* chore lint

* change: BadDebt->PrepaidBadDebt

Co-authored-by: Matthias <[email protected]>
  • Loading branch information
testinginprod and matthiasmatt authored Sep 22, 2022
1 parent 52033d5 commit 94cbcf7
Show file tree
Hide file tree
Showing 40 changed files with 593 additions and 865 deletions.
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 {
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

0 comments on commit 94cbcf7

Please sign in to comment.