diff --git a/.codecov.yml b/.codecov.yml index 744d4b486..af96a8beb 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -17,3 +17,5 @@ ignore: - "docs" - "*.md" - "*.rst" + - "**/cli" + - "**/rest" diff --git a/app/app.go b/app/app.go index 41ba2d571..1645bcb21 100644 --- a/app/app.go +++ b/app/app.go @@ -187,7 +187,7 @@ func NewTerraApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest // register the staking hooks // NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks app.stakingKeeper = *stakingKeeper.SetHooks( - staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks(), app.oracleKeeper.Hooks())) + staking.NewMultiStakingHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks(), app.oracleKeeper.StakingHooks())) app.mm = module.NewManager( genaccounts.NewAppModule(app.accountKeeper), @@ -217,8 +217,8 @@ func NewTerraApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest // initialized with tokens from genesis accounts. app.mm.SetOrderInitGenesis(genaccounts.ModuleName, distr.ModuleName, staking.ModuleName, auth.ModuleName, bank.ModuleName, slashing.ModuleName, - oracle.ModuleName, market.ModuleName, treasury.ModuleName, gov.ModuleName, - supply.ModuleName, crisis.ModuleName, genutil.ModuleName) + supply.ModuleName, oracle.ModuleName, treasury.ModuleName, gov.ModuleName, + market.ModuleName, crisis.ModuleName, genutil.ModuleName) app.mm.RegisterInvariants(&app.crisisKeeper) app.mm.RegisterRoutes(app.Router(), app.QueryRouter()) diff --git a/app/export.go b/app/export.go index 228aaf72a..a751c26fb 100644 --- a/app/export.go +++ b/app/export.go @@ -163,4 +163,16 @@ func (app *TerraApp) prepForZeroHeightGenesis(ctx sdk.Context, jailWhiteList []s return false }, ) + + /* Handle market state. */ + + // clear all market pools + app.marketKeeper.SetTerraPoolDelta(ctx, sdk.ZeroDec()) + + /* Handle treasury state. */ + + // clear all historical issuance info + app.treasuryKeeper.ClearHistoricalIssuance(ctx) + // clear all tax proceeds + app.treasuryKeeper.ClearTaxProceeds(ctx) } diff --git a/client/lcd/swagger-ui/swagger.yaml b/client/lcd/swagger-ui/swagger.yaml index df3332383..b11e074ac 100644 --- a/client/lcd/swagger-ui/swagger.yaml +++ b/client/lcd/swagger-ui/swagger.yaml @@ -2104,9 +2104,9 @@ paths: $ref: "#/definitions/Coin" 500: description: Internal Server Error - /market/prev_day_issuance: + /market/terra_pool_delta: get: - summary: Get prev day issuance + summary: Get Terra pool delta, which is the gap between TerraPool and BasePool tags: - Market produces: @@ -2115,9 +2115,9 @@ paths: 200: description: OK schema: - type: array - items: - $ref: "#/definitions/Coin" + type: number + format: float + example: "10000000.00" 400: description: Bad Request 500: @@ -3565,4 +3565,4 @@ definitions: gas: type: number format: integer - example: 10000 + example: "10000" diff --git a/go.sum b/go.sum index 0f2dd3047..3ed2bcf75 100644 --- a/go.sum +++ b/go.sum @@ -215,6 +215,7 @@ github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJk github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU= github.com/tendermint/tendermint v0.32.2 h1:FvZWdksfDg/65vKKr5Lgo57keARFnmhrUEXHwyrV1QY= github.com/tendermint/tendermint v0.32.2/go.mod h1:NwMyx58S8VJ7tEpFKqRVlVWKO9N9zjTHu+Dx96VsnOE= +github.com/tendermint/tendermint v0.32.3 h1:GEnWpGQ795h5oTFNbfBLsY0LW/CW2j6p6HtiYNfxsgg= github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0= github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= diff --git a/x/auth/client/utils/feeutils.go b/x/auth/client/utils/feeutils.go index 903a40c0a..93b87c09a 100644 --- a/x/auth/client/utils/feeutils.go +++ b/x/auth/client/utils/feeutils.go @@ -32,6 +32,7 @@ type ( } ) +// String implements fmt.Stringer interface func (r EstimateFeeResp) String() string { return fmt.Sprintf(`EstimateFeeResp fees: %s, diff --git a/x/auth/internal/types/lazy_vesting.go b/x/auth/internal/types/lazy_vesting.go index fcabf739b..630bd29c2 100644 --- a/x/auth/internal/types/lazy_vesting.go +++ b/x/auth/internal/types/lazy_vesting.go @@ -41,7 +41,7 @@ func (s LazySchedule) GetRatio() sdk.Dec { return s.Ratio } -// String implements the fmt.Stringer interface +// String implements fmt.Stringer interface func (s LazySchedule) String() string { return fmt.Sprintf(`LazySchedule: StartTime: %v, @@ -121,7 +121,7 @@ func (vs VestingSchedule) IsValid() bool { return sumRatio.Equal(sdk.OneDec()) } -// String implements the fmt.Stringer interface +// String implements fmt.Stringer interface func (vs VestingSchedule) String() string { return fmt.Sprintf(`VestingSchedule: Denom: %v, @@ -233,6 +233,7 @@ func (lgva BaseLazyGradedVestingAccount) GetEndTime() int64 { return 0 } +// String implements fmt.Stringer interface func (lgva BaseLazyGradedVestingAccount) String() string { var pubkey string diff --git a/x/market/abci.go b/x/market/abci.go index dc1140259..741cac489 100644 --- a/x/market/abci.go +++ b/x/market/abci.go @@ -2,24 +2,12 @@ package market import ( sdk "github.com/cosmos/cosmos-sdk/types" - - core "github.com/terra-project/core/types" - "github.com/terra-project/core/x/market/internal/types" ) // EndBlocker is called at the end of every block func EndBlocker(ctx sdk.Context, k Keeper) { - if !core.IsPeriodLastBlock(ctx, core.BlocksPerDay) { - return - } - // update luna issuance at last block of a day - updatedIssuance := k.UpdatePrevDayIssuance(ctx) + // Replenishes each pools towards equilibrium + k.ReplenishPools(ctx) - ctx.EventManager().EmitEvent( - sdk.NewEvent( - types.EventDaliyIssuanceUpdate, - sdk.NewAttribute(types.AttributeKeyIssuance, updatedIssuance.String()), - ), - ) } diff --git a/x/market/abci_test.go b/x/market/abci_test.go index a58d1bcea..26e4ee58f 100644 --- a/x/market/abci_test.go +++ b/x/market/abci_test.go @@ -6,20 +6,18 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - core "github.com/terra-project/core/types" "github.com/terra-project/core/x/market/internal/keeper" ) -func TestOracleThreshold(t *testing.T) { +func TestReplenishPools(t *testing.T) { input := keeper.CreateTestInput(t) - targetIssuance := sdk.NewInt(1000000) - supply := input.SupplyKeeper.GetSupply(input.Ctx) - supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, targetIssuance))) - input.SupplyKeeper.SetSupply(input.Ctx, supply) + delta := sdk.NewDec(1000000) + regressionAmt := delta.QuoInt64(input.MarketKeeper.PoolRecoveryPeriod(input.Ctx)) + input.MarketKeeper.SetTerraPoolDelta(input.Ctx, delta) - input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerDay - 1) EndBlocker(input.Ctx, input.MarketKeeper) - issuance := input.MarketKeeper.GetPrevDayIssuance(input.Ctx).AmountOf(core.MicroLunaDenom) - require.Equal(t, targetIssuance, issuance) + + terraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + require.Equal(t, delta.Sub(regressionAmt), terraPoolDelta) } diff --git a/x/market/alias.go b/x/market/alias.go index 617d1bb40..6dc94fb11 100644 --- a/x/market/alias.go +++ b/x/market/alias.go @@ -15,14 +15,14 @@ const ( CodeInsufficientSwap = types.CodeInsufficientSwap CodeNoEffectivePrice = types.CodeNoEffectivePrice CodeRecursiveSwap = types.CodeRecursiveSwap - CodeExceedsSwapLimit = types.CodeExceedsSwapLimit + CodeInactive = types.CodeInactive ModuleName = types.ModuleName StoreKey = types.StoreKey RouterKey = types.RouterKey QuerierRoute = types.QuerierRoute DefaultParamspace = types.DefaultParamspace QuerySwap = types.QuerySwap - QueryPrevDayIssuance = types.QueryPrevDayIssuance + QueryTerraPoolDelta = types.QueryTerraPoolDelta QueryParameters = types.QueryParameters ) @@ -32,7 +32,7 @@ var ( ErrNoEffectivePrice = types.ErrNoEffectivePrice ErrInsufficientSwapCoins = types.ErrInsufficientSwapCoins ErrRecursiveSwap = types.ErrRecursiveSwap - ErrExceedsDailySwapLimit = types.ErrExceedsDailySwapLimit + ErrInactive = types.ErrInactive NewGenesisState = types.NewGenesisState DefaultGenesisState = types.DefaultGenesisState ValidateGenesis = types.ValidateGenesis @@ -44,14 +44,17 @@ var ( NewQuerier = keeper.NewQuerier // variable aliases - ModuleCdc = types.ModuleCdc - PrevDayIssuanceKey = types.PrevDayIssuanceKey - ParamStoreKeyDailyLunaDeltaCap = types.ParamStoreKeyDailyLunaDeltaCap - ParamStoreKeyMaxSwapSpread = types.ParamStoreKeyMaxSwapSpread - ParamStoreKeyMinSwapSpread = types.ParamStoreKeyMinSwapSpread - DefaultDailyLunaDeltaCap = types.DefaultDailyLunaDeltaCap - DefaultMaxSwapSpread = types.DefaultMaxSwapSpread - DefaultMinSwapSpread = types.DefaultMinSwapSpread + ModuleCdc = types.ModuleCdc + TerraPoolDeltaKey = types.TerraPoolDeltaKey + ParamStoreKeyBasePool = types.ParamStoreKeyBasePool + ParamStoreKeyPoolRecoveryPeriod = types.ParamStoreKeyPoolRecoveryPeriod + ParamStoreKeyMinSpread = types.ParamStoreKeyMinSpread + ParmamStoreKeyTobinTax = types.ParmamStoreKeyTobinTax + DefaultBasePool = types.DefaultBasePool + DefaultPoolRecoveryPeriod = types.DefaultPoolRecoveryPeriod + DefaultTerraLiquidityRatio = types.DefaultTerraLiquidityRatio + DefaultMinSpread = types.DefaultMinSpread + DefaultTobinTax = types.DefaultTobinTax ) type ( diff --git a/x/market/client/cli/query.go b/x/market/client/cli/query.go index e2b5cb292..2c188376a 100644 --- a/x/market/client/cli/query.go +++ b/x/market/client/cli/query.go @@ -26,8 +26,8 @@ func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command { marketQueryCmd.AddCommand(client.GetCommands( GetCmdQuerySwap(queryRoute, cdc), + GetCmdQueryTerraPoolDelta(queryRoute, cdc), GetCmdQueryParams(queryRoute, cdc), - GetCmdQueryPrevDayIssuance(queryRoute, cdc), )...) return marketQueryCmd @@ -64,7 +64,7 @@ $ terracli query query swap 5000000uluna usdr } var retCoin sdk.Coin - cdc.MustUnmarshalBinaryLengthPrefixed(res, &retCoin) + cdc.MustUnmarshalJSON(res, &retCoin) return cliCtx.PrintOutput(retCoin) }, } @@ -72,23 +72,27 @@ $ terracli query query swap 5000000uluna usdr return cmd } -// GetCmdQueryPrevDayIssuance implements the query params command. -func GetCmdQueryPrevDayIssuance(queryRoute string, cdc *codec.Codec) *cobra.Command { +// GetCmdQueryTerraPoolDelta implements the query params command. +func GetCmdQueryTerraPoolDelta(queryRoute string, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "prev-day-issuance", - Args: cobra.ExactArgs(1), - Short: "Query the prev day issuance", + Use: "terra-pool-delta", + Args: cobra.NoArgs, + Short: "Query terra pool delta", + Long: `Query terra pool delta, which is the gap between TerraPool and BasePool. + + $ terracli query market terra-pool-delta + `, RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) - res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryPrevDayIssuance), nil) + res, _, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryTerraPoolDelta), nil) if err != nil { return err } - var prevDayIssuance sdk.Coins - cdc.MustUnmarshalJSON(res, &prevDayIssuance) - return cliCtx.PrintOutput(prevDayIssuance) + var poolDelta sdk.Dec + cdc.MustUnmarshalJSON(res, &poolDelta) + return cliCtx.PrintOutput(poolDelta) }, } diff --git a/x/market/client/rest/query.go b/x/market/client/rest/query.go index 0f3de2c4d..b46b3eb26 100644 --- a/x/market/client/rest/query.go +++ b/x/market/client/rest/query.go @@ -16,7 +16,7 @@ import ( func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { r.HandleFunc("/market/swap", querySwapHandlerFn(cliCtx)).Methods("GET") - r.HandleFunc("/market/prev_day_issuance", queryPrevDayIssuanceHandlerFn(cliCtx)).Methods("GET") + r.HandleFunc("/market/terra_pool_delta", queryTerraPoolDeltaHandlerFn(cliCtx)).Methods("GET") r.HandleFunc("/market/parameters", queryParamsHandlerFn(cliCtx)).Methods("GET") } @@ -63,14 +63,14 @@ func querySwapHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } } -func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func queryTerraPoolDeltaHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters), nil) + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryTerraPoolDelta), nil) if err != nil { rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return @@ -81,14 +81,14 @@ func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { } } -func queryPrevDayIssuanceHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { +func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r) if !ok { return } - res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryPrevDayIssuance), nil) + res, height, err := cliCtx.QueryWithData(fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryParameters), nil) if err != nil { rest.WriteErrorResponse(w, http.StatusNotFound, err.Error()) return diff --git a/x/market/genesis.go b/x/market/genesis.go index ad5f41f47..6ae6f51a1 100644 --- a/x/market/genesis.go +++ b/x/market/genesis.go @@ -8,6 +8,7 @@ import ( // and the keeper's address to pubkey map func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { keeper.SetParams(ctx, data.Params) + keeper.SetTerraPoolDelta(ctx, data.TerraPoolDelta) } // ExportGenesis writes the current store values @@ -15,6 +16,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { // with InitGenesis func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { params := keeper.GetParams(ctx) + terraPoolDelta := keeper.GetTerraPoolDelta(ctx) - return NewGenesisState(params) + return NewGenesisState(terraPoolDelta, params) } diff --git a/x/market/genesis_test.go b/x/market/genesis_test.go new file mode 100644 index 000000000..888bcea0d --- /dev/null +++ b/x/market/genesis_test.go @@ -0,0 +1,22 @@ +package market + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/terra-project/core/x/market/internal/keeper" +) + +func TestExportInitGenesis(t *testing.T) { + input := keeper.CreateTestInput(t) + input.MarketKeeper.SetTerraPoolDelta(input.Ctx, sdk.NewDec(1123)) + genesis := ExportGenesis(input.Ctx, input.MarketKeeper) + + newInput := keeper.CreateTestInput(t) + InitGenesis(newInput.Ctx, newInput.MarketKeeper, genesis) + newGenesis := ExportGenesis(newInput.Ctx, newInput.MarketKeeper) + + require.Equal(t, genesis, newGenesis) +} diff --git a/x/market/handler.go b/x/market/handler.go index f6870890d..d58c4a007 100644 --- a/x/market/handler.go +++ b/x/market/handler.go @@ -4,6 +4,7 @@ import ( "reflect" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/terra-project/core/x/market/internal/types" ) @@ -29,11 +30,17 @@ func handleMsgSwap(ctx sdk.Context, k Keeper, ms MsgSwap) sdk.Result { } // Compute exchange rates between the ask and offer - swapCoin, spread, swapErr := k.GetSwapCoin(ctx, ms.OfferCoin, ms.AskDenom, false) + swapCoin, spread, swapErr := k.ComputeSwap(ctx, ms.OfferCoin, ms.AskDenom) if swapErr != nil { return swapErr.Result() } + // Update pool delta + deltaUpdateErr := k.ApplySwapToPool(ctx, ms.OfferCoin, swapCoin) + if deltaUpdateErr != nil { + return deltaUpdateErr.Result() + } + // Send offer coins to module account offerCoins := sdk.NewCoins(ms.OfferCoin) err := k.SupplyKeeper.SendCoinsFromAccountToModule(ctx, ms.Trader, ModuleName, offerCoins) @@ -42,11 +49,11 @@ func handleMsgSwap(ctx sdk.Context, k Keeper, ms MsgSwap) sdk.Result { } // Charge a spread if applicable; distributed to vote winners in the oracle module - var swapFee sdk.Coin + var swapFee sdk.DecCoin if spread.IsPositive() { - swapFeeAmt := spread.MulInt(swapCoin.Amount).TruncateInt() + swapFeeAmt := spread.Mul(swapCoin.Amount) if swapFeeAmt.IsPositive() { - swapFee = sdk.NewCoin(swapCoin.Denom, swapFeeAmt) + swapFee = sdk.NewDecCoinFromDec(swapCoin.Denom, swapFeeAmt) swapCoin = swapCoin.Sub(swapFee) } } @@ -58,7 +65,9 @@ func handleMsgSwap(ctx sdk.Context, k Keeper, ms MsgSwap) sdk.Result { } // Mint asked coins and credit Trader's account - swapCoins := sdk.NewCoins(swapCoin) + retCoin, decimalCoin := swapCoin.TruncateDecimal() + swapFee = swapFee.Add(decimalCoin) // add truncated decimalCoin to swapFee + swapCoins := sdk.NewCoins(retCoin) mintErr := k.SupplyKeeper.MintCoins(ctx, ModuleName, swapCoins) if mintErr != nil { return mintErr.Result() @@ -74,7 +83,7 @@ func handleMsgSwap(ctx sdk.Context, k Keeper, ms MsgSwap) sdk.Result { types.EventSwap, sdk.NewAttribute(types.AttributeKeyOffer, ms.OfferCoin.String()), sdk.NewAttribute(types.AttributeKeyTrader, ms.Trader.String()), - sdk.NewAttribute(types.AttributeKeySwapCoin, swapCoin.String()), + sdk.NewAttribute(types.AttributeKeySwapCoin, retCoin.String()), sdk.NewAttribute(types.AttributeKeySwapFee, swapFee.String()), ), sdk.NewEvent( diff --git a/x/market/handler_test.go b/x/market/handler_test.go index c83d21f2c..38c7b7086 100644 --- a/x/market/handler_test.go +++ b/x/market/handler_test.go @@ -30,8 +30,16 @@ func TestMarketFilters(t *testing.T) { func TestSwapMsg(t *testing.T) { input, h := setup(t) - offerCoin := sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(10)) - prevoteMsg := NewMsgSwap(keeper.Addrs[0], offerCoin, core.MicroSDRDenom) - res := h(input.Ctx, prevoteMsg) + beforeTerraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + + amt := sdk.NewInt(10) + offerCoin := sdk.NewCoin(core.MicroLunaDenom, amt) + swapMsg := NewMsgSwap(keeper.Addrs[0], offerCoin, core.MicroSDRDenom) + res := h(input.Ctx, swapMsg) require.True(t, res.IsOK()) + + afterTerraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + diff := beforeTerraPoolDelta.Sub(afterTerraPoolDelta) + price, _ := input.OracleKeeper.GetLunaPrice(input.Ctx, core.MicroSDRDenom) + require.Equal(t, price.MulInt(amt), diff.Abs()) } diff --git a/x/market/internal/keeper/keeper.go b/x/market/internal/keeper/keeper.go index db5e8fdc5..4689bf81f 100644 --- a/x/market/internal/keeper/keeper.go +++ b/x/market/internal/keeper/keeper.go @@ -9,7 +9,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" - core "github.com/terra-project/core/types" "github.com/terra-project/core/x/market/internal/types" ) @@ -50,118 +49,43 @@ func (k Keeper) Codespace() sdk.CodespaceType { return k.codespace } -// GetPrevDayIssuance returns the prev day issuance -func (k Keeper) GetPrevDayIssuance(ctx sdk.Context) (issuance sdk.Coins) { +// GetTerraPoolDelta returns the gap between TerraPool and BasePool +func (k Keeper) GetTerraPoolDelta(ctx sdk.Context) (delta sdk.Dec) { store := ctx.KVStore(k.storeKey) - bz := store.Get(types.PrevDayIssuanceKey) + bz := store.Get(types.TerraPoolDeltaKey) if bz == nil { - return sdk.Coins{} + return sdk.ZeroDec() } - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &issuance) + k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &delta) return } -// UpdatePrevDayIssuance stores the prev day issuance -func (k Keeper) UpdatePrevDayIssuance(ctx sdk.Context) sdk.Coins { +// SetTerraPoolDelta updates TerraPoolDelta which is gap between TerraPool and BasePool +func (k Keeper) SetTerraPoolDelta(ctx sdk.Context, delta sdk.Dec) { store := ctx.KVStore(k.storeKey) - totalCoins := k.SupplyKeeper.GetSupply(ctx).GetTotal() - bz := k.cdc.MustMarshalBinaryLengthPrefixed(totalCoins) - store.Set(types.PrevDayIssuanceKey, bz) - - return totalCoins + bz := k.cdc.MustMarshalBinaryLengthPrefixed(delta) + store.Set(types.TerraPoolDeltaKey, bz) } -// ComputeLunaDelta returns the issuance change rate of Luna for the day post-swap -func (k Keeper) ComputeLunaDelta(ctx sdk.Context, change sdk.Int) sdk.Dec { - curDay := ctx.BlockHeight() / core.BlocksPerDay - if curDay == 0 { - return sdk.ZeroDec() - } - - prevDayLunaIssuance := k.GetPrevDayIssuance(ctx).AmountOf(core.MicroLunaDenom) - if prevDayLunaIssuance.IsZero() { - return sdk.ZeroDec() - } - - supply := k.SupplyKeeper.GetSupply(ctx) - lunaIssuance := supply.GetTotal().AmountOf(core.MicroLunaDenom) - - postSwapIssunace := lunaIssuance.Add(change) - - return sdk.NewDecFromInt(postSwapIssunace.Sub(prevDayLunaIssuance)).QuoInt(prevDayLunaIssuance) -} - -// ComputeLunaSwapSpread returns a spread, which is initialiy MinSwapSpread and grows linearly to MaxSwapSpread with delta -func (k Keeper) ComputeLunaSwapSpread(ctx sdk.Context, postLunaDelta sdk.Dec) sdk.Dec { - if postLunaDelta.GTE(k.DailyLunaDeltaCap(ctx)) { - return k.MaxSwapSpread(ctx) - } - - // min + (p / l) (max - min); l = dailyDeltaCap, p = postDailyDelta, - return k.MinSwapSpread(ctx).Add(postLunaDelta.Quo(k.DailyLunaDeltaCap(ctx)).Mul(k.MaxSwapSpread(ctx).Sub(k.MinSwapSpread(ctx)))) -} - -// GetSwapCoin returns the amount of asked coins should be returned for a given offerCoin at the effective -// exchange rate registered with the oracle. -// Returns an Error if the swap is recursive, or the coins to be traded are unknown by the oracle, or the amount -// to trade is too small. -// Ignores caps and spreads if isInternal = true. -func (k Keeper) GetSwapCoin(ctx sdk.Context, offerCoin sdk.Coin, askDenom string, isInternal bool) (retCoin sdk.Coin, spread sdk.Dec, err sdk.Error) { - offerRate, err := k.oracleKeeper.GetLunaPrice(ctx, offerCoin.Denom) - if err != nil { - return sdk.Coin{}, sdk.ZeroDec(), types.ErrNoEffectivePrice(types.DefaultCodespace, offerCoin.Denom) - } - - askRate, err := k.oracleKeeper.GetLunaPrice(ctx, askDenom) - if err != nil { - return sdk.Coin{}, sdk.ZeroDec(), types.ErrNoEffectivePrice(types.DefaultCodespace, askDenom) - } - - retAmount := sdk.NewDecFromInt(offerCoin.Amount).Mul(askRate).Quo(offerRate).TruncateInt() - if retAmount.Equal(sdk.ZeroInt()) { - return sdk.Coin{}, sdk.ZeroDec(), types.ErrInsufficientSwapCoins(types.DefaultCodespace, offerCoin.Amount) - } - - // We only charge spread for NON-INTERNAL swaps involving luna; if not, just pass. - if isInternal || (offerCoin.Denom != core.MicroLunaDenom && askDenom != core.MicroLunaDenom) { - return sdk.NewCoin(askDenom, retAmount), sdk.ZeroDec(), nil - } - - dailyDelta := sdk.ZeroDec() - if offerCoin.Denom == core.MicroLunaDenom { - dailyDelta = k.ComputeLunaDelta(ctx, offerCoin.Amount.Neg()) - } else if askDenom == core.MicroLunaDenom { - dailyDelta = k.ComputeLunaDelta(ctx, retAmount) - } - - // delta should be positive to apply spread - dailyDelta = dailyDelta.Abs() - spread = k.ComputeLunaSwapSpread(ctx, dailyDelta) - - return sdk.NewCoin(askDenom, retAmount), spread, nil -} - -// GetSwapDecCoin returns the amount of asked DecCoins should be returned for a given offerCoin at the effective -// exchange rate registered with the oracle. -// Different from swapcoins, SwapDecCoins does not charge a spread as its use is system internal. -// Similar to SwapCoins, but operates over sdk.DecCoins for convenience and accuracy. -func (k Keeper) GetSwapDecCoin(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) { - offerRate, err := k.oracleKeeper.GetLunaPrice(ctx, offerCoin.Denom) - if err != nil { - return sdk.DecCoin{}, types.ErrNoEffectivePrice(types.DefaultCodespace, offerCoin.Denom) - } - - askRate, err := k.oracleKeeper.GetLunaPrice(ctx, askDenom) - if err != nil { - return sdk.DecCoin{}, types.ErrNoEffectivePrice(types.DefaultCodespace, askDenom) - } - - retAmount := offerCoin.Amount.Mul(askRate).Quo(offerRate) - if retAmount.LTE(sdk.ZeroDec()) { - return sdk.DecCoin{}, types.ErrInsufficientSwapCoins(types.DefaultCodespace, offerCoin.Amount.TruncateInt()) +// ReplenishPools replenishes each pool(Terra,Luna) to BasePool +func (k Keeper) ReplenishPools(ctx sdk.Context) { + delta := k.GetTerraPoolDelta(ctx) + regressionAmt := delta.QuoInt64(k.PoolRecoveryPeriod(ctx)) + + // Replenish terra pool towards base pool + if delta.IsPositive() { + delta = delta.Sub(regressionAmt) + if delta.IsNegative() { + delta = sdk.ZeroDec() + } + } else if delta.IsNegative() { + delta = delta.Add(regressionAmt) + if delta.IsPositive() { + delta = sdk.ZeroDec() + } } - return sdk.NewDecCoinFromDec(askDenom, retAmount), nil + k.SetTerraPoolDelta(ctx, delta) } diff --git a/x/market/internal/keeper/keeper_test.go b/x/market/internal/keeper/keeper_test.go index f8fe7ab1e..ea78bc55b 100644 --- a/x/market/internal/keeper/keeper_test.go +++ b/x/market/internal/keeper/keeper_test.go @@ -1,7 +1,6 @@ package keeper import ( - "math/rand" "testing" "github.com/stretchr/testify/require" @@ -10,89 +9,36 @@ import ( core "github.com/terra-project/core/types" ) -func TestPrevDayLunaIssuanceUpdate(t *testing.T) { +func TestTerraPoolDeltaUpdate(t *testing.T) { input := CreateTestInput(t) - issuance := input.MarketKeeper.GetPrevDayIssuance(input.Ctx).AmountOf(core.MicroLunaDenom) - require.True(t, issuance.IsZero()) + terraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + require.Equal(t, sdk.ZeroDec(), terraPoolDelta) - supply := input.SupplyKeeper.GetSupply(input.Ctx) - supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sdk.OneInt()))) - input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.MarketKeeper.UpdatePrevDayIssuance(input.Ctx) - issuance = input.MarketKeeper.GetPrevDayIssuance(input.Ctx).AmountOf(core.MicroLunaDenom) - require.Equal(t, sdk.OneInt(), issuance) -} - -func TestComputeLunaDelta(t *testing.T) { - input := CreateTestInput(t) - - for i := 0; i < 100; i++ { - expectedDelta := sdk.NewDecWithPrec(rand.Int63n(1000), 3) - issuance := input.SupplyKeeper.GetSupply(input.Ctx).GetTotal().AmountOf(core.MicroLunaDenom) - change := expectedDelta.MulInt(issuance).TruncateInt() - input.MarketKeeper.UpdatePrevDayIssuance(input.Ctx) - delta := input.MarketKeeper.ComputeLunaDelta(input.Ctx.WithBlockHeight(core.BlocksPerDay), change) - - require.Equal(t, expectedDelta, delta) - } -} - -func TestComputeLunaSwapSpread(t *testing.T) { - input := CreateTestInput(t) - - for i := 0; i < 100; i++ { - delta := sdk.NewDecWithPrec(rand.Int63n(1000), 3) - spread := input.MarketKeeper.ComputeLunaSwapSpread(input.Ctx, delta) - require.True(t, spread.GTE(input.MarketKeeper.MinSwapSpread(input.Ctx))) - require.True(t, spread.LTE(input.MarketKeeper.MaxSwapSpread(input.Ctx))) - } + diff := sdk.NewDec(10) + input.MarketKeeper.SetTerraPoolDelta(input.Ctx, diff) - spread := input.MarketKeeper.ComputeLunaSwapSpread(input.Ctx, sdk.ZeroDec()) - require.Equal(t, input.MarketKeeper.MinSwapSpread(input.Ctx), spread) - - spread = input.MarketKeeper.ComputeLunaSwapSpread(input.Ctx, sdk.OneDec()) - require.Equal(t, input.MarketKeeper.MaxSwapSpread(input.Ctx), spread) + terraPoolDelta = input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + require.Equal(t, diff, terraPoolDelta) } -func TestGetSwapCoin(t *testing.T) { +// TestReplenishPools tests that +// each pools move towards base pool +func TestReplenishPools(t *testing.T) { input := CreateTestInput(t) - lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) - - // zero day (min spread) - for i := 0; i < 100; i++ { - offerCoin := sdk.NewCoin(core.MicroSDRDenom, lunaPriceInSDR.MulInt64(rand.Int63()+1).TruncateInt()) - retCoin, spread, err := input.MarketKeeper.GetSwapCoin(input.Ctx, offerCoin, core.MicroLunaDenom, false) - require.NoError(t, err) - require.Equal(t, input.MarketKeeper.MinSwapSpread(input.Ctx), spread) - require.Equal(t, sdk.NewDecFromInt(offerCoin.Amount).Quo(lunaPriceInSDR).TruncateInt(), retCoin.Amount) + input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, sdk.OneDec()) - retCoin, spread, err = input.MarketKeeper.GetSwapCoin(input.Ctx, offerCoin, core.MicroLunaDenom, true) - require.NoError(t, err) - require.Equal(t, sdk.ZeroDec(), spread) - require.Equal(t, sdk.NewDecFromInt(offerCoin.Amount).Quo(lunaPriceInSDR).TruncateInt(), retCoin.Amount) - } + basePool := input.MarketKeeper.BasePool(input.Ctx) + terraPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + require.True(t, terraPoolDelta.IsZero()) - offerCoin := sdk.NewCoin(core.MicroSDRDenom, lunaPriceInSDR.QuoInt64(2).TruncateInt()) - _, _, err := input.MarketKeeper.GetSwapCoin(input.Ctx, offerCoin, core.MicroLunaDenom, false) - require.Error(t, err) -} - -func TestGetDecSwapCoin(t *testing.T) { - input := CreateTestInput(t) - lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) - input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + diff := basePool.QuoInt64(core.BlocksPerDay) + input.MarketKeeper.SetTerraPoolDelta(input.Ctx, diff) - // zero day (min spread) - for i := 0; i < 100; i++ { - offerCoin := sdk.NewDecCoin(core.MicroSDRDenom, lunaPriceInSDR.MulInt64(rand.Int63()+1).TruncateInt()) - retCoin, err := input.MarketKeeper.GetSwapDecCoin(input.Ctx, offerCoin, core.MicroLunaDenom) - require.NoError(t, err) - require.Equal(t, offerCoin.Amount.Quo(lunaPriceInSDR), retCoin.Amount) - } + input.MarketKeeper.ReplenishPools(input.Ctx) - offerCoin := sdk.NewDecCoin(core.MicroSDRDenom, lunaPriceInSDR.QuoInt64(2).TruncateInt()) - _, err := input.MarketKeeper.GetSwapDecCoin(input.Ctx, offerCoin, core.MicroLunaDenom) - require.Error(t, err) + terraPoolDelta = input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + replenishAmt := diff.QuoInt64(input.MarketKeeper.PoolRecoveryPeriod(input.Ctx)) + expectedDelta := diff.Sub(replenishAmt) + require.Equal(t, expectedDelta, terraPoolDelta) } diff --git a/x/market/internal/keeper/params.go b/x/market/internal/keeper/params.go index 2cfa5b89f..501961258 100644 --- a/x/market/internal/keeper/params.go +++ b/x/market/internal/keeper/params.go @@ -6,26 +6,32 @@ import ( "github.com/terra-project/core/x/market/internal/types" ) -// ParamTable for market module +// ParamKeyTable for market module func ParamKeyTable() params.KeyTable { return params.NewKeyTable().RegisterParamSet(&types.Params{}) } -// DailyLunaDeltaCap -func (k Keeper) DailyLunaDeltaCap(ctx sdk.Context) (res sdk.Dec) { - k.paramSpace.Get(ctx, types.ParamStoreKeyDailyLunaDeltaCap, &res) +// BasePool is Terra liquidity pool(usdr unit) which will be made available per PoolRecoveryPeriod +func (k Keeper) BasePool(ctx sdk.Context) (res sdk.Dec) { + k.paramSpace.Get(ctx, types.ParamStoreKeyBasePool, &res) return } -// MinSwapSpread -func (k Keeper) MinSwapSpread(ctx sdk.Context) (res sdk.Dec) { - k.paramSpace.Get(ctx, types.ParamStoreKeyMinSwapSpread, &res) +// MinSpread is the minimum swap fee(spread) +func (k Keeper) MinSpread(ctx sdk.Context) (res sdk.Dec) { + k.paramSpace.Get(ctx, types.ParamStoreKeyMinSpread, &res) return } -// MaxSwapSpread -func (k Keeper) MaxSwapSpread(ctx sdk.Context) (res sdk.Dec) { - k.paramSpace.Get(ctx, types.ParamStoreKeyMaxSwapSpread, &res) +// PoolRecoveryPeriod is the period required to recover Terra&Luna Pool to BasePool +func (k Keeper) PoolRecoveryPeriod(ctx sdk.Context) (res int64) { + k.paramSpace.Get(ctx, types.ParamStoreKeyPoolRecoveryPeriod, &res) + return +} + +// TobinTax is a tax 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) return } diff --git a/x/market/internal/keeper/querier.go b/x/market/internal/keeper/querier.go index 7095e3b7e..db70b38d9 100644 --- a/x/market/internal/keeper/querier.go +++ b/x/market/internal/keeper/querier.go @@ -14,8 +14,8 @@ func NewQuerier(keeper Keeper) sdk.Querier { switch path[0] { case types.QuerySwap: return querySwap(ctx, req, keeper) - case types.QueryPrevDayIssuance: - return queryPrevDayIssuance(ctx, req, keeper) + case types.QueryTerraPoolDelta: + return queryTerraPoolDelta(ctx, keeper) case types.QueryParameters: return queryParameters(ctx, keeper) default: @@ -35,33 +35,34 @@ func querySwap(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, s return nil, types.ErrRecursiveSwap(types.DefaultCodespace, params.AskDenom) } - swapCoin, spread, err := keeper.GetSwapCoin(ctx, params.OfferCoin, params.AskDenom, false) + swapCoin, spread, err := keeper.ComputeSwap(ctx, params.OfferCoin, params.AskDenom) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("Failed to get swapped coin amount", err.Error())) } if spread.IsPositive() { - swapFeeAmt := spread.MulInt(swapCoin.Amount).TruncateInt() + swapFeeAmt := spread.Mul(swapCoin.Amount) if swapFeeAmt.IsPositive() { - swapFee := sdk.NewCoin(swapCoin.Denom, swapFeeAmt) + swapFee := sdk.NewDecCoinFromDec(swapCoin.Denom, swapFeeAmt) swapCoin = swapCoin.Sub(swapFee) } } - bz, err2 := codec.MarshalJSONIndent(keeper.cdc, swapCoin) - if err2 != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err2.Error())) + retCoin, _ := swapCoin.TruncateDecimal() + bz, err := codec.MarshalJSONIndent(keeper.cdc, retCoin) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } return bz, nil } -func queryPrevDayIssuance(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - - bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetPrevDayIssuance(ctx)) +func queryTerraPoolDelta(ctx sdk.Context, keeper Keeper) ([]byte, sdk.Error) { + bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetTerraPoolDelta(ctx)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } + return bz, nil } diff --git a/x/market/internal/keeper/querier_test.go b/x/market/internal/keeper/querier_test.go index 0eec436d9..b742e8cbf 100644 --- a/x/market/internal/keeper/querier_test.go +++ b/x/market/internal/keeper/querier_test.go @@ -25,6 +25,9 @@ func TestNewQuerier(t *testing.T) { _, err := querier(input.Ctx, []string{types.QueryParameters}, query) require.NoError(t, err) + + _, err = querier(input.Ctx, []string{"INVALID_PATH"}, query) + require.Error(t, err) } func TestQueryParams(t *testing.T) { @@ -48,22 +51,71 @@ func TestQuerySwap(t *testing.T) { price := sdk.NewDecWithPrec(17, 1) input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, price) + querier := NewQuerier(input.MarketKeeper) + var err error + + // empty data will occur error + query := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + res, err := querier(input.Ctx, []string{types.QuerySwap}, query) + require.Error(t, err) + + // recursive query offerCoin := sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(10)) - queryParams := types.NewQuerySwapParams(offerCoin, core.MicroSDRDenom) + queryParams := types.NewQuerySwapParams(offerCoin, core.MicroLunaDenom) bz, err := cdc.MarshalJSON(queryParams) require.NoError(t, err) - req := abci.RequestQuery{ + query = abci.RequestQuery{ + Path: "", + Data: bz, + } + + res, err = querier(input.Ctx, []string{types.QuerySwap}, query) + require.Error(t, err) + + // valid query + queryParams = types.NewQuerySwapParams(offerCoin, core.MicroSDRDenom) + bz, err = cdc.MarshalJSON(queryParams) + require.NoError(t, err) + + query = abci.RequestQuery{ Path: "", Data: bz, } - res, err := querySwap(input.Ctx, req, input.MarketKeeper) + res, err = querier(input.Ctx, []string{types.QuerySwap}, query) require.NoError(t, err) var swapCoin sdk.Coin err = cdc.UnmarshalJSON(res, &swapCoin) require.NoError(t, err) require.Equal(t, core.MicroSDRDenom, swapCoin.Denom) - require.Equal(t, sdk.NewInt(17), swapCoin.Amount) + require.True(t, sdk.NewInt(17).GTE(swapCoin.Amount)) + require.True(t, swapCoin.Amount.IsPositive()) +} + +func TestQueryTerraPool(t *testing.T) { + cdc := codec.New() + input := CreateTestInput(t) + + poolDelta := sdk.NewDecWithPrec(17, 1) + input.MarketKeeper.SetTerraPoolDelta(input.Ctx, poolDelta) + + querier := NewQuerier(input.MarketKeeper) + query := abci.RequestQuery{ + Path: "", + Data: nil, + } + + res, errRes := querier(input.Ctx, []string{types.QueryTerraPoolDelta}, query) + require.NoError(t, errRes) + + var retPool sdk.Dec + err := cdc.UnmarshalJSON(res, &retPool) + require.NoError(t, err) + require.Equal(t, poolDelta, retPool) } diff --git a/x/market/internal/keeper/swap.go b/x/market/internal/keeper/swap.go new file mode 100644 index 000000000..e113d8275 --- /dev/null +++ b/x/market/internal/keeper/swap.go @@ -0,0 +1,138 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + core "github.com/terra-project/core/types" + "github.com/terra-project/core/x/market/internal/types" +) + +// ApplySwapToPool updates each pool with offerCoin and askCoin taken from swap operation, +// OfferPool = OfferPool + offerAmt (Fills the swap pool with offerAmt) +// AskPool = AskPool - askAmt (Uses askAmt from the swap pool) +func (k Keeper) ApplySwapToPool(ctx sdk.Context, offerCoin sdk.Coin, askCoin sdk.DecCoin) sdk.Error { + // No delta update in case Terra to Terra swap + if offerCoin.Denom != core.MicroLunaDenom && askCoin.Denom != core.MicroLunaDenom { + return nil + } + + terraPoolDelta := k.GetTerraPoolDelta(ctx) + + offerBaseCoin, err := k.ComputeInternalSwap(ctx, sdk.NewDecCoinFromCoin(offerCoin), core.MicroSDRDenom) + if err != nil { + return err + } + + askBaseCoin, err := k.ComputeInternalSwap(ctx, askCoin, core.MicroSDRDenom) + if err != nil { + return err + } + + // In case swapping Terra to Luna, the terra swap pool(offer) is increased and the luna swap pool(ask) is decreased + if offerCoin.Denom != core.MicroLunaDenom && askCoin.Denom == core.MicroLunaDenom { + terraPoolDelta = terraPoolDelta.Add(offerBaseCoin.Amount) + } + + // In case swapping Luna to Terra, the luna swap pool(offer) is increased and the terra swap pool(ask) is decreased + if offerCoin.Denom == core.MicroLunaDenom && askCoin.Denom != core.MicroLunaDenom { + terraPoolDelta = terraPoolDelta.Sub(askBaseCoin.Amount) + } + + k.SetTerraPoolDelta(ctx, terraPoolDelta) + + return nil +} + +// ComputeSwap returns the amount of asked coins should be returned for a given offerCoin at the effective +// exchange rate registered with the oracle. +// Returns an Error if the swap is recursive, or the coins to be traded are unknown by the oracle, or the amount +// to trade is too small. +func (k Keeper) ComputeSwap(ctx sdk.Context, offerCoin sdk.Coin, askDenom string) (retDecCoin sdk.DecCoin, spread sdk.Dec, err sdk.Error) { + + // Return invalid recursive swap err + if offerCoin.Denom == askDenom { + return sdk.DecCoin{}, sdk.ZeroDec(), types.ErrRecursiveSwap(k.codespace, askDenom) + } + + // Swap offer coin to base denom for simplicity of swap process + baseOfferDecCoin, err := k.ComputeInternalSwap(ctx, sdk.NewDecCoinFromCoin(offerCoin), core.MicroSDRDenom) + if err != nil { + return sdk.DecCoin{}, sdk.Dec{}, err + } + + // Get swap amount based on the oracle price + retDecCoin, err = k.ComputeInternalSwap(ctx, baseOfferDecCoin, askDenom) + if err != nil { + return sdk.DecCoin{}, sdk.Dec{}, err + } + + // Terra->Terra swap + // Apply only tobin tax without constant product spread + if offerCoin.Denom != core.MicroLunaDenom && askDenom != core.MicroLunaDenom { + spread = k.TobinTax(ctx) + return + } + + basePool := k.BasePool(ctx) + minSpread := k.MinSpread(ctx) + + // constant-product, which by construction is square of base(equilibrium) pool + cp := basePool.Mul(basePool) + terraPoolDelta := k.GetTerraPoolDelta(ctx) + terraPool := basePool.Add(terraPoolDelta) + lunaPool := cp.Quo(terraPool) + + var offerPool sdk.Dec // base denom(usdr) unit + var askPool sdk.Dec // base denom(usdr) unit + if offerCoin.Denom != core.MicroLunaDenom { + // Terra->Luna swap + offerPool = terraPool + askPool = lunaPool + } else { + // Luna->Terra swap + offerPool = lunaPool + askPool = terraPool + } + + // Get cp(constant-product) based swap amount + // askBaseAmount = askPool - cp / (offerPool + offerBaseAmount) + // askBaseAmount is base denom(usdr) unit + askBaseAmount := askPool.Sub(cp.Quo(offerPool.Add(baseOfferDecCoin.Amount))) + + // Both baseOffer and baseAsk are usdr units, so spread can be calculated by + // spread = (baseOfferAmt - baseAskAmt) / baseOfferAmt + baseOfferAmount := baseOfferDecCoin.Amount + spread = baseOfferAmount.Sub(askBaseAmount).Quo(baseOfferAmount) + + if spread.LT(minSpread) { + spread = minSpread + } + + return +} + +// ComputeInternalSwap returns the amount of asked DecCoin should be returned for a given offerCoin at the effective +// exchange rate registered with the oracle. +// Different from ComputeSwap, ComputeInternalSwap does not charge a spread as its use is system internal. +func (k Keeper) ComputeInternalSwap(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) { + if offerCoin.Denom == askDenom { + return offerCoin, nil + } + + offerRate, err := k.oracleKeeper.GetLunaPrice(ctx, offerCoin.Denom) + if err != nil { + return sdk.DecCoin{}, types.ErrNoEffectivePrice(types.DefaultCodespace, offerCoin.Denom) + } + + askRate, err := k.oracleKeeper.GetLunaPrice(ctx, askDenom) + if err != nil { + return sdk.DecCoin{}, types.ErrNoEffectivePrice(types.DefaultCodespace, askDenom) + } + + retAmount := offerCoin.Amount.Mul(askRate).Quo(offerRate) + if retAmount.LTE(sdk.ZeroDec()) { + return sdk.DecCoin{}, types.ErrInsufficientSwapCoins(types.DefaultCodespace, offerCoin.Amount.TruncateInt()) + } + + return sdk.NewDecCoinFromDec(askDenom, retAmount), nil +} diff --git a/x/market/internal/keeper/swap_test.go b/x/market/internal/keeper/swap_test.go new file mode 100644 index 000000000..f80778b5a --- /dev/null +++ b/x/market/internal/keeper/swap_test.go @@ -0,0 +1,70 @@ +package keeper + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" + core "github.com/terra-project/core/types" +) + +func TestApplySwapToPool(t *testing.T) { + input := CreateTestInput(t) + + lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) + input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + + offerCoin := sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(1000)) + askCoin := sdk.NewDecCoin(core.MicroSDRDenom, sdk.NewInt(1700)) + + oldSDRPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + input.MarketKeeper.ApplySwapToPool(input.Ctx, offerCoin, askCoin) + newSDRPoolDelta := input.MarketKeeper.GetTerraPoolDelta(input.Ctx) + + sdrDiff := newSDRPoolDelta.Sub(oldSDRPoolDelta) + + require.Equal(t, sdk.NewDec(-1700), sdrDiff) +} + +func TestComputeSwap(t *testing.T) { + input := CreateTestInput(t) + + // Set Oracle Price + lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) + input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + + for i := 0; i < 100; i++ { + swapAmountInSDR := lunaPriceInSDR.MulInt64(rand.Int63()%10000 + 2).TruncateInt() + offerCoin := sdk.NewCoin(core.MicroSDRDenom, swapAmountInSDR) + retCoin, spread, err := input.MarketKeeper.ComputeSwap(input.Ctx, offerCoin, core.MicroLunaDenom) + + require.NoError(t, err) + require.True(t, spread.GTE(input.MarketKeeper.MinSpread(input.Ctx))) + require.Equal(t, sdk.NewDecFromInt(offerCoin.Amount).Quo(lunaPriceInSDR), retCoin.Amount) + } + + offerCoin := sdk.NewCoin(core.MicroSDRDenom, lunaPriceInSDR.QuoInt64(2).TruncateInt()) + _, _, err := input.MarketKeeper.ComputeSwap(input.Ctx, offerCoin, core.MicroLunaDenom) + require.Error(t, err) +} + +func TestComputeInternalSwap(t *testing.T) { + input := CreateTestInput(t) + + // Set Oracle Price + lunaPriceInSDR := sdk.NewDecWithPrec(17, 1) + input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lunaPriceInSDR) + + for i := 0; i < 100; i++ { + offerCoin := sdk.NewDecCoin(core.MicroSDRDenom, lunaPriceInSDR.MulInt64(rand.Int63()+1).TruncateInt()) + retCoin, err := input.MarketKeeper.ComputeInternalSwap(input.Ctx, offerCoin, core.MicroLunaDenom) + require.NoError(t, err) + require.Equal(t, offerCoin.Amount.Quo(lunaPriceInSDR), retCoin.Amount) + } + + offerCoin := sdk.NewDecCoin(core.MicroSDRDenom, lunaPriceInSDR.QuoInt64(2).TruncateInt()) + _, err := input.MarketKeeper.ComputeInternalSwap(input.Ctx, offerCoin, core.MicroLunaDenom) + require.Error(t, err) +} diff --git a/x/market/internal/keeper/test_utils.go b/x/market/internal/keeper/test_utils.go index 586088516..4085b6fd5 100644 --- a/x/market/internal/keeper/test_utils.go +++ b/x/market/internal/keeper/test_utils.go @@ -175,5 +175,9 @@ func CreateTestInput(t *testing.T) TestInput { require.NoError(t, err) } + supply := supplyKeeper.GetSupply(ctx) + supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, InitTokens.MulRaw(int64(len(Addrs)))))) + supplyKeeper.SetSupply(ctx, supply) + return TestInput{ctx, cdc, oracleKeeper, supplyKeeper, keeper} } diff --git a/x/market/internal/types/errors.go b/x/market/internal/types/errors.go index 157444ce3..77b4d025a 100644 --- a/x/market/internal/types/errors.go +++ b/x/market/internal/types/errors.go @@ -13,7 +13,7 @@ const ( CodeInsufficientSwap codeType = 1 CodeNoEffectivePrice codeType = 2 CodeRecursiveSwap codeType = 3 - CodeExceedsSwapLimit codeType = 4 + CodeInactive codeType = 4 ) // ---------------------------------------- @@ -34,7 +34,7 @@ func ErrRecursiveSwap(codespace sdk.CodespaceType, denom string) sdk.Error { return sdk.NewError(codespace, CodeRecursiveSwap, "Can't swap tokens with the same denomination: "+denom) } -// ErrExceedsDailySwapLimit called when the coin swap exceeds the daily swap limit for Luna -func ErrExceedsDailySwapLimit(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeExceedsSwapLimit, "Exceeded the daily swap limit for Luna") +// ErrInactive called when the coin swap exceeds the daily swap limit for Luna +func ErrInactive(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInactive, "Can't swap because the market is inactive.") } diff --git a/x/market/internal/types/events.go b/x/market/internal/types/events.go index 0e0fea371..2f86907f8 100644 --- a/x/market/internal/types/events.go +++ b/x/market/internal/types/events.go @@ -3,14 +3,12 @@ package types // Market module event types const ( - EventSwap = "swap" - EventDaliyIssuanceUpdate = "daliy_issuance_update" + EventSwap = "swap" AttributeKeyOffer = "offer" AttributeKeyTrader = "trader" AttributeKeySwapCoin = "swap_coin" AttributeKeySwapFee = "swap_fee" - AttributeKeyIssuance = "issuance" AttributeValueCategory = ModuleName ) diff --git a/x/market/internal/types/genesis.go b/x/market/internal/types/genesis.go index 4fbe81e44..ba2e760b8 100644 --- a/x/market/internal/types/genesis.go +++ b/x/market/internal/types/genesis.go @@ -1,23 +1,30 @@ package types -import "bytes" +import ( + "bytes" + + sdk "github.com/cosmos/cosmos-sdk/types" +) // GenesisState - all market state that must be provided at genesis type GenesisState struct { - Params Params `json:"params" yaml:"params"` // market params + TerraPoolDelta sdk.Dec `json:"terra_pool_delta" yaml:"terra_pool_delta"` + Params Params `json:"params" yaml:"params"` // market params } // NewGenesisState creates a new GenesisState object -func NewGenesisState(params Params) GenesisState { +func NewGenesisState(terraPoolDelta sdk.Dec, params Params) GenesisState { return GenesisState{ - Params: params, + TerraPoolDelta: terraPoolDelta, + Params: params, } } -// get raw genesis raw message for testing +// DefaultGenesisState returns raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), + TerraPoolDelta: sdk.ZeroDec(), + Params: DefaultParams(), } } @@ -27,14 +34,14 @@ func ValidateGenesis(data GenesisState) error { return data.Params.Validate() } -// Checks whether 2 GenesisState structs are equivalent. +// Equal checks whether 2 GenesisState structs are equivalent. func (data GenesisState) Equal(data2 GenesisState) bool { b1 := ModuleCdc.MustMarshalBinaryBare(data) b2 := ModuleCdc.MustMarshalBinaryBare(data2) return bytes.Equal(b1, b2) } -// Returns if a GenesisState is empty or has data in it +// IsEmpty returns if a GenesisState is empty or has data in it func (data GenesisState) IsEmpty() bool { emptyGenState := GenesisState{} return data.Equal(emptyGenState) diff --git a/x/market/internal/types/genesis_test.go b/x/market/internal/types/genesis_test.go index 06d989ca5..ee010d0c1 100644 --- a/x/market/internal/types/genesis_test.go +++ b/x/market/internal/types/genesis_test.go @@ -12,15 +12,13 @@ func TestGenesisValidation(t *testing.T) { genState := DefaultGenesisState() require.NoError(t, ValidateGenesis(genState)) - genState.Params.DailyLunaDeltaCap = sdk.NewDec(-1) + genState.Params.BasePool = sdk.NewDec(-1) require.Error(t, ValidateGenesis(genState)) - genState.Params.DailyLunaDeltaCap = sdk.OneDec() - genState.Params.MaxSwapSpread = sdk.NewDec(-1) + genState.Params.PoolRecoveryPeriod = -1 require.Error(t, ValidateGenesis(genState)) - genState.Params.MaxSwapSpread = sdk.ZeroDec() - genState.Params.MinSwapSpread = sdk.NewDec(-1) + genState.Params.MinSpread = sdk.NewDec(-1) require.Error(t, ValidateGenesis(genState)) } diff --git a/x/market/internal/types/keys.go b/x/market/internal/types/keys.go index beaef0aa3..456eb0cc8 100644 --- a/x/market/internal/types/keys.go +++ b/x/market/internal/types/keys.go @@ -17,8 +17,8 @@ const ( // Keys for market store // Items are stored with the following key: values // -// - 0x01: sdk.Int +// - 0x01: sdk.Dec var ( //Keys for store prefixed - PrevDayIssuanceKey = []byte{0x01} // key for prev day issuance + TerraPoolDeltaKey = []byte{0x02} // key for Terra pool delta which gap between TerraPool from BasePool ) diff --git a/x/market/internal/types/msgs.go b/x/market/internal/types/msgs.go index 885b198f4..988e2f916 100644 --- a/x/market/internal/types/msgs.go +++ b/x/market/internal/types/msgs.go @@ -63,7 +63,7 @@ func (msg MsgSwap) ValidateBasic() sdk.Error { return nil } -// String Implements Msg +// String implements fmt.Stringer interface func (msg MsgSwap) String() string { return fmt.Sprintf(`MsgSwap trader: %s, diff --git a/x/market/internal/types/params.go b/x/market/internal/types/params.go index 26f3e77e0..f9ca085a0 100644 --- a/x/market/internal/types/params.go +++ b/x/market/internal/types/params.go @@ -5,74 +5,90 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params/subspace" + + core "github.com/terra-project/core/types" ) -// DefaultParamspace +// DefaultParamspace nolint const DefaultParamspace = ModuleName // Parameter keys var ( - ParamStoreKeyDailyLunaDeltaCap = []byte("dailylunadeltalimit") - ParamStoreKeyMaxSwapSpread = []byte("maxswapspread") - ParamStoreKeyMinSwapSpread = []byte("minswapspread") + //Terra liquidity pool(usdr unit) made available per ${poolrecoveryperiod} (usdr unit) + ParamStoreKeyBasePool = []byte("basepool") + // The period required to recover BasePool + ParamStoreKeyPoolRecoveryPeriod = []byte("poolrecoveryperiod") + // Min spread + ParamStoreKeyMinSpread = []byte("minspread") + // Tobin tax + ParmamStoreKeyTobinTax = []byte("tobintax") ) // Default parameter values var ( - DefaultDailyLunaDeltaCap = sdk.NewDecWithPrec(5, 3) // 0.5% - DefaultMaxSwapSpread = sdk.NewDec(1) // 100% - DefaultMinSwapSpread = sdk.NewDecWithPrec(2, 2) // 2% + DefaultBasePool = sdk.NewDec(1000000 * core.MicroUnit) // 1000,000sdr = 1000,000,000,000usdr + DefaultPoolRecoveryPeriod = core.BlocksPerDay // 14,400 + DefaultTerraLiquidityRatio = sdk.NewDecWithPrec(1, 2) // 1% + DefaultMinSpread = sdk.NewDecWithPrec(2, 2) // 2% + DefaultTobinTax = sdk.NewDecWithPrec(30, 4) // 0.3% ) var _ subspace.ParamSet = &Params{} // Params market parameters type Params struct { - DailyLunaDeltaCap sdk.Dec `json:"daily_luna_delta_cap" yaml:"daily_luna_delta_cap"` - MaxSwapSpread sdk.Dec `json:"max_swap_spread" yaml:"max_swap_spread"` - MinSwapSpread sdk.Dec `json:"min_swap_spread" yaml:"min_swap_spread"` + 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"` } // DefaultParams creates default market module parameters func DefaultParams() Params { return Params{ - DailyLunaDeltaCap: DefaultDailyLunaDeltaCap, - MaxSwapSpread: DefaultMaxSwapSpread, - MinSwapSpread: DefaultMinSwapSpread, + BasePool: DefaultBasePool, + PoolRecoveryPeriod: DefaultPoolRecoveryPeriod, + MinSpread: DefaultMinSpread, + TobinTax: DefaultTobinTax, } } // Validate a set of params func (params Params) Validate() error { - if params.DailyLunaDeltaCap.IsNegative() { - return fmt.Errorf("market daily luna issuance change should be non-negative, is %s", params.DailyLunaDeltaCap.String()) + if params.BasePool.IsNegative() { + return fmt.Errorf("base pool should be positive or zero, is %d", params.BasePool) + } + if params.PoolRecoveryPeriod <= 0 { + return fmt.Errorf("pool recovery period should be positive, is %d", params.PoolRecoveryPeriod) } - if params.MinSwapSpread.IsNegative() || params.MinSwapSpread.GT(sdk.OneDec()) { - return fmt.Errorf("market minimum swap spead should be non-negative, is %s", params.MinSwapSpread.String()) + if params.MinSpread.IsNegative() || params.MinSpread.GT(sdk.OneDec()) { + return fmt.Errorf("market minimum spead should be a value between [0,1], is %s", params.MinSpread.String()) } - if params.MaxSwapSpread.LT(params.MinSwapSpread) || params.MaxSwapSpread.GT(sdk.OneDec()) { - return fmt.Errorf("market maximum swap spead should be larger or equal to the minimum, is %s", params.MaxSwapSpread.String()) + 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.String()) } return nil } // ParamSetPairs implements the ParamSet interface and returns all the key/value pairs -// pairs of oracle module's parameters. +// pairs of market module's parameters. // nolint func (params *Params) ParamSetPairs() subspace.ParamSetPairs { return subspace.ParamSetPairs{ - {Key: ParamStoreKeyDailyLunaDeltaCap, Value: ¶ms.DailyLunaDeltaCap}, - {Key: ParamStoreKeyMaxSwapSpread, Value: ¶ms.MaxSwapSpread}, - {Key: ParamStoreKeyMinSwapSpread, Value: ¶ms.MinSwapSpread}, + {Key: ParamStoreKeyBasePool, Value: ¶ms.BasePool}, + {Key: ParamStoreKeyPoolRecoveryPeriod, Value: ¶ms.PoolRecoveryPeriod}, + {Key: ParamStoreKeyMinSpread, Value: ¶ms.MinSpread}, + {Key: ParmamStoreKeyTobinTax, Value: ¶ms.TobinTax}, } } -// implements fmt.Stringer +// String implements fmt.Stringer interface func (params Params) String() string { return fmt.Sprintf(`Treasury Params: - DailyLunaDeltaCap: %s - MaxSwapSpread: %s - MinSwapSpread: %s - `, params.DailyLunaDeltaCap, params.MaxSwapSpread, params.MinSwapSpread) + BasePool: %d + PoolRecoveryPeriod: %d + MinSpread: %s + TobinTax: %s + `, params.BasePool, params.PoolRecoveryPeriod, params.MinSpread, params.TobinTax) } diff --git a/x/market/internal/types/querier.go b/x/market/internal/types/querier.go index cca9f02bb..922ff943f 100644 --- a/x/market/internal/types/querier.go +++ b/x/market/internal/types/querier.go @@ -6,9 +6,9 @@ import ( // query endpoints supported by the oracle Querier const ( - QuerySwap = "swap" - QueryPrevDayIssuance = "prevDayIssuance" - QueryParameters = "parameters" + QuerySwap = "swap" + QueryTerraPoolDelta = "terra_pool_delta" + QueryParameters = "parameters" ) // QuerySwapParams for query @@ -18,6 +18,7 @@ type QuerySwapParams struct { AskDenom string } +// NewQuerySwapParams returns param object for swap query func NewQuerySwapParams(offerCoin sdk.Coin, askDenom string) QuerySwapParams { return QuerySwapParams{ OfferCoin: offerCoin, diff --git a/x/oracle/abci.go b/x/oracle/abci.go index 7813fee3c..d96155f73 100644 --- a/x/oracle/abci.go +++ b/x/oracle/abci.go @@ -62,6 +62,7 @@ func EndBlocker(ctx sdk.Context, k Keeper) { // Set price to the store k.SetLunaPrice(ctx, denom, mod) + ctx.EventManager().EmitEvent( sdk.NewEvent(types.EventTypePriceUpdate, sdk.NewAttribute(types.AttributeKeyDenom, denom), diff --git a/x/oracle/alias.go b/x/oracle/alias.go index 0f6094680..019a93d92 100644 --- a/x/oracle/alias.go +++ b/x/oracle/alias.go @@ -133,6 +133,6 @@ type ( PriceVote = types.PriceVote PriceVotes = types.PriceVotes VotingInfo = types.VotingInfo - Hooks = keeper.Hooks Keeper = keeper.Keeper + Hooks = keeper.Hooks ) diff --git a/x/oracle/client/cli/query.go b/x/oracle/client/cli/query.go index 2647d54e0..1d5dc9c42 100644 --- a/x/oracle/client/cli/query.go +++ b/x/oracle/client/cli/query.go @@ -45,7 +45,7 @@ func GetCmdQueryPrice(cdc *codec.Codec) *cobra.Command { Long: strings.TrimSpace(` Query the current exchange rate of Luna with an asset. You can find the current list of active denoms by running: terracli query oracle active -$ terracli query oracle price --denom ukrw +$ terracli query oracle price ukrw `), RunE: func(cmd *cobra.Command, args []string) error { cliCtx := context.NewCLIContext().WithCodec(cdc) diff --git a/x/oracle/internal/keeper/hooks.go b/x/oracle/internal/keeper/staking_hooks.go similarity index 92% rename from x/oracle/internal/keeper/hooks.go rename to x/oracle/internal/keeper/staking_hooks.go index cec4aecce..a25e343de 100644 --- a/x/oracle/internal/keeper/hooks.go +++ b/x/oracle/internal/keeper/staking_hooks.go @@ -20,19 +20,19 @@ func (k Keeper) AfterValidatorBonded(ctx sdk.Context, _ sdk.ConsAddress, address } } -// Hooks wrapper struct for slashing keeper +// Hooks wrapper struct for oracle keeper type Hooks struct { k Keeper } -var _ types.StakingHooks = Hooks{} +var _ types.StakingHooks = Keeper{} // Return the wrapper struct -func (k Keeper) Hooks() Hooks { +func (k Keeper) StakingHooks() Hooks { return Hooks{k} } -// Implements sdk.ValidatorHooks +// Implements StakingHooks func (h Hooks) AfterValidatorBonded(ctx sdk.Context, consAddr sdk.ConsAddress, valAddr sdk.ValAddress) { h.k.AfterValidatorBonded(ctx, consAddr, valAddr) } diff --git a/x/oracle/internal/keeper/test_utils.go b/x/oracle/internal/keeper/test_utils.go index fdb08be57..2bc568318 100644 --- a/x/oracle/internal/keeper/test_utils.go +++ b/x/oracle/internal/keeper/test_utils.go @@ -176,7 +176,7 @@ func CreateTestInput(t *testing.T) TestInput { defaults := types.DefaultParams() keeper.SetParams(ctx, defaults) - stakingKeeper.SetHooks(staking.NewMultiStakingHooks(distrKeeper.Hooks(), keeper.Hooks())) + stakingKeeper.SetHooks(staking.NewMultiStakingHooks(distrKeeper.Hooks(), keeper.StakingHooks())) return TestInput{ctx, cdc, accountKeeper, bankKeeper, keeper, supplyKeeper, stakingKeeper, distrKeeper} } diff --git a/x/oracle/internal/types/claim.go b/x/oracle/internal/types/claim.go index 998e42515..3361cf25f 100644 --- a/x/oracle/internal/types/claim.go +++ b/x/oracle/internal/types/claim.go @@ -24,6 +24,7 @@ func NewClaim(weight int64, recipient sdk.ValAddress) Claim { } } +// String implements fmt.Stringer interface func (c Claim) String() string { return fmt.Sprintf(`Claim Weight: %v diff --git a/x/oracle/internal/types/claimpool.go b/x/oracle/internal/types/claimpool.go index 4e7ecc100..b473b31da 100644 --- a/x/oracle/internal/types/claimpool.go +++ b/x/oracle/internal/types/claimpool.go @@ -31,6 +31,7 @@ func (cp ClaimPool) Sort() ClaimPool { return cp } +// String implements fmt.Stringer interface func (cp ClaimPool) String() (out string) { out = "ClaimPool " for _, claim := range cp { diff --git a/x/oracle/internal/types/denom.go b/x/oracle/internal/types/denom.go index a41cb1054..56db8ea62 100644 --- a/x/oracle/internal/types/denom.go +++ b/x/oracle/internal/types/denom.go @@ -7,6 +7,7 @@ import ( // DenomList is array of denom type DenomList []string +// String implements fmt.Stringer interface func (dl DenomList) String() (out string) { out = strings.Join(dl, "\n") return diff --git a/x/oracle/internal/types/msgs.go b/x/oracle/internal/types/msgs.go index 27f22959b..c8122f73d 100644 --- a/x/oracle/internal/types/msgs.go +++ b/x/oracle/internal/types/msgs.go @@ -76,7 +76,7 @@ func (msg MsgPricePrevote) ValidateBasic() sdk.Error { return nil } -// String Implements Msg +// String implements fmt.Stringer interface func (msg MsgPricePrevote) String() string { return fmt.Sprintf(`MsgPriceVote hash: %s, @@ -150,7 +150,7 @@ func (msg MsgPriceVote) ValidateBasic() sdk.Error { return nil } -// String Implements Msg +// String implements fmt.Stringer interface func (msg MsgPriceVote) String() string { return fmt.Sprintf(`MsgPriceVote price: %s, @@ -204,7 +204,7 @@ func (msg MsgDelegateFeederPermission) ValidateBasic() sdk.Error { return nil } -// String Implements Msg +// String implements fmt.Stringer interface func (msg MsgDelegateFeederPermission) String() string { return fmt.Sprintf(`MsgDelegateFeederPermission operator: %s, diff --git a/x/oracle/internal/types/params.go b/x/oracle/internal/types/params.go index 3fbddb005..7b9530065 100644 --- a/x/oracle/internal/types/params.go +++ b/x/oracle/internal/types/params.go @@ -105,7 +105,7 @@ func (params *Params) ParamSetPairs() subspace.ParamSetPairs { } } -// implements fmt.Stringer +// String implements fmt.Stringer interface func (params Params) String() string { return fmt.Sprintf(`Treasury Params: VotePeriod: %d diff --git a/x/oracle/internal/types/vote.go b/x/oracle/internal/types/vote.go index ad9af7e2f..10691b782 100644 --- a/x/oracle/internal/types/vote.go +++ b/x/oracle/internal/types/vote.go @@ -26,7 +26,7 @@ func NewPricePrevote(hash string, denom string, voter sdk.ValAddress, submitBloc } } -// String implements fmt.Stringer +// String implements fmt.Stringer interface func (pp PricePrevote) String() string { return fmt.Sprintf(`PricePrevote Hash: %s, @@ -39,6 +39,7 @@ func (pp PricePrevote) String() string { // PricePrevotes is a collection of PreicePrevote type PricePrevotes []PricePrevote +// String implements fmt.Stringer interface func (v PricePrevotes) String() (out string) { for _, val := range v { out += val.String() + "\n" @@ -79,7 +80,7 @@ func (pv PriceVote) getPower(ctx sdk.Context, sk StakingKeeper) int64 { return validator.GetConsensusPower() } -// String implements fmt.Stringer +// String implements fmt.Stringer interface func (pv PriceVote) String() string { return fmt.Sprintf(`PriceVote Denom: %s, @@ -91,6 +92,7 @@ func (pv PriceVote) String() string { // PriceVotes is a collection of PriceVote type PriceVotes []PriceVote +// String implements fmt.Stringer interface func (v PriceVotes) String() (out string) { for _, val := range v { out += val.String() + "\n" diff --git a/x/oracle/internal/types/voting_info.go b/x/oracle/internal/types/voting_info.go index 8072ea451..82593926d 100644 --- a/x/oracle/internal/types/voting_info.go +++ b/x/oracle/internal/types/voting_info.go @@ -28,7 +28,7 @@ func NewVotingInfo( } } -// String implements the stringer interface for NewVotingInfo +// String implements fmt.Stringer interface func (i VotingInfo) String() string { return fmt.Sprintf(`Validator Signing Info: Address: %s diff --git a/x/oracle/test_utils.go b/x/oracle/test_utils.go index 6c3e9d737..e1780a64c 100644 --- a/x/oracle/test_utils.go +++ b/x/oracle/test_utils.go @@ -5,182 +5,14 @@ import ( "testing" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/bank" - distr "github.com/cosmos/cosmos-sdk/x/distribution" - "github.com/cosmos/cosmos-sdk/x/mock" "github.com/cosmos/cosmos-sdk/x/staking" - "github.com/cosmos/cosmos-sdk/x/supply" core "github.com/terra-project/core/types" "github.com/terra-project/core/x/oracle/internal/keeper" - "github.com/terra-project/core/x/oracle/internal/types" -) - -var ( - valTokens = sdk.TokensFromConsensusPower(42) - initTokens = sdk.TokensFromConsensusPower(100000) - valCoins = sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, valTokens)) - initCoins = sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, initTokens)) ) -type testInput struct { - mApp *mock.App - oracleKeeper Keeper - stakingKeeper staking.Keeper - supplyKeeper supply.Keeper - distrKeeper distr.Keeper - addrs []sdk.AccAddress - pubKeys []crypto.PubKey - privKeys []crypto.PrivKey -} - -func getMockApp(t *testing.T, numGenAccs int, genState GenesisState, genAccs []auth.Account) testInput { - mApp := mock.NewApp() - - staking.RegisterCodec(mApp.Cdc) - RegisterCodec(mApp.Cdc) - supply.RegisterCodec(mApp.Cdc) - - keyStaking := sdk.NewKVStoreKey(staking.StoreKey) - tKeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) - keyDistr := sdk.NewKVStoreKey(distr.StoreKey) - keyOracle := sdk.NewKVStoreKey(StoreKey) - keySupply := sdk.NewKVStoreKey(supply.StoreKey) - - blackListAddrs := map[string]bool{ - auth.FeeCollectorName: true, - staking.NotBondedPoolName: true, - staking.BondedPoolName: true, - distr.ModuleName: true, - types.ModuleName: true, - } - - pk := mApp.ParamsKeeper - - bk := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blackListAddrs) - - maccPerms := map[string][]string{ - distr.ModuleName: nil, - staking.NotBondedPoolName: {supply.Burner, supply.Staking}, - staking.BondedPoolName: {supply.Burner, supply.Staking}, - types.ModuleName: nil, - } - - supplyKeeper := supply.NewKeeper(mApp.Cdc, keySupply, mApp.AccountKeeper, bk, maccPerms) - stakingKeeper := staking.NewKeeper(mApp.Cdc, keyStaking, tKeyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) - distrKeeper := distr.NewKeeper(mApp.Cdc, keyDistr, pk.Subspace(distr.DefaultParamspace), stakingKeeper, supplyKeeper, distr.DefaultCodespace, auth.FeeCollectorName, blackListAddrs) - - keeper := NewKeeper(mApp.Cdc, keyOracle, pk.Subspace(DefaultParamspace), distrKeeper, stakingKeeper, supplyKeeper, distr.ModuleName, DefaultCodespace) - - mApp.Router().AddRoute(RouterKey, NewHandler(keeper)) - mApp.QueryRouter().AddRoute(QuerierRoute, NewQuerier(keeper)) - - mApp.SetBeginBlocker(getBeginBloker(distrKeeper)) - mApp.SetEndBlocker(getEndBlocker(keeper, stakingKeeper)) - mApp.SetInitChainer(getInitChainer(mApp, keeper, stakingKeeper, supplyKeeper, genAccs, genState)) - - require.NoError(t, mApp.CompleteSetup(keyStaking, tKeyStaking, keyOracle, keySupply, keyDistr)) - - var ( - addrs []sdk.AccAddress - pubKeys []crypto.PubKey - privKeys []crypto.PrivKey - ) - - if genAccs == nil || len(genAccs) == 0 { - genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs, valCoins) - } - - mock.SetGenesis(mApp, genAccs) - - return testInput{mApp, keeper, stakingKeeper, supplyKeeper, distrKeeper, addrs, pubKeys, privKeys} -} - -func getBeginBloker(distrKeeper distr.Keeper) sdk.BeginBlocker { - return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { - distr.BeginBlocker(ctx, req, distrKeeper) - - return abci.ResponseBeginBlock{ - Events: ctx.EventManager().ABCIEvents(), - } - } -} - -// oracle and staking endblocker -func getEndBlocker(keeper Keeper, sk staking.Keeper) sdk.EndBlocker { - return func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - EndBlocker(ctx, keeper) - staking.EndBlocker(ctx, sk) - return abci.ResponseEndBlock{} - } -} - -// gov and staking initchainer -func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, supplyKeeper supply.Keeper, accs []auth.Account, genState GenesisState) sdk.InitChainer { - return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain { - mapp.InitChainer(ctx, req) - - stakingGenesis := staking.DefaultGenesisState() - - totalSupply := sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, initTokens.MulRaw(int64(len(mapp.GenesisAccounts))))) - supplyKeeper.SetSupply(ctx, supply.NewSupply(totalSupply)) - - // set module accounts - govAcc := supply.NewEmptyModuleAccount(types.ModuleName, supply.Burner) - notBondedPool := supply.NewEmptyModuleAccount(staking.NotBondedPoolName, supply.Burner, supply.Staking) - bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking) - - supplyKeeper.SetModuleAccount(ctx, govAcc) - supplyKeeper.SetModuleAccount(ctx, notBondedPool) - supplyKeeper.SetModuleAccount(ctx, bondPool) - - validators := staking.InitGenesis(ctx, stakingKeeper, mapp.AccountKeeper, supplyKeeper, stakingGenesis) - if genState.IsEmpty() { - InitGenesis(ctx, keeper, DefaultGenesisState()) - } else { - InitGenesis(ctx, keeper, genState) - } - - return abci.ResponseInitChain{ - Validators: validators, - } - } -} - -var ( - pubkeys = []crypto.PubKey{ - ed25519.GenPrivKey().PubKey(), - ed25519.GenPrivKey().PubKey(), - ed25519.GenPrivKey().PubKey(), - } - - testDescription = staking.NewDescription("T", "E", "S", "T") - testCommissionRates = staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) -) - -func createValidators(t *testing.T, stakingHandler sdk.Handler, ctx sdk.Context, addrs []sdk.ValAddress, powerAmt []int64) { - require.True(t, len(addrs) <= len(pubkeys), "Not enough pubkeys specified at top of file.") - - for i := 0; i < len(addrs); i++ { - - valTokens := sdk.TokensFromConsensusPower(powerAmt[i]) - valCreateMsg := staking.NewMsgCreateValidator( - addrs[i], pubkeys[i], sdk.NewCoin(core.MicroLunaDenom, valTokens), - testDescription, testCommissionRates, sdk.OneInt(), - ) - - res := stakingHandler(ctx, valCreateMsg) - require.True(t, res.IsOK()) - } -} - var ( uSDRAmt = sdk.NewInt(1005 * core.MicroUnit) stakingAmt = sdk.TokensFromConsensusPower(10) diff --git a/x/treasury/abci.go b/x/treasury/abci.go index faf227af0..3bf29da38 100644 --- a/x/treasury/abci.go +++ b/x/treasury/abci.go @@ -17,7 +17,7 @@ func EndBlocker(ctx sdk.Context, k Keeper) { } // Update luna issuance after finish all works - defer k.UpdateIssuance(ctx) + defer k.RecordHistoricalIssuance(ctx) // Check probation period if ctx.BlockHeight() < (core.BlocksPerEpoch * k.WindowProbation(ctx)) { diff --git a/x/treasury/genesis.go b/x/treasury/genesis.go index f5ec4ad9a..f6657cc68 100644 --- a/x/treasury/genesis.go +++ b/x/treasury/genesis.go @@ -13,8 +13,17 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { keeper.SetRewardWeight(ctx, data.RewardWeight) // store tax cap for SDT & LUNA(no tax) - keeper.SetTaxCap(ctx, data.Params.TaxPolicy.Cap.Denom, data.Params.TaxPolicy.Cap.Amount) - keeper.SetTaxCap(ctx, core.MicroLunaDenom, sdk.ZeroInt()) + for denom, taxCap := range data.TaxCaps { + keeper.SetTaxCap(ctx, denom, taxCap) + } + + for epoch, historicalIssuance := range data.HistoricalIssuances { + keeper.SetHistoricalIssuance(ctx, int64(epoch), historicalIssuance) + } + + for epoch, taxProceed := range data.TaxProceeds { + keeper.SetTaxProceeds(ctx, int64(epoch), taxProceed) + } } // ExportGenesis writes the current store values @@ -24,5 +33,19 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) (data GenesisState) { params := keeper.GetParams(ctx) taxRate := keeper.GetTaxRate(ctx, core.GetEpoch(ctx)) rewardWeight := keeper.GetRewardWeight(ctx, core.GetEpoch(ctx)) - return NewGenesisState(params, taxRate, rewardWeight) + + taxCaps := make(map[string]sdk.Int) + keeper.IterateTaxCap(ctx, func(denom string, taxCap sdk.Int) bool { + taxCaps[denom] = taxCap + return false + }) + + var taxProceeds []sdk.Coins + var historicalIssuancees []sdk.Coins + for e := int64(0); e <= core.GetEpoch(ctx); e++ { + taxProceeds = append(taxProceeds, keeper.PeekTaxProceeds(ctx, e)) + historicalIssuancees = append(historicalIssuancees, keeper.GetHistoricalIssuance(ctx, e)) + } + + return NewGenesisState(params, taxRate, rewardWeight, taxCaps, taxProceeds, historicalIssuancees) } diff --git a/x/treasury/genesis_test.go b/x/treasury/genesis_test.go new file mode 100644 index 000000000..81ae39c9b --- /dev/null +++ b/x/treasury/genesis_test.go @@ -0,0 +1,30 @@ +package treasury + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" + + "github.com/terra-project/core/x/treasury/internal/keeper" +) + +func TestExportInitGenesis(t *testing.T) { + input := keeper.CreateTestInput(t) + input.TreasuryKeeper.SetHistoricalIssuance(input.Ctx, 0, sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(33)))) + input.TreasuryKeeper.SetHistoricalIssuance(input.Ctx, 1, sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(123)))) + input.TreasuryKeeper.SetHistoricalIssuance(input.Ctx, 2, sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(131)))) + input.TreasuryKeeper.SetRewardWeight(input.Ctx, sdk.NewDec(1123)) + input.TreasuryKeeper.SetTaxCap(input.Ctx, "foo", sdk.NewInt(1234)) + input.TreasuryKeeper.SetTaxRate(input.Ctx, sdk.NewDec(5435)) + input.TreasuryKeeper.SetTaxProceeds(input.Ctx, 0, sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(923)))) + input.TreasuryKeeper.SetTaxProceeds(input.Ctx, 1, sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(123)))) + input.TreasuryKeeper.SetTaxProceeds(input.Ctx, 2, sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(623)))) + genesis := ExportGenesis(input.Ctx, input.TreasuryKeeper) + + newInput := keeper.CreateTestInput(t) + InitGenesis(newInput.Ctx, newInput.TreasuryKeeper, genesis) + newGenesis := ExportGenesis(newInput.Ctx, newInput.TreasuryKeeper) + + require.Equal(t, genesis, newGenesis) +} diff --git a/x/treasury/internal/keeper/indicator.go b/x/treasury/internal/keeper/indicator.go index 7b66931c8..65b1ab226 100644 --- a/x/treasury/internal/keeper/indicator.go +++ b/x/treasury/internal/keeper/indicator.go @@ -24,7 +24,7 @@ func TaxRewardsForEpoch(ctx sdk.Context, k Keeper, epoch int64) sdk.Dec { taxRewardInMicroSDR := sdk.ZeroDec() for _, coinReward := range taxRewards { if coinReward.Denom != core.MicroSDRDenom { - swappedReward, err := k.marketKeeper.GetSwapDecCoin(ctx, coinReward, core.MicroSDRDenom) + swappedReward, err := k.marketKeeper.ComputeInternalSwap(ctx, coinReward, core.MicroSDRDenom) if err != nil { continue } @@ -43,7 +43,7 @@ func SeigniorageRewardsForEpoch(ctx sdk.Context, k Keeper, epoch int64) sdk.Dec rewardAmt := k.GetRewardWeight(ctx, epoch).MulInt(seignioragePool) seigniorageReward := sdk.NewDecCoinFromDec(core.MicroLunaDenom, rewardAmt) - microSDRReward, err := k.marketKeeper.GetSwapDecCoin(ctx, seigniorageReward, core.MicroSDRDenom) + microSDRReward, err := k.marketKeeper.ComputeInternalSwap(ctx, seigniorageReward, core.MicroSDRDenom) if err != nil { return sdk.ZeroDec() } diff --git a/x/treasury/internal/keeper/indicator_test.go b/x/treasury/internal/keeper/indicator_test.go index 8c8663d02..a4c3c3f9e 100644 --- a/x/treasury/internal/keeper/indicator_test.go +++ b/x/treasury/internal/keeper/indicator_test.go @@ -46,7 +46,7 @@ func TestSeigniorageRewardsForEpoch(t *testing.T) { supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sAmt))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) // Set random prices input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroSDRDenom, lnasdrRate) @@ -69,7 +69,7 @@ func TestMiningRewardsForEpoch(t *testing.T) { supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, amt))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) // Set random prices input.OracleKeeper.SetLunaPrice(input.Ctx, core.MicroKRWDenom, sdk.NewDec(1)) @@ -211,7 +211,7 @@ func TestRollingAverageIndicator(t *testing.T) { supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(100000000*core.MicroUnit)))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) for i := int64(201); i <= 500; i++ { input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch * i) @@ -220,7 +220,7 @@ func TestRollingAverageIndicator(t *testing.T) { supply = supply.SetTotal(supply.GetTotal().Sub(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(i).MulRaw(core.MicroUnit))))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) } totalBondedTokens := sdk.NewDecFromInt(input.StakingKeeper.TotalBondedTokens(input.Ctx)) diff --git a/x/treasury/internal/keeper/keeper.go b/x/treasury/internal/keeper/keeper.go index 66cb8690b..5ea72fe36 100644 --- a/x/treasury/internal/keeper/keeper.go +++ b/x/treasury/internal/keeper/keeper.go @@ -56,12 +56,12 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) } -// return the codespace +// Codespace returns the codespace func (k Keeper) Codespace() sdk.CodespaceType { return k.codespace } -// Load the tax-rate +// GetTaxRate loads the tax-rate func (k Keeper) GetTaxRate(ctx sdk.Context, epoch int64) (taxRate sdk.Dec) { store := ctx.KVStore(k.storeKey) b := store.Get(types.GetTaxRateKey(epoch)) @@ -73,7 +73,7 @@ func (k Keeper) GetTaxRate(ctx sdk.Context, epoch int64) (taxRate sdk.Dec) { return } -// Set the tax-rate +// SetTaxRate sets the tax-rate func (k Keeper) SetTaxRate(ctx sdk.Context, taxRate sdk.Dec) { epoch := core.GetEpoch(ctx) store := ctx.KVStore(k.storeKey) @@ -81,7 +81,7 @@ func (k Keeper) SetTaxRate(ctx sdk.Context, taxRate sdk.Dec) { store.Set(types.GetTaxRateKey(epoch), b) } -// Load the reward weight +// GetRewardWeight loads the reward weight func (k Keeper) GetRewardWeight(ctx sdk.Context, epoch int64) (rewardWeight sdk.Dec) { store := ctx.KVStore(k.storeKey) b := store.Get(types.GetRewardWeightKey(epoch)) @@ -93,7 +93,7 @@ func (k Keeper) GetRewardWeight(ctx sdk.Context, epoch int64) (rewardWeight sdk. return } -// Set the reward weight +// SetRewardWeight sets the reward weight func (k Keeper) SetRewardWeight(ctx sdk.Context, rewardWeight sdk.Dec) { epoch := core.GetEpoch(ctx) store := ctx.KVStore(k.storeKey) @@ -101,7 +101,7 @@ func (k Keeper) SetRewardWeight(ctx sdk.Context, rewardWeight sdk.Dec) { store.Set(types.GetRewardWeightKey(epoch), b) } -// setTaxCap sets the Tax Cap. Denominated in integer units of the reference {denom} +// SetTaxCap sets the Tax Cap. Denominated in integer units of the reference {denom} func (k Keeper) SetTaxCap(ctx sdk.Context, denom string, cap sdk.Int) { store := ctx.KVStore(k.storeKey) bz := k.cdc.MustMarshalBinaryLengthPrefixed(cap) @@ -122,7 +122,26 @@ func (k Keeper) GetTaxCap(ctx sdk.Context, denom string) (taxCap sdk.Int) { return } -// RecordTaxProceeds add tax proceeds that have been added this epoch +// IterateTaxCap iterates tax caps +func (k Keeper) IterateTaxCap(ctx sdk.Context, handler func(denom string, taxCap sdk.Int) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.TaxCapKey) + + defer iter.Close() + for ; iter.Valid(); iter.Next() { + denom := string(iter.Key()[len(types.TaxCapKey):]) + var taxCap sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &taxCap) + + if handler(denom, taxCap) { + break + } + } + + return +} + +// RecordTaxProceeds adds tax proceeds that have been added this epoch func (k Keeper) RecordTaxProceeds(ctx sdk.Context, delta sdk.Coins) { if delta.Empty() { return @@ -132,8 +151,13 @@ func (k Keeper) RecordTaxProceeds(ctx sdk.Context, delta sdk.Coins) { proceeds := k.PeekTaxProceeds(ctx, epoch) proceeds = proceeds.Add(delta) + k.SetTaxProceeds(ctx, epoch, proceeds) +} + +// SetTaxProceeds stores tax proceeds for the given epoch +func (k Keeper) SetTaxProceeds(ctx sdk.Context, epoch int64, taxProceeds sdk.Coins) { store := ctx.KVStore(k.storeKey) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(proceeds) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(taxProceeds) store.Set(types.GetTaxProceedsKey(epoch), bz) } @@ -149,17 +173,34 @@ func (k Keeper) PeekTaxProceeds(ctx sdk.Context, epoch int64) (res sdk.Coins) { return } -// UpdateIssuance update epoch issuance from supply keeper (historical) -func (k Keeper) UpdateIssuance(ctx sdk.Context) { +// ClearTaxProceeds clear all taxProceeds +func (k Keeper) ClearTaxProceeds(ctx sdk.Context) { store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.TaxProceedsKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} +// RecordHistoricalIssuance update epoch issuance from supply keeper (historical) +func (k Keeper) RecordHistoricalIssuance(ctx sdk.Context) { epoch := core.GetEpoch(ctx) totalCoins := k.supplyKeeper.GetSupply(ctx).GetTotal() - bz := k.cdc.MustMarshalBinaryLengthPrefixed(totalCoins) + k.SetHistoricalIssuance(ctx, epoch, totalCoins) + +} + +// SetHistoricalIssuance stores epoch issuance +func (k Keeper) SetHistoricalIssuance(ctx sdk.Context, epoch int64, issuance sdk.Coins) { + store := ctx.KVStore(k.storeKey) + + bz := k.cdc.MustMarshalBinaryLengthPrefixed(issuance) store.Set(types.GetHistoricalIssuanceKey(epoch), bz) + return } -// GetHistoricalIssuance returns epoch issuance of a denom +// GetHistoricalIssuance returns epoch issuance func (k Keeper) GetHistoricalIssuance(ctx sdk.Context, epoch int64) (res sdk.Coins) { store := ctx.KVStore(k.storeKey) bz := store.Get(types.GetHistoricalIssuanceKey(epoch)) @@ -172,6 +213,16 @@ func (k Keeper) GetHistoricalIssuance(ctx sdk.Context, epoch int64) (res sdk.Coi return } +// ClearHistoricalIssuance clear all taxProceeds +func (k Keeper) ClearHistoricalIssuance(ctx sdk.Context) { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.HistoricalIssuanceKey) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + store.Delete(iter.Key()) + } +} + // PeekEpochSeigniorage retursn epoch seigniorage func (k Keeper) PeekEpochSeigniorage(ctx sdk.Context, epoch int64) sdk.Int { if epoch == 0 { diff --git a/x/treasury/internal/keeper/keeper_test.go b/x/treasury/internal/keeper/keeper_test.go index be8b55bba..612f1dda8 100644 --- a/x/treasury/internal/keeper/keeper_test.go +++ b/x/treasury/internal/keeper/keeper_test.go @@ -57,6 +57,31 @@ func TestTaxCap(t *testing.T) { } } +func TestIterateTaxCap(t *testing.T) { + input := CreateTestInput(t) + + cnyCap := sdk.NewInt(123) + usdCap := sdk.NewInt(13) + krwCap := sdk.NewInt(1300) + input.TreasuryKeeper.SetTaxCap(input.Ctx, core.MicroCNYDenom, cnyCap) + input.TreasuryKeeper.SetTaxCap(input.Ctx, core.MicroUSDDenom, usdCap) + input.TreasuryKeeper.SetTaxCap(input.Ctx, core.MicroKRWDenom, krwCap) + + input.TreasuryKeeper.IterateTaxCap(input.Ctx, func(denom string, taxCap sdk.Int) bool { + switch denom { + case core.MicroCNYDenom: + require.Equal(t, cnyCap, taxCap) + case core.MicroUSDDenom: + require.Equal(t, usdCap, taxCap) + case core.MicroKRWDenom: + require.Equal(t, krwCap, taxCap) + } + + return false + }) + +} + func TestTaxProceeds(t *testing.T) { input := CreateTestInput(t) @@ -75,6 +100,9 @@ func TestTaxProceeds(t *testing.T) { require.Equal(t, proceeds, input.TreasuryKeeper.PeekTaxProceeds(input.Ctx, i)) } + + input.TreasuryKeeper.ClearTaxProceeds(input.Ctx) + require.Equal(t, sdk.Coins{}, input.TreasuryKeeper.PeekTaxProceeds(input.Ctx, 0)) } func TestMicroLunaIssuance(t *testing.T) { @@ -91,7 +119,7 @@ func TestMicroLunaIssuance(t *testing.T) { supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sdk.NewInt(i)))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) } for i := int64(0); i < 10; i++ { @@ -99,6 +127,9 @@ func TestMicroLunaIssuance(t *testing.T) { require.Equal(t, sdk.NewInt(i), input.TreasuryKeeper.GetHistoricalIssuance(input.Ctx, i).AmountOf(core.MicroLunaDenom)) } + + input.TreasuryKeeper.ClearHistoricalIssuance(input.Ctx) + require.Equal(t, sdk.Coins{}, input.TreasuryKeeper.GetHistoricalIssuance(input.Ctx, 0)) } func TestPeekEpochSeigniorage(t *testing.T) { @@ -111,7 +142,7 @@ func TestPeekEpochSeigniorage(t *testing.T) { preIssuance := sdk.NewInt(rand.Int63() + 1) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, preIssuance))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) nowIssuance := sdk.NewInt(rand.Int63() + 1) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, nowIssuance))) diff --git a/x/treasury/internal/keeper/policy.go b/x/treasury/internal/keeper/policy.go index eabd9476e..cb393054d 100644 --- a/x/treasury/internal/keeper/policy.go +++ b/x/treasury/internal/keeper/policy.go @@ -18,8 +18,9 @@ func (k Keeper) UpdateTaxCap(ctx sdk.Context) sdk.Coins { continue } - newCap, _, err := k.marketKeeper.GetSwapCoin(ctx, cap, coin.Denom, true) + newDecCap, err := k.marketKeeper.ComputeInternalSwap(ctx, sdk.NewDecCoinFromCoin(cap), coin.Denom) if err == nil { + newCap, _ := newDecCap.TruncateDecimal() newCaps = append(newCaps, newCap) k.SetTaxCap(ctx, newCap.Denom, newCap.Amount) } diff --git a/x/treasury/internal/keeper/querier_test.go b/x/treasury/internal/keeper/querier_test.go index c3b5da48a..4e1771b9c 100644 --- a/x/treasury/internal/keeper/querier_test.go +++ b/x/treasury/internal/keeper/querier_test.go @@ -276,7 +276,7 @@ func TestQuerySeigniorageProceeds(t *testing.T) { supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, targetIssuance))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, targetIssuance.Sub(targetSeigniorage)))) @@ -295,7 +295,7 @@ func TestQueryHistoricalIssuance(t *testing.T) { supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, targetIssuance))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) queriedHistoricalIssuance := getQueriedHistoricalIssuance(t, input.Ctx, input.Cdc, querier, core.GetEpoch(input.Ctx)).AmountOf(core.MicroLunaDenom) diff --git a/x/treasury/internal/keeper/seigniorage.go b/x/treasury/internal/keeper/seigniorage.go index 462a45d3c..0a051523f 100644 --- a/x/treasury/internal/keeper/seigniorage.go +++ b/x/treasury/internal/keeper/seigniorage.go @@ -24,7 +24,7 @@ func (k Keeper) SettleSeigniorage(ctx sdk.Context) { // Align seigniorage to usdr seigniorageLunaDecCoin := sdk.NewDecCoin(core.MicroLunaDenom, seigniorageLunaAmt) - seigniorageDecCoin, err := k.marketKeeper.GetSwapDecCoin(ctx, seigniorageLunaDecCoin, core.MicroSDRDenom) + seigniorageDecCoin, err := k.marketKeeper.ComputeInternalSwap(ctx, seigniorageLunaDecCoin, core.MicroSDRDenom) if err != nil { k.Logger(ctx).Error(fmt.Sprintf("[Treasury] Failed to swap seigniorage to usdr, %s", err.Error())) return diff --git a/x/treasury/internal/keeper/seigniorage_test.go b/x/treasury/internal/keeper/seigniorage_test.go index d21c76779..07b19cd3d 100644 --- a/x/treasury/internal/keeper/seigniorage_test.go +++ b/x/treasury/internal/keeper/seigniorage_test.go @@ -21,7 +21,7 @@ func TestSettle(t *testing.T) { supply := input.SupplyKeeper.GetSupply(input.Ctx) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, issuance))) input.SupplyKeeper.SetSupply(input.Ctx, supply) - input.TreasuryKeeper.UpdateIssuance(input.Ctx) + input.TreasuryKeeper.RecordHistoricalIssuance(input.Ctx) input.Ctx = input.Ctx.WithBlockHeight(core.BlocksPerEpoch) supply = supply.SetTotal(sdk.NewCoins(sdk.NewCoin(core.MicroLunaDenom, sdk.ZeroInt()))) diff --git a/x/treasury/internal/keeper/test_utils.go b/x/treasury/internal/keeper/test_utils.go index 92029a18a..697eaafcf 100644 --- a/x/treasury/internal/keeper/test_utils.go +++ b/x/treasury/internal/keeper/test_utils.go @@ -208,7 +208,7 @@ func CreateTestInput(t *testing.T) TestInput { require.NoError(t, err) } - stakingKeeper.SetHooks(staking.NewMultiStakingHooks(distrKeeper.Hooks(), oracleKeeper.Hooks())) + stakingKeeper.SetHooks(staking.NewMultiStakingHooks(distrKeeper.Hooks(), oracleKeeper.StakingHooks())) return TestInput{ctx, cdc, treasuryKeeper, stakingKeeper, oracleKeeper, supplyKeeper, marketKeeper, distrKeeper} } diff --git a/x/treasury/internal/types/constraint.go b/x/treasury/internal/types/constraint.go index 26fdc2732..941416901 100644 --- a/x/treasury/internal/types/constraint.go +++ b/x/treasury/internal/types/constraint.go @@ -14,7 +14,7 @@ type PolicyConstraints struct { ChangeRateMax sdk.Dec `json:"change_max"` } -// String implements fmt.Stringer +// String implements fmt.Stringer interface func (pc PolicyConstraints) String() string { return fmt.Sprintf(`PolicyConstraints : RateMin: %s diff --git a/x/treasury/internal/types/exptected_keepers.go b/x/treasury/internal/types/exptected_keepers.go index d892809a0..3b2f3a6d1 100644 --- a/x/treasury/internal/types/exptected_keepers.go +++ b/x/treasury/internal/types/exptected_keepers.go @@ -15,8 +15,7 @@ type SupplyKeeper interface { // expected market keeper type MarketKeeper interface { - GetSwapDecCoin(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) - GetSwapCoin(ctx sdk.Context, offerCoin sdk.Coin, askDenom string, isInternal bool) (retCoin sdk.Coin, spread sdk.Dec, err sdk.Error) + ComputeInternalSwap(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) } // expected keeper for staking module diff --git a/x/treasury/internal/types/genesis.go b/x/treasury/internal/types/genesis.go index f094956ee..f97fbfda7 100644 --- a/x/treasury/internal/types/genesis.go +++ b/x/treasury/internal/types/genesis.go @@ -9,26 +9,36 @@ import ( // GenesisState - all market state that must be provided at genesis type GenesisState struct { - Params Params `json:"params" yaml:"params"` // market params - TaxRate sdk.Dec `json:"tax_rate" yaml:"tax_rate"` - RewardWeight sdk.Dec `json:"reward_weight" yaml:"reward_weight"` + Params Params `json:"params" yaml:"params"` // market params + TaxRate sdk.Dec `json:"tax_rate" yaml:"tax_rate"` + RewardWeight sdk.Dec `json:"reward_weight" yaml:"reward_weight"` + TaxCaps map[string]sdk.Int `json:"tax_cap" yaml:"tax_cap"` + TaxProceeds []sdk.Coins `json:"tax_proceeds" yaml:"tax_proceeds"` + HistoricalIssuances []sdk.Coins `json:"historical_issuance" yaml:"historical_issuance"` } // NewGenesisState creates a new GenesisState object -func NewGenesisState(params Params, taxRate sdk.Dec, rewardWeight sdk.Dec) GenesisState { +func NewGenesisState(params Params, taxRate sdk.Dec, rewardWeight sdk.Dec, + taxCaps map[string]sdk.Int, taxProceeds []sdk.Coins, historicalIssuances []sdk.Coins) GenesisState { return GenesisState{ - Params: params, - TaxRate: taxRate, - RewardWeight: rewardWeight, + Params: params, + TaxRate: taxRate, + RewardWeight: rewardWeight, + TaxCaps: taxCaps, + TaxProceeds: taxProceeds, + HistoricalIssuances: historicalIssuances, } } -// get raw genesis raw message for testing +// DefaultGenesisState gets raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - Params: DefaultParams(), - TaxRate: DefaultTaxRate, - RewardWeight: DefaultRewardWeight, + Params: DefaultParams(), + TaxRate: DefaultTaxRate, + RewardWeight: DefaultRewardWeight, + TaxCaps: make(map[string]sdk.Int), + TaxProceeds: []sdk.Coins{}, + HistoricalIssuances: []sdk.Coins{}, } } @@ -46,14 +56,14 @@ func ValidateGenesis(data GenesisState) error { return data.Params.Validate() } -// Checks whether 2 GenesisState structs are equivalent. +// Equal checks whether 2 GenesisState structs are equivalent. func (data GenesisState) Equal(data2 GenesisState) bool { b1 := ModuleCdc.MustMarshalBinaryBare(data) b2 := ModuleCdc.MustMarshalBinaryBare(data2) return bytes.Equal(b1, b2) } -// Returns if a GenesisState is empty or has data in it +// IsEmpty returns if a GenesisState is empty or has data in it func (data GenesisState) IsEmpty() bool { emptyGenState := GenesisState{} return data.Equal(emptyGenState) diff --git a/x/treasury/internal/types/params.go b/x/treasury/internal/types/params.go index fa43446e3..c423da198 100644 --- a/x/treasury/internal/types/params.go +++ b/x/treasury/internal/types/params.go @@ -110,7 +110,7 @@ func (params *Params) ParamSetPairs() subspace.ParamSetPairs { } } -// implements fmt.Stringer +// String implements fmt.Stringer interface func (params Params) String() string { return fmt.Sprintf(`Treasury Params: Tax Policy : { %v }