diff --git a/types/alias.go b/types/alias.go index 6cbf87f57..f2a004f9f 100644 --- a/types/alias.go +++ b/types/alias.go @@ -19,6 +19,7 @@ const ( MicroJPYDenom = assets.MicroJPYDenom MicroEURDenom = assets.MicroEURDenom MicroGBPDenom = assets.MicroGBPDenom + MicroMNTDenom = assets.MicroMNTDenom MicroUnit = assets.MicroUnit BlocksPerMinute = util.BlocksPerMinute BlocksPerHour = util.BlocksPerHour diff --git a/types/assets/assets.go b/types/assets/assets.go index 77b30c6da..5d69044ed 100644 --- a/types/assets/assets.go +++ b/types/assets/assets.go @@ -10,6 +10,7 @@ const ( MicroJPYDenom = "ujpy" MicroEURDenom = "ueur" MicroGBPDenom = "ugbp" + MicroMNTDenom = "umnt" MicroUnit = int64(1e6) ) diff --git a/x/market/alias.go b/x/market/alias.go index 6f6142c39..90c6fdb7c 100644 --- a/x/market/alias.go +++ b/x/market/alias.go @@ -42,16 +42,17 @@ var ( NewQuerier = keeper.NewQuerier // variable aliases - ModuleCdc = types.ModuleCdc - TerraPoolDeltaKey = types.TerraPoolDeltaKey - ParamStoreKeyBasePool = types.ParamStoreKeyBasePool - ParamStoreKeyPoolRecoveryPeriod = types.ParamStoreKeyPoolRecoveryPeriod - ParamStoreKeyMinSpread = types.ParamStoreKeyMinSpread - ParmamStoreKeyTobinTax = types.ParmamStoreKeyTobinTax - DefaultBasePool = types.DefaultBasePool - DefaultPoolRecoveryPeriod = types.DefaultPoolRecoveryPeriod - DefaultMinSpread = types.DefaultMinSpread - DefaultTobinTax = types.DefaultTobinTax + ModuleCdc = types.ModuleCdc + TerraPoolDeltaKey = types.TerraPoolDeltaKey + ParamStoreKeyBasePool = types.ParamStoreKeyBasePool + ParamStoreKeyPoolRecoveryPeriod = types.ParamStoreKeyPoolRecoveryPeriod + ParamStoreKeyMinSpread = types.ParamStoreKeyMinSpread + ParmaStoreKeyTobinTax = types.ParmaStoreKeyTobinTax + ParmaStoreKeyIlliquidTobinTaxList = types.ParmaStoreKeyIlliquidTobinTaxList + DefaultBasePool = types.DefaultBasePool + DefaultPoolRecoveryPeriod = types.DefaultPoolRecoveryPeriod + DefaultMinSpread = types.DefaultMinSpread + DefaultTobinTax = types.DefaultTobinTax ) type ( diff --git a/x/market/internal/keeper/params.go b/x/market/internal/keeper/params.go index 8f76a7e64..539c73658 100644 --- a/x/market/internal/keeper/params.go +++ b/x/market/internal/keeper/params.go @@ -29,9 +29,16 @@ func (k Keeper) PoolRecoveryPeriod(ctx sdk.Context) (res int64) { return } -// TobinTax is a tax on all spot conversions of one Terra into another Terra +// TobinTax is a tax rate on all spot conversions of one Terra into another Terra func (k Keeper) TobinTax(ctx sdk.Context) (res sdk.Dec) { - k.paramSpace.Get(ctx, types.ParmamStoreKeyTobinTax, &res) + k.paramSpace.Get(ctx, types.ParmaStoreKeyTobinTax, &res) + return +} + +// IlliquidTobinTaxList is the exceptions that have to pay a higher tobin tax due to illiquidity +// TobinTax will be used for the denoms which are not in the list +func (k Keeper) IlliquidTobinTaxList(ctx sdk.Context) (res types.TobinTaxList) { + k.paramSpace.Get(ctx, types.ParmaStoreKeyIlliquidTobinTaxList, &res) return } diff --git a/x/market/internal/keeper/swap.go b/x/market/internal/keeper/swap.go index a6d29117b..d486353b6 100644 --- a/x/market/internal/keeper/swap.go +++ b/x/market/internal/keeper/swap.go @@ -70,6 +70,18 @@ func (k Keeper) ComputeSwap(ctx sdk.Context, offerCoin sdk.Coin, askDenom string // Apply only tobin tax without constant product spread if offerCoin.Denom != core.MicroLunaDenom && askDenom != core.MicroLunaDenom { spread = k.TobinTax(ctx) + illiquidTobinTaxList := k.IlliquidTobinTaxList(ctx) + + // Apply highest tobin tax for the denoms in the swap operation + for _, tobinTax := range illiquidTobinTaxList { + if tobinTax.Denom == offerCoin.Denom || + tobinTax.Denom == askDenom { + if tobinTax.TaxRate.GT(spread) { + spread = tobinTax.TaxRate + } + } + } + return } diff --git a/x/market/internal/keeper/swap_test.go b/x/market/internal/keeper/swap_test.go index 410234016..e8e3a86af 100644 --- a/x/market/internal/keeper/swap_test.go +++ b/x/market/internal/keeper/swap_test.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" core "github.com/terra-project/core/types" + "github.com/terra-project/core/x/market/internal/types" ) func TestApplySwapToPool(t *testing.T) { @@ -82,3 +83,45 @@ func TestComputeInternalSwap(t *testing.T) { _, err := input.MarketKeeper.ComputeInternalSwap(input.Ctx, offerCoin, core.MicroLunaDenom) require.Error(t, err) } + +func TestIlliquidTobinTaxListParams(t *testing.T) { + input := CreateTestInput(t) + + // Set Oracle Price + lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) + lunaPriceInMNT := sdk.NewDecWithPrec(7652, 1) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + input.OracleKeeper.SetLunaExchangeRate(input.Ctx, core.MicroMNTDenom, lunaPriceInMNT) + + // Case 1: tobin tax 2% due to umnt denom + params := input.MarketKeeper.GetParams(input.Ctx) + params.TobinTax = sdk.NewDecWithPrec(25, 4) + params.IlliquidTobinTaxList = types.TobinTaxList{ + types.TobinTax{ + Denom: core.MicroSDRDenom, + TaxRate: sdk.NewDecWithPrec(25, 4), + }, + types.TobinTax{ + Denom: core.MicroMNTDenom, + TaxRate: sdk.NewDecWithPrec(2, 2), + }, + } + input.MarketKeeper.SetParams(input.Ctx, params) + + swapAmountInSDR := lunaPriceInSDR.MulInt64(rand.Int63()%10000 + 2).TruncateInt() + offerCoin := sdk.NewCoin(core.MicroSDRDenom, swapAmountInSDR) + _, spread, err := input.MarketKeeper.ComputeSwap(input.Ctx, offerCoin, core.MicroMNTDenom) + require.NoError(t, err) + require.Equal(t, sdk.NewDecWithPrec(2, 2), spread) + + // Case 2: tobin tax 5% due to default + params.TobinTax = sdk.NewDecWithPrec(5, 2) + input.MarketKeeper.SetParams(input.Ctx, params) + + swapAmountInSDR = lunaPriceInSDR.MulInt64(rand.Int63()%10000 + 2).TruncateInt() + offerCoin = sdk.NewCoin(core.MicroSDRDenom, swapAmountInSDR) + _, spread, err = input.MarketKeeper.ComputeSwap(input.Ctx, offerCoin, core.MicroMNTDenom) + require.NoError(t, err) + require.Equal(t, sdk.NewDecWithPrec(5, 2), spread) + +} diff --git a/x/market/internal/types/genesis_test.go b/x/market/internal/types/genesis_test.go index ee010d0c1..e157c8427 100644 --- a/x/market/internal/types/genesis_test.go +++ b/x/market/internal/types/genesis_test.go @@ -20,6 +20,8 @@ func TestGenesisValidation(t *testing.T) { genState.Params.MinSpread = sdk.NewDec(-1) require.Error(t, ValidateGenesis(genState)) + + require.True(t, len(genState.Params.String()) != 0) } func TestGenesisEqual(t *testing.T) { diff --git a/x/market/internal/types/params.go b/x/market/internal/types/params.go index 23c02dace..77bd66fb6 100644 --- a/x/market/internal/types/params.go +++ b/x/market/internal/types/params.go @@ -21,34 +21,44 @@ var ( // Min spread ParamStoreKeyMinSpread = []byte("minspread") // Tobin tax - ParmamStoreKeyTobinTax = []byte("tobintax") + ParmaStoreKeyTobinTax = []byte("tobintax") + // Illiquid tobin tax list + ParmaStoreKeyIlliquidTobinTaxList = []byte("illiquidtobintaxlist") ) // Default parameter values var ( - DefaultBasePool = sdk.NewDec(250000 * core.MicroUnit) // 250,000sdr = 250,000,000,000usdr - DefaultPoolRecoveryPeriod = core.BlocksPerDay // 14,400 - DefaultMinSpread = sdk.NewDecWithPrec(2, 2) // 2% - DefaultTobinTax = sdk.NewDecWithPrec(25, 4) // 0.25% + DefaultBasePool = sdk.NewDec(250000 * core.MicroUnit) // 250,000sdr = 250,000,000,000usdr + DefaultPoolRecoveryPeriod = core.BlocksPerDay // 14,400 + DefaultMinSpread = sdk.NewDecWithPrec(2, 2) // 2% + DefaultTobinTax = sdk.NewDecWithPrec(25, 4) // 0.25% + DefaultIlliquidTobinTaxList = TobinTaxList{ + { + Denom: core.MicroMNTDenom, + TaxRate: sdk.NewDecWithPrec(2, 2), // 2% + }, + } ) var _ subspace.ParamSet = &Params{} // Params market parameters type Params struct { - PoolRecoveryPeriod int64 `json:"pool_recovery_period" yaml:"pool_recovery_period"` - BasePool sdk.Dec `json:"base_pool" yaml:"base_pool"` - MinSpread sdk.Dec `json:"min_spread" yaml:"min_spread"` - TobinTax sdk.Dec `json:"tobin_tax" yaml:"tobin_tax"` + PoolRecoveryPeriod int64 `json:"pool_recovery_period" yaml:"pool_recovery_period"` + BasePool sdk.Dec `json:"base_pool" yaml:"base_pool"` + MinSpread sdk.Dec `json:"min_spread" yaml:"min_spread"` + TobinTax sdk.Dec `json:"tobin_tax" yaml:"tobin_tax"` + IlliquidTobinTaxList TobinTaxList `json:"illiquid_tobin_tax_list" yaml:"illiquid_tobin_tax_list"` } // DefaultParams creates default market module parameters func DefaultParams() Params { return Params{ - BasePool: DefaultBasePool, - PoolRecoveryPeriod: DefaultPoolRecoveryPeriod, - MinSpread: DefaultMinSpread, - TobinTax: DefaultTobinTax, + BasePool: DefaultBasePool, + PoolRecoveryPeriod: DefaultPoolRecoveryPeriod, + MinSpread: DefaultMinSpread, + TobinTax: DefaultTobinTax, + IlliquidTobinTaxList: DefaultIlliquidTobinTaxList, } } @@ -66,6 +76,11 @@ func (params Params) Validate() error { if params.TobinTax.IsNegative() || params.TobinTax.GT(sdk.OneDec()) { return fmt.Errorf("tobin tax should be a value between [0,1], is %s", params.TobinTax) } + for _, val := range params.IlliquidTobinTaxList { + if val.TaxRate.IsNegative() || val.TaxRate.GT(sdk.OneDec()) { + return fmt.Errorf("tobin tax should be a value between [0,1], is %s", val) + } + } return nil } @@ -78,7 +93,8 @@ func (params *Params) ParamSetPairs() subspace.ParamSetPairs { {Key: ParamStoreKeyBasePool, Value: ¶ms.BasePool}, {Key: ParamStoreKeyPoolRecoveryPeriod, Value: ¶ms.PoolRecoveryPeriod}, {Key: ParamStoreKeyMinSpread, Value: ¶ms.MinSpread}, - {Key: ParmamStoreKeyTobinTax, Value: ¶ms.TobinTax}, + {Key: ParmaStoreKeyTobinTax, Value: ¶ms.TobinTax}, + {Key: ParmaStoreKeyIlliquidTobinTaxList, Value: ¶ms.IlliquidTobinTaxList}, } } @@ -89,5 +105,6 @@ func (params Params) String() string { PoolRecoveryPeriod: %d MinSpread: %s TobinTax: %s - `, params.BasePool, params.PoolRecoveryPeriod, params.MinSpread, params.TobinTax) + IlliquidTobinTaxList: %s + `, params.BasePool, params.PoolRecoveryPeriod, params.MinSpread, params.TobinTax, params.IlliquidTobinTaxList) } diff --git a/x/market/internal/types/tobin_tax.go b/x/market/internal/types/tobin_tax.go new file mode 100644 index 000000000..55439fbef --- /dev/null +++ b/x/market/internal/types/tobin_tax.go @@ -0,0 +1,34 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TobinTax - struct to store tobin tax for the specific denom with high volatility +type TobinTax struct { + Denom string `json:"denom" yaml:"denom"` + TaxRate sdk.Dec `json:"tax_rate" yaml:"tax_rate"` +} + +// String implements fmt.Stringer interface +func (tt TobinTax) String() string { + return fmt.Sprintf(`TobinTax + Denom: %s, + TaxRate: %s`, + tt.Denom, tt.TaxRate) +} + +// TobinTaxList is convience wrapper to handle TobinTax array +type TobinTaxList []TobinTax + +// String implements fmt.Stringer interface +func (ttl TobinTaxList) String() (out string) { + out = "" + for _, tt := range ttl { + out += tt.String() + "\n" + } + + return +} diff --git a/x/oracle/internal/types/denom.go b/x/oracle/internal/types/denom.go index 125f37e9e..3724761f4 100644 --- a/x/oracle/internal/types/denom.go +++ b/x/oracle/internal/types/denom.go @@ -8,7 +8,6 @@ import ( type DenomList []string // String implements fmt.Stringer interface -func (dl DenomList) String() (out string) { - strings.Join(dl, "\n") - return +func (dl DenomList) String() string { + return strings.Join(dl, "\n") } diff --git a/x/oracle/internal/types/vote.go b/x/oracle/internal/types/vote.go index 1ef32ea11..21bce4a45 100644 --- a/x/oracle/internal/types/vote.go +++ b/x/oracle/internal/types/vote.go @@ -83,8 +83,8 @@ func (pv ExchangeRateVote) getPower(ctx sdk.Context, powerMap map[string]int64) // String implements fmt.Stringer interface func (pv ExchangeRateVote) String() string { return fmt.Sprintf(`ExchangeRateVote - Denom: %s, - Voter: %s, + Denom: %s, + Voter: %s, ExchangeRate: %s`, pv.Denom, pv.Voter, pv.ExchangeRate) }