mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 15:55:18 +00:00
Swap Genesis State (#960)
* wip: add swap state persistent to genesis * separate pool record constructors; add tests for json and yaml encoding of record structs * beef up validation checks for state records * fix integration with master - renamed method * add test coverage for basic state array validations * extra test around pool record reserve and id ordering to ensure no regressions in the future * add validations to ensure pool records and share records are unique within the collection types * test genesis json and yaml encoding * validate in genesis that the total shares owned for each pool is equal to the total shares of each pool * update alias * nit lint * test genesis init and export * add migration todo Co-authored-by: Nick DeLuca <nickdeluca08@gmail.com>
This commit is contained in:
parent
013093ecb5
commit
d45fa58f5c
@ -305,5 +305,6 @@ func loadStabilityComMembers() ([]sdk.AccAddress, error) {
|
|||||||
|
|
||||||
// Swap introduces new v0.15 swap genesis state
|
// Swap introduces new v0.15 swap genesis state
|
||||||
func Swap() v0_15swap.GenesisState {
|
func Swap() v0_15swap.GenesisState {
|
||||||
return v0_15swap.NewGenesisState(v0_15swap.DefaultParams())
|
// TODO add swap genesis state
|
||||||
|
return v0_15swap.DefaultGenesisState()
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,10 @@ const (
|
|||||||
ModuleAccountName = types.ModuleAccountName
|
ModuleAccountName = types.ModuleAccountName
|
||||||
ModuleName = types.ModuleName
|
ModuleName = types.ModuleName
|
||||||
QuerierRoute = types.QuerierRoute
|
QuerierRoute = types.QuerierRoute
|
||||||
|
QueryGetDeposits = types.QueryGetDeposits
|
||||||
QueryGetParams = types.QueryGetParams
|
QueryGetParams = types.QueryGetParams
|
||||||
|
QueryGetPool = types.QueryGetPool
|
||||||
|
QueryGetPools = types.QueryGetPools
|
||||||
RouterKey = types.RouterKey
|
RouterKey = types.RouterKey
|
||||||
StoreKey = types.StoreKey
|
StoreKey = types.StoreKey
|
||||||
)
|
)
|
||||||
@ -43,6 +46,7 @@ var (
|
|||||||
NewBasePoolWithExistingShares = types.NewBasePoolWithExistingShares
|
NewBasePoolWithExistingShares = types.NewBasePoolWithExistingShares
|
||||||
NewDenominatedPool = types.NewDenominatedPool
|
NewDenominatedPool = types.NewDenominatedPool
|
||||||
NewDenominatedPoolWithExistingShares = types.NewDenominatedPoolWithExistingShares
|
NewDenominatedPoolWithExistingShares = types.NewDenominatedPoolWithExistingShares
|
||||||
|
NewDepositsQueryResult = types.NewDepositsQueryResult
|
||||||
NewGenesisState = types.NewGenesisState
|
NewGenesisState = types.NewGenesisState
|
||||||
NewMsgDeposit = types.NewMsgDeposit
|
NewMsgDeposit = types.NewMsgDeposit
|
||||||
NewMsgSwapExactForTokens = types.NewMsgSwapExactForTokens
|
NewMsgSwapExactForTokens = types.NewMsgSwapExactForTokens
|
||||||
@ -50,6 +54,10 @@ var (
|
|||||||
NewMsgWithdraw = types.NewMsgWithdraw
|
NewMsgWithdraw = types.NewMsgWithdraw
|
||||||
NewParams = types.NewParams
|
NewParams = types.NewParams
|
||||||
NewPoolRecord = types.NewPoolRecord
|
NewPoolRecord = types.NewPoolRecord
|
||||||
|
NewPoolRecordFromPool = types.NewPoolRecordFromPool
|
||||||
|
NewPoolStatsQueryResult = types.NewPoolStatsQueryResult
|
||||||
|
NewQueryDepositsParams = types.NewQueryDepositsParams
|
||||||
|
NewQueryPoolParams = types.NewQueryPoolParams
|
||||||
NewShareRecord = types.NewShareRecord
|
NewShareRecord = types.NewShareRecord
|
||||||
ParamKeyTable = types.ParamKeyTable
|
ParamKeyTable = types.ParamKeyTable
|
||||||
PoolID = types.PoolID
|
PoolID = types.PoolID
|
||||||
@ -59,6 +67,8 @@ var (
|
|||||||
|
|
||||||
// variable aliases
|
// variable aliases
|
||||||
DefaultAllowedPools = types.DefaultAllowedPools
|
DefaultAllowedPools = types.DefaultAllowedPools
|
||||||
|
DefaultPoolRecords = types.DefaultPoolRecords
|
||||||
|
DefaultShareRecords = types.DefaultShareRecords
|
||||||
DefaultSwapFee = types.DefaultSwapFee
|
DefaultSwapFee = types.DefaultSwapFee
|
||||||
DepositorPoolSharesPrefix = types.DepositorPoolSharesPrefix
|
DepositorPoolSharesPrefix = types.DepositorPoolSharesPrefix
|
||||||
ErrDeadlineExceeded = types.ErrDeadlineExceeded
|
ErrDeadlineExceeded = types.ErrDeadlineExceeded
|
||||||
@ -86,6 +96,8 @@ type (
|
|||||||
AllowedPools = types.AllowedPools
|
AllowedPools = types.AllowedPools
|
||||||
BasePool = types.BasePool
|
BasePool = types.BasePool
|
||||||
DenominatedPool = types.DenominatedPool
|
DenominatedPool = types.DenominatedPool
|
||||||
|
DepositsQueryResult = types.DepositsQueryResult
|
||||||
|
DepositsQueryResults = types.DepositsQueryResults
|
||||||
GenesisState = types.GenesisState
|
GenesisState = types.GenesisState
|
||||||
MsgDeposit = types.MsgDeposit
|
MsgDeposit = types.MsgDeposit
|
||||||
MsgSwapExactForTokens = types.MsgSwapExactForTokens
|
MsgSwapExactForTokens = types.MsgSwapExactForTokens
|
||||||
@ -94,6 +106,13 @@ type (
|
|||||||
MsgWithdraw = types.MsgWithdraw
|
MsgWithdraw = types.MsgWithdraw
|
||||||
Params = types.Params
|
Params = types.Params
|
||||||
PoolRecord = types.PoolRecord
|
PoolRecord = types.PoolRecord
|
||||||
|
PoolRecords = types.PoolRecords
|
||||||
|
PoolStatsQueryResult = types.PoolStatsQueryResult
|
||||||
|
PoolStatsQueryResults = types.PoolStatsQueryResults
|
||||||
|
QueryDepositsParams = types.QueryDepositsParams
|
||||||
|
QueryPoolParams = types.QueryPoolParams
|
||||||
ShareRecord = types.ShareRecord
|
ShareRecord = types.ShareRecord
|
||||||
|
ShareRecords = types.ShareRecords
|
||||||
SupplyKeeper = types.SupplyKeeper
|
SupplyKeeper = types.SupplyKeeper
|
||||||
|
SwapHooks = types.SwapHooks
|
||||||
)
|
)
|
||||||
|
@ -3,9 +3,9 @@ package swap
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/swap/types"
|
"github.com/kava-labs/kava/x/swap/types"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// InitGenesis initializes story state from genesis file
|
// InitGenesis initializes story state from genesis file
|
||||||
@ -15,10 +15,18 @@ func InitGenesis(ctx sdk.Context, k Keeper, gs types.GenesisState) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
k.SetParams(ctx, gs.Params)
|
k.SetParams(ctx, gs.Params)
|
||||||
|
for _, pr := range gs.PoolRecords {
|
||||||
|
k.SetPool(ctx, pr)
|
||||||
|
}
|
||||||
|
for _, sh := range gs.ShareRecords {
|
||||||
|
k.SetDepositorShares(ctx, sh)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExportGenesis exports the genesis state
|
// ExportGenesis exports the genesis state
|
||||||
func ExportGenesis(ctx sdk.Context, k Keeper) types.GenesisState {
|
func ExportGenesis(ctx sdk.Context, k Keeper) types.GenesisState {
|
||||||
params := k.GetParams(ctx)
|
params := k.GetParams(ctx)
|
||||||
return types.NewGenesisState(params)
|
pools := k.GetAllPools(ctx)
|
||||||
|
shares := k.GetAllDepositorShares(ctx)
|
||||||
|
return types.NewGenesisState(params, pools, shares)
|
||||||
}
|
}
|
||||||
|
73
x/swap/genesis_test.go
Normal file
73
x/swap/genesis_test.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package swap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kava-labs/kava/x/swap"
|
||||||
|
"github.com/kava-labs/kava/x/swap/testutil"
|
||||||
|
"github.com/kava-labs/kava/x/swap/types"
|
||||||
|
"github.com/stretchr/testify/suite"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type genesisTestSuite struct {
|
||||||
|
testutil.Suite
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *genesisTestSuite) Test_InitGenesis_ValidationPanic() {
|
||||||
|
invalidState := types.NewGenesisState(
|
||||||
|
types.Params{
|
||||||
|
SwapFee: sdk.NewDec(-1),
|
||||||
|
},
|
||||||
|
types.PoolRecords{},
|
||||||
|
types.ShareRecords{},
|
||||||
|
)
|
||||||
|
|
||||||
|
suite.Panics(func() {
|
||||||
|
swap.InitGenesis(suite.Ctx, suite.Keeper, invalidState)
|
||||||
|
}, "expected init genesis to panic with invalid state")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (suite *genesisTestSuite) Test_InitAndExportGenesis() {
|
||||||
|
depositor_1, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
depositor_2, err := sdk.AccAddressFromBech32("kava1esagqd83rhqdtpy5sxhklaxgn58k2m3s3mnpea")
|
||||||
|
suite.Require().NoError(err)
|
||||||
|
|
||||||
|
// slices are sorted by key as stored in the data store, so init and export can be compared with equal
|
||||||
|
state := types.NewGenesisState(
|
||||||
|
types.Params{
|
||||||
|
AllowedPools: swap.AllowedPools{swap.NewAllowedPool("ukava", "usdx")},
|
||||||
|
SwapFee: sdk.MustNewDecFromStr("0.00255"),
|
||||||
|
},
|
||||||
|
types.PoolRecords{
|
||||||
|
swap.NewPoolRecord(sdk.NewCoins(sdk.NewCoin("hard", sdk.NewInt(1e6)), sdk.NewCoin("usdx", sdk.NewInt(2e6))), sdk.NewInt(1e6)),
|
||||||
|
swap.NewPoolRecord(sdk.NewCoins(sdk.NewCoin("ukava", sdk.NewInt(1e6)), sdk.NewCoin("usdx", sdk.NewInt(5e6))), sdk.NewInt(3e6)),
|
||||||
|
},
|
||||||
|
types.ShareRecords{
|
||||||
|
types.NewShareRecord(depositor_2, "hard/usdx", sdk.NewInt(1e6)),
|
||||||
|
types.NewShareRecord(depositor_1, "ukava/usdx", sdk.NewInt(3e6)),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
swap.InitGenesis(suite.Ctx, suite.Keeper, state)
|
||||||
|
suite.Equal(state.Params, suite.Keeper.GetParams(suite.Ctx))
|
||||||
|
|
||||||
|
poolRecord1, _ := suite.Keeper.GetPool(suite.Ctx, "hard/usdx")
|
||||||
|
suite.Equal(state.PoolRecords[0], poolRecord1)
|
||||||
|
poolRecord2, _ := suite.Keeper.GetPool(suite.Ctx, "ukava/usdx")
|
||||||
|
suite.Equal(state.PoolRecords[1], poolRecord2)
|
||||||
|
|
||||||
|
shareRecord1, _ := suite.Keeper.GetDepositorShares(suite.Ctx, depositor_2, "hard/usdx")
|
||||||
|
suite.Equal(state.ShareRecords[0], shareRecord1)
|
||||||
|
shareRecord2, _ := suite.Keeper.GetDepositorShares(suite.Ctx, depositor_1, "ukava/usdx")
|
||||||
|
suite.Equal(state.ShareRecords[1], shareRecord2)
|
||||||
|
|
||||||
|
exportedState := swap.ExportGenesis(suite.Ctx, suite.Keeper)
|
||||||
|
suite.Equal(state, exportedState)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesisTestSuite(t *testing.T) {
|
||||||
|
suite.Run(t, new(genesisTestSuite))
|
||||||
|
}
|
@ -113,7 +113,7 @@ func (k Keeper) initializePool(ctx sdk.Context, poolID string, depositor sdk.Acc
|
|||||||
return sdk.Coins{}, sdk.ZeroInt(), err
|
return sdk.Coins{}, sdk.ZeroInt(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
poolRecord := types.NewPoolRecord(pool)
|
poolRecord := types.NewPoolRecordFromPool(pool)
|
||||||
shareRecord := types.NewShareRecord(depositor, poolRecord.PoolID, pool.TotalShares())
|
shareRecord := types.NewShareRecord(depositor, poolRecord.PoolID, pool.TotalShares())
|
||||||
|
|
||||||
k.SetPool(ctx, poolRecord)
|
k.SetPool(ctx, poolRecord)
|
||||||
@ -132,7 +132,7 @@ func (k Keeper) addLiquidityToPool(ctx sdk.Context, record types.PoolRecord, dep
|
|||||||
|
|
||||||
depositAmount, shares := pool.AddLiquidity(desiredAmount)
|
depositAmount, shares := pool.AddLiquidity(desiredAmount)
|
||||||
|
|
||||||
poolRecord := types.NewPoolRecord(pool)
|
poolRecord := types.NewPoolRecordFromPool(pool)
|
||||||
|
|
||||||
shareRecord, sharesFound := k.GetDepositorShares(ctx, depositor, poolRecord.PoolID)
|
shareRecord, sharesFound := k.GetDepositorShares(ctx, depositor, poolRecord.PoolID)
|
||||||
if sharesFound {
|
if sharesFound {
|
||||||
|
@ -88,7 +88,7 @@ func (suite *keeperTestSuite) TestPool_Persistance() {
|
|||||||
|
|
||||||
pool, err := types.NewDenominatedPool(reserves)
|
pool, err := types.NewDenominatedPool(reserves)
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
record := types.NewPoolRecord(pool)
|
record := types.NewPoolRecordFromPool(pool)
|
||||||
|
|
||||||
suite.Keeper.SetPool(suite.Ctx, record)
|
suite.Keeper.SetPool(suite.Ctx, record)
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ func (suite *querierTestSuite) TestQueryPool() {
|
|||||||
|
|
||||||
pool, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinB))
|
pool, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinB))
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
poolRecord := types.NewPoolRecord(pool)
|
poolRecord := types.NewPoolRecordFromPool(pool)
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
||||||
|
|
||||||
ctx := suite.Ctx.WithIsCheckTx(false)
|
ctx := suite.Ctx.WithIsCheckTx(false)
|
||||||
@ -101,12 +101,12 @@ func (suite *querierTestSuite) TestQueryPools() {
|
|||||||
|
|
||||||
poolAB, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinB))
|
poolAB, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinB))
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
poolRecordAB := types.NewPoolRecord(poolAB)
|
poolRecordAB := types.NewPoolRecordFromPool(poolAB)
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecordAB)
|
suite.Keeper.SetPool(suite.Ctx, poolRecordAB)
|
||||||
|
|
||||||
poolAC, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinC))
|
poolAC, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinC))
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
poolRecordAC := types.NewPoolRecord(poolAC)
|
poolRecordAC := types.NewPoolRecordFromPool(poolAC)
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecordAC)
|
suite.Keeper.SetPool(suite.Ctx, poolRecordAC)
|
||||||
|
|
||||||
// Build a map of pools to compare to query results
|
// Build a map of pools to compare to query results
|
||||||
@ -142,7 +142,7 @@ func (suite *querierTestSuite) TestQueryDeposit() {
|
|||||||
coinB := sdk.NewCoin("usdx", sdk.NewInt(200))
|
coinB := sdk.NewCoin("usdx", sdk.NewInt(200))
|
||||||
pool, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinB))
|
pool, err := types.NewDenominatedPool(sdk.NewCoins(coinA, coinB))
|
||||||
suite.Nil(err)
|
suite.Nil(err)
|
||||||
poolRecord := types.NewPoolRecord(pool)
|
poolRecord := types.NewPoolRecordFromPool(pool)
|
||||||
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
suite.Keeper.SetPool(suite.Ctx, poolRecord)
|
||||||
|
|
||||||
// Deposit into pool
|
// Deposit into pool
|
||||||
|
@ -96,7 +96,7 @@ func (k Keeper) commitSwap(
|
|||||||
feePaid sdk.Coin,
|
feePaid sdk.Coin,
|
||||||
exactDirection string,
|
exactDirection string,
|
||||||
) error {
|
) error {
|
||||||
k.SetPool(ctx, types.NewPoolRecord(pool))
|
k.SetPool(ctx, types.NewPoolRecordFromPool(pool))
|
||||||
|
|
||||||
if err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, requester, types.ModuleAccountName, sdk.NewCoins(swapInput)); err != nil {
|
if err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, requester, types.ModuleAccountName, sdk.NewCoins(swapInput)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -78,7 +78,7 @@ func (k Keeper) updatePool(ctx sdk.Context, poolID string, pool *types.Denominat
|
|||||||
if pool.TotalShares().IsZero() {
|
if pool.TotalShares().IsZero() {
|
||||||
k.DeletePool(ctx, poolID)
|
k.DeletePool(ctx, poolID)
|
||||||
} else {
|
} else {
|
||||||
k.SetPool(ctx, types.NewPoolRecord(pool))
|
k.SetPool(ctx, types.NewPoolRecordFromPool(pool))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,37 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import "bytes"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type poolShares struct {
|
||||||
|
totalShares sdk.Int
|
||||||
|
totalSharesOwned sdk.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// DefaultPoolRecords is used to set default records in default genesis state
|
||||||
|
DefaultPoolRecords = PoolRecords{}
|
||||||
|
// DefaultShareRecords is used to set default records in default genesis state
|
||||||
|
DefaultShareRecords = ShareRecords{}
|
||||||
|
)
|
||||||
|
|
||||||
// GenesisState is the state that must be provided at genesis.
|
// GenesisState is the state that must be provided at genesis.
|
||||||
type GenesisState struct {
|
type GenesisState struct {
|
||||||
Params Params `json:"params" yaml:"params"`
|
Params Params `json:"params" yaml:"params"`
|
||||||
|
PoolRecords `json:"pool_records" yaml:"pool_records"`
|
||||||
|
ShareRecords `json:"share_records" yaml:"share_records"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGenesisState creates a new genesis state.
|
// NewGenesisState creates a new genesis state.
|
||||||
func NewGenesisState(params Params) GenesisState {
|
func NewGenesisState(params Params, poolRecords PoolRecords, shareRecords ShareRecords) GenesisState {
|
||||||
return GenesisState{
|
return GenesisState{
|
||||||
Params: params,
|
Params: params,
|
||||||
|
PoolRecords: poolRecords,
|
||||||
|
ShareRecords: shareRecords,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -19,6 +40,38 @@ func (gs GenesisState) Validate() error {
|
|||||||
if err := gs.Params.Validate(); err != nil {
|
if err := gs.Params.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := gs.PoolRecords.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := gs.ShareRecords.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
totalShares := make(map[string]poolShares)
|
||||||
|
for _, pr := range gs.PoolRecords {
|
||||||
|
totalShares[pr.PoolID] = poolShares{
|
||||||
|
totalShares: pr.TotalShares,
|
||||||
|
totalSharesOwned: sdk.ZeroInt(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, sr := range gs.ShareRecords {
|
||||||
|
if shares, found := totalShares[sr.PoolID]; found {
|
||||||
|
shares.totalSharesOwned = shares.totalSharesOwned.Add(sr.SharesOwned)
|
||||||
|
totalShares[sr.PoolID] = shares
|
||||||
|
} else {
|
||||||
|
totalShares[sr.PoolID] = poolShares{
|
||||||
|
totalShares: sdk.ZeroInt(),
|
||||||
|
totalSharesOwned: sr.SharesOwned,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for poolID, ps := range totalShares {
|
||||||
|
if !ps.totalShares.Equal(ps.totalSharesOwned) {
|
||||||
|
return fmt.Errorf("total depositor shares %s not equal to pool '%s' total shares %s", ps.totalSharesOwned.String(), poolID, ps.totalShares.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,6 +79,8 @@ func (gs GenesisState) Validate() error {
|
|||||||
func DefaultGenesisState() GenesisState {
|
func DefaultGenesisState() GenesisState {
|
||||||
return NewGenesisState(
|
return NewGenesisState(
|
||||||
DefaultParams(),
|
DefaultParams(),
|
||||||
|
DefaultPoolRecords,
|
||||||
|
DefaultShareRecords,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package types_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/kava-labs/kava/x/swap/types"
|
"github.com/kava-labs/kava/x/swap/types"
|
||||||
@ -8,6 +9,7 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGenesis_Default(t *testing.T) {
|
func TestGenesis_Default(t *testing.T) {
|
||||||
@ -133,8 +135,8 @@ func TestGenesis_Equal(t *testing.T) {
|
|||||||
sdk.MustNewDecFromStr("0.85"),
|
sdk.MustNewDecFromStr("0.85"),
|
||||||
}
|
}
|
||||||
|
|
||||||
genesisA := types.GenesisState{params}
|
genesisA := types.GenesisState{params, types.DefaultPoolRecords, types.DefaultShareRecords}
|
||||||
genesisB := types.GenesisState{params}
|
genesisB := types.GenesisState{params, types.DefaultPoolRecords, types.DefaultShareRecords}
|
||||||
|
|
||||||
assert.True(t, genesisA.Equal(genesisB))
|
assert.True(t, genesisA.Equal(genesisB))
|
||||||
}
|
}
|
||||||
@ -147,17 +149,17 @@ func TestGenesis_NotEqual(t *testing.T) {
|
|||||||
|
|
||||||
// Base params
|
// Base params
|
||||||
genesisAParams := baseParams
|
genesisAParams := baseParams
|
||||||
genesisA := types.GenesisState{genesisAParams}
|
genesisA := types.GenesisState{genesisAParams, types.DefaultPoolRecords, types.DefaultShareRecords}
|
||||||
|
|
||||||
// Different swap fee
|
// Different swap fee
|
||||||
genesisBParams := baseParams
|
genesisBParams := baseParams
|
||||||
genesisBParams.SwapFee = sdk.MustNewDecFromStr("0.84")
|
genesisBParams.SwapFee = sdk.MustNewDecFromStr("0.84")
|
||||||
genesisB := types.GenesisState{genesisBParams}
|
genesisB := types.GenesisState{genesisBParams, types.DefaultPoolRecords, types.DefaultShareRecords}
|
||||||
|
|
||||||
// Different pairs
|
// Different pairs
|
||||||
genesisCParams := baseParams
|
genesisCParams := baseParams
|
||||||
genesisCParams.AllowedPools = types.NewAllowedPools(types.NewAllowedPool("ukava", "hard"))
|
genesisCParams.AllowedPools = types.NewAllowedPools(types.NewAllowedPool("ukava", "hard"))
|
||||||
genesisC := types.GenesisState{genesisCParams}
|
genesisC := types.GenesisState{genesisCParams, types.DefaultPoolRecords, types.DefaultShareRecords}
|
||||||
|
|
||||||
// A and B have different swap fees
|
// A and B have different swap fees
|
||||||
assert.False(t, genesisA.Equal(genesisB))
|
assert.False(t, genesisA.Equal(genesisB))
|
||||||
@ -166,3 +168,229 @@ func TestGenesis_NotEqual(t *testing.T) {
|
|||||||
// A and B and different swap fees and pair token B denoms
|
// A and B and different swap fees and pair token B denoms
|
||||||
assert.False(t, genesisA.Equal(genesisB))
|
assert.False(t, genesisA.Equal(genesisB))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGenesis_JSONEncoding(t *testing.T) {
|
||||||
|
raw := `{
|
||||||
|
"params": {
|
||||||
|
"allowed_pools": [
|
||||||
|
{
|
||||||
|
"token_a": "ukava",
|
||||||
|
"token_b": "usdx"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token_a": "hard",
|
||||||
|
"token_b": "busd"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"swap_fee": "0.003000000000000000"
|
||||||
|
},
|
||||||
|
"pool_records": [
|
||||||
|
{
|
||||||
|
"pool_id": "ukava/usdx",
|
||||||
|
"reserves_a": { "denom": "ukava", "amount": "1000000" },
|
||||||
|
"reserves_b": { "denom": "usdx", "amount": "5000000" },
|
||||||
|
"total_shares": "3000000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pool_id": "hard/usdx",
|
||||||
|
"reserves_a": { "denom": "ukava", "amount": "1000000" },
|
||||||
|
"reserves_b": { "denom": "usdx", "amount": "2000000" },
|
||||||
|
"total_shares": "2000000"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"share_records": [
|
||||||
|
{
|
||||||
|
"depositor": "kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w",
|
||||||
|
"pool_id": "ukava/usdx",
|
||||||
|
"shares_owned": "100000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"depositor": "kava1esagqd83rhqdtpy5sxhklaxgn58k2m3s3mnpea",
|
||||||
|
"pool_id": "hard/usdx",
|
||||||
|
"shares_owned": "200000"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`
|
||||||
|
|
||||||
|
var state types.GenesisState
|
||||||
|
err := json.Unmarshal([]byte(raw), &state)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, 2, len(state.Params.AllowedPools))
|
||||||
|
assert.Equal(t, sdk.MustNewDecFromStr("0.003"), state.Params.SwapFee)
|
||||||
|
assert.Equal(t, 2, len(state.PoolRecords))
|
||||||
|
assert.Equal(t, 2, len(state.ShareRecords))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesis_YAMLEncoding(t *testing.T) {
|
||||||
|
expected := `params:
|
||||||
|
allowed_pools:
|
||||||
|
- token_a: ukava
|
||||||
|
token_b: usdx
|
||||||
|
- token_a: hard
|
||||||
|
token_b: busd
|
||||||
|
swap_fee: "0.003000000000000000"
|
||||||
|
pool_records:
|
||||||
|
- pool_id: ukava/usdx
|
||||||
|
reserves_a:
|
||||||
|
denom: ukava
|
||||||
|
amount: "1000000"
|
||||||
|
reserves_b:
|
||||||
|
denom: usdx
|
||||||
|
amount: "5000000"
|
||||||
|
total_shares: "3000000"
|
||||||
|
- pool_id: hard/usdx
|
||||||
|
reserves_a:
|
||||||
|
denom: hard
|
||||||
|
amount: "1000000"
|
||||||
|
reserves_b:
|
||||||
|
denom: usdx
|
||||||
|
amount: "2000000"
|
||||||
|
total_shares: "1500000"
|
||||||
|
share_records:
|
||||||
|
- depositor: kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w
|
||||||
|
pool_id: ukava/usdx
|
||||||
|
shares_owned: "100000"
|
||||||
|
- depositor: kava1esagqd83rhqdtpy5sxhklaxgn58k2m3s3mnpea
|
||||||
|
pool_id: hard/usdx
|
||||||
|
shares_owned: "200000"
|
||||||
|
`
|
||||||
|
|
||||||
|
depositor_1, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
depositor_2, err := sdk.AccAddressFromBech32("kava1esagqd83rhqdtpy5sxhklaxgn58k2m3s3mnpea")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
state := types.NewGenesisState(
|
||||||
|
types.NewParams(
|
||||||
|
types.NewAllowedPools(
|
||||||
|
types.NewAllowedPool("ukava", "usdx"),
|
||||||
|
types.NewAllowedPool("hard", "busd"),
|
||||||
|
),
|
||||||
|
sdk.MustNewDecFromStr("0.003"),
|
||||||
|
),
|
||||||
|
types.PoolRecords{
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(3e6)),
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(hard(1e6), usdx(2e6)), i(15e5)),
|
||||||
|
},
|
||||||
|
types.ShareRecords{
|
||||||
|
types.NewShareRecord(depositor_1, "ukava/usdx", i(1e5)),
|
||||||
|
types.NewShareRecord(depositor_2, "hard/usdx", i(2e5)),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
data, err := yaml.Marshal(state)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesis_ValidatePoolRecords(t *testing.T) {
|
||||||
|
invalidPoolRecord := types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(-1))
|
||||||
|
|
||||||
|
state := types.NewGenesisState(
|
||||||
|
types.DefaultParams(),
|
||||||
|
types.PoolRecords{invalidPoolRecord},
|
||||||
|
types.ShareRecords{},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Error(t, state.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesis_ValidateShareRecords(t *testing.T) {
|
||||||
|
depositor, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
invalidShareRecord := types.NewShareRecord(depositor, "", i(-1))
|
||||||
|
|
||||||
|
state := types.NewGenesisState(
|
||||||
|
types.DefaultParams(),
|
||||||
|
types.PoolRecords{},
|
||||||
|
types.ShareRecords{invalidShareRecord},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Error(t, state.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenesis_Validate_PoolShareIntegration(t *testing.T) {
|
||||||
|
depositor_1, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
depositor_2, err := sdk.AccAddressFromBech32("kava1esagqd83rhqdtpy5sxhklaxgn58k2m3s3mnpea")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
poolRecords types.PoolRecords
|
||||||
|
shareRecords types.ShareRecords
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "single pool record, zero share records",
|
||||||
|
poolRecords: types.PoolRecords{
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(3e6)),
|
||||||
|
},
|
||||||
|
shareRecords: types.ShareRecords{},
|
||||||
|
expectedErr: "total depositor shares 0 not equal to pool 'ukava/usdx' total shares 3000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero pool records, one share record",
|
||||||
|
poolRecords: types.PoolRecords{},
|
||||||
|
shareRecords: types.ShareRecords{
|
||||||
|
types.NewShareRecord(depositor_1, "ukava/usdx", i(5e6)),
|
||||||
|
},
|
||||||
|
expectedErr: "total depositor shares 5000000 not equal to pool 'ukava/usdx' total shares 0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "one pool record, one share record",
|
||||||
|
poolRecords: types.PoolRecords{
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(3e6)),
|
||||||
|
},
|
||||||
|
shareRecords: types.ShareRecords{
|
||||||
|
types.NewShareRecord(depositor_1, "ukava/usdx", i(15e5)),
|
||||||
|
},
|
||||||
|
expectedErr: "total depositor shares 1500000 not equal to pool 'ukava/usdx' total shares 3000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "more than one pool records, more than one share record",
|
||||||
|
poolRecords: types.PoolRecords{
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(3e6)),
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(hard(1e6), usdx(2e6)), i(2e6)),
|
||||||
|
},
|
||||||
|
shareRecords: types.ShareRecords{
|
||||||
|
types.NewShareRecord(depositor_1, "ukava/usdx", i(15e5)),
|
||||||
|
types.NewShareRecord(depositor_2, "ukava/usdx", i(15e5)),
|
||||||
|
types.NewShareRecord(depositor_1, "hard/usdx", i(1e6)),
|
||||||
|
},
|
||||||
|
expectedErr: "total depositor shares 1000000 not equal to pool 'hard/usdx' total shares 2000000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid case with many pool records and share records",
|
||||||
|
poolRecords: types.PoolRecords{
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(3e6)),
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(hard(1e6), usdx(2e6)), i(2e6)),
|
||||||
|
types.NewPoolRecord(sdk.NewCoins(hard(7e6), ukava(10e6)), i(8e6)),
|
||||||
|
},
|
||||||
|
shareRecords: types.ShareRecords{
|
||||||
|
types.NewShareRecord(depositor_1, "ukava/usdx", i(15e5)),
|
||||||
|
types.NewShareRecord(depositor_2, "ukava/usdx", i(15e5)),
|
||||||
|
types.NewShareRecord(depositor_1, "hard/usdx", i(2e6)),
|
||||||
|
types.NewShareRecord(depositor_1, "hard/ukava", i(3e6)),
|
||||||
|
types.NewShareRecord(depositor_2, "hard/ukava", i(5e6)),
|
||||||
|
},
|
||||||
|
expectedErr: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
state := types.NewGenesisState(types.DefaultParams(), tc.poolRecords, tc.shareRecords)
|
||||||
|
err := state.Validate()
|
||||||
|
|
||||||
|
if tc.expectedErr == "" {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
assert.EqualError(t, err, tc.expectedErr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
)
|
)
|
||||||
@ -25,20 +27,32 @@ func PoolID(denomA string, denomB string) string {
|
|||||||
// and is used to store the state of a denominated pool
|
// and is used to store the state of a denominated pool
|
||||||
type PoolRecord struct {
|
type PoolRecord struct {
|
||||||
// primary key
|
// primary key
|
||||||
PoolID string
|
PoolID string `json:"pool_id" yaml:"pool_id"`
|
||||||
ReservesA sdk.Coin `json:"reserves_a" yaml:"reserves_a"`
|
ReservesA sdk.Coin `json:"reserves_a" yaml:"reserves_a"`
|
||||||
ReservesB sdk.Coin `json:"reserves_b" yaml:"reserves_b"`
|
ReservesB sdk.Coin `json:"reserves_b" yaml:"reserves_b"`
|
||||||
TotalShares sdk.Int `json:"total_shares" yaml:"total_shares"`
|
TotalShares sdk.Int `json:"total_shares" yaml:"total_shares"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reserves returns the total reserves for a pool
|
// NewPoolRecord takes reserve coins and total shares, returning
|
||||||
func (p PoolRecord) Reserves() sdk.Coins {
|
// a new pool record with a id
|
||||||
return sdk.NewCoins(p.ReservesA, p.ReservesB)
|
func NewPoolRecord(reserves sdk.Coins, totalShares sdk.Int) PoolRecord {
|
||||||
|
if len(reserves) != 2 {
|
||||||
|
panic("reserves must have two denominations")
|
||||||
|
}
|
||||||
|
|
||||||
|
poolID := PoolIDFromCoins(reserves)
|
||||||
|
|
||||||
|
return PoolRecord{
|
||||||
|
PoolID: poolID,
|
||||||
|
ReservesA: reserves[0],
|
||||||
|
ReservesB: reserves[1],
|
||||||
|
TotalShares: totalShares,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPoolRecord takes a pointer to a denominated pool and returns a
|
// NewPoolRecordFromPool takes a pointer to a denominated pool and returns a
|
||||||
// pool record for storage in state.
|
// pool record for storage in state.
|
||||||
func NewPoolRecord(pool *DenominatedPool) PoolRecord {
|
func NewPoolRecordFromPool(pool *DenominatedPool) PoolRecord {
|
||||||
reserves := pool.Reserves()
|
reserves := pool.Reserves()
|
||||||
poolID := PoolIDFromCoins(reserves)
|
poolID := PoolIDFromCoins(reserves)
|
||||||
|
|
||||||
@ -50,9 +64,65 @@ func NewPoolRecord(pool *DenominatedPool) PoolRecord {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs basic validation checks of the record data
|
||||||
|
func (p PoolRecord) Validate() error {
|
||||||
|
if p.PoolID == "" {
|
||||||
|
return errors.New("poolID must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(p.PoolID, "/")
|
||||||
|
if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" || tokens[1] < tokens[0] || tokens[0] == tokens[1] {
|
||||||
|
return fmt.Errorf("poolID '%s' is invalid", p.PoolID)
|
||||||
|
}
|
||||||
|
if sdk.ValidateDenom(tokens[0]) != nil || sdk.ValidateDenom(tokens[1]) != nil {
|
||||||
|
return fmt.Errorf("poolID '%s' is invalid", p.PoolID)
|
||||||
|
}
|
||||||
|
if tokens[0] != p.ReservesA.Denom || tokens[1] != p.ReservesB.Denom {
|
||||||
|
return fmt.Errorf("poolID '%s' does not match reserves", p.PoolID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.ReservesA.IsPositive() {
|
||||||
|
return fmt.Errorf("pool '%s' has invalid reserves: %s", p.PoolID, p.ReservesA)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.ReservesB.IsPositive() {
|
||||||
|
return fmt.Errorf("pool '%s' has invalid reserves: %s", p.PoolID, p.ReservesB)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.TotalShares.IsPositive() {
|
||||||
|
return fmt.Errorf("pool '%s' has invalid total shares: %s", p.PoolID, p.TotalShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reserves returns the total reserves for a pool
|
||||||
|
func (p PoolRecord) Reserves() sdk.Coins {
|
||||||
|
return sdk.NewCoins(p.ReservesA, p.ReservesB)
|
||||||
|
}
|
||||||
|
|
||||||
// PoolRecords is a slice of PoolRecord
|
// PoolRecords is a slice of PoolRecord
|
||||||
type PoolRecords []PoolRecord
|
type PoolRecords []PoolRecord
|
||||||
|
|
||||||
|
// Validate performs basic validation checks on all records in the slice
|
||||||
|
func (prs PoolRecords) Validate() error {
|
||||||
|
seenPoolIDs := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, p := range prs {
|
||||||
|
if err := p.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if seenPoolIDs[p.PoolID] {
|
||||||
|
return fmt.Errorf("duplicate poolID '%s'", p.PoolID)
|
||||||
|
}
|
||||||
|
|
||||||
|
seenPoolIDs[p.PoolID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ShareRecord stores the shares owned for a depositor and pool
|
// ShareRecord stores the shares owned for a depositor and pool
|
||||||
type ShareRecord struct {
|
type ShareRecord struct {
|
||||||
// primary key
|
// primary key
|
||||||
@ -72,5 +142,54 @@ func NewShareRecord(depositor sdk.AccAddress, poolID string, sharesOwned sdk.Int
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate performs basic validation checks of the record data
|
||||||
|
func (sr ShareRecord) Validate() error {
|
||||||
|
if sr.PoolID == "" {
|
||||||
|
return errors.New("poolID must be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := strings.Split(sr.PoolID, "/")
|
||||||
|
if len(tokens) != 2 || tokens[0] == "" || tokens[1] == "" || tokens[1] < tokens[0] || tokens[0] == tokens[1] {
|
||||||
|
return fmt.Errorf("poolID '%s' is invalid", sr.PoolID)
|
||||||
|
}
|
||||||
|
if sdk.ValidateDenom(tokens[0]) != nil || sdk.ValidateDenom(tokens[1]) != nil {
|
||||||
|
return fmt.Errorf("poolID '%s' is invalid", sr.PoolID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sr.Depositor.Empty() {
|
||||||
|
return fmt.Errorf("share record cannot have empty depositor address")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sr.SharesOwned.IsPositive() {
|
||||||
|
return fmt.Errorf("depositor '%s' and pool '%s' has invalid total shares: %s", sr.Depositor.String(), sr.PoolID, sr.SharesOwned.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ShareRecords is a slice of ShareRecord
|
// ShareRecords is a slice of ShareRecord
|
||||||
type ShareRecords []ShareRecord
|
type ShareRecords []ShareRecord
|
||||||
|
|
||||||
|
// Validate performs basic validation checks on all records in the slice
|
||||||
|
func (srs ShareRecords) Validate() error {
|
||||||
|
seenDepositors := make(map[string]map[string]bool)
|
||||||
|
|
||||||
|
for _, sr := range srs {
|
||||||
|
if err := sr.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if seenPools, found := seenDepositors[sr.Depositor.String()]; found {
|
||||||
|
if seenPools[sr.PoolID] {
|
||||||
|
return fmt.Errorf("duplicate depositor '%s' and poolID '%s'", sr.Depositor.String(), sr.PoolID)
|
||||||
|
}
|
||||||
|
seenPools[sr.PoolID] = true
|
||||||
|
} else {
|
||||||
|
seenPools := make(map[string]bool)
|
||||||
|
seenPools[sr.PoolID] = true
|
||||||
|
seenDepositors[sr.Depositor.String()] = seenPools
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package types_test
|
package types_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
types "github.com/kava-labs/kava/x/swap/types"
|
types "github.com/kava-labs/kava/x/swap/types"
|
||||||
@ -8,6 +9,7 @@ import (
|
|||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestState_PoolID(t *testing.T) {
|
func TestState_PoolID(t *testing.T) {
|
||||||
@ -37,17 +39,308 @@ func TestState_PoolID(t *testing.T) {
|
|||||||
|
|
||||||
func TestState_NewPoolRecord(t *testing.T) {
|
func TestState_NewPoolRecord(t *testing.T) {
|
||||||
reserves := sdk.NewCoins(usdx(50e6), ukava(10e6))
|
reserves := sdk.NewCoins(usdx(50e6), ukava(10e6))
|
||||||
|
totalShares := sdk.NewInt(30e6)
|
||||||
|
|
||||||
|
poolRecord := types.NewPoolRecord(reserves, totalShares)
|
||||||
|
|
||||||
|
assert.Equal(t, reserves[0], poolRecord.ReservesA)
|
||||||
|
assert.Equal(t, reserves[1], poolRecord.ReservesB)
|
||||||
|
assert.Equal(t, reserves, poolRecord.Reserves())
|
||||||
|
assert.Equal(t, totalShares, poolRecord.TotalShares)
|
||||||
|
|
||||||
|
assert.PanicsWithValue(t, "reserves must have two denominations", func() {
|
||||||
|
reserves := sdk.NewCoins(ukava(10e6))
|
||||||
|
_ = types.NewPoolRecord(reserves, totalShares)
|
||||||
|
}, "expected panic with 1 coin in reserves")
|
||||||
|
|
||||||
|
assert.PanicsWithValue(t, "reserves must have two denominations", func() {
|
||||||
|
reserves := sdk.NewCoins(ukava(10e6), hard(1e6), usdx(20e6))
|
||||||
|
_ = types.NewPoolRecord(reserves, totalShares)
|
||||||
|
}, "expected panic with 3 coins in reserves")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_NewPoolRecordFromPool(t *testing.T) {
|
||||||
|
reserves := sdk.NewCoins(usdx(50e6), ukava(10e6))
|
||||||
|
|
||||||
pool, err := types.NewDenominatedPool(reserves)
|
pool, err := types.NewDenominatedPool(reserves)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
record := types.NewPoolRecord(pool)
|
record := types.NewPoolRecordFromPool(pool)
|
||||||
|
|
||||||
assert.Equal(t, types.PoolID("ukava", "usdx"), record.PoolID)
|
assert.Equal(t, types.PoolID("ukava", "usdx"), record.PoolID)
|
||||||
assert.Equal(t, ukava(10e6), record.ReservesA)
|
assert.Equal(t, ukava(10e6), record.ReservesA)
|
||||||
assert.Equal(t, record.ReservesB, usdx(50e6))
|
assert.Equal(t, record.ReservesB, usdx(50e6))
|
||||||
assert.Equal(t, pool.TotalShares(), record.TotalShares)
|
assert.Equal(t, pool.TotalShares(), record.TotalShares)
|
||||||
assert.Equal(t, sdk.NewCoins(ukava(10e6), usdx(50e6)), record.Reserves())
|
assert.Equal(t, sdk.NewCoins(ukava(10e6), usdx(50e6)), record.Reserves())
|
||||||
|
assert.Nil(t, record.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_PoolRecord_JSONEncoding(t *testing.T) {
|
||||||
|
raw := `{
|
||||||
|
"pool_id": "ukava/usdx",
|
||||||
|
"reserves_a": { "denom": "ukava", "amount": "1000000" },
|
||||||
|
"reserves_b": { "denom": "usdx", "amount": "5000000" },
|
||||||
|
"total_shares": "3000000"
|
||||||
|
}`
|
||||||
|
|
||||||
|
var record types.PoolRecord
|
||||||
|
err := json.Unmarshal([]byte(raw), &record)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "ukava/usdx", record.PoolID)
|
||||||
|
assert.Equal(t, ukava(1e6), record.ReservesA)
|
||||||
|
assert.Equal(t, usdx(5e6), record.ReservesB)
|
||||||
|
assert.Equal(t, i(3e6), record.TotalShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_PoolRecord_YamlEncoding(t *testing.T) {
|
||||||
|
expected := `pool_id: ukava/usdx
|
||||||
|
reserves_a:
|
||||||
|
denom: ukava
|
||||||
|
amount: "1000000"
|
||||||
|
reserves_b:
|
||||||
|
denom: usdx
|
||||||
|
amount: "5000000"
|
||||||
|
total_shares: "3000000"
|
||||||
|
`
|
||||||
|
record := types.NewPoolRecord(sdk.NewCoins(ukava(1e6), usdx(5e6)), i(3e6))
|
||||||
|
data, err := yaml.Marshal(record)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_PoolRecord_Validations(t *testing.T) {
|
||||||
|
validRecord := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(usdx(500e6), ukava(100e6)),
|
||||||
|
i(300e6),
|
||||||
|
)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
poolID string
|
||||||
|
reservesA sdk.Coin
|
||||||
|
reservesB sdk.Coin
|
||||||
|
totalShares sdk.Int
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty pool id",
|
||||||
|
poolID: "",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID must be set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no poolID tokens",
|
||||||
|
poolID: "ukavausdx",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'ukavausdx' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID empty tokens",
|
||||||
|
poolID: "/",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID '/' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID empty token a",
|
||||||
|
poolID: "/usdx",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID '/usdx' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID empty token b",
|
||||||
|
poolID: "ukava/",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'ukava/' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID is not sorted",
|
||||||
|
poolID: "usdx/ukava",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'usdx/ukava' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID has invalid denom a",
|
||||||
|
poolID: "UKAVA/usdx",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'UKAVA/usdx' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID has invalid denom b",
|
||||||
|
poolID: "ukava/USDX",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'ukava/USDX' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID has duplicate denoms",
|
||||||
|
poolID: "ukava/ukava",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'ukava/ukava' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID does not match reserve A",
|
||||||
|
poolID: "ukava/usdx",
|
||||||
|
reservesA: hard(5e6),
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'ukava/usdx' does not match reserves",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID does not match reserve B",
|
||||||
|
poolID: "ukava/usdx",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: hard(5e6),
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "poolID 'ukava/usdx' does not match reserves",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative reserve a",
|
||||||
|
poolID: "ukava/usdx",
|
||||||
|
reservesA: sdk.Coin{Denom: "ukava", Amount: sdk.NewInt(-1)},
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "pool 'ukava/usdx' has invalid reserves: -1ukava",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero reserve a",
|
||||||
|
poolID: "ukava/usdx",
|
||||||
|
reservesA: sdk.Coin{Denom: "ukava", Amount: sdk.ZeroInt()},
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "pool 'ukava/usdx' has invalid reserves: 0ukava",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative reserve b",
|
||||||
|
poolID: "ukava/usdx",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: sdk.Coin{Denom: "usdx", Amount: sdk.NewInt(-1)},
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "pool 'ukava/usdx' has invalid reserves: -1usdx",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero reserve b",
|
||||||
|
poolID: "ukava/usdx",
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: sdk.Coin{Denom: "usdx", Amount: sdk.ZeroInt()},
|
||||||
|
totalShares: validRecord.TotalShares,
|
||||||
|
expectedErr: "pool 'ukava/usdx' has invalid reserves: 0usdx",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative total shares",
|
||||||
|
poolID: validRecord.PoolID,
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: sdk.NewInt(-1),
|
||||||
|
expectedErr: "pool 'ukava/usdx' has invalid total shares: -1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero total shares",
|
||||||
|
poolID: validRecord.PoolID,
|
||||||
|
reservesA: validRecord.ReservesA,
|
||||||
|
reservesB: validRecord.ReservesB,
|
||||||
|
totalShares: sdk.ZeroInt(),
|
||||||
|
expectedErr: "pool 'ukava/usdx' has invalid total shares: 0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
record := types.PoolRecord{
|
||||||
|
PoolID: tc.poolID,
|
||||||
|
ReservesA: tc.reservesA,
|
||||||
|
ReservesB: tc.reservesB,
|
||||||
|
TotalShares: tc.totalShares,
|
||||||
|
}
|
||||||
|
err := record.Validate()
|
||||||
|
assert.EqualError(t, err, tc.expectedErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_PoolRecord_OrderedReserves(t *testing.T) {
|
||||||
|
invalidOrder := types.NewPoolRecord(
|
||||||
|
// force order to not be sorted
|
||||||
|
sdk.Coins{usdx(500e6), ukava(100e6)},
|
||||||
|
i(300e6),
|
||||||
|
)
|
||||||
|
assert.Error(t, invalidOrder.Validate())
|
||||||
|
|
||||||
|
validOrder := types.NewPoolRecord(
|
||||||
|
// force order to not be sorted
|
||||||
|
sdk.Coins{ukava(500e6), usdx(100e6)},
|
||||||
|
i(300e6),
|
||||||
|
)
|
||||||
|
assert.NoError(t, validOrder.Validate())
|
||||||
|
|
||||||
|
record_1 := types.NewPoolRecord(sdk.NewCoins(usdx(500e6), ukava(100e6)), i(300e6))
|
||||||
|
record_2 := types.NewPoolRecord(sdk.NewCoins(ukava(100e6), usdx(500e6)), i(300e6))
|
||||||
|
// ensure no regresssions in NewCoins ordering
|
||||||
|
assert.Equal(t, record_1, record_2)
|
||||||
|
assert.Equal(t, "ukava/usdx", record_1.PoolID)
|
||||||
|
assert.Equal(t, "ukava/usdx", record_2.PoolID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_PoolRecords_Validation(t *testing.T) {
|
||||||
|
validRecord := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(usdx(500e6), ukava(100e6)),
|
||||||
|
i(300e6),
|
||||||
|
)
|
||||||
|
|
||||||
|
invalidRecord := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(usdx(500e6), ukava(100e6)),
|
||||||
|
i(-1),
|
||||||
|
)
|
||||||
|
|
||||||
|
records := types.PoolRecords{
|
||||||
|
validRecord,
|
||||||
|
}
|
||||||
|
assert.NoError(t, records.Validate())
|
||||||
|
|
||||||
|
records = append(records, invalidRecord)
|
||||||
|
err := records.Validate()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, "pool 'ukava/usdx' has invalid total shares: -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_PoolRecords_ValidateUniquePools(t *testing.T) {
|
||||||
|
record_1 := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(usdx(500e6), ukava(100e6)),
|
||||||
|
i(300e6),
|
||||||
|
)
|
||||||
|
|
||||||
|
record_2 := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(usdx(5000e6), ukava(1000e6)),
|
||||||
|
i(3000e6),
|
||||||
|
)
|
||||||
|
|
||||||
|
record_3 := types.NewPoolRecord(
|
||||||
|
sdk.NewCoins(usdx(5000e6), hard(1000e6)),
|
||||||
|
i(3000e6),
|
||||||
|
)
|
||||||
|
|
||||||
|
validRecords := types.PoolRecords{record_1, record_3}
|
||||||
|
assert.NoError(t, validRecords.Validate())
|
||||||
|
|
||||||
|
invalidRecords := types.PoolRecords{record_1, record_2}
|
||||||
|
assert.EqualError(t, invalidRecords.Validate(), "duplicate poolID 'ukava/usdx'")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestState_NewShareRecord(t *testing.T) {
|
func TestState_NewShareRecord(t *testing.T) {
|
||||||
@ -61,3 +354,205 @@ func TestState_NewShareRecord(t *testing.T) {
|
|||||||
assert.Equal(t, poolID, record.PoolID)
|
assert.Equal(t, poolID, record.PoolID)
|
||||||
assert.Equal(t, shares, record.SharesOwned)
|
assert.Equal(t, shares, record.SharesOwned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestState_ShareRecord_JSONEncoding(t *testing.T) {
|
||||||
|
raw := `{
|
||||||
|
"depositor": "kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w",
|
||||||
|
"pool_id": "ukava/usdx",
|
||||||
|
"shares_owned": "3000000"
|
||||||
|
}`
|
||||||
|
|
||||||
|
var record types.ShareRecord
|
||||||
|
err := json.Unmarshal([]byte(raw), &record)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w", record.Depositor.String())
|
||||||
|
assert.Equal(t, "ukava/usdx", record.PoolID)
|
||||||
|
assert.Equal(t, i(3e6), record.SharesOwned)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_ShareRecord_YamlEncoding(t *testing.T) {
|
||||||
|
expected := `depositor: kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w
|
||||||
|
pool_id: ukava/usdx
|
||||||
|
shares_owned: "3000000"
|
||||||
|
`
|
||||||
|
depositor, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
record := types.NewShareRecord(depositor, "ukava/usdx", i(3e6))
|
||||||
|
data, err := yaml.Marshal(record)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, expected, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_InvalidShareRecordEmptyDepositor(t *testing.T) {
|
||||||
|
record := types.ShareRecord{
|
||||||
|
Depositor: sdk.AccAddress{},
|
||||||
|
PoolID: types.PoolID("ukava", "usdx"),
|
||||||
|
SharesOwned: sdk.NewInt(1e6),
|
||||||
|
}
|
||||||
|
require.Error(t, record.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_InvalidShareRecordNegativeShares(t *testing.T) {
|
||||||
|
record := types.ShareRecord{
|
||||||
|
Depositor: sdk.AccAddress("some user"),
|
||||||
|
PoolID: types.PoolID("ukava", "usdx"),
|
||||||
|
SharesOwned: sdk.NewInt(-1e6),
|
||||||
|
}
|
||||||
|
require.Error(t, record.Validate())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_ShareRecord_Validations(t *testing.T) {
|
||||||
|
depositor, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
validRecord := types.NewShareRecord(
|
||||||
|
depositor,
|
||||||
|
types.PoolID("ukava", "usdx"),
|
||||||
|
i(30e6),
|
||||||
|
)
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
depositor sdk.AccAddress
|
||||||
|
poolID string
|
||||||
|
sharesOwned sdk.Int
|
||||||
|
expectedErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty pool id",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID must be set",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no poolID tokens",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "ukavausdx",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID 'ukavausdx' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID empty tokens",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "/",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID '/' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID empty token a",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "/usdx",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID '/usdx' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID empty token b",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "ukava/",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID 'ukava/' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID is not sorted",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "usdx/ukava",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID 'usdx/ukava' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID has invalid denom a",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "UKAVA/usdx",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID 'UKAVA/usdx' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID has invalid denom b",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "ukava/USDX",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID 'ukava/USDX' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "poolID has duplicate denoms",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: "ukava/ukava",
|
||||||
|
sharesOwned: validRecord.SharesOwned,
|
||||||
|
expectedErr: "poolID 'ukava/ukava' is invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "negative total shares",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: validRecord.PoolID,
|
||||||
|
sharesOwned: sdk.NewInt(-1),
|
||||||
|
expectedErr: "depositor 'kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w' and pool 'ukava/usdx' has invalid total shares: -1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zero total shares",
|
||||||
|
depositor: validRecord.Depositor,
|
||||||
|
poolID: validRecord.PoolID,
|
||||||
|
sharesOwned: sdk.ZeroInt(),
|
||||||
|
expectedErr: "depositor 'kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w' and pool 'ukava/usdx' has invalid total shares: 0",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
record := types.ShareRecord{
|
||||||
|
Depositor: tc.depositor,
|
||||||
|
PoolID: tc.poolID,
|
||||||
|
SharesOwned: tc.sharesOwned,
|
||||||
|
}
|
||||||
|
err := record.Validate()
|
||||||
|
assert.EqualError(t, err, tc.expectedErr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_ShareRecords_Validation(t *testing.T) {
|
||||||
|
depositor, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
validRecord := types.NewShareRecord(
|
||||||
|
depositor,
|
||||||
|
"ukava/usdx",
|
||||||
|
i(300e6),
|
||||||
|
)
|
||||||
|
|
||||||
|
invalidRecord := types.NewShareRecord(
|
||||||
|
depositor,
|
||||||
|
"hard/usdx",
|
||||||
|
i(-1),
|
||||||
|
)
|
||||||
|
|
||||||
|
records := types.ShareRecords{
|
||||||
|
validRecord,
|
||||||
|
}
|
||||||
|
assert.NoError(t, records.Validate())
|
||||||
|
|
||||||
|
records = append(records, invalidRecord)
|
||||||
|
err = records.Validate()
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.EqualError(t, err, "depositor 'kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w' and pool 'hard/usdx' has invalid total shares: -1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestState_ShareRecords_ValidateUniqueShareRecords(t *testing.T) {
|
||||||
|
depositor_1, err := sdk.AccAddressFromBech32("kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
depositor_2, err := sdk.AccAddressFromBech32("kava1esagqd83rhqdtpy5sxhklaxgn58k2m3s3mnpea")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
record_1 := types.NewShareRecord(depositor_1, "ukava/usdx", i(20e6))
|
||||||
|
record_2 := types.NewShareRecord(depositor_1, "ukava/usdx", i(10e6))
|
||||||
|
record_3 := types.NewShareRecord(depositor_1, "hard/usdx", i(20e6))
|
||||||
|
record_4 := types.NewShareRecord(depositor_2, "ukava/usdx", i(20e6))
|
||||||
|
|
||||||
|
validRecords := types.ShareRecords{record_1, record_3, record_4}
|
||||||
|
assert.NoError(t, validRecords.Validate())
|
||||||
|
|
||||||
|
invalidRecords := types.ShareRecords{record_1, record_3, record_2, record_4}
|
||||||
|
assert.EqualError(t, invalidRecords.Validate(), "duplicate depositor 'kava1mq9qxlhze029lm0frzw2xr6hem8c3k9ts54w0w' and poolID 'ukava/usdx'")
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user