diff --git a/types/stdlib/stdlib.go b/types/stdlib/stdlib.go new file mode 100644 index 0000000000..dd9f4efad1 --- /dev/null +++ b/types/stdlib/stdlib.go @@ -0,0 +1,232 @@ +package types + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type ListMapper interface { // Solidity list like structure + Len(sdk.Context) int64 + Get(sdk.Context, int64, interface{}) + Set(sdk.Context, int64, interface{}) + Push(sdk.Context, interface{}) + Iterate(sdk.Context, interface{}, func(sdk.Context, int64)) +} + +type listMapper struct { + key sdk.StoreKey + cdc *wire.Codec + lk []byte +} + +func NewListMapper(cdc *wire.Codec, key sdk.StoreKey) ListMapper { + lk, err := cdc.MarshalBinary(int64(-1)) + if err != nil { + panic(err) + } + return listMapper{ + key: key, + cdc: cdc, + lk: lk, + } +} + +func (lm listMapper) Len(ctx sdk.Context) int64 { + store := ctx.KVStore(lm.key) + bz := store.Get(lm.lk) + if bz == nil { + zero, err := lm.cdc.MarshalBinary(0) + if err != nil { + panic(err) + } + store.Set(lm.lk, zero) + return 0 + } + var res int64 + if err := lm.cdc.UnmarshalBinary(bz, &res); err != nil { + panic(err) + } + return res +} + +func (lm listMapper) Get(ctx sdk.Context, index int64, ptr interface{}) { + if index < 0 { + panic(errors.New("")) + } + store := ctx.KVStore(lm.key) + bz := store.Get(marshalInt64(lm.cdc, index)) + if err := lm.cdc.UnmarshalBinary(bz, ptr); err != nil { + panic(err) + } +} + +func (lm listMapper) Set(ctx sdk.Context, index int64, value interface{}) { + if index < 0 { + panic(errors.New("")) + } + store := ctx.KVStore(lm.key) + bz, err := lm.cdc.MarshalBinary(value) + if err != nil { + panic(err) + } + store.Set(marshalInt64(lm.cdc, index), bz) +} + +func (lm listMapper) Push(ctx sdk.Context, value interface{}) { + length := lm.Len(ctx) + lm.Set(ctx, length, value) + + store := ctx.KVStore(lm.key) + store.Set(lm.lk, marshalInt64(lm.cdc, length+1)) +} + +func (lm listMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, int64)) { + length := lm.Len(ctx) + for i := int64(0); i < length; i++ { + lm.Get(ctx, i, ptr) + fn(ctx, i) + } +} + +type QueueMapper interface { + Push(sdk.Context, interface{}) + Peek(sdk.Context, interface{}) + Pop(sdk.Context) + IsEmpty(sdk.Context) bool + Iterate(sdk.Context, interface{}, func(sdk.Context) bool) +} + +type queueMapper struct { + key sdk.StoreKey + cdc *wire.Codec + ik []byte +} + +func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey) QueueMapper { + ik, err := cdc.MarshalBinary(int64(-1)) + if err != nil { + panic(err) + } + return queueMapper{ + key: key, + cdc: cdc, + ik: ik, + } +} + +type queueInfo struct { + // begin <= elems < end + Begin int64 + End int64 +} + +func (info queueInfo) validateBasic() error { + if info.End < info.Begin || info.Begin < 0 || info.End < 0 { + return errors.New("") + } + return nil +} + +func (info queueInfo) isEmpty() bool { + return info.Begin == info.End +} + +func (qm queueMapper) getQueueInfo(store sdk.KVStore) queueInfo { + bz := store.Get(qm.ik) + if bz == nil { + store.Set(qm.ik, marshalQueueInfo(qm.cdc, queueInfo{0, 0})) + return queueInfo{0, 0} + } + var info queueInfo + if err := qm.cdc.UnmarshalBinary(bz, &info); err != nil { + panic(err) + } + if err := info.validateBasic(); err != nil { + panic(err) + } + return info +} + +func (qm queueMapper) setQueueInfo(store sdk.KVStore, info queueInfo) { + bz, err := qm.cdc.MarshalBinary(info) + if err != nil { + panic(err) + } + store.Set(qm.ik, bz) +} + +func (qm queueMapper) Push(ctx sdk.Context, value interface{}) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + + bz, err := qm.cdc.MarshalBinary(value) + if err != nil { + panic(err) + } + store.Set(marshalInt64(qm.cdc, info.End), bz) + + info.End++ + qm.setQueueInfo(store, info) +} + +func (qm queueMapper) Peek(ctx sdk.Context, ptr interface{}) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + bz := store.Get(marshalInt64(qm.cdc, info.Begin)) + if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil { + panic(err) + } +} + +func (qm queueMapper) Pop(ctx sdk.Context) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + store.Delete(marshalInt64(qm.cdc, info.Begin)) + info.Begin++ + qm.setQueueInfo(store, info) +} + +func (qm queueMapper) IsEmpty(ctx sdk.Context) bool { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + return info.isEmpty() +} + +func (qm queueMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + + var i int64 + for i = info.Begin; i < info.End; i++ { + key := marshalInt64(qm.cdc, i) + bz := store.Get(key) + if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil { + panic(err) + } + store.Delete(key) + if fn(ctx) { + break + } + } + + info.Begin = i + qm.setQueueInfo(store, info) +} + +func marshalQueueInfo(cdc *wire.Codec, info queueInfo) []byte { + bz, err := cdc.MarshalBinary(info) + if err != nil { + panic(err) + } + return bz +} + +func marshalInt64(cdc *wire.Codec, i int64) []byte { + bz, err := cdc.MarshalBinary(i) + if err != nil { + panic(err) + } + return bz +} diff --git a/types/stdlib/stdlib_test.go b/types/stdlib/stdlib_test.go new file mode 100644 index 0000000000..7c871ccf3c --- /dev/null +++ b/types/stdlib/stdlib_test.go @@ -0,0 +1,69 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbm "github.com/tendermint/tmlibs/db" + + abci "github.com/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type S struct { + I int64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + cdc := wire.NewCodec() + return ctx, cdc +} + +func TestListMapper(t *testing.T) { + key := sdk.NewKVStoreKey("list") + ctx, cdc := defaultComponents(key) + lm := NewListMapper(cdc, key) + + val := S{1, true} + var res S + + lm.Push(ctx, val) + assert.Equal(t, int64(1), lm.Len(ctx)) + lm.Get(ctx, int64(0), &res) + assert.Equal(t, val, res) + + val = S{2, false} + lm.Set(ctx, int64(0), val) + lm.Get(ctx, int64(0), &res) + assert.Equal(t, val, res) +} + +func TestQueueMapper(t *testing.T) { + key := sdk.NewKVStoreKey("queue") + ctx, cdc := defaultComponents(key) + qm := NewQueueMapper(cdc, key) + + val := S{1, true} + var res S + + qm.Push(ctx, val) + qm.Peek(ctx, &res) + assert.Equal(t, val, res) + + qm.Pop(ctx) + empty := qm.IsEmpty(ctx) + + assert.Equal(t, true, empty) + + assert.Panics(t, func() { qm.Peek(ctx, &res) }) +}