diff --git a/app/app.go b/app/app.go index 84b100e83..dfe71a4ef 100755 --- a/app/app.go +++ b/app/app.go @@ -15,6 +15,8 @@ import ( "github.com/terra-project/core/x/pay" "github.com/terra-project/core/x/treasury" + "github.com/terra-project/core/types/assets" + tdistr "github.com/terra-project/core/x/distribution" tslashing "github.com/terra-project/core/x/slashing" tstaking "github.com/terra-project/core/x/staking" @@ -166,12 +168,6 @@ func NewTerraApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest, app.bankKeeper, app.feeCollectionKeeper, ) - app.oracleKeeper = oracle.NewKeeper( - app.cdc, - app.keyOracle, - stakingKeeper.GetValidatorSet(), - app.paramsKeeper.Subspace(oracle.DefaultParamspace), - ) app.mintKeeper = mint.NewKeeper( app.cdc, app.keyMint, @@ -179,7 +175,18 @@ func NewTerraApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest, app.bankKeeper, app.accountKeeper, ) + app.oracleKeeper = oracle.NewKeeper( + app.cdc, + app.keyOracle, + app.mintKeeper, + app.distrKeeper, + app.feeCollectionKeeper, + stakingKeeper.GetValidatorSet(), + app.paramsKeeper.Subspace(oracle.DefaultParamspace), + ) app.marketKeeper = market.NewKeeper( + app.cdc, + app.keyMarket, app.oracleKeeper, app.mintKeeper, app.paramsKeeper.Subspace(market.DefaultParamspace), @@ -190,14 +197,14 @@ func NewTerraApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest, stakingKeeper.GetValidatorSet(), app.mintKeeper, app.marketKeeper, - app.distrKeeper, - app.feeCollectionKeeper, app.paramsKeeper.Subspace(treasury.DefaultParamspace), ) app.budgetKeeper = budget.NewKeeper( app.cdc, app.keyBudget, + app.marketKeeper, app.mintKeeper, + app.treasuryKeeper, stakingKeeper.GetValidatorSet(), app.paramsKeeper.Subspace(budget.DefaultParamspace), ) @@ -318,17 +325,11 @@ func (app *TerraApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) a func (app *TerraApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { validatorUpdates, tags := staking.EndBlocker(ctx, app.stakingKeeper) - oracleClaims, oracleTags := oracle.EndBlocker(ctx, app.oracleKeeper) + oracleTags := oracle.EndBlocker(ctx, app.oracleKeeper) tags = append(tags, oracleTags...) - for _, oracleClaim := range oracleClaims { - app.treasuryKeeper.AddClaim(ctx, oracleClaim) - } - budgetClaims, budgetTags := budget.EndBlocker(ctx, app.budgetKeeper) + budgetTags := budget.EndBlocker(ctx, app.budgetKeeper) tags = append(tags, budgetTags...) - for _, budgetClaim := range budgetClaims { - app.treasuryKeeper.AddClaim(ctx, budgetClaim) - } treasuryTags := treasury.EndBlocker(ctx, app.treasuryKeeper) tags = append(tags, treasuryTags...) @@ -427,6 +428,9 @@ func (app *TerraApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abc } } + // GetIssuance needs to be called once to read account balances to the store + app.mintKeeper.GetIssuance(ctx, assets.MicroLunaDenom, sdk.ZeroInt()) + // assert runtime invariants app.assertRuntimeInvariants() diff --git a/docs/guide/validators.md b/docs/guide/validators.md index 73c05e770..1568167b2 100644 --- a/docs/guide/validators.md +++ b/docs/guide/validators.md @@ -153,7 +153,7 @@ You can edit your validator's public description. This info is to identify your The `--identity` can be used as to verify identity with systems like Keybase or UPort. When using with Keybase `--identity` should be populated with a 16-digit string that is generated with a [keybase.io](https://keybase.io) account. It's a cryptographically secure method of verifying your identity across multiple online networks. The Keybase API allows us to retrieve your Keybase avatar. This is how you can add a logo to your validator profile. ```bash -terracli tx staking edit-validator +terracli tx staking edit-validator \ --moniker="choose a moniker" \ --website="https://terra.money" \ --identity=6A0D65E29A4CBC8E \ diff --git a/types/claim.go b/types/claim.go index a5baa0204..dbbf49d31 100644 --- a/types/claim.go +++ b/types/claim.go @@ -6,54 +6,26 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// ClaimClass is used to categorize type of Claim -type ClaimClass byte - -const ( - // OracleClaimClass represents oracle claim type - OracleClaimClass ClaimClass = iota - // BudgetClaimClass represents budget claim type - BudgetClaimClass -) - //------------------------------------ //------------------------------------ //------------------------------------ // Claim is an interface that directs its rewards to an attached bank account. type Claim struct { - Class ClaimClass `json:"class"` Weight sdk.Int `json:"weight"` Recipient sdk.AccAddress `json:"recipient"` } // NewClaim generates a Claim instance. -func NewClaim(class ClaimClass, weight sdk.Int, recipient sdk.AccAddress) Claim { +func NewClaim(weight sdk.Int, recipient sdk.AccAddress) Claim { return Claim{ - Class: class, Weight: weight, Recipient: recipient, } } -// ID returns the id of the claim -func (c Claim) ID() string { - return fmt.Sprintf("%d:%s", c.Class, c.Recipient.String()) -} - -func (c Claim) getClassString() string { - switch c.Class { - case OracleClaimClass: - return "oracle" - case BudgetClaimClass: - return "budget" - } - return "unknown" -} - func (c Claim) String() string { return fmt.Sprintf(`Claim - Class: %v Weight: %v - Recipient: %v`, c.getClassString(), c.Weight, c.Recipient) + Recipient: %v`, c.Weight, c.Recipient) } diff --git a/types/claimpool.go b/types/claimpool.go index 056846da3..1bbf25127 100644 --- a/types/claimpool.go +++ b/types/claimpool.go @@ -1,6 +1,8 @@ package types -import "fmt" +import ( + "fmt" +) // ClaimPool is a list of Claims type ClaimPool []Claim diff --git a/types/util/epoch.go b/types/util/epoch.go index 281acd9b0..937fadf1f 100644 --- a/types/util/epoch.go +++ b/types/util/epoch.go @@ -6,7 +6,7 @@ import ( // nolint const ( - BlocksPerMinute = int64(12) + BlocksPerMinute = int64(10) BlocksPerHour = BlocksPerMinute * 60 BlocksPerDay = BlocksPerHour * 24 BlocksPerWeek = BlocksPerDay * 7 diff --git a/x/bench/bench_test_common.go b/x/bench/bench_test_common.go index 9421632fd..c2dec26c2 100644 --- a/x/bench/bench_test_common.go +++ b/x/bench/bench_test_common.go @@ -2,7 +2,6 @@ package bench import ( "github.com/terra-project/core/types/assets" - "github.com/terra-project/core/types/mock" "github.com/terra-project/core/x/budget" "github.com/terra-project/core/x/market" "github.com/terra-project/core/x/mint" @@ -14,6 +13,7 @@ import ( abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -34,6 +34,9 @@ var ( pubKeys [numOfValidators]crypto.PubKey addrs [numOfValidators]sdk.AccAddress + valConsPubKeys [numOfValidators]crypto.PubKey + valConsAddrs [numOfValidators]sdk.ConsAddress + uLunaAmt = sdk.NewInt(10000000000).MulRaw(assets.MicroUnit) uSDRAmt = sdk.NewInt(10000000000).MulRaw(assets.MicroUnit) ) @@ -66,6 +69,7 @@ func createTestInput() testInput { keyAcc := sdk.NewKVStoreKey(auth.StoreKey) keyParams := sdk.NewKVStoreKey(params.StoreKey) tKeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + keyMarket := sdk.NewKVStoreKey(market.StoreKey) keyMint := sdk.NewKVStoreKey(mint.StoreKey) keyFee := sdk.NewKVStoreKey(auth.FeeStoreKey) keyBudget := sdk.NewKVStoreKey(budget.StoreKey) @@ -84,6 +88,7 @@ func createTestInput() testInput { ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyMarket, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyMint, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyBudget, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyOracle, sdk.StoreTypeIAVL, db) @@ -120,7 +125,18 @@ func createTestInput() testInput { ) stakingKeeper.SetPool(ctx, staking.InitialPool()) - stakingKeeper.SetParams(ctx, staking.DefaultParams()) + stakingParams := staking.DefaultParams() + stakingParams.BondDenom = assets.MicroLunaDenom + stakingKeeper.SetParams(ctx, stakingParams) + + feeKeeper := auth.NewFeeCollectionKeeper( + cdc, keyFee, + ) + + distrKeeper := distr.NewKeeper( + cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), + bankKeeper, stakingKeeper, feeKeeper, distr.DefaultCodespace, + ) mintKeeper := mint.NewKeeper( cdc, @@ -130,60 +146,63 @@ func createTestInput() testInput { accKeeper, ) - valset := mock.NewMockValSet() + sh := staking.NewHandler(stakingKeeper) for i := 0; i < 100; i++ { pubKeys[i] = secp256k1.GenPrivKey().PubKey() addrs[i] = sdk.AccAddress(pubKeys[i].Address()) - err := mintKeeper.Mint(ctx, addrs[i], sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt)) - if err != nil { - panic(err) - } + valConsPubKeys[i] = ed25519.GenPrivKey().PubKey() + valConsAddrs[i] = sdk.ConsAddress(valConsPubKeys[i].Address()) - err = mintKeeper.Mint(ctx, addrs[i], sdk.NewCoin(assets.MicroSDRDenom, uSDRAmt)) - if err != nil { - panic(err) + err2 := mintKeeper.Mint(ctx, addrs[i], sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt.MulRaw(3))) + if err2 != nil { + panic(err2) } // Add validators - validator := mock.NewMockValidator(sdk.ValAddress(addrs[i].Bytes()), uLunaAmt) - valset.Validators = append(valset.Validators, validator) - } + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(sdk.ValAddress(addrs[i]), valConsPubKeys[i], + sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt), staking.Description{}, commission, sdk.OneInt()) + res := sh(ctx, msg) + if !res.IsOK() { + panic(res.Log) + } - budgetKeeper := budget.NewKeeper( - cdc, keyBudget, mintKeeper, valset, - paramsKeeper.Subspace(budget.DefaultParamspace), - ) + distrKeeper.Hooks().AfterValidatorCreated(ctx, sdk.ValAddress(addrs[i])) + staking.EndBlocker(ctx, stakingKeeper) + } oracleKeeper := oracle.NewKeeper( cdc, keyOracle, - valset, + mintKeeper, + distrKeeper, + feeKeeper, + stakingKeeper.GetValidatorSet(), paramsKeeper.Subspace(oracle.DefaultParamspace), ) - marketKeeper := market.NewKeeper(oracleKeeper, mintKeeper, paramsKeeper.Subspace(market.DefaultParamspace)) - - feeKeeper := auth.NewFeeCollectionKeeper( - cdc, keyFee, - ) - - distrKeeper := distr.NewKeeper( - cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), - bankKeeper, stakingKeeper, feeKeeper, distr.DefaultCodespace, - ) + marketKeeper := market.NewKeeper( + cdc, + keyMarket, + oracleKeeper, + mintKeeper, + paramsKeeper.Subspace(market.DefaultParamspace)) treasuryKeeper := treasury.NewKeeper( cdc, keyTreasury, - valset, + stakingKeeper.GetValidatorSet(), mintKeeper, marketKeeper, - distrKeeper, - feeKeeper, paramsKeeper.Subspace(treasury.DefaultParamspace), ) + budgetKeeper := budget.NewKeeper( + cdc, keyBudget, marketKeeper, mintKeeper, treasuryKeeper, stakingKeeper.GetValidatorSet(), + paramsKeeper.Subspace(budget.DefaultParamspace), + ) + budget.InitGenesis(ctx, budgetKeeper, budget.DefaultGenesisState()) oracle.InitGenesis(ctx, oracleKeeper, oracle.DefaultGenesisState()) treasury.InitGenesis(ctx, treasuryKeeper, treasury.DefaultGenesisState()) diff --git a/x/bench/treasury_bench_test.go b/x/bench/treasury_bench_test.go index b27b7b9ea..542154b9e 100644 --- a/x/bench/treasury_bench_test.go +++ b/x/bench/treasury_bench_test.go @@ -28,7 +28,7 @@ func BenchmarkTreasuryUpdatePerEpoch(b *testing.B) { for i := int64(0); i < int64(b.N)+probationEpoch; i++ { input.ctx = input.ctx.WithBlockHeight(i*util.BlocksPerEpoch - 1) - input.mintKeeper.AddSeigniorage(input.ctx, uLunaAmt) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt)) input.treasuryKeeper.RecordTaxProceeds(input.ctx, sdk.Coins{ sdk.NewCoin(assets.MicroSDRDenom, taxAmount), diff --git a/x/budget/end_blocker.go b/x/budget/end_blocker.go index d76951ab2..80b686d62 100644 --- a/x/budget/end_blocker.go +++ b/x/budget/end_blocker.go @@ -4,6 +4,7 @@ import ( "strconv" "github.com/terra-project/core/types" + "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" "github.com/terra-project/core/x/budget/tags" @@ -43,9 +44,8 @@ func clearsThreshold(votePower, totalPower sdk.Int, threshold sdk.Dec) bool { } // EndBlocker is called at the end of every block -func EndBlocker(ctx sdk.Context, k Keeper) (claims types.ClaimPool, resTags sdk.Tags) { +func EndBlocker(ctx sdk.Context, k Keeper) (resTags sdk.Tags) { params := k.GetParams(ctx) - claims = types.ClaimPool{} resTags = sdk.EmptyTags() k.CandQueueIterateExpired(ctx, ctx.BlockHeight(), func(programID uint64) (stop bool) { @@ -77,35 +77,72 @@ func EndBlocker(ctx sdk.Context, k Keeper) (claims types.ClaimPool, resTags sdk. }) // Time to re-weight programs - if !util.IsPeriodLastBlock(ctx, params.VotePeriod) { - return + if util.IsPeriodLastBlock(ctx, params.VotePeriod) { + claims := types.ClaimPool{} + + // iterate programs and weight them + k.IteratePrograms(ctx, true, func(program Program) (stop bool) { + votePower, totalPower := tally(ctx, k, program.ProgramID) + + // Need to check if the program should be legacied + if !clearsThreshold(votePower, totalPower, params.LegacyThreshold) { + // Delete all votes on target program + k.DeleteVotesForProgram(ctx, program.ProgramID) + k.DeleteProgram(ctx, program.ProgramID) + resTags.AppendTag(tags.Action, tags.ActionProgramLegacied) + } else { + claims = append(claims, types.NewClaim(votePower, program.Executor)) + resTags.AppendTag(tags.Action, tags.ActionProgramGranted) + } + + resTags.AppendTags( + sdk.NewTags( + tags.ProgramID, strconv.FormatUint(program.ProgramID, 10), + tags.Weight, votePower.String(), + ), + ) + + return false + }) + + k.addClaimPool(ctx, claims) } - claims = types.ClaimPool{} + // Time to distribute rewards to claims + if util.IsPeriodLastBlock(ctx, util.BlocksPerEpoch) { + epoch := util.GetEpoch(ctx) + rewardWeight := k.tk.GetRewardWeight(ctx, epoch) + seigniorage := k.mk.PeekEpochSeigniorage(ctx, epoch) + rewardPool := sdk.OneDec().Sub(rewardWeight).MulInt(seigniorage) + + if rewardPool.GT(sdk.ZeroDec()) { + rewardPoolCoin, err := k.mrk.GetSwapDecCoin(ctx, sdk.NewDecCoinFromDec(assets.MicroLunaDenom, rewardPool), assets.MicroSDRDenom) + if err != nil { + // No SDR swap rate exists + rewardPoolCoin = sdk.NewDecCoinFromDec(assets.MicroLunaDenom, rewardPool) + } - // iterate programs and weight them - k.IteratePrograms(ctx, true, func(program Program) (stop bool) { - votePower, totalPower := tally(ctx, k, program.ProgramID) + weightSum := sdk.ZeroInt() + k.iterateClaimPool(ctx, func(_ sdk.AccAddress, weight sdk.Int) (stop bool) { + weightSum = weightSum.Add(weight) + return false + }) - // Need to check if the program should be legacied - if !clearsThreshold(votePower, totalPower, params.LegacyThreshold) { - // Delete all votes on target program - k.DeleteVotesForProgram(ctx, program.ProgramID) - k.DeleteProgram(ctx, program.ProgramID) - resTags.AppendTag(tags.Action, tags.ActionProgramLegacied) - } else { - claims = append(claims, types.NewClaim(types.BudgetClaimClass, votePower, program.Executor)) - resTags.AppendTag(tags.Action, tags.ActionProgramGranted) - } + k.iterateClaimPool(ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + rewardAmt := rewardPoolCoin.Amount.MulInt(weight).QuoInt(weightSum).TruncateInt() - resTags.AppendTags( - sdk.NewTags( - tags.ProgramID, strconv.FormatUint(program.ProgramID, 10), - tags.Weight, votePower.String(), - ), - ) + // never return err, but handle err for lint + err := k.mk.Mint(ctx, recipient, sdk.NewCoin(rewardPoolCoin.Denom, rewardAmt)) + if err != nil { + panic(err) + } - return false - }) + return false + }) + } + + // Clear all claims + k.clearClaimPool(ctx) + } return } diff --git a/x/budget/end_blocker_test.go b/x/budget/end_blocker_test.go index c86af7fc5..43b98c099 100644 --- a/x/budget/end_blocker_test.go +++ b/x/budget/end_blocker_test.go @@ -5,7 +5,9 @@ import ( "testing" "time" + "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/mock" + "github.com/terra-project/core/types/util" "github.com/stretchr/testify/require" @@ -95,16 +97,62 @@ func TestEndBlockerTiming(t *testing.T) { } // No claims should have been settled yet - claims, _ := EndBlocker(input.ctx, input.budgetKeeper) - require.Equal(t, 0, len(claims)) + EndBlocker(input.ctx, input.budgetKeeper) + + claimCount := countClaimPool(input.ctx, input.budgetKeeper) + require.Equal(t, 0, claimCount) + + // Advance block height by voteperiod - 1, and the program should be settled. + params := input.budgetKeeper.GetParams(input.ctx) + input.ctx = input.ctx.WithBlockHeight(params.VotePeriod - 1) + EndBlocker(input.ctx, input.budgetKeeper) + + claimCount = countClaimPool(input.ctx, input.budgetKeeper) + require.Equal(t, 1, claimCount) + + input.budgetKeeper.iterateClaimPool(input.ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + require.Equal(t, input.budgetKeeper.valset.TotalBondedTokens(input.ctx), weight) + return true + }) + +} + +func TestEndBlockerClaimDistribution(t *testing.T) { + input := createTestInput(t) + + // create test program + testProgram := generateTestProgram(input.ctx, input.budgetKeeper) + + input.budgetKeeper.StoreProgram(input.ctx, testProgram) + + // Add a vote each from validators + for _, addr := range addrs { + input.budgetKeeper.AddVote(input.ctx, testProgram.ProgramID, addr, true) + } + + // No claims should have been settled yet + EndBlocker(input.ctx, input.budgetKeeper) + + claimCount := countClaimPool(input.ctx, input.budgetKeeper) + require.Equal(t, 0, claimCount) // Advance block height by voteperiod - 1, and the program should be settled. params := input.budgetKeeper.GetParams(input.ctx) input.ctx = input.ctx.WithBlockHeight(params.VotePeriod - 1) - claims, _ = EndBlocker(input.ctx, input.budgetKeeper) + EndBlocker(input.ctx, input.budgetKeeper) + + claimCount = countClaimPool(input.ctx, input.budgetKeeper) + require.Equal(t, 1, claimCount) + + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(1000))) + + // after 5 week, distribution date reach + input.ctx = input.ctx.WithBlockHeight(util.BlocksPerEpoch*5 - 1) + input.treasuryKeeper.SetRewardWeight(input.ctx, sdk.NewDecWithPrec(1, 1)) + EndBlocker(input.ctx, input.budgetKeeper) - require.Equal(t, 1, len(claims)) - require.Equal(t, input.budgetKeeper.valset.TotalBondedTokens(input.ctx), claims[0].Weight) + claimCount = countClaimPool(input.ctx, input.budgetKeeper) + require.Equal(t, 0, claimCount) } func TestEndBlockerLegacy(t *testing.T) { @@ -127,8 +175,9 @@ func TestEndBlockerLegacy(t *testing.T) { } // Claims should have been settled - claims, _ := EndBlocker(ctx, input.budgetKeeper) - require.Equal(t, 1, len(claims)) + EndBlocker(ctx, input.budgetKeeper) + claimCount := countClaimPool(input.ctx, input.budgetKeeper) + require.Equal(t, 1, claimCount) ctx = input.ctx.WithBlockHeight(2) @@ -137,10 +186,8 @@ func TestEndBlockerLegacy(t *testing.T) { } // Program should be legacy - claims, _ = EndBlocker(ctx, input.budgetKeeper) - require.Equal(t, 0, len(claims)) - - _, err := input.budgetKeeper.GetProgram(ctx, 1) + EndBlocker(ctx, input.budgetKeeper) + _, err := input.budgetKeeper.GetProgram(ctx, testProgram.ProgramID) require.Error(t, err) } @@ -194,3 +241,12 @@ func TestEndBlockerPassOrReject(t *testing.T) { _, err = input.budgetKeeper.GetProgram(input.ctx, testProgram2.ProgramID) require.Nil(t, err) } + +func countClaimPool(ctx sdk.Context, keeper Keeper) (claimCount int) { + keeper.iterateClaimPool(ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + claimCount++ + return false + }) + + return claimCount +} diff --git a/x/budget/expected_keepers.go b/x/budget/expected_keepers.go new file mode 100644 index 000000000..3afa9c2fe --- /dev/null +++ b/x/budget/expected_keepers.go @@ -0,0 +1,23 @@ +package budget + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// expected mint keeper +type MintKeeper interface { + Mint(ctx sdk.Context, recipient sdk.AccAddress, coin sdk.Coin) (err sdk.Error) + Burn(ctx sdk.Context, payer sdk.AccAddress, coin sdk.Coin) (err sdk.Error) + PeekEpochSeigniorage(ctx sdk.Context, epoch sdk.Int) (epochSeigniorage sdk.Int) +} + +// expected treasury keeper +type TreasuryKeeper interface { + GetRewardWeight(ctx sdk.Context, epoch sdk.Int) (rewardWeight sdk.Dec) + SetRewardWeight(ctx sdk.Context, weight sdk.Dec) +} + +// expected market keeper +type MarketKeeper interface { + GetSwapDecCoin(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) +} diff --git a/x/budget/keeper.go b/x/budget/keeper.go index 4f01913ac..5afc4456f 100644 --- a/x/budget/keeper.go +++ b/x/budget/keeper.go @@ -4,7 +4,7 @@ import ( "strconv" "strings" - "github.com/terra-project/core/x/mint" + "github.com/terra-project/core/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,20 +17,26 @@ type Keeper struct { key sdk.StoreKey // Key to our module's store valset sdk.ValidatorSet // Needed to compute voting power. - mk mint.Keeper // Needed to handle deposits. This module only requires read/writes to Terra balance + mrk MarketKeeper // Needed to handle claims. This module only requires read swap rate between SDR and LUNA + mk MintKeeper // Needed to handle deposits. This module only requires read/writes to Terra balance and read seigniorage + tk TreasuryKeeper // Needed to handle claims. This module only requires read current reward weight paramSpace params.Subspace } // NewKeeper crates a new keeper func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, - mk mint.Keeper, + mrk MarketKeeper, + mk MintKeeper, + tk TreasuryKeeper, valset sdk.ValidatorSet, paramspace params.Subspace) Keeper { return Keeper{ cdc: cdc, key: key, + mrk: mrk, mk: mk, + tk: tk, valset: valset, paramSpace: paramspace.WithKeyTable(paramKeyTable()), } @@ -103,7 +109,6 @@ func (k Keeper) IterateVotesWithPrefix(ctx sdk.Context, prefix []byte, handler f if handler(programID, voterAddr, option) { break } - } } @@ -256,3 +261,51 @@ func (k Keeper) CandQueueRemove(ctx sdk.Context, endBlock int64, programID uint6 store := ctx.KVStore(k.key) store.Delete(keyCandidate(endBlock, programID)) } + +//----------------------------------- +// Claim pool logic + +// Iterate over oracle reward claims in the store +func (k Keeper) iterateClaimPool(ctx sdk.Context, handler func(recipient sdk.AccAddress, weight sdk.Int) (stop bool)) { + store := ctx.KVStore(k.key) + iter := sdk.KVStorePrefixIterator(store, prefixClaim) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + recipientAddress := strings.Split(string(iter.Key()), ":")[1] + recipient, _ := sdk.AccAddressFromBech32(recipientAddress) + + var weight sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &weight) + if handler(recipient, weight) { + break + } + } +} + +// addClaimPool adds a claim to the the claim pool in the store +func (k Keeper) addClaimPool(ctx sdk.Context, pool types.ClaimPool) { + store := ctx.KVStore(k.key) + + for _, claim := range pool { + storeKeyClaim := keyClaim(claim.Recipient) + b := store.Get(storeKeyClaim) + weight := claim.Weight + if b != nil { + var prevWeight sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &prevWeight) + + weight = weight.Add(prevWeight) + } + b = k.cdc.MustMarshalBinaryLengthPrefixed(weight) + store.Set(storeKeyClaim, b) + } +} + +// clearClaimPool clears the claim pool from the store +func (k Keeper) clearClaimPool(ctx sdk.Context) { + store := ctx.KVStore(k.key) + k.iterateClaimPool(ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + store.Delete(keyClaim(recipient)) + return false + }) +} diff --git a/x/budget/keeper_keys.go b/x/budget/keeper_keys.go index ab6e8d71b..a4af04bed 100644 --- a/x/budget/keeper_keys.go +++ b/x/budget/keeper_keys.go @@ -15,6 +15,7 @@ var ( prefixProgram = []byte("program") prefixVote = []byte("vote") prefixCandQueue = []byte("candidate-queue") + prefixClaim = []byte("claim") paramStoreKeyParams = []byte("params") ) @@ -35,6 +36,10 @@ func keyCandidate(endBlock int64, programID uint64) []byte { return []byte(fmt.Sprintf("%s:%020d:%d", prefixCandQueue, endBlock, programID)) } +func keyClaim(recipient sdk.AccAddress) []byte { + return []byte(fmt.Sprintf("%s:%s", prefixClaim, recipient)) +} + func paramKeyTable() params.KeyTable { return params.NewKeyTable( paramStoreKeyParams, Params{}, diff --git a/x/budget/keeper_test.go b/x/budget/keeper_test.go index eb770d01a..932aab3ea 100644 --- a/x/budget/keeper_test.go +++ b/x/budget/keeper_test.go @@ -8,6 +8,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/terra-project/core/types" ) func TestKeeperProgramID(t *testing.T) { @@ -214,3 +215,30 @@ func TestKeeperCandidateQueue(t *testing.T) { require.Equal(t, numTests, counter) } + +func TestKeeperClaimPool(t *testing.T) { + input := createTestInput(t) + + // Test addClaimPool + claim := types.NewClaim(sdk.NewInt(10), addrs[0]) + claim2 := types.NewClaim(sdk.NewInt(20), addrs[1]) + claimPool := types.ClaimPool{claim, claim2} + input.budgetKeeper.addClaimPool(input.ctx, claimPool) + + claim = types.NewClaim(sdk.NewInt(15), addrs[0]) + claim2 = types.NewClaim(sdk.NewInt(30), addrs[2]) + claimPool = types.ClaimPool{claim, claim2} + input.budgetKeeper.addClaimPool(input.ctx, claimPool) + + // Test iterateClaimPool + input.budgetKeeper.iterateClaimPool(input.ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + if recipient.Equals(addrs[0]) { + require.Equal(t, sdk.NewInt(25), weight) + } else if recipient.Equals(addrs[1]) { + require.Equal(t, sdk.NewInt(20), weight) + } else if recipient.Equals(addrs[2]) { + require.Equal(t, sdk.NewInt(30), weight) + } + return false + }) +} diff --git a/x/budget/test_common.go b/x/budget/test_common.go index 7d3dc676e..2e1a9f643 100644 --- a/x/budget/test_common.go +++ b/x/budget/test_common.go @@ -5,16 +5,17 @@ import ( "github.com/stretchr/testify/require" "github.com/terra-project/core/types/assets" - "github.com/terra-project/core/types/mock" "github.com/terra-project/core/types/util" + "github.com/terra-project/core/x/market" "github.com/terra-project/core/x/mint" - - "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/terra-project/core/x/oracle" + "github.com/terra-project/core/x/treasury" "time" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -24,7 +25,9 @@ import ( 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/params" + "github.com/cosmos/cosmos-sdk/x/staking" ) var ( @@ -40,17 +43,29 @@ var ( sdk.AccAddress(pubKeys[2].Address()), } + valConsPubKeys = []crypto.PubKey{ + ed25519.GenPrivKey().PubKey(), + ed25519.GenPrivKey().PubKey(), + ed25519.GenPrivKey().PubKey(), + } + + valConsAddrs = []sdk.ConsAddress{ + sdk.ConsAddress(valConsPubKeys[0].Address()), + sdk.ConsAddress(valConsPubKeys[1].Address()), + sdk.ConsAddress(valConsPubKeys[2].Address()), + } + uSDRAmt = sdk.NewInt(1005 * assets.MicroUnit) uLunaAmt = sdk.NewInt(10 * assets.MicroUnit) ) type testInput struct { - ctx sdk.Context - cdc *codec.Codec - mintKeeper mint.Keeper - bankKeeper bank.Keeper - budgetKeeper Keeper - valset mock.MockValset + ctx sdk.Context + cdc *codec.Codec + mintKeeper mint.Keeper + bankKeeper bank.Keeper + budgetKeeper Keeper + treasuryKeeper TreasuryKeeper } func newTestCodec() *codec.Codec { @@ -72,6 +87,12 @@ func createTestInput(t *testing.T) testInput { keyMint := sdk.NewKVStoreKey(mint.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) tKeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keyTreasury := sdk.NewKVStoreKey(treasury.StoreKey) + keyMarket := sdk.NewKVStoreKey(market.StoreKey) + keyOracle := sdk.NewKVStoreKey(oracle.StoreKey) + keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keyDistr := sdk.NewKVStoreKey(distr.StoreKey) + tKeyDistr := sdk.NewTransientStoreKey(distr.TStoreKey) cdc := newTestCodec() db := dbm.NewMemDB() @@ -85,6 +106,12 @@ func createTestInput(t *testing.T) testInput { ms.MountStoreWithDB(keyMint, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tKeyStaking, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyTreasury, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyMarket, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyOracle, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyDistr, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) if err := ms.LoadLatestVersion(); err != nil { require.Nil(t, err) @@ -104,6 +131,11 @@ func createTestInput(t *testing.T) testInput { bank.DefaultCodespace, ) + feeCollectionKeeper := auth.NewFeeCollectionKeeper( + cdc, + keyFeeCollection, + ) + stakingKeeper := staking.NewKeeper( cdc, keyStaking, tKeyStaking, @@ -112,7 +144,14 @@ func createTestInput(t *testing.T) testInput { ) stakingKeeper.SetPool(ctx, staking.InitialPool()) - stakingKeeper.SetParams(ctx, staking.DefaultParams()) + stakingParams := staking.DefaultParams() + stakingParams.BondDenom = assets.MicroLunaDenom + stakingKeeper.SetParams(ctx, stakingParams) + + distrKeeper := distr.NewKeeper( + cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), + bankKeeper, &stakingKeeper, feeCollectionKeeper, distr.DefaultCodespace, + ) mintKeeper := mint.NewKeeper( cdc, @@ -122,32 +161,65 @@ func createTestInput(t *testing.T) testInput { accKeeper, ) - valset := mock.NewMockValSet() - for _, addr := range addrs { + oracleKeeper := oracle.NewKeeper( + cdc, + keyOracle, + mintKeeper, + distrKeeper, + feeCollectionKeeper, + stakingKeeper.GetValidatorSet(), + paramsKeeper.Subspace(oracle.DefaultParamspace), + ) + + marketKeeper := market.NewKeeper( + cdc, + keyMarket, + oracleKeeper, + mintKeeper, + paramsKeeper.Subspace(market.DefaultParamspace), + ) + + treasuryKeeper := treasury.NewKeeper( + cdc, + keyTreasury, + stakingKeeper.GetValidatorSet(), + mintKeeper, + marketKeeper, + paramsKeeper.Subspace(treasury.DefaultParamspace), + ) + + sh := staking.NewHandler(stakingKeeper) + for i, addr := range addrs { err := mintKeeper.Mint(ctx, addr, sdk.NewCoin(assets.MicroSDRDenom, uSDRAmt)) err2 := mintKeeper.Mint(ctx, addr, sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt)) - if err != nil { - require.Nil(t, err) - } - - if err2 != nil { - require.Nil(t, err2) - } + require.NoError(t, err) + require.NoError(t, err2) // Add validators - validator := mock.NewMockValidator(sdk.ValAddress(addr.Bytes()), uLunaAmt) - valset.Validators = append(valset.Validators, validator) + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(sdk.ValAddress(addr), valConsPubKeys[i], + sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt), staking.Description{}, commission, sdk.OneInt()) + res := sh(ctx, msg) + require.True(t, res.IsOK()) + + distrKeeper.Hooks().AfterValidatorCreated(ctx, sdk.ValAddress(addr)) + staking.EndBlocker(ctx, stakingKeeper) } budgetKeeper := NewKeeper( - cdc, keyBudget, mintKeeper, valset, + cdc, + keyBudget, + marketKeeper, + mintKeeper, + treasuryKeeper, + stakingKeeper.GetValidatorSet(), paramsKeeper.Subspace(DefaultParamspace), ) InitGenesis(ctx, budgetKeeper, DefaultGenesisState()) - return testInput{ctx, cdc, mintKeeper, bankKeeper, budgetKeeper, valset} + return testInput{ctx, cdc, mintKeeper, bankKeeper, budgetKeeper, treasuryKeeper} } func generateTestProgram(ctx sdk.Context, budgetKeeper Keeper, accounts ...sdk.AccAddress) Program { diff --git a/x/market/errors.go b/x/market/errors.go index a289fc900..2632790ea 100644 --- a/x/market/errors.go +++ b/x/market/errors.go @@ -32,7 +32,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 -func ErrExceedsDailySwapLimit(codespace sdk.CodespaceType, denom string) sdk.Error { - return sdk.NewError(codespace, CodeExceedsSwapLimit, "Exceeded the daily swap limit for the ask 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") } diff --git a/x/market/expected_keeper.go b/x/market/expected_keeper.go new file mode 100644 index 000000000..e94bf7528 --- /dev/null +++ b/x/market/expected_keeper.go @@ -0,0 +1,16 @@ +package market + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// expected oracle keeper +type OracleKeeper interface { + AddSwapFeePool(ctx sdk.Context, fees sdk.Coins) + GetLunaSwapRate(ctx sdk.Context, denom string) (price sdk.Dec, err sdk.Error) +} + +// expected mint keeper +type MintKeeper interface { + Mint(ctx sdk.Context, recipient sdk.AccAddress, coin sdk.Coin) (err sdk.Error) + Burn(ctx sdk.Context, payer sdk.AccAddress, coin sdk.Coin) (err sdk.Error) + GetIssuance(ctx sdk.Context, denom string, day sdk.Int) (issuance sdk.Int) +} diff --git a/x/market/handler.go b/x/market/handler.go index c29bc1fd7..92e4409d1 100644 --- a/x/market/handler.go +++ b/x/market/handler.go @@ -3,9 +3,6 @@ package market import ( "reflect" - "github.com/terra-project/core/types/assets" - "github.com/terra-project/core/types/util" - "github.com/terra-project/core/x/market/tags" sdk "github.com/cosmos/cosmos-sdk/types" @@ -24,49 +21,30 @@ func NewHandler(k Keeper) sdk.Handler { } } -// Returns true if the amount of total coins swapped INTO Luna during a 24 hr window -// (block approximation) exceeds 1% of coinissuance. -func exceedsDailySwapLimit(ctx sdk.Context, k Keeper, swapCoin sdk.Coin, dailyLimit sdk.Dec) bool { - curDay := ctx.BlockHeight() / util.BlocksPerDay - - // Start limits on day 2 - if curDay != 0 { - curIssuance := k.mk.GetIssuance(ctx, swapCoin.Denom, sdk.NewInt(curDay)) - prevIssuance := k.mk.GetIssuance(ctx, swapCoin.Denom, sdk.NewInt(curDay-1)) - - if curIssuance.GT(prevIssuance) { - dailyDelta := curIssuance.Sub(prevIssuance).Add(swapCoin.Amount) - - // If daily inflation is greater than 1% - if dailyDelta.MulRaw(sdk.NewDec(1).Quo(dailyLimit).TruncateInt64()).GT(curIssuance) { - return true - } - } - } - - return false -} - // handleMsgSwap handles the logic of a MsgSwap func handleMsgSwap(ctx sdk.Context, k Keeper, msg MsgSwap) sdk.Result { - params := k.GetParams(ctx) - // Can't swap to the same coin if msg.OfferCoin.Denom == msg.AskDenom { return ErrRecursiveSwap(DefaultCodespace, msg.AskDenom).Result() } // Compute exchange rates between the ask and offer - swapCoin, swapErr := k.GetSwapCoins(ctx, msg.OfferCoin, msg.AskDenom) + swapCoin, spread, swapErr := k.GetSwapCoin(ctx, msg.OfferCoin, msg.AskDenom, false) if swapErr != nil { return swapErr.Result() } - // We've passed the daily swap limit for Luna. Fail. - // TODO: add safety checks for Terra as well. - if msg.OfferCoin.Denom == assets.MicroLunaDenom && exceedsDailySwapLimit(ctx, k, swapCoin, params.DailySwapLimit) { - return ErrExceedsDailySwapLimit(DefaultCodespace, swapCoin.Denom).Result() + // Charge a spread if applicable; distributed to vote winners in the oracle module + swapFee := sdk.Coin{} + if spread.IsPositive() { + swapFeeAmt := spread.MulInt(swapCoin.Amount).TruncateInt() + if swapFeeAmt.IsPositive() { + swapFee = sdk.NewCoin(swapCoin.Denom, swapFeeAmt) + k.ok.AddSwapFeePool(ctx, sdk.NewCoins(swapFee)) + + swapCoin = swapCoin.Sub(swapFee) + } } // Burn offered coins and subtract from the trader's account @@ -75,11 +53,6 @@ func handleMsgSwap(ctx sdk.Context, k Keeper, msg MsgSwap) sdk.Result { return burnErr.Result() } - // Record seigniorage if the offered coin is Luna - if msg.OfferCoin.Denom == assets.MicroLunaDenom { - k.mk.AddSeigniorage(ctx, msg.OfferCoin.Amount) - } - // Mint asked coins and credit Trader's account mintErr := k.mk.Mint(ctx, msg.Trader, swapCoin) if mintErr != nil { @@ -87,12 +60,12 @@ func handleMsgSwap(ctx sdk.Context, k Keeper, msg MsgSwap) sdk.Result { } log := NewLog() - log.append(LogKeySwapCoin, swapCoin.String()) + log = log.append(LogKeySwapCoin, swapCoin.String()) + log = log.append(LogKeySwapFee, swapFee.String()) return sdk.Result{ Tags: sdk.NewTags( tags.Offer, msg.OfferCoin.Denom, - tags.Ask, swapCoin.String(), tags.Trader, msg.Trader.String(), ), Log: log.String(), diff --git a/x/market/handler_test.go b/x/market/handler_test.go index 9df5ec578..5fe15a7d5 100644 --- a/x/market/handler_test.go +++ b/x/market/handler_test.go @@ -6,7 +6,6 @@ import ( "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" sdk "github.com/cosmos/cosmos-sdk/types" @@ -98,7 +97,7 @@ func TestHandlerExceedDailySwapLimit(t *testing.T) { input := createTestInput(t) handler := NewHandler(input.marketKeeper) - offerCoin := sdk.NewInt64Coin(assets.MicroSDRDenom, 100) + offerCoin := sdk.NewInt64Coin(assets.MicroSDRDenom, 1000) // Set oracle price offerLunaPrice := sdk.NewDec(1) @@ -111,14 +110,13 @@ func TestHandlerExceedDailySwapLimit(t *testing.T) { // Day 1+ ... Set luna issuance, try to oscillate within the limit, and things should be ok input.ctx = input.ctx.WithBlockHeight(util.BlocksPerWeek) - err := input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewInt64Coin(assets.MicroLunaDenom, 1000000)) - assert.Nil(t, err) + offerCoin = sdk.NewCoin(offerCoin.Denom, sdk.NewInt(4)) msg = NewMsgSwap(addrs[0], offerCoin, assets.MicroLunaDenom) res = handler(input.ctx, msg) require.True(t, res.IsOK()) // Day 1+ ... Outside of the limit fails - msg = NewMsgSwap(addrs[0], sdk.NewInt64Coin(assets.MicroLunaDenom, 10005), assets.MicroLunaDenom) + msg = NewMsgSwap(addrs[0], sdk.NewInt64Coin(assets.MicroLunaDenom, 6), assets.MicroLunaDenom) res = handler(input.ctx, msg) require.False(t, res.IsOK()) diff --git a/x/market/keeper.go b/x/market/keeper.go index 2dc531cfa..f568b2e62 100644 --- a/x/market/keeper.go +++ b/x/market/keeper.go @@ -1,56 +1,109 @@ package market import ( - "github.com/terra-project/core/x/mint" - "github.com/terra-project/core/x/oracle" + "github.com/terra-project/core/types/assets" + "github.com/terra-project/core/types/util" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" ) // Keeper holds data structures for the market module type Keeper struct { - ok oracle.Keeper - mk mint.Keeper + cdc *codec.Codec // Codec to encore/decode structs + key sdk.StoreKey // Key to our module's store + ok OracleKeeper + mk MintKeeper paramSpace params.Subspace } // NewKeeper creates a new Keeper for the market module -func NewKeeper(ok oracle.Keeper, mk mint.Keeper, paramspace params.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ok OracleKeeper, mk MintKeeper, paramspace params.Subspace) Keeper { return Keeper{ + cdc: cdc, + key: key, ok: ok, mk: mk, paramSpace: paramspace.WithKeyTable(paramKeyTable()), } } -// GetSwapCoins returns the amount of asked coins should be returned for a given offerCoin at the effective +// ComputeLunaDelta returns the issuance rate change of Luna for the day post-swap +func (k Keeper) ComputeLunaDelta(ctx sdk.Context, change sdk.Int) sdk.Dec { + curDay := ctx.BlockHeight() / util.BlocksPerDay + + // Start limits on day 2 + if curDay == 0 { + return sdk.ZeroDec() + } + + prevIssuance := k.mk.GetIssuance(ctx, assets.MicroLunaDenom, sdk.NewInt(curDay-1)) + if !prevIssuance.IsZero() { + curIssuance := k.mk.GetIssuance(ctx, assets.MicroLunaDenom, sdk.NewInt(curDay)) + postSwapIssuance := curIssuance.Add(change) + + return sdk.NewDecFromInt(postSwapIssuance.Sub(prevIssuance)).QuoInt(prevIssuance) + } + + return sdk.ZeroDec() +} + +// 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. -func (k Keeper) GetSwapCoins(ctx sdk.Context, offerCoin sdk.Coin, askDenom string) (sdk.Coin, sdk.Error) { +// 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) { + params := k.GetParams(ctx) + offerRate, err := k.ok.GetLunaSwapRate(ctx, offerCoin.Denom) if err != nil { - return sdk.Coin{}, ErrNoEffectivePrice(DefaultCodespace, offerCoin.Denom) + return sdk.Coin{}, sdk.ZeroDec(), ErrNoEffectivePrice(DefaultCodespace, offerCoin.Denom) } askRate, err := k.ok.GetLunaSwapRate(ctx, askDenom) if err != nil { - return sdk.Coin{}, ErrNoEffectivePrice(DefaultCodespace, askDenom) + return sdk.Coin{}, sdk.ZeroDec(), ErrNoEffectivePrice(DefaultCodespace, askDenom) } retAmount := sdk.NewDecFromInt(offerCoin.Amount).Mul(askRate).Quo(offerRate).TruncateInt() if retAmount.Equal(sdk.ZeroInt()) { - return sdk.Coin{}, ErrInsufficientSwapCoins(DefaultCodespace, offerCoin.Amount) + return sdk.Coin{}, sdk.ZeroDec(), ErrInsufficientSwapCoins(DefaultCodespace, offerCoin.Amount) } - return sdk.NewCoin(askDenom, retAmount), nil + // We only charge spread for NON-INTERNAL swaps involving luna; if not, just pass. + if isInternal || (offerCoin.Denom != assets.MicroLunaDenom && askDenom != assets.MicroLunaDenom) { + return sdk.NewCoin(askDenom, retAmount), sdk.ZeroDec(), nil + } + + dailyDelta := sdk.ZeroDec() + if offerCoin.Denom == assets.MicroLunaDenom { + dailyDelta = k.ComputeLunaDelta(ctx, offerCoin.Amount.Neg()) + } else if askDenom == assets.MicroLunaDenom { + dailyDelta = k.ComputeLunaDelta(ctx, retAmount) + } + + // delta should be positive to apply spread + dailyDelta = dailyDelta.Abs() + + // Do not allow swaps beyond the daily cap + maxDelta := params.DailyLunaDeltaCap + if dailyDelta.GT(maxDelta) { + return sdk.Coin{}, sdk.ZeroDec(), ErrExceedsDailySwapLimit(DefaultCodespace) + } + + // Compute a spread, which is initialiy MinSwapSpread and grows linearly to MaxSwapSpread with delta + spread = params.MinSwapSpread.Add(dailyDelta.Quo(maxDelta).Mul(params.MaxSwapSpread.Sub(params.MinSwapSpread))) + + return sdk.NewCoin(askDenom, retAmount), spread, nil } -// GetSwapDecCoins returns the amount of asked DecCoins should be returned for a given offerCoin at the effective +// GetSwapDecCoin returns the amount of asked DecCoins should be returned for a given offerCoin at the effective // exchange rate registered with the oracle. -// Similar to GetSwapCoins, but operates over sdk.DecCoins for convinience and accuracy. -func (k Keeper) GetSwapDecCoins(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) { +// 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.ok.GetLunaSwapRate(ctx, offerCoin.Denom) if err != nil { return sdk.DecCoin{}, ErrNoEffectivePrice(DefaultCodespace, offerCoin.Denom) diff --git a/x/market/keeper_test.go b/x/market/keeper_test.go index d2996b48c..17f6ec477 100644 --- a/x/market/keeper_test.go +++ b/x/market/keeper_test.go @@ -1,15 +1,17 @@ package market import ( + "math" "testing" "github.com/terra-project/core/types/assets" + "github.com/terra-project/core/types/util" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) -func TestKeeperSwapCoins(t *testing.T) { +func TestKeeperSwapCoinsBasic(t *testing.T) { input := createTestInput(t) @@ -21,12 +23,55 @@ func TestKeeperSwapCoins(t *testing.T) { input.oracleKeeper.SetLunaSwapRate(input.ctx, offerCoin.Denom, lnasdrRate) input.oracleKeeper.SetLunaSwapRate(input.ctx, askCoin.Denom, lnacnyRate) - retCoin, err := input.marketKeeper.GetSwapCoins(input.ctx, offerCoin, askCoin.Denom) + retCoin, spread, err := input.marketKeeper.GetSwapCoin(input.ctx, offerCoin, askCoin.Denom, false) require.Nil(t, err) + require.Zero(t, spread.TruncateInt64(), "Spread should be 0 for non luna swaps") require.Equal(t, retCoin, askCoin) } +func TestKeeperSwapCoinsLunaCap(t *testing.T) { + + input := createTestInput(t) + + // Set params + params := DefaultParams() + input.marketKeeper.SetParams(input.ctx, params) + + baseAmount := sdk.NewInt(int64(math.Pow10(9))) + // Set day to 2 and issuance as the same as the day before + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, baseAmount)) + input.ctx = input.ctx.WithBlockHeight(util.BlocksPerDay + 1) + + // Set exchange rate. Keep it at 1:1 for simplicity + lnasdrRate := sdk.NewDec(1) + input.oracleKeeper.SetLunaSwapRate(input.ctx, assets.MicroSDRDenom, lnasdrRate) + + maxDelta := params.DailyLunaDeltaCap.MulInt(baseAmount).TruncateInt() + + // Check cap luna -> sdr swap, at the cap. Should succeed + offerCoin := sdk.NewCoin(assets.MicroLunaDenom, maxDelta) + _, spread, err := input.marketKeeper.GetSwapCoin(input.ctx, offerCoin, assets.MicroSDRDenom, false) + require.Nil(t, err) + require.Equal(t, params.MaxSwapSpread, spread) + + // Check cap luna -> sdr swap, 1 coin higher than the cap. Should fail + offerCoin = sdk.NewCoin(assets.MicroLunaDenom, maxDelta.Add(sdk.OneInt())) + _, _, err = input.marketKeeper.GetSwapCoin(input.ctx, offerCoin, assets.MicroSDRDenom, false) + require.NotNil(t, err) + + // Check cap sdr -> luna swap,at the cap. Should succeed + offerCoin = sdk.NewCoin(assets.MicroSDRDenom, maxDelta) + _, spread, err = input.marketKeeper.GetSwapCoin(input.ctx, offerCoin, assets.MicroLunaDenom, false) + require.Nil(t, err) + require.Equal(t, params.MaxSwapSpread, spread) + + // Check cap sdr -> luna swap, 1 coin higher than the cap. Should fail + offerCoin = sdk.NewCoin(assets.MicroSDRDenom, maxDelta.Add(sdk.OneInt())) + _, _, err = input.marketKeeper.GetSwapCoin(input.ctx, offerCoin, assets.MicroLunaDenom, false) + require.NotNil(t, err) +} + func TestKeeperSwapDecCoins(t *testing.T) { input := createTestInput(t) @@ -38,7 +83,7 @@ func TestKeeperSwapDecCoins(t *testing.T) { input.oracleKeeper.SetLunaSwapRate(input.ctx, offerCoin.Denom, lnasdrRate) input.oracleKeeper.SetLunaSwapRate(input.ctx, askCoin.Denom, lnacnyRate) - retCoin, err := input.marketKeeper.GetSwapDecCoins(input.ctx, offerCoin, askCoin.Denom) + retCoin, err := input.marketKeeper.GetSwapDecCoin(input.ctx, offerCoin, askCoin.Denom) require.Nil(t, err) require.Equal(t, retCoin, askCoin) diff --git a/x/market/log.go b/x/market/log.go index 381eeceae..778a68839 100644 --- a/x/market/log.go +++ b/x/market/log.go @@ -5,8 +5,10 @@ import ( ) const ( - // LogKeyTax is to record treasury tax for a pay msg + // LogKeySwapCoin is the amount of swapped coin LogKeySwapCoin = string("swap_coin") + // LogKeySwapFee is the fee for swap operation + LogKeySwapFee = string("swap_fee") ) // Log is map type object to organize msg result diff --git a/x/market/params.go b/x/market/params.go index 7a41e37f4..9b27ab195 100644 --- a/x/market/params.go +++ b/x/market/params.go @@ -8,26 +8,38 @@ import ( // Params market parameters type Params struct { - DailySwapLimit sdk.Dec `json:"daily_swap_limit"` // daily % inflation cap on a currency from swaps + DailyLunaDeltaCap sdk.Dec `json:"daily_luna_delta_limit"` // daily % inflation or deflation cap on Luna + MinSwapSpread sdk.Dec `json:"min_swap_spread"` // minimum spread for swaps involving Luna + MaxSwapSpread sdk.Dec `json:"max_swap_spread"` // maximum spread for swaps involving Luna } // NewParams creates a new param instance -func NewParams(dailySwapLimit sdk.Dec) Params { +func NewParams(dailyLunaDeltaCap, minSwapSpread, maxSwapSpread sdk.Dec) Params { return Params{ - DailySwapLimit: dailySwapLimit, + DailyLunaDeltaCap: dailyLunaDeltaCap, + MinSwapSpread: minSwapSpread, + MaxSwapSpread: maxSwapSpread, } } // DefaultParams creates default market module parameters func DefaultParams() Params { return NewParams( - sdk.NewDecWithPrec(1, 2), // 1% + sdk.NewDecWithPrec(5, 3), // 0.5% + sdk.NewDecWithPrec(2, 2), // 2% + sdk.NewDecWithPrec(10, 2), // 10% ) } func validateParams(params Params) error { - if params.DailySwapLimit.IsNegative() { - return fmt.Errorf("market daily swap limit should be non-negative, is %s", params.DailySwapLimit.String()) + if params.DailyLunaDeltaCap.IsNegative() { + return fmt.Errorf("market daily luna issuance change should be non-negative, is %s", params.DailyLunaDeltaCap.String()) + } + if params.MinSwapSpread.IsNegative() { + return fmt.Errorf("market minimum swap spead should be non-negative, is %s", params.MinSwapSpread.String()) + } + if params.MaxSwapSpread.LT(params.MinSwapSpread) { + return fmt.Errorf("market maximum swap spead should be larger or equal to the minimum, is %s", params.MaxSwapSpread.String()) } return nil @@ -35,6 +47,8 @@ func validateParams(params Params) error { func (params Params) String() string { return fmt.Sprintf(`market Params: - DailySwapLimit: %v - `, params.DailySwapLimit) + DailyLunaDeltaCap: %v, + MinSwapSpread: %v, + MaxSwapSpread: %v + `, params.DailyLunaDeltaCap, params.MinSwapSpread, params.MaxSwapSpread) } diff --git a/x/market/spread.go b/x/market/spread.go new file mode 100644 index 000000000..0274b033b --- /dev/null +++ b/x/market/spread.go @@ -0,0 +1 @@ +package market diff --git a/x/market/tags/tags.go b/x/market/tags/tags.go index 456575a74..9ae267a50 100644 --- a/x/market/tags/tags.go +++ b/x/market/tags/tags.go @@ -3,6 +3,5 @@ package tags // Market tags var ( Offer = "offer" - Ask = "ask" Trader = "trader" ) diff --git a/x/market/test_common.go b/x/market/test_common.go index adf24c495..42293653d 100644 --- a/x/market/test_common.go +++ b/x/market/test_common.go @@ -19,6 +19,7 @@ import ( 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/params" "github.com/cosmos/cosmos-sdk/x/staking" ) @@ -62,6 +63,9 @@ func createTestInput(t *testing.T) testInput { keyMint := sdk.NewKVStoreKey(mint.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) tKeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) + keyDistr := sdk.NewKVStoreKey(distr.StoreKey) + tKeyDistr := sdk.NewTransientStoreKey(distr.TStoreKey) + keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) cdc := newTestCodec() db := dbm.NewMemDB() @@ -76,6 +80,9 @@ func createTestInput(t *testing.T) testInput { ms.MountStoreWithDB(keyMint, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tKeyStaking, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyDistr, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) require.NoError(t, ms.LoadLatestVersion()) @@ -100,15 +107,19 @@ func createTestInput(t *testing.T) testInput { staking.DefaultCodespace, ) - stakingKeeper.SetPool(ctx, staking.InitialPool()) - stakingKeeper.SetParams(ctx, staking.DefaultParams()) + feeCollectionKeeper := auth.NewFeeCollectionKeeper( + cdc, + keyFeeCollection, + ) - var valset sdk.ValidatorSet - oracleKeeper := oracle.NewKeeper( - cdc, keyOracle, valset, - paramsKeeper.Subspace(oracle.DefaultParamspace), + distrKeeper := distr.NewKeeper( + cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), + bankKeeper, &stakingKeeper, feeCollectionKeeper, distr.DefaultCodespace, ) + stakingKeeper.SetPool(ctx, staking.InitialPool()) + stakingKeeper.SetParams(ctx, staking.DefaultParams()) + mintKeeper := mint.NewKeeper( cdc, keyMint, @@ -117,14 +128,28 @@ func createTestInput(t *testing.T) testInput { accKeeper, ) + oracleKeeper := oracle.NewKeeper( + cdc, + keyOracle, + mintKeeper, + distrKeeper, + feeCollectionKeeper, + stakingKeeper.GetValidatorSet(), + paramsKeeper.Subspace(oracle.DefaultParamspace), + ) + marketKeeper := NewKeeper( - oracleKeeper, mintKeeper, paramsKeeper.Subspace(DefaultParamspace), + cdc, + keyMarket, + oracleKeeper, + mintKeeper, + paramsKeeper.Subspace(DefaultParamspace), ) marketKeeper.SetParams(ctx, DefaultParams()) for _, addr := range addrs { - _, _, err := bankKeeper.AddCoins(ctx, addr, sdk.Coins{sdk.NewCoin(assets.MicroSDRDenom, uSDRAmt)}) + err := mintKeeper.Mint(ctx, addr, sdk.NewCoin(assets.MicroSDRDenom, uSDRAmt)) require.NoError(t, err) } diff --git a/x/mint/keeper.go b/x/mint/keeper.go index 5796a61d5..52adf3ae3 100644 --- a/x/mint/keeper.go +++ b/x/mint/keeper.go @@ -39,12 +39,9 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, sk staking.Keeper, bk bank.Ke // Mint credits {coin} to the {recipient} account, and reflects the increase in issuance func (k Keeper) Mint(ctx sdk.Context, recipient sdk.AccAddress, coin sdk.Coin) (err sdk.Error) { - // recipient could be empty in case minting to validator outstanding rewards - if !recipient.Empty() { - _, _, err = k.bk.AddCoins(ctx, recipient, sdk.Coins{coin}) - if err != nil { - return err - } + _, _, err = k.bk.AddCoins(ctx, recipient, sdk.Coins{coin}) + if err != nil { + return err } if coin.Denom == assets.MicroLunaDenom { @@ -53,7 +50,7 @@ func (k Keeper) Mint(ctx sdk.Context, recipient sdk.AccAddress, coin sdk.Coin) ( k.sk.SetPool(ctx, pool) } - return k.changeIssuance(ctx, coin.Denom, coin.Amount) + return k.ChangeIssuance(ctx, coin.Denom, coin.Amount) } // Burn deducts {coin} from the {payer} account, and reflects the decrease in issuance @@ -69,27 +66,28 @@ func (k Keeper) Burn(ctx sdk.Context, payer sdk.AccAddress, coin sdk.Coin) (err k.sk.SetPool(ctx, pool) } - return k.changeIssuance(ctx, coin.Denom, coin.Amount.Neg()) + return k.ChangeIssuance(ctx, coin.Denom, coin.Amount.Neg()) } // ChangeIssuance updates the issuance to reflect -func (k Keeper) changeIssuance(ctx sdk.Context, denom string, delta sdk.Int) (err sdk.Error) { +func (k Keeper) ChangeIssuance(ctx sdk.Context, denom string, delta sdk.Int) (err sdk.Error) { store := ctx.KVStore(k.key) - curEpoch := util.GetEpoch(ctx) - - issuanceOnDisk := store.Has(keyIssuance(denom, curEpoch)) - curIssuance := k.GetIssuance(ctx, denom, curEpoch) + curDay := sdk.NewInt(ctx.BlockHeight() / util.BlocksPerDay) - // If the issuance is not on disk, GetIssuance will do a fresh read of account balances + // If genesis issuance is not on disk, GetIssuance will do a fresh read of account balances // and the change in issuance should be reported automatically. - if issuanceOnDisk { - newIssuance := curIssuance.Add(delta) - if newIssuance.IsNegative() { - err = sdk.ErrInternal("Issuance should never fall below 0") - } else { - bz := k.cdc.MustMarshalBinaryLengthPrefixed(newIssuance) - store.Set(keyIssuance(denom, curEpoch), bz) - } + if !store.Has(keyIssuance(denom, sdk.ZeroInt())) { + return + } + + curIssuance := k.GetIssuance(ctx, denom, curDay) + newIssuance := curIssuance.Add(delta) + + if newIssuance.IsNegative() { + err = sdk.ErrInternal("Issuance should never fall below 0") + } else { + bz := k.cdc.MustMarshalBinaryLengthPrefixed(newIssuance) + store.Set(keyIssuance(denom, curDay), bz) } return @@ -103,54 +101,49 @@ func (k Keeper) GetIssuance(ctx sdk.Context, denom string, day sdk.Int) (issuanc if bz := store.Get(keyIssuance(denom, day)); bz != nil { k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &issuance) - return - } - - for d := day; d.GTE(sdk.ZeroInt()); d = d.Sub(sdk.OneInt()) { - - if bz := store.Get(keyIssuance(denom, d)); bz != nil { - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &issuance) - break - } else if d.LTE(sdk.ZeroInt()) { - // Genesis epoch; nothing exists in store so we must read it - // from accountkeeper + } else { + // Genesis epoch; nothing exists in store so we must read it + // from accountkeeper + if day.LTE(sdk.ZeroInt()) { issuance = sdk.ZeroInt() countIssuance := func(acc auth.Account) (stop bool) { issuance = issuance.Add(acc.GetCoins().AmountOf(denom)) return false } - k.ak.IterateAccounts(ctx, countIssuance) - break + } else { + // Fetch the issuance snapshot of the previous epoch + issuance = k.GetIssuance(ctx, denom, day.Sub(sdk.OneInt())) } - } - // Set issuance to the store - bz := k.cdc.MustMarshalBinaryLengthPrefixed(issuance) - store.Set(keyIssuance(denom, day), bz) + // Set issuance to the store + store := ctx.KVStore(k.key) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(issuance) + store.Set(keyIssuance(denom, day), bz) + } return } -// AddSeigniorage adds seigniorage to the current epochal seigniorage pool -func (k Keeper) AddSeigniorage(ctx sdk.Context, seigniorage sdk.Int) { - curEpoch := util.GetEpoch(ctx) - seignioragePool := k.PeekSeignioragePool(ctx, curEpoch) - seignioragePool = seignioragePool.Add(seigniorage) +// PeekEpochSeigniorage retrieves the size of the seigniorage pool at epoch +func (k Keeper) PeekEpochSeigniorage(ctx sdk.Context, epoch sdk.Int) (epochSeigniorage sdk.Int) { - store := ctx.KVStore(k.key) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(seignioragePool) - store.Set(keySeignioragePool(curEpoch), bz) -} + daysPerEpoch := util.BlocksPerEpoch / util.BlocksPerDay + epochLastDay := epoch.Add(sdk.OneInt()).MulRaw(daysPerEpoch).Sub(sdk.OneInt()) -// PeekSeignioragePool retrieves the size of the seigniorage pool at epoch -func (k Keeper) PeekSeignioragePool(ctx sdk.Context, epoch sdk.Int) (seignioragePool sdk.Int) { - store := ctx.KVStore(k.key) - b := store.Get(keySeignioragePool(epoch)) - if b == nil { - seignioragePool = sdk.ZeroInt() - } else { - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &seignioragePool) + //fmt.Println(epochLastDay) + today := sdk.NewInt(ctx.BlockHeight() / util.BlocksPerDay) + if epochLastDay.GT(today) { + epochLastDay = today } + + prevEpochLastDay := epochLastDay.SubRaw(daysPerEpoch) + if prevEpochLastDay.IsNegative() { + prevEpochLastDay = sdk.ZeroInt() + } + + prevEpochIssuance := k.GetIssuance(ctx, assets.MicroLunaDenom, prevEpochLastDay) + epochIssuance := k.GetIssuance(ctx, assets.MicroLunaDenom, epochLastDay) + epochSeigniorage = epochIssuance.Sub(prevEpochIssuance) return } diff --git a/x/mint/keeper_test.go b/x/mint/keeper_test.go index a9231db44..0f4e7ba95 100644 --- a/x/mint/keeper_test.go +++ b/x/mint/keeper_test.go @@ -1,6 +1,7 @@ package mint import ( + "math/rand" "testing" "time" @@ -122,19 +123,19 @@ func TestKeeperIssuance(t *testing.T) { require.Equal(t, uSDRAmount.MulRaw(3), issuance) // Lowering issuance works - err := input.mintKeeper.changeIssuance(input.ctx, assets.MicroSDRDenom, sdk.OneInt().MulRaw(assets.MicroUnit).Neg()) + err := input.mintKeeper.ChangeIssuance(input.ctx, assets.MicroSDRDenom, sdk.OneInt().MulRaw(assets.MicroUnit).Neg()) require.Nil(t, err) issuance = input.mintKeeper.GetIssuance(input.ctx, assets.MicroSDRDenom, curDay) require.Equal(t, uSDRAmount.MulRaw(3).Sub(sdk.OneInt().MulRaw(assets.MicroUnit)), issuance) // ... but not too much - err = input.mintKeeper.changeIssuance(input.ctx, assets.MicroSDRDenom, sdk.NewInt(5000).MulRaw(assets.MicroUnit).Neg()) + err = input.mintKeeper.ChangeIssuance(input.ctx, assets.MicroSDRDenom, sdk.NewInt(5000).MulRaw(assets.MicroUnit).Neg()) require.NotNil(t, err) issuance = input.mintKeeper.GetIssuance(input.ctx, assets.MicroSDRDenom, curDay) require.Equal(t, uSDRAmount.MulRaw(3).Sub(sdk.OneInt().MulRaw(assets.MicroUnit)), issuance) // Raising issuance works, too - err = input.mintKeeper.changeIssuance(input.ctx, assets.MicroSDRDenom, sdk.NewInt(986).MulRaw(assets.MicroUnit)) + err = input.mintKeeper.ChangeIssuance(input.ctx, assets.MicroSDRDenom, sdk.NewInt(986).MulRaw(assets.MicroUnit)) require.Nil(t, err) issuance = input.mintKeeper.GetIssuance(input.ctx, assets.MicroSDRDenom, curDay) require.Equal(t, sdk.NewInt(4000).MulRaw(assets.MicroUnit), issuance) @@ -180,14 +181,63 @@ func TestKeeperMintBurn(t *testing.T) { func TestKeeperSeigniorage(t *testing.T) { input := createTestInput(t) - for e := 0; e < 3; e++ { - input.ctx = input.ctx.WithBlockHeight(util.BlocksPerEpoch * int64(e)) - for i := 0; i < 100; i++ { - input.mintKeeper.AddSeigniorage(input.ctx, sdk.NewInt(int64(10*(e+1)))) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(100))) + input.mintKeeper.PeekEpochSeigniorage(input.ctx, sdk.NewInt(0)) + + input.mintKeeper.Mint(input.ctx.WithBlockHeight(util.BlocksPerEpoch-1), addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(100))) + seigniorage := input.mintKeeper.PeekEpochSeigniorage(input.ctx.WithBlockHeight(util.BlocksPerEpoch), sdk.NewInt(0)) + + require.Equal(t, sdk.NewInt(100), seigniorage) +} + +func TestKeeperMintStress(t *testing.T) { + input := createTestInput(t) + rand.Seed(int64(time.Now().Nanosecond())) + + balance := int64(20000) + epochDelta := int64(0) + + // Genesis mint + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(balance))) + + for day := int64(0); day < 100; day++ { + input.ctx = input.ctx.WithBlockHeight(day * util.BlocksPerDay) + amt := rand.Int63()%100 + 1 // Cap at 100; prevents possibility of balance falling negative + option := rand.Int63() % 3 + + switch option { + case 0: // mint + err := input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(amt))) + require.Nil(t, err) + + balance += amt + epochDelta += amt + break + case 1: // burn + err := input.mintKeeper.Burn(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(amt))) + require.Nil(t, err) + + balance -= amt + epochDelta -= amt + break + case 2: // skip + amt = 0 + break } - } - require.Equal(t, sdk.NewInt(1000), input.mintKeeper.PeekSeignioragePool(input.ctx, sdk.NewInt(0))) - require.Equal(t, sdk.NewInt(2000), input.mintKeeper.PeekSeignioragePool(input.ctx, sdk.NewInt(1))) - require.Equal(t, sdk.NewInt(3000), input.mintKeeper.PeekSeignioragePool(input.ctx, sdk.NewInt(2))) + // Ignore first update; just how seigniorage recording works + if day == 0 { + epochDelta = 0 + } + + issuance := input.mintKeeper.GetIssuance(input.ctx, assets.MicroLunaDenom, sdk.NewInt(day)) + require.Equal(t, sdk.NewInt(balance), issuance) + + // last day of epoch + if (day+1)*util.BlocksPerDay%util.BlocksPerEpoch == 0 { + seigniorage := input.mintKeeper.PeekEpochSeigniorage(input.ctx, sdk.NewInt(day)) + require.Equal(t, sdk.NewInt(epochDelta), seigniorage) + epochDelta = 0 + } + } } diff --git a/x/oracle/end_blocker.go b/x/oracle/end_blocker.go index bcfc42604..5fd9a67d0 100644 --- a/x/oracle/end_blocker.go +++ b/x/oracle/end_blocker.go @@ -10,15 +10,78 @@ import ( "github.com/terra-project/core/x/oracle/tags" ) -// Calculates the median and returns the set of voters to be rewarded, i.e. voted within -// a reasonable spread from the weighted median. -func tally(ctx sdk.Context, k Keeper, pb PriceBallot) (weightedMedian sdk.Dec, ballotWinners types.ClaimPool) { +// At the end of every VotePeriod, we give out all the market swap fees collected to the +// oracle voters that voted faithfully. +func rewardPrevBallotWinners(ctx sdk.Context, k Keeper) { + // Sum weight of the claimpool + prevBallotWeightSum := sdk.ZeroInt() + k.iterateClaimPool(ctx, func(_ sdk.AccAddress, weight sdk.Int) (stop bool) { + prevBallotWeightSum = prevBallotWeightSum.Add(weight) + return false + }) + + if !prevBallotWeightSum.IsZero() { + + accmFeePool := k.GetSwapFeePool(ctx) + if !accmFeePool.Empty() { + + // Dole out rewards + var distributedFee sdk.Coins + k.iterateClaimPool(ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + + rewardCoins := sdk.NewCoins() + rewardeeVal := k.valset.Validator(ctx, sdk.ValAddress(recipient)) + for _, feeCoin := range accmFeePool { + rewardAmt := sdk.NewDecCoinFromCoin(feeCoin).Amount.QuoInt(prevBallotWeightSum).MulInt(weight).TruncateInt() + rewardCoins = rewardCoins.Add(sdk.NewCoins(sdk.NewCoin(feeCoin.Denom, rewardAmt))) + } + + // In case absence of the validator, we collect the rewards to fee collect keeper + if rewardeeVal != nil { + k.dk.AllocateTokensToValidator(ctx, rewardeeVal, sdk.NewDecCoins(rewardCoins)) + } else { + k.fck.AddCollectedFees(ctx, rewardCoins) + } + + distributedFee = distributedFee.Add(rewardCoins) + + return false + }) + + // move left fees to fee collect keeper + leftFee := accmFeePool.Sub(distributedFee) + if !leftFee.Empty() && leftFee.IsValid() { + k.fck.AddCollectedFees(ctx, leftFee) + } + + // Change Issuerance + for _, feeCoin := range accmFeePool { + + // never return err, but handle err for lint + err := k.mk.ChangeIssuance(ctx, feeCoin.Denom, feeCoin.Amount) + if err != nil { + panic(err) + } + } + + // Clear swap fee pool + k.clearSwapFeePool(ctx) + } + + // Clear claim and fee pool + k.clearClaimPool(ctx) + } +} + +// Calculates the median and returns it. Sets the set of voters to be rewarded, i.e. voted within +// a reasonable spread from the weighted median to the store +func tally(ctx sdk.Context, k Keeper, pb PriceBallot) sdk.Dec { if !sort.IsSorted(pb) { sort.Sort(pb) } - ballotWinners = types.ClaimPool{} - weightedMedian = pb.weightedMedian(ctx, k.valset) + ballotWinners := types.ClaimPool{} + weightedMedian := pb.weightedMedian(ctx, k.valset) rewardSpread := k.GetParams(ctx).OracleRewardBand.QuoInt64(2) for _, vote := range pb { @@ -29,34 +92,15 @@ func tally(ctx sdk.Context, k Keeper, pb PriceBallot) (weightedMedian sdk.Dec, b ballotWinners = append(ballotWinners, types.Claim{ Recipient: sdk.AccAddress(vote.Voter), Weight: bondSize, - Class: types.OracleClaimClass, }) } } } - return -} - -// Drop the ballot. If the ballot drops params.DropThreshold times sequentially, then blacklist -func dropBallot(ctx sdk.Context, k Keeper, denom string, params Params) sdk.Tags { - actionTag := tags.ActionTallyDropped - - // Not enough votes received - dropCounter := k.incrementDropCounter(ctx, denom) - if dropCounter.GTE(params.DropThreshold) { + // add claim winners to the store + k.addClaimPool(ctx, ballotWinners) - // Too many drops, blacklist currency - k.deletePrice(ctx, denom) - k.resetDropCounter(ctx, denom) - - actionTag = tags.ActionBlacklist - } - - return sdk.NewTags( - tags.Action, actionTag, - tags.Denom, denom, - ) + return weightedMedian } // ballot for the asset is passing the threshold amount of voting power @@ -66,7 +110,7 @@ func ballotIsPassing(totalBondedTokens sdk.Int, voteThreshold sdk.Dec, ballotPow } // EndBlocker is called at the end of every block -func EndBlocker(ctx sdk.Context, k Keeper) (rewardees types.ClaimPool, resTags sdk.Tags) { +func EndBlocker(ctx sdk.Context, k Keeper) (resTags sdk.Tags) { params := k.GetParams(ctx) // Not yet time for a tally @@ -74,18 +118,17 @@ func EndBlocker(ctx sdk.Context, k Keeper) (rewardees types.ClaimPool, resTags s return } + // Reward previous ballot winners + rewardPrevBallotWinners(ctx, k) + actives := k.getActiveDenoms(ctx) votes := k.collectVotes(ctx) - // Iterate through active oracle assets and drop assets that have no votes received. + // Clear swap rates for _, activeDenom := range actives { - if _, found := votes[activeDenom]; !found { - dropTags := dropBallot(ctx, k, activeDenom, params) - resTags = resTags.AppendTags(dropTags) - } + k.deletePrice(ctx, activeDenom) } - rewardees = types.ClaimPool{} totalBondedTokens := k.valset.TotalBondedTokens(ctx) // Iterate through votes and update prices; drop if not enough votes have been achieved. @@ -93,38 +136,27 @@ func EndBlocker(ctx sdk.Context, k Keeper) (rewardees types.ClaimPool, resTags s if ballotIsPassing(totalBondedTokens, params.VoteThreshold, filteredVotes.power(ctx, k.valset)) { // Get weighted median prices, and faithful respondants - mod, ballotWinners := tally(ctx, k, filteredVotes) - - // Append ballot winners for the denom - rewardees = append(rewardees, ballotWinners...) - - actionTag := tags.ActionPriceUpdate - if _, err := k.GetLunaSwapRate(ctx, denom); err != nil { - actionTag = tags.ActionWhitelist - } + mod := tally(ctx, k, filteredVotes) // Set price to the store k.SetLunaSwapRate(ctx, denom, mod) - // Reset drop counter for the passed ballot - k.resetDropCounter(ctx, denom) - - resTags = resTags.AppendTags( - sdk.NewTags( - tags.Action, actionTag, - tags.Denom, denom, - tags.Price, mod.String(), - ), + resTags = sdk.NewTags( + tags.Action, tags.ActionPriceUpdate, + tags.Denom, denom, + tags.Price, mod.String(), ) } else { - dropTags := dropBallot(ctx, k, denom, params) - resTags = resTags.AppendTags(dropTags) + resTags = sdk.NewTags( + tags.Action, tags.ActionTallyDropped, + tags.Denom, denom, + ) } } // Clear all prevotes k.iteratePrevotes(ctx, func(prevote PricePrevote) (stop bool) { - if (ctx.BlockHeight() - prevote.SubmitBlock) > params.VotePeriod { + if ctx.BlockHeight() > prevote.SubmitBlock+params.VotePeriod { k.deletePrevote(ctx, prevote) } @@ -132,10 +164,10 @@ func EndBlocker(ctx sdk.Context, k Keeper) (rewardees types.ClaimPool, resTags s }) // Clear all votes - k.iterateVotes(ctx, func(vote PriceVote) (stop bool) { k.deleteVote(ctx, vote); return false }) - - // Sort rewardees before we return - rewardees.Sort() + k.iterateVotes(ctx, func(vote PriceVote) (stop bool) { + k.deleteVote(ctx, vote) + return false + }) return } diff --git a/x/oracle/end_blocker_test.go b/x/oracle/end_blocker_test.go index e06f99d8f..85de35c36 100644 --- a/x/oracle/end_blocker_test.go +++ b/x/oracle/end_blocker_test.go @@ -7,10 +7,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/terra-project/core/types/assets" - "github.com/terra-project/core/types/mock" - mcVal "github.com/terra-project/core/types/mock" ) func TestOracleThreshold(t *testing.T) { @@ -65,10 +62,9 @@ func TestOracleThreshold(t *testing.T) { require.Nil(t, err) require.Equal(t, randomPrice, price) - // A new validator joins, we are now below threshold. Price update should now fail - newValidator := mock.NewMockValidator(sdk.ValAddress(addrs[2].Bytes()), sdk.NewInt(30)) - input.valset.Validators = append(input.valset.Validators, newValidator) - input.oracleKeeper.valset = input.valset + // Less than the threshold signs, msg fails + val, _ := input.stakingKeeper.GetValidator(input.ctx, sdk.ValAddress(addrs[2])) + input.stakingKeeper.Delegate(input.ctx.WithBlockHeight(0), addrs[2], uLunaAmt.MulRaw(2), val, false) salt = "1" bz, err = VoteHash(salt, randomPrice, assets.MicroSDRDenom, sdk.ValAddress(addrs[0])) @@ -89,8 +85,7 @@ func TestOracleThreshold(t *testing.T) { EndBlocker(input.ctx.WithBlockHeight(1), input.oracleKeeper) price, err = input.oracleKeeper.GetLunaSwapRate(input.ctx.WithBlockHeight(1), assets.MicroSDRDenom) - require.Nil(t, err) - require.Equal(t, randomPrice, price) + require.NotNil(t, err) } func TestOracleMultiVote(t *testing.T) { @@ -152,7 +147,6 @@ func TestOracleMultiVote(t *testing.T) { func TestOracleDrop(t *testing.T) { input, h := setup(t) - dropThreshold := input.oracleKeeper.GetParams(input.ctx).DropThreshold input.oracleKeeper.SetLunaSwapRate(input.ctx, assets.MicroKRWDenom, randomPrice) salt := "1" @@ -164,37 +158,13 @@ func TestOracleDrop(t *testing.T) { voteMsg := NewMsgPriceVote(randomPrice, salt, assets.MicroKRWDenom, addrs[0], sdk.ValAddress(addrs[0])) h(input.ctx, voteMsg) - for i := 0; i < int(dropThreshold.Int64())-1; i++ { - EndBlocker(input.ctx, input.oracleKeeper) - } - - price, err := input.oracleKeeper.GetLunaSwapRate(input.ctx, assets.MicroKRWDenom) - require.Nil(t, err) - require.Equal(t, price, randomPrice) - - // Going over dropthreshold should blacklist the price + // Immediately swap halt after an illiquid oracle vote EndBlocker(input.ctx, input.oracleKeeper) _, err = input.oracleKeeper.GetLunaSwapRate(input.ctx, assets.MicroKRWDenom) require.NotNil(t, err) } -func generateValset(valWeights []int64) mock.MockValset { - mockValset := mock.NewMockValSet() - - for i := 0; i < len(valWeights); i++ { - valAccAddr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) - - power := sdk.NewInt(valWeights[i]) - mockValAddr := sdk.ValAddress(valAccAddr.Bytes()) - mockVal := mcVal.NewMockValidator(mockValAddr, power) - - mockValset.Validators = append(mockValset.Validators, mockVal) - } - - return mockValset -} - func TestOracleTally(t *testing.T) { input, _ := setup(t) @@ -250,9 +220,9 @@ func TestOracleTally(t *testing.T) { } } - tallyMedian, tallyClaims := tally(input.ctx, input.oracleKeeper, ballot) + tallyMedian := tally(input.ctx, input.oracleKeeper, ballot) - require.Equal(t, len(tallyClaims), len(rewardees)) + require.Equal(t, countClaimPool(input.ctx, input.oracleKeeper), len(rewardees)) require.Equal(t, tallyMedian.MulInt64(100).TruncateInt(), weightedMedian.MulInt64(100).TruncateInt()) } @@ -290,13 +260,52 @@ func TestOracleTallyTiming(t *testing.T) { params := input.oracleKeeper.GetParams(input.ctx) params.VotePeriod = 10 // set vote period to 10 for now, for convinience input.oracleKeeper.SetParams(input.ctx, params) - require.Equal(t, 0, int(input.ctx.BlockHeight())) - rewardees, _ := EndBlocker(input.ctx, input.oracleKeeper) - require.Equal(t, 0, len(rewardees)) + + EndBlocker(input.ctx, input.oracleKeeper) + require.Equal(t, 0, countClaimPool(input.ctx, input.oracleKeeper)) input.ctx = input.ctx.WithBlockHeight(params.VotePeriod - 1) - rewardees, _ = EndBlocker(input.ctx, input.oracleKeeper) - require.Equal(t, len(addrs), len(rewardees)) + EndBlocker(input.ctx, input.oracleKeeper) + require.Equal(t, len(addrs), countClaimPool(input.ctx, input.oracleKeeper)) +} + +func countClaimPool(ctx sdk.Context, oracleKeeper Keeper) (claimCount int) { + oracleKeeper.iterateClaimPool(ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + claimCount++ + return false + }) + + return claimCount +} + +func TestOracleRewardDistribution(t *testing.T) { + input, h := setup(t) + + salt := "1" + bz, _ := VoteHash(salt, randomPrice, assets.MicroSDRDenom, sdk.ValAddress(addrs[0])) + prevoteMsg := NewMsgPricePrevote(hex.EncodeToString(bz), assets.MicroSDRDenom, addrs[0], sdk.ValAddress(addrs[0])) + h(input.ctx.WithBlockHeight(0), prevoteMsg) + + voteMsg := NewMsgPriceVote(randomPrice, salt, assets.MicroSDRDenom, addrs[0], sdk.ValAddress(addrs[0])) + h(input.ctx.WithBlockHeight(1), voteMsg) + + salt = "2" + bz, _ = VoteHash(salt, randomPrice, assets.MicroSDRDenom, sdk.ValAddress(addrs[1])) + prevoteMsg = NewMsgPricePrevote(hex.EncodeToString(bz), assets.MicroSDRDenom, addrs[1], sdk.ValAddress(addrs[1])) + h(input.ctx.WithBlockHeight(0), prevoteMsg) + + voteMsg = NewMsgPriceVote(randomPrice, salt, assets.MicroSDRDenom, addrs[1], sdk.ValAddress(addrs[1])) + h(input.ctx.WithBlockHeight(1), voteMsg) + + input.oracleKeeper.AddSwapFeePool(input.ctx.WithBlockHeight(1), sdk.NewCoins(sdk.NewCoin(assets.MicroSDRDenom, uLunaAmt.MulRaw(100)))) + + EndBlocker(input.ctx.WithBlockHeight(1), input.oracleKeeper) + EndBlocker(input.ctx.WithBlockHeight(2), input.oracleKeeper) + + rewards := input.distrKeeper.GetValidatorOutstandingRewards(input.ctx.WithBlockHeight(2), sdk.ValAddress(addrs[0])) + require.Equal(t, uLunaAmt.MulRaw(50), rewards.AmountOf(assets.MicroSDRDenom).TruncateInt()) + rewards = input.distrKeeper.GetValidatorOutstandingRewards(input.ctx.WithBlockHeight(2), sdk.ValAddress(addrs[1])) + require.Equal(t, uLunaAmt.MulRaw(50), rewards.AmountOf(assets.MicroSDRDenom).TruncateInt()) } diff --git a/x/oracle/expected_keepers.go b/x/oracle/expected_keepers.go new file mode 100644 index 000000000..25216c1a9 --- /dev/null +++ b/x/oracle/expected_keepers.go @@ -0,0 +1,18 @@ +package oracle + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// expected coin keeper +type DistributionKeeper interface { + AllocateTokensToValidator(ctx sdk.Context, val sdk.Validator, tokens sdk.DecCoins) +} + +// expected fee keeper +type FeeCollectionKeeper interface { + AddCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins +} + +// expected mint keeper +type MintKeeper interface { + ChangeIssuance(ctx sdk.Context, denom string, delta sdk.Int) (err sdk.Error) +} diff --git a/x/oracle/keeper.go b/x/oracle/keeper.go index 0fab085ae..88ae3db1b 100644 --- a/x/oracle/keeper.go +++ b/x/oracle/keeper.go @@ -3,6 +3,7 @@ package oracle import ( "strings" + "github.com/terra-project/core/types" "github.com/terra-project/core/types/assets" "github.com/cosmos/cosmos-sdk/codec" @@ -15,16 +16,25 @@ type Keeper struct { cdc *codec.Codec key sdk.StoreKey + mk MintKeeper + dk DistributionKeeper + fck FeeCollectionKeeper + valset sdk.ValidatorSet paramSpace params.Subspace } // NewKeeper constructs a new keeper for oracle -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, valset sdk.ValidatorSet, paramspace params.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, mk MintKeeper, dk DistributionKeeper, fck FeeCollectionKeeper, + valset sdk.ValidatorSet, paramspace params.Subspace) Keeper { return Keeper{ cdc: cdc, key: key, + mk: mk, + dk: dk, + fck: fck, + valset: valset, paramSpace: paramspace.WithKeyTable(paramKeyTable()), } @@ -154,32 +164,6 @@ func (k Keeper) deleteVote(ctx sdk.Context, vote PriceVote) { store.Delete(keyVote(vote.Denom, vote.Voter)) } -//----------------------------------- -// Drop counter logic - -// Increment drop counter. Called when an oracle vote is illiquid. -func (k Keeper) incrementDropCounter(ctx sdk.Context, denom string) (counter sdk.Int) { - store := ctx.KVStore(k.key) - b := store.Get(keyDropCounter(denom)) - if b == nil { - counter = sdk.ZeroInt() - } else { - k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &counter) - } - - // Increment counter - counter = counter.Add(sdk.OneInt()) - bz := k.cdc.MustMarshalBinaryLengthPrefixed(counter) - store.Set(keyDropCounter(denom), bz) - return -} - -// resets the drop counter. -func (k Keeper) resetDropCounter(ctx sdk.Context, denom string) { - store := ctx.KVStore(k.key) - store.Delete(keyDropCounter(denom)) -} - //----------------------------------- // Price logic @@ -257,18 +241,6 @@ func (k Keeper) GetFeedDelegate(ctx sdk.Context, operator sdk.ValAddress) (deleg return } -// GetOperatorForDelegate gets the operator address that the feeder right was delegated from. -func (k Keeper) GetOperatorsForDelegate(ctx sdk.Context, delegate sdk.AccAddress) (operators []sdk.ValAddress) { - handler := func(del sdk.AccAddress, op sdk.ValAddress) bool { - if del.Equals(delegate) { - operators = append(operators, op) - } - return false - } - k.iterateFeederDelegations(ctx, handler) - return -} - // SetFeedDelegate sets the account address that the feeder right was delegated to by the validator operator. func (k Keeper) SetFeedDelegate(ctx sdk.Context, operator sdk.ValAddress, delegatedFeeder sdk.AccAddress) { store := ctx.KVStore(k.key) @@ -292,3 +264,81 @@ func (k Keeper) iterateFeederDelegations(ctx sdk.Context, handler func(delegate } } } + +//----------------------------------- +// Swap fee pool logic + +// GetSwapFeePool retrieves the swap fee pool from the store +func (k Keeper) GetSwapFeePool(ctx sdk.Context) (pool sdk.Coins) { + store := ctx.KVStore(k.key) + b := store.Get(keySwapFeePool) + if b == nil { + return sdk.Coins{} + } + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &pool) + return +} + +// setSwapFeePool sets the swap fee pool to the store +func (k Keeper) AddSwapFeePool(ctx sdk.Context, fees sdk.Coins) { + pool := k.GetSwapFeePool(ctx) + pool = pool.Add(fees) + + store := ctx.KVStore(k.key) + bz := k.cdc.MustMarshalBinaryLengthPrefixed(pool) + store.Set(keySwapFeePool, bz) +} + +// clearSwapFeePool clears the swap fee pool from the store +func (k Keeper) clearSwapFeePool(ctx sdk.Context) { + store := ctx.KVStore(k.key) + store.Delete(keySwapFeePool) +} + +//----------------------------------- +// Claim pool logic + +// Iterate over oracle reward claims in the store +func (k Keeper) iterateClaimPool(ctx sdk.Context, handler func(recipient sdk.AccAddress, weight sdk.Int) (stop bool)) { + store := ctx.KVStore(k.key) + iter := sdk.KVStorePrefixIterator(store, prefixClaim) + defer iter.Close() + for ; iter.Valid(); iter.Next() { + recipientAddress := strings.Split(string(iter.Key()), ":")[1] + recipient, _ := sdk.AccAddressFromBech32(recipientAddress) + + var weight sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(iter.Value(), &weight) + if handler(recipient, weight) { + break + } + } +} + +// addClaimPool adds a claim to the the claim pool in the store +func (k Keeper) addClaimPool(ctx sdk.Context, pool types.ClaimPool) { + store := ctx.KVStore(k.key) + + for _, claim := range pool { + storeKeyClaim := keyClaim(claim.Recipient) + b := store.Get(storeKeyClaim) + weight := claim.Weight + if b != nil { + var prevWeight sdk.Int + k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &prevWeight) + + weight = weight.Add(prevWeight) + } + b = k.cdc.MustMarshalBinaryLengthPrefixed(weight) + store.Set(storeKeyClaim, b) + } +} + +// clearClaimPool clears the claim pool from the store +func (k Keeper) clearClaimPool(ctx sdk.Context) { + store := ctx.KVStore(k.key) + k.iterateClaimPool(ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + store.Delete(keyClaim(recipient)) + return false + }) +} diff --git a/x/oracle/keeper_keys.go b/x/oracle/keeper_keys.go index b6f2e2a80..c6b3a6e6f 100644 --- a/x/oracle/keeper_keys.go +++ b/x/oracle/keeper_keys.go @@ -14,6 +14,9 @@ var ( prefixDropCounter = []byte("drop") paramStoreKeyParams = []byte("params") prefixFeederDelegation = []byte("feederdelegation") + prefixClaim = []byte("claim") + + keySwapFeePool = []byte("swapfeepool") ) func keyPrevote(denom string, voter sdk.ValAddress) []byte { @@ -28,6 +31,10 @@ func keyPrice(denom string) []byte { return []byte(fmt.Sprintf("%s:%s", prefixPrice, denom)) } +func keyClaim(recipient sdk.AccAddress) []byte { + return []byte(fmt.Sprintf("%s:%s", prefixClaim, recipient)) +} + func keyDropCounter(denom string) []byte { return []byte(fmt.Sprintf("%s:%s", prefixDropCounter, denom)) } diff --git a/x/oracle/keeper_test.go b/x/oracle/keeper_test.go index 6cad46f02..c8ea6067d 100644 --- a/x/oracle/keeper_test.go +++ b/x/oracle/keeper_test.go @@ -3,6 +3,7 @@ package oracle import ( "encoding/hex" + "github.com/terra-project/core/types" "github.com/terra-project/core/types/assets" "testing" @@ -41,6 +42,50 @@ func TestKeeperPrice(t *testing.T) { require.Equal(t, sdk.OneDec(), price) } +func TestKeeperSwapPool(t *testing.T) { + input := createTestInput(t) + + // Test AddSwapFeePool + fees := sdk.NewCoins(sdk.NewCoin(assets.MicroSDRDenom, sdk.NewInt(1000))) + input.oracleKeeper.AddSwapFeePool(input.ctx, fees) + + // Test GetSwapFeePool + feesQuery := input.oracleKeeper.GetSwapFeePool(input.ctx) + require.Equal(t, fees, feesQuery) + + // Test clearSwapFeePool + input.oracleKeeper.clearSwapFeePool(input.ctx) + feesQuery = input.oracleKeeper.GetSwapFeePool(input.ctx) + + require.True(t, feesQuery.Empty()) +} + +func TestKeeperClaimPool(t *testing.T) { + input := createTestInput(t) + + // Test addClaimPool + claim := types.NewClaim(sdk.NewInt(10), addrs[0]) + claim2 := types.NewClaim(sdk.NewInt(20), addrs[1]) + claimPool := types.ClaimPool{claim, claim2} + input.oracleKeeper.addClaimPool(input.ctx, claimPool) + + claim = types.NewClaim(sdk.NewInt(15), addrs[0]) + claim2 = types.NewClaim(sdk.NewInt(30), addrs[2]) + claimPool = types.ClaimPool{claim, claim2} + input.oracleKeeper.addClaimPool(input.ctx, claimPool) + + // Test iterateClaimPool + input.oracleKeeper.iterateClaimPool(input.ctx, func(recipient sdk.AccAddress, weight sdk.Int) (stop bool) { + if recipient.Equals(addrs[0]) { + require.Equal(t, sdk.NewInt(25), weight) + } else if recipient.Equals(addrs[1]) { + require.Equal(t, sdk.NewInt(20), weight) + } else if recipient.Equals(addrs[2]) { + require.Equal(t, sdk.NewInt(30), weight) + } + return false + }) +} func TestKeeperVote(t *testing.T) { input := createTestInput(t) @@ -98,20 +143,6 @@ func TestKeeperPrevote(t *testing.T) { require.NotNil(t, err) } -func TestKeeperDropCounter(t *testing.T) { - input := createTestInput(t) - - for i := 1; i < 40; i++ { - counter := input.oracleKeeper.incrementDropCounter(input.ctx, assets.MicroSDRDenom) - require.Equal(t, sdk.NewInt(int64(i)), counter) - } - - input.oracleKeeper.resetDropCounter(input.ctx, assets.MicroSDRDenom) - store := input.ctx.KVStore(input.oracleKeeper.key) - b := store.Get(keyDropCounter(assets.MicroSDRDenom)) - require.Nil(t, b) -} - func TestKeeperParams(t *testing.T) { input := createTestInput(t) @@ -124,10 +155,9 @@ func TestKeeperParams(t *testing.T) { votePeriod := int64(10) voteThreshold := sdk.NewDecWithPrec(1, 10) oracleRewardBand := sdk.NewDecWithPrec(1, 2) - dropThreshold := sdk.NewInt(10) // Should really test validateParams, but skipping because obvious - newParams := NewParams(votePeriod, voteThreshold, oracleRewardBand, dropThreshold) + newParams := NewParams(votePeriod, voteThreshold, oracleRewardBand) input.oracleKeeper.SetParams(input.ctx, newParams) storedParams := input.oracleKeeper.GetParams(input.ctx) @@ -145,11 +175,4 @@ func TestKeeperFeederDelegation(t *testing.T) { input.oracleKeeper.SetFeedDelegate(input.ctx, sdk.ValAddress(addrs[0]), addrs[1]) delegate = input.oracleKeeper.GetFeedDelegate(input.ctx, sdk.ValAddress(addrs[0])) require.Equal(t, delegate, addrs[1]) - - // Test iterator - input.oracleKeeper.SetFeedDelegate(input.ctx, sdk.ValAddress(addrs[1]), addrs[1]) - delegations := input.oracleKeeper.GetOperatorsForDelegate(input.ctx, addrs[1]) - require.Equal(t, len(delegations), 2) - require.Contains(t, delegations, sdk.ValAddress(addrs[0])) - require.Contains(t, delegations, sdk.ValAddress(addrs[1])) } diff --git a/x/oracle/params.go b/x/oracle/params.go index 19966d7ed..a90a16159 100644 --- a/x/oracle/params.go +++ b/x/oracle/params.go @@ -11,28 +11,25 @@ import ( // Params oracle parameters type Params struct { VotePeriod int64 `json:"vote_period"` // voting period in block height; tallys and reward claim period - VoteThreshold sdk.Dec `json:"vote_threshold"` // minimum stake power threshold to clear vote - DropThreshold sdk.Int `json:"drop_threshold"` // tolerated drops before blacklist + VoteThreshold sdk.Dec `json:"vote_threshold"` // minimum stake power threshold to update price OracleRewardBand sdk.Dec `json:"oracle_reward_band"` // band around the oracle weighted median to reward } // NewParams creates a new param instance -func NewParams(votePeriod int64, voteThreshold, oracleRewardBand sdk.Dec, dropThreshold sdk.Int) Params { +func NewParams(votePeriod int64, voteThreshold sdk.Dec, oracleRewardBand sdk.Dec) Params { return Params{ VotePeriod: votePeriod, VoteThreshold: voteThreshold, OracleRewardBand: oracleRewardBand, - DropThreshold: dropThreshold, } } // DefaultParams creates default oracle module parameters func DefaultParams() Params { return NewParams( - util.BlocksPerMinute/2, // 30 seconds - sdk.NewDecWithPrec(67, 2), // 67% + util.BlocksPerMinute, // 1 minute + sdk.NewDecWithPrec(50, 2), // 50% sdk.NewDecWithPrec(1, 2), // 1% - sdk.NewInt(10), ) } @@ -40,23 +37,19 @@ func validateParams(params Params) error { if params.VotePeriod <= 0 { return fmt.Errorf("oracle parameter VotePeriod must be > 0, is %d", params.VotePeriod) } - if params.VoteThreshold.LT(sdk.NewDecWithPrec(33, 2)) { - return fmt.Errorf("oracle parameter VoteThreshold must be greater than 33 percent") + if params.VoteThreshold.LTE(sdk.NewDecWithPrec(33, 2)) { + return fmt.Errorf("oracle parameter VoteTheshold must be greater than 33 percent") } if params.OracleRewardBand.IsNegative() { return fmt.Errorf("oracle parameter OracleRewardBand must be positive") } - if params.DropThreshold.LTE(sdk.NewInt(3)) { - return fmt.Errorf("oracle parameter DropThreshold must be greater than 3") - } return nil } func (params Params) String() string { return fmt.Sprintf(`Oracle Params: - VotePeriod: %d - VoteThreshold: %s - OracleRewardBand: %s - DropThresdhold: %s - `, params.VotePeriod, params.VoteThreshold, params.OracleRewardBand, params.DropThreshold) + VotePeriod: %d + VoteThreshold: %s + OracleRewardBand: %s + `, params.VotePeriod, params.VoteThreshold, params.OracleRewardBand) } diff --git a/x/oracle/tags/tags.go b/x/oracle/tags/tags.go index b466f4259..6dd736852 100644 --- a/x/oracle/tags/tags.go +++ b/x/oracle/tags/tags.go @@ -8,8 +8,6 @@ import ( var ( ActionPriceUpdate = "price-update" // normal cases ActionTallyDropped = "tally-dropped" // emitted when price update is illiquid - ActionWhitelist = "whitelist" // emitted on virgin listing - ActionBlacklist = "blacklist" // emitted on delisting Action = sdk.TagAction Denom = "denom" diff --git a/x/oracle/test_common.go b/x/oracle/test_common.go index 0d2a0deab..c1d8876a1 100644 --- a/x/oracle/test_common.go +++ b/x/oracle/test_common.go @@ -5,14 +5,13 @@ import ( "github.com/stretchr/testify/require" "github.com/terra-project/core/types/assets" - "github.com/terra-project/core/types/mock" - - "github.com/cosmos/cosmos-sdk/x/staking" + "github.com/terra-project/core/x/mint" "time" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -22,7 +21,9 @@ import ( 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/params" + "github.com/cosmos/cosmos-sdk/x/staking" ) var ( @@ -38,6 +39,18 @@ var ( sdk.AccAddress(pubKeys[2].Address()), } + valConsPubKeys = []crypto.PubKey{ + ed25519.GenPrivKey().PubKey(), + ed25519.GenPrivKey().PubKey(), + ed25519.GenPrivKey().PubKey(), + } + + valConsAddrs = []sdk.ConsAddress{ + sdk.ConsAddress(valConsPubKeys[0].Address()), + sdk.ConsAddress(valConsPubKeys[1].Address()), + sdk.ConsAddress(valConsPubKeys[2].Address()), + } + uSDRAmt = sdk.NewInt(1005 * assets.MicroUnit) uLunaAmt = sdk.NewInt(10 * assets.MicroUnit) @@ -59,12 +72,13 @@ func setup(t *testing.T) (testInput, sdk.Handler) { } type testInput struct { - ctx sdk.Context - cdc *codec.Codec - accKeeper auth.AccountKeeper - bankKeeper bank.Keeper - oracleKeeper Keeper - valset mock.MockValset + ctx sdk.Context + cdc *codec.Codec + accKeeper auth.AccountKeeper + bankKeeper bank.Keeper + oracleKeeper Keeper + stakingKeeper staking.Keeper + distrKeeper distr.Keeper } func newTestCodec() *codec.Codec { @@ -83,8 +97,12 @@ func createTestInput(t *testing.T) testInput { keyParams := sdk.NewKVStoreKey(params.StoreKey) tKeyParams := sdk.NewTransientStoreKey(params.TStoreKey) keyOracle := sdk.NewKVStoreKey(StoreKey) + keyMint := sdk.NewKVStoreKey(mint.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) - tkeyStaking := sdk.NewKVStoreKey(staking.TStoreKey) + tKeyStaking := sdk.NewKVStoreKey(staking.TStoreKey) + keyDistr := sdk.NewKVStoreKey(distr.StoreKey) + tKeyDistr := sdk.NewTransientStoreKey(distr.TStoreKey) + keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) cdc := newTestCodec() db := dbm.NewMemDB() @@ -95,8 +113,12 @@ func createTestInput(t *testing.T) testInput { ms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyOracle, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyMint, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(tkeyStaking, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyStaking, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tKeyDistr, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) require.NoError(t, ms.LoadLatestVersion()) @@ -114,24 +136,61 @@ func createTestInput(t *testing.T) testInput { bank.DefaultCodespace, ) - valset := mock.NewMockValSet() - for _, addr := range addrs { - _, _, err := bankKeeper.AddCoins(ctx, addr, sdk.Coins{ - sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt), - sdk.NewCoin(assets.MicroSDRDenom, uSDRAmt), - }) + stakingKeeper := staking.NewKeeper( + cdc, + keyStaking, tKeyStaking, + bankKeeper, paramsKeeper.Subspace(staking.DefaultParamspace), + staking.DefaultCodespace, + ) + + feeCollectionKeeper := auth.NewFeeCollectionKeeper( + cdc, + keyFeeCollection, + ) + + distrKeeper := distr.NewKeeper( + cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), + bankKeeper, &stakingKeeper, feeCollectionKeeper, distr.DefaultCodespace, + ) + + mintKeeper := mint.NewKeeper( + cdc, + keyMint, + stakingKeeper, + bankKeeper, + accKeeper, + ) - require.NoError(t, err) + stakingKeeper.SetPool(ctx, staking.InitialPool()) + stakingParams := staking.DefaultParams() + stakingParams.BondDenom = assets.MicroLunaDenom + stakingKeeper.SetParams(ctx, stakingParams) + + sh := staking.NewHandler(stakingKeeper) + for i, addr := range addrs { + err2 := mintKeeper.Mint(ctx, addr, sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt.MulRaw(3))) + require.NoError(t, err2) // Add validators - validator := mock.NewMockValidator(sdk.ValAddress(addr.Bytes()), uLunaAmt) - valset.Validators = append(valset.Validators, validator) + commission := staking.NewCommissionMsg(sdk.NewDecWithPrec(5, 1), sdk.NewDecWithPrec(5, 1), sdk.NewDec(0)) + msg := staking.NewMsgCreateValidator(sdk.ValAddress(addr), valConsPubKeys[i], + sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt), staking.Description{}, commission, sdk.OneInt()) + res := sh(ctx, msg) + require.True(t, res.IsOK()) + + distrKeeper.Hooks().AfterValidatorCreated(ctx, sdk.ValAddress(addr)) + staking.EndBlocker(ctx, stakingKeeper) } oracleKeeper := NewKeeper( - cdc, keyOracle, valset, + cdc, + keyOracle, + mintKeeper, + distrKeeper, + feeCollectionKeeper, + stakingKeeper.GetValidatorSet(), paramsKeeper.Subspace(DefaultParamspace), ) - return testInput{ctx, cdc, accKeeper, bankKeeper, oracleKeeper, valset} + return testInput{ctx, cdc, accKeeper, bankKeeper, oracleKeeper, stakingKeeper, distrKeeper} } diff --git a/x/pay/handler_test.go b/x/pay/handler_test.go index c6895d111..c5545cced 100644 --- a/x/pay/handler_test.go +++ b/x/pay/handler_test.go @@ -14,6 +14,8 @@ import ( "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" @@ -35,6 +37,18 @@ var ( sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()), } + valConsPubKeys = []crypto.PubKey{ + ed25519.GenPrivKey().PubKey(), + ed25519.GenPrivKey().PubKey(), + ed25519.GenPrivKey().PubKey(), + } + + valConsAddrs = []sdk.ConsAddress{ + sdk.ConsAddress(valConsPubKeys[0].Address()), + sdk.ConsAddress(valConsPubKeys[1].Address()), + sdk.ConsAddress(valConsPubKeys[2].Address()), + } + uSDRAmount = sdk.NewInt(1005).MulRaw(assets.MicroUnit) ) @@ -62,13 +76,14 @@ func createTestInput(t *testing.T) testInput { keyParams := sdk.NewKVStoreKey(params.StoreKey) tKeyParams := sdk.NewTransientStoreKey(params.TStoreKey) keyTreasury := sdk.NewKVStoreKey(treasury.StoreKey) - keyFee := sdk.NewKVStoreKey(auth.FeeStoreKey) keyMint := sdk.NewKVStoreKey(mint.StoreKey) keyOracle := sdk.NewKVStoreKey(oracle.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) tKeyStaking := sdk.NewTransientStoreKey(staking.TStoreKey) keyDistr := sdk.NewKVStoreKey(distr.StoreKey) tKeyDistr := sdk.NewTransientStoreKey(distr.TStoreKey) + keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keyMarket := sdk.NewKVStoreKey(market.StoreKey) cdc := newTestCodec() db := dbm.NewMemDB() @@ -79,13 +94,14 @@ func createTestInput(t *testing.T) testInput { ms.MountStoreWithDB(tKeyParams, sdk.StoreTypeTransient, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyTreasury, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyFee, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyMint, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyOracle, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tKeyStaking, sdk.StoreTypeTransient, db) ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tKeyDistr, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyMarket, sdk.StoreTypeIAVL, db) require.NoError(t, ms.LoadLatestVersion()) @@ -110,6 +126,16 @@ func createTestInput(t *testing.T) testInput { staking.DefaultCodespace, ) + feeCollectionKeeper := auth.NewFeeCollectionKeeper( + cdc, + keyFeeCollection, + ) + + distrKeeper := distr.NewKeeper( + cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), + bankKeeper, &stakingKeeper, feeCollectionKeeper, distr.DefaultCodespace, + ) + stakingKeeper.SetPool(ctx, staking.InitialPool()) stakingKeeper.SetParams(ctx, staking.DefaultParams()) @@ -124,30 +150,23 @@ func createTestInput(t *testing.T) testInput { oracleKeeper := oracle.NewKeeper( cdc, keyOracle, + mintKeeper, + distrKeeper, + feeCollectionKeeper, &stakingKeeper, paramsKeeper.Subspace(oracle.DefaultParamspace), ) - marketKeeper := market.NewKeeper(oracleKeeper, mintKeeper, + marketKeeper := market.NewKeeper(cdc, keyMarket, oracleKeeper, mintKeeper, paramsKeeper.Subspace(market.DefaultParamspace)) - - feeKeeper := auth.NewFeeCollectionKeeper( - cdc, keyFee, - ) - - distrKeeper := distr.NewKeeper( - cdc, keyDistr, paramsKeeper.Subspace(distr.DefaultParamspace), - bankKeeper, stakingKeeper, feeKeeper, distr.DefaultCodespace, - ) + marketKeeper.SetParams(ctx, market.DefaultParams()) treasuryKeeper := treasury.NewKeeper( cdc, keyTreasury, - &stakingKeeper, + stakingKeeper.GetValidatorSet(), mintKeeper, marketKeeper, - distrKeeper, - feeKeeper, paramsKeeper.Subspace(treasury.DefaultParamspace), ) @@ -156,7 +175,7 @@ func createTestInput(t *testing.T) testInput { require.NoError(t, err) } - return testInput{ctx, accKeeper, bankKeeper, treasuryKeeper, feeKeeper} + return testInput{ctx, accKeeper, bankKeeper, treasuryKeeper, feeCollectionKeeper} } func TestHandlerMsgSendTransfersDisabled(t *testing.T) { diff --git a/x/treasury/end_blocker.go b/x/treasury/end_blocker.go index 546029fa2..3418bc0cf 100644 --- a/x/treasury/end_blocker.go +++ b/x/treasury/end_blocker.go @@ -1,8 +1,6 @@ package treasury import ( - "github.com/terra-project/core/types" - "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" "github.com/terra-project/core/x/treasury/tags" @@ -25,9 +23,6 @@ func EndBlocker(ctx sdk.Context, k Keeper) (resTags sdk.Tags) { return resTags } - // Settle and clear claims from the store - resTags = k.settleClaims(ctx) - if isProbationPeriod(ctx, k) { return resTags } @@ -36,107 +31,9 @@ func EndBlocker(ctx sdk.Context, k Keeper) (resTags sdk.Tags) { taxRate := updateTaxPolicy(ctx, k) rewardWeight := updateRewardPolicy(ctx, k) - return resTags.AppendTags( - sdk.NewTags( - tags.Action, tags.ActionPolicyUpdate, - tags.Tax, taxRate.String(), - tags.MinerReward, rewardWeight.String(), - ), - ) -} - -// compute scales by which the total reward pool must be -func getScales(ctx sdk.Context, k Keeper, oracleSum, budgetSum sdk.Int) (minerScale, oracleScale, budgetScale sdk.Dec) { - params := k.GetParams(ctx) - curEpoch := util.GetEpoch(ctx) - - oracleScale = sdk.ZeroDec() - budgetScale = sdk.ZeroDec() - rewardWeight := k.GetRewardWeight(ctx, curEpoch) - if oracleSum.GT(sdk.ZeroInt()) { - oracleScale = sdk.OneDec().Sub(rewardWeight).Mul(params.OracleClaimShare).QuoInt(oracleSum) - } - - if budgetSum.GT(sdk.ZeroInt()) { - budgetScale = sdk.OneDec().Sub(rewardWeight).Mul(params.BudgetClaimShare).QuoInt(budgetSum) - } - - minerScale = sdk.OneDec().Sub(oracleScale.MulInt(oracleSum)).Sub(budgetScale.MulInt(budgetSum)) - return -} - -// settleClaims distributes the current treasury to the registered claims, and deletes all claims from the store. -func (k Keeper) settleClaims(ctx sdk.Context) (settleTags sdk.Tags) { - curEpoch := util.GetEpoch(ctx) - store := ctx.KVStore(k.key) - - // Convert seigniorage to TerraSDR for rewards - seigPool := k.mtk.PeekSeignioragePool(ctx, curEpoch) - rewardPool, err := k.mk.GetSwapDecCoins(ctx, sdk.NewDecCoin(assets.MicroLunaDenom, seigPool), assets.MicroSDRDenom) - if err != nil { - return // No or too little seigniorage - } - - oracleSumWeight := sdk.ZeroInt() - budgetSumWeight := sdk.ZeroInt() - - // Sum weights by class - k.IterateClaims(ctx, func(claim types.Claim) (stop bool) { - switch claim.Class { - case types.OracleClaimClass: - oracleSumWeight = oracleSumWeight.Add(claim.Weight) - case types.BudgetClaimClass: - budgetSumWeight = budgetSumWeight.Add(claim.Weight) - } - return false - }) - - // Need to scale weights in claims by dividing class shares and total amount of weights - minerScale, oracleScale, budgetScale := getScales(ctx, k, oracleSumWeight, budgetSumWeight) - - // Settle and delete all claims from the store - k.IterateClaims(ctx, func(claim types.Claim) (stop bool) { - - var err error - if claim.Class == types.OracleClaimClass { - rewardAmt := rewardPool.Amount.Mul(oracleScale).MulInt(claim.Weight).TruncateInt() - rewardeeVallidator := k.valset.Validator(ctx, sdk.ValAddress(claim.Recipient)) - rewardCoins := sdk.NewCoins(sdk.NewCoin(assets.MicroSDRDenom, rewardAmt)) - - // In case absence of the validator, we collect the rewards to fee collect keeper - if rewardeeVallidator != nil { - k.dk.AllocateTokensToValidator(ctx, rewardeeVallidator, sdk.NewDecCoins(rewardCoins)) - } else { - k.fck.AddCollectedFees(ctx, rewardCoins) - } - - // Minted amount is goes to validator outstanding pool or fee pool, so no repcipient is specified - err = k.mtk.Mint(ctx, sdk.AccAddress{}, sdk.NewCoin(assets.MicroSDRDenom, rewardAmt)) - } else { - rewardAmt := rewardPool.Amount.Mul(budgetScale).MulInt(claim.Weight).TruncateInt() - - // Credit the recipient's account with the reward - err = k.mtk.Mint(ctx, claim.Recipient, sdk.NewCoin(assets.MicroSDRDenom, rewardAmt)) - } - - if err != nil { - return false - } - - // We are now done with the claim; remove it from the store - store.Delete(keyClaim(claim.ID())) - return false - }) - - // Just a rough approximation ... we are leaving some dust by rounding down each claim - oracleRewards := rewardPool.Amount.Mul(oracleScale).MulInt(oracleSumWeight) - budgetRewards := rewardPool.Amount.Mul(budgetScale).MulInt(budgetSumWeight) - minerRewards := rewardPool.Amount.Mul(minerScale) - return sdk.NewTags( - tags.Action, tags.ActionSettle, - tags.MinerReward, minerRewards.TruncateInt().String(), - tags.Oracle, oracleRewards.TruncateInt().String(), - tags.Budget, budgetRewards.TruncateInt().String(), + tags.Action, tags.ActionPolicyUpdate, + tags.Tax, taxRate.String(), + tags.MinerReward, rewardWeight.String(), ) } diff --git a/x/treasury/end_blocker_test.go b/x/treasury/end_blocker_test.go index 5635183dc..cde875a57 100644 --- a/x/treasury/end_blocker_test.go +++ b/x/treasury/end_blocker_test.go @@ -5,7 +5,6 @@ import ( "testing" "time" - "github.com/terra-project/core/types" "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" "github.com/terra-project/core/x/treasury/tags" @@ -18,41 +17,17 @@ func TestEndBlockerTiming(t *testing.T) { input := createTestInput(t) input = reset(input) - // First endblocker should fail - tTags := EndBlocker(input.ctx, input.treasuryKeeper) - require.True(t, len(tTags.ToKVPairs()) == 0) - - // Subsequent endblocker should settle, but NOT update policy params := input.treasuryKeeper.GetParams(input.ctx) - for i := int64(1); i < params.WindowProbation.Int64(); i++ { - if i%params.WindowShort.Int64() == 0 { - // Last block should settle - input.ctx = input.ctx.WithBlockHeight(i*util.BlocksPerEpoch - 1) - input.mintKeeper.AddSeigniorage(input.ctx, uLunaAmt) - - tTags := EndBlocker(input.ctx, input.treasuryKeeper) - - require.Equal(t, 4, len(tTags)) - - // Non-last block should not settle - input.ctx = input.ctx.WithBlockHeight(i * util.BlocksPerEpoch) - input.mintKeeper.AddSeigniorage(input.ctx, uLunaAmt) - - tTags = EndBlocker(input.ctx, input.treasuryKeeper) - - require.Equal(t, 0, len(tTags)) - } - } // After probationary period, we should also be updating policy variables for i := params.WindowProbation.Int64(); i < params.WindowProbation.Int64()+12; i++ { if i%params.WindowShort.Int64() == 0 { input.ctx = input.ctx.WithBlockHeight(i*util.BlocksPerEpoch - 1) - input.mintKeeper.AddSeigniorage(input.ctx, uLunaAmt) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt)) tTags := EndBlocker(input.ctx, input.treasuryKeeper) - require.Equal(t, tTags.ToKVPairs()[4].GetValue(), []byte(tags.ActionPolicyUpdate)) + require.Equal(t, tTags.ToKVPairs()[0].GetValue(), []byte(tags.ActionPolicyUpdate)) } } } @@ -97,7 +72,7 @@ func updatePolicy(input testInput, startIndex int, input.treasuryKeeper.RecordTaxProceeds(input.ctx, sdk.Coins{sdk.NewCoin(assets.MicroSDRDenom, taxRevenue)}) seigniorageRevenue := seigniorageRevenues[i] - input.mintKeeper.AddSeigniorage(input.ctx, seigniorageRevenue) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, seigniorageRevenue)) // Call endblocker EndBlocker(input.ctx, input.treasuryKeeper) @@ -140,79 +115,3 @@ func TestEndBlockerUpdatePolicy(t *testing.T) { require.Equal(t, taxRate, newTaxRate) require.Equal(t, rewardWeight, newSeigniorageWeight) } - -func TestEndBlockerSettleClaims(t *testing.T) { - input := createTestInput(t) - input = reset(input) - - tests := []struct { - claims []types.Claim - seigniorage sdk.Int - sdrRewards []int64 - }{ - // Test 1: no claims - {[]types.Claim{}, sdk.NewInt(1000), []int64{0, 0, 0}}, - - // Test 2: two claims of the same class - {[]types.Claim{ - types.NewClaim(types.OracleClaimClass, sdk.NewInt(10), addrs[0]), - types.NewClaim(types.OracleClaimClass, sdk.NewInt(90), addrs[1]), - }, sdk.NewInt(1000), []int64{10, 90, 0}}, - - // Test 3: similar to case 2, except different class - {[]types.Claim{ - types.NewClaim(types.BudgetClaimClass, sdk.NewInt(10), addrs[0]), - types.NewClaim(types.BudgetClaimClass, sdk.NewInt(90), addrs[1]), - }, sdk.NewInt(1000), []int64{90, 810, 0}}, - - // Test 4: Many claims of different classes - {[]types.Claim{ - types.NewClaim(types.OracleClaimClass, sdk.NewInt(10), addrs[0]), - types.NewClaim(types.BudgetClaimClass, sdk.NewInt(10), addrs[0]), - types.NewClaim(types.OracleClaimClass, sdk.NewInt(10), addrs[1]), - types.NewClaim(types.OracleClaimClass, sdk.NewInt(80), addrs[2]), - types.NewClaim(types.BudgetClaimClass, sdk.NewInt(90), addrs[2]), - }, sdk.NewInt(1000), []int64{100, 10, 890}}, - } - - params := input.treasuryKeeper.GetParams(input.ctx) - blocksPerEpoch := util.BlocksPerEpoch - - for i, tc := range tests { - - // Advance blockcount - input.ctx = input.ctx.WithBlockHeight(params.WindowShort.Int64()*blocksPerEpoch*int64(i) - 1) - - // clear SDR balances for testing; keep luna for policy update safety - for _, addr := range addrs { - err := input.bankKeeper.SetCoins(input.ctx, addr, sdk.Coins{sdk.NewCoin(assets.MicroLunaDenom, uLunaAmt)}) - input.distrKeeper.Hooks().AfterValidatorCreated(input.ctx, sdk.ValAddress(addr)) - require.Nil(t, err) - } - - // Reset reward weight - input.treasuryKeeper.SetRewardWeight(input.ctx, sdk.ZeroDec()) - input.mintKeeper.AddSeigniorage(input.ctx, tc.seigniorage) - - // Call endblocker - for _, claim := range tc.claims { - input.treasuryKeeper.AddClaim(input.ctx, claim) - } - EndBlocker(input.ctx, input.treasuryKeeper) - - for j, addr := range addrs { - - balance := input.bankKeeper.GetCoins(input.ctx, addr).AmountOf(assets.MicroSDRDenom) - outstandingBalance := input.distrKeeper.GetValidatorOutstandingRewards(input.ctx, sdk.ValAddress(addr)).AmountOf(assets.MicroSDRDenom) - require.Equal(t, balance.Add(outstandingBalance.TruncateInt()), sdk.NewInt(tc.sdrRewards[j]), "test: %v", i) - } - - counter := 0 - input.treasuryKeeper.IterateClaims(input.ctx, func(claim types.Claim) bool { - counter++ - return false - }) - - require.Equal(t, 0, counter, "Claims expected to be cleared after treasury update") - } -} diff --git a/x/treasury/expected_keepers.go b/x/treasury/expected_keepers.go index 3ba78e214..9053bd58e 100644 --- a/x/treasury/expected_keepers.go +++ b/x/treasury/expected_keepers.go @@ -4,15 +4,15 @@ import sdk "github.com/cosmos/cosmos-sdk/types" // expected mint keeper type MintKeeper interface { - PeekSeignioragePool(ctx sdk.Context, epoch sdk.Int) (seignioragePool sdk.Int) + PeekEpochSeigniorage(ctx sdk.Context, epoch sdk.Int) (seignioragePool sdk.Int) Mint(ctx sdk.Context, recipient sdk.AccAddress, coin sdk.Coin) (err sdk.Error) GetIssuance(ctx sdk.Context, denom string, day sdk.Int) (issuance sdk.Int) } // expected market keeper type MarketKeeper interface { - GetSwapDecCoins(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) - GetSwapCoins(ctx sdk.Context, offerCoin sdk.Coin, askDenom string) (sdk.Coin, sdk.Error) + GetSwapDecCoin(ctx sdk.Context, offerCoin sdk.DecCoin, askDenom string) (sdk.DecCoin, sdk.Error) + GetSwapCoin(ctx sdk.Context, offerCoin sdk.Coin, askDenom string, isInternal bool) (sdk.Coin, sdk.Dec, sdk.Error) } // expected coin keeper diff --git a/x/treasury/genesis.go b/x/treasury/genesis.go index 705ad703f..c63f13aef 100644 --- a/x/treasury/genesis.go +++ b/x/treasury/genesis.go @@ -3,7 +3,6 @@ package treasury import ( "fmt" - "github.com/terra-project/core/types" "github.com/terra-project/core/types/util" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,19 +10,17 @@ import ( // GenesisState - all treasury state that must be provided at genesis type GenesisState struct { - Params Params `json:"params"` // treasury params - GenesisTaxRate sdk.Dec `json:"tax_rate"` - GenesisRewardWeight sdk.Dec `json:"reward_weight"` - Claims []types.Claim `json:"claims"` + Params Params `json:"params"` // treasury params + GenesisTaxRate sdk.Dec `json:"tax_rate"` + GenesisRewardWeight sdk.Dec `json:"reward_weight"` } // NewGenesisState constructs a new genesis state -func NewGenesisState(params Params, taxRate, rewardWeight sdk.Dec, claims []types.Claim) GenesisState { +func NewGenesisState(params Params, taxRate, rewardWeight sdk.Dec) GenesisState { return GenesisState{ Params: params, GenesisTaxRate: taxRate, GenesisRewardWeight: rewardWeight, - Claims: claims, } } @@ -34,7 +31,6 @@ func DefaultGenesisState() GenesisState { Params: params, GenesisTaxRate: sdk.NewDecWithPrec(1, 3), // 0.1% GenesisRewardWeight: sdk.NewDecWithPrec(5, 2), // 5% - Claims: []types.Claim{}, } } @@ -44,11 +40,6 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { keeper.SetTaxRate(ctx, data.GenesisTaxRate) keeper.setTaxCap(ctx, data.Params.TaxPolicy.Cap.Denom, data.Params.TaxPolicy.Cap.Amount) keeper.SetRewardWeight(ctx, data.GenesisRewardWeight) - - for _, claim := range data.Claims { - keeper.AddClaim(ctx, claim) - } - } // ExportGenesis returns a GenesisState for a given context and keeper. The @@ -58,14 +49,7 @@ func ExportGenesis(ctx sdk.Context, k Keeper) GenesisState { taxRate := k.GetTaxRate(ctx, sdk.ZeroInt()) rewardWeight := k.GetRewardWeight(ctx, util.GetEpoch(ctx)) - var claims []types.Claim - k.IterateClaims(ctx, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - - return false - }) - - return NewGenesisState(params, taxRate, rewardWeight, claims) + return NewGenesisState(params, taxRate, rewardWeight) } // ValidateGenesis validates the provided treasury genesis state to ensure the diff --git a/x/treasury/indicator.go b/x/treasury/indicator.go index 6793b933b..090ee2e70 100644 --- a/x/treasury/indicator.go +++ b/x/treasury/indicator.go @@ -25,7 +25,7 @@ func TaxRewardsForEpoch(ctx sdk.Context, k Keeper, epoch sdk.Int) sdk.Dec { taxRewardInMicroSDR := sdk.ZeroDec() for _, coinReward := range taxRewards { if coinReward.Denom != assets.MicroSDRDenom { - swappedReward, err := k.mk.GetSwapDecCoins(ctx, coinReward, assets.MicroSDRDenom) + swappedReward, err := k.mk.GetSwapDecCoin(ctx, coinReward, assets.MicroSDRDenom) if err != nil { continue } @@ -40,11 +40,11 @@ func TaxRewardsForEpoch(ctx sdk.Context, k Keeper, epoch sdk.Int) sdk.Dec { // SeigniorageRewardsForEpoch returns seigniorage rewards for the epoch func SeigniorageRewardsForEpoch(ctx sdk.Context, k Keeper, epoch sdk.Int) sdk.Dec { - seignioragePool := k.mtk.PeekSeignioragePool(ctx, epoch) + seignioragePool := k.mtk.PeekEpochSeigniorage(ctx, epoch) rewardAmt := k.GetRewardWeight(ctx, epoch).MulInt(seignioragePool) seigniorageReward := sdk.NewDecCoinFromDec(assets.MicroLunaDenom, rewardAmt) - microSDRReward, err := k.mk.GetSwapDecCoins(ctx, seigniorageReward, assets.MicroSDRDenom) + microSDRReward, err := k.mk.GetSwapDecCoin(ctx, seigniorageReward, assets.MicroSDRDenom) if err != nil { return sdk.ZeroDec() } diff --git a/x/treasury/indicator_test.go b/x/treasury/indicator_test.go index 18c15995b..5511ce0f2 100644 --- a/x/treasury/indicator_test.go +++ b/x/treasury/indicator_test.go @@ -1,9 +1,10 @@ package treasury import ( + "testing" + "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" - "testing" "github.com/stretchr/testify/require" @@ -40,11 +41,15 @@ func TestSeigniorageRewardsForEpoch(t *testing.T) { sAmt := sdk.NewInt(1000) lnasdrRate := sdk.NewDec(10) + SeigniorageRewardsForEpoch(input.ctx, input.treasuryKeeper, util.GetEpoch(input.ctx)) + // Set random prices input.oracleKeeper.SetLunaSwapRate(input.ctx, assets.MicroSDRDenom, lnasdrRate) + input.ctx = input.ctx.WithBlockHeight(util.BlocksPerEpoch) + // Add seigniorage - input.mintKeeper.AddSeigniorage(input.ctx, sAmt) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sAmt)) // Get seigniorage rewards seigniorageProceeds := SeigniorageRewardsForEpoch(input.ctx, input.treasuryKeeper, util.GetEpoch(input.ctx)) @@ -72,7 +77,7 @@ func TestMiningRewardsForEpoch(t *testing.T) { }) // Add seigniorage - input.mintKeeper.AddSeigniorage(input.ctx, amt) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, amt)) tProceeds := TaxRewardsForEpoch(input.ctx, input.treasuryKeeper, util.GetEpoch(input.ctx)) sProceeds := SeigniorageRewardsForEpoch(input.ctx, input.treasuryKeeper, util.GetEpoch(input.ctx)) @@ -165,11 +170,13 @@ func TestRollingAverageIndicator(t *testing.T) { // Test all of our reporting functions input.oracleKeeper.SetLunaSwapRate(input.ctx, assets.MicroSDRDenom, sdk.OneDec()) + input.mintKeeper.PeekEpochSeigniorage(input.ctx, sdk.ZeroInt()) for i := int64(201); i <= 500; i++ { input.ctx = input.ctx.WithBlockHeight(util.BlocksPerEpoch * i) input.treasuryKeeper.RecordTaxProceeds(input.ctx, sdk.Coins{sdk.NewCoin(assets.MicroSDRDenom, sdk.NewInt(i).MulRaw(assets.MicroUnit))}) - input.mintKeeper.AddSeigniorage(input.ctx, sdk.NewInt(i).MulRaw(assets.MicroUnit)) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(i).MulRaw(assets.MicroUnit))) + input.treasuryKeeper.SetRewardWeight(input.ctx, sdk.OneDec()) } diff --git a/x/treasury/keeper.go b/x/treasury/keeper.go index b3e64dd29..3f1820693 100644 --- a/x/treasury/keeper.go +++ b/x/treasury/keeper.go @@ -1,7 +1,6 @@ package treasury import ( - "github.com/terra-project/core/types" "github.com/terra-project/core/types/util" "github.com/cosmos/cosmos-sdk/codec" @@ -18,23 +17,19 @@ type Keeper struct { mtk MintKeeper mk MarketKeeper - dk DistributionKeeper - fck FeeCollectionKeeper paramSpace params.Subspace } // NewKeeper constructs a new keeper func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, valset sdk.ValidatorSet, - mtk MintKeeper, mk MarketKeeper, dk DistributionKeeper, fck FeeCollectionKeeper, paramspace params.Subspace) Keeper { + mtk MintKeeper, mk MarketKeeper, paramspace params.Subspace) Keeper { return Keeper{ cdc: cdc, key: key, valset: valset, mtk: mtk, mk: mk, - dk: dk, - fck: fck, paramSpace: paramspace.WithKeyTable(paramKeyTable()), } } @@ -77,40 +72,6 @@ func (k Keeper) GetRewardWeight(ctx sdk.Context, epoch sdk.Int) (rewardWeight sd return } -//----------------------------------- -// Claims logic - -// AddClaim adds a claim to the store, to be settled and cleared at the end of the epoch -func (k Keeper) AddClaim(ctx sdk.Context, claim types.Claim) { - store := ctx.KVStore(k.key) - claimKey := keyClaim(claim.ID()) - - // If the recipient has an existing claim in the same class, add to the previous claim - if bz := store.Get(claimKey); bz != nil { - var prevClaim types.Claim - k.cdc.MustUnmarshalBinaryLengthPrefixed(bz, &prevClaim) - claim.Weight = claim.Weight.Add(prevClaim.Weight) - } - bz := k.cdc.MustMarshalBinaryLengthPrefixed(claim) - store.Set(claimKey, bz) -} - -// IterateClaims iterates over all the claims in the store. -func (k Keeper) IterateClaims(ctx sdk.Context, handler func(types.Claim) (stop bool)) { - store := ctx.KVStore(k.key) - claimIter := sdk.KVStorePrefixIterator(store, PrefixClaim) - - defer claimIter.Close() - for ; claimIter.Valid(); claimIter.Next() { - var claim types.Claim - k.cdc.MustUnmarshalBinaryLengthPrefixed(claimIter.Value(), &claim) - - if handler(claim) { - break - } - } -} - //----------------------------------- // Params logic @@ -174,7 +135,7 @@ func (k Keeper) GetTaxCap(ctx sdk.Context, denom string) (taxCap sdk.Int) { // Tax cap does not exist for the asset; compute it by // comparing it with the tax cap for TerraSDR referenceCap := k.GetParams(ctx).TaxPolicy.Cap - reqCap, err := k.mk.GetSwapCoins(ctx, referenceCap, denom) + reqCap, _, err := k.mk.GetSwapCoin(ctx, referenceCap, denom, true) // The coin is more valuable than TaxPolicy asset. just follow the Policy Cap. if err != nil { diff --git a/x/treasury/keeper_test.go b/x/treasury/keeper_test.go index ba24aecc5..9d4f39d47 100644 --- a/x/treasury/keeper_test.go +++ b/x/treasury/keeper_test.go @@ -1,10 +1,10 @@ package treasury import ( - "github.com/terra-project/core/types" + "testing" + "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" - "testing" "github.com/stretchr/testify/require" @@ -56,33 +56,6 @@ func TestTax(t *testing.T) { require.Equal(t, sdrCap.Amount.MulRaw(100), krwCap) } -func TestClaim(t *testing.T) { - input := createTestInput(t) - - for i := 0; i < 99; i++ { - oracleClaim := types.NewClaim( - types.OracleClaimClass, sdk.OneInt(), addrs[i%3], - ) - input.treasuryKeeper.AddClaim(input.ctx, oracleClaim) - - budgetClaim := types.NewClaim( - types.OracleClaimClass, sdk.OneInt(), addrs[i%3], - ) - input.treasuryKeeper.AddClaim(input.ctx, budgetClaim) - } - - // There should only be 3 unique claims, for each of the three addresses. - // Each claim should have coalesced its weight to 33. - counter := 0 - input.treasuryKeeper.IterateClaims(input.ctx, func(claim types.Claim) bool { - counter++ - require.Equal(t, int64(66), claim.Weight.Int64()) - return false - }) - - require.Equal(t, 3, counter) -} - func TestParams(t *testing.T) { input := createTestInput(t) diff --git a/x/treasury/params.go b/x/treasury/params.go index c027e1917..0d8d3ff35 100644 --- a/x/treasury/params.go +++ b/x/treasury/params.go @@ -19,9 +19,6 @@ type Params struct { WindowShort sdk.Int `json:"window_short"` WindowLong sdk.Int `json:"window_long"` WindowProbation sdk.Int `json:"window_probation"` - - OracleClaimShare sdk.Dec `json:"oracle_share"` - BudgetClaimShare sdk.Dec `json:"budget_share"` } // NewParams creates a new param instance @@ -30,7 +27,6 @@ func NewParams( seigniorageBurden sdk.Dec, miningIncrement sdk.Dec, windowShort, windowLong, windowProbation sdk.Int, - oracleShare, budgetShare sdk.Dec, ) Params { return Params{ TaxPolicy: taxPolicy, @@ -40,8 +36,6 @@ func NewParams( WindowShort: windowShort, WindowLong: windowLong, WindowProbation: windowProbation, - OracleClaimShare: oracleShare, - BudgetClaimShare: budgetShare, } } @@ -60,7 +54,7 @@ func DefaultParams() Params { // Reward update policy PolicyConstraints{ RateMin: sdk.NewDecWithPrec(5, 2), // 5% - RateMax: sdk.NewDecWithPrec(20, 2), // 20% + RateMax: sdk.NewDecWithPrec(90, 2), // 90% ChangeRateMax: sdk.NewDecWithPrec(25, 3), // 2.5% Cap: sdk.NewCoin("unused", sdk.ZeroInt()), // UNUSED }, @@ -71,9 +65,6 @@ func DefaultParams() Params { sdk.NewInt(4), sdk.NewInt(52), sdk.NewInt(12), - - sdk.NewDecWithPrec(1, 1), // 10% - sdk.NewDecWithPrec(9, 1), // 90% ) } @@ -96,11 +87,6 @@ func validateParams(params Params) error { return fmt.Errorf("treasury parameter RewardPolicy.RateMin must be >= 0, is %s", params.RewardPolicy.RateMin.String()) } - shareSum := params.OracleClaimShare.Add(params.BudgetClaimShare) - if !shareSum.Equal(sdk.OneDec()) { - return fmt.Errorf("treasury parameter ClaimShares must sum to 1, but sums to %s", shareSum.String()) - } - return nil } @@ -115,10 +101,6 @@ func (params Params) String() string { WindowShort : %v WindowLong : %v - - OracleClaimShare : %v - BudgetClaimShare : %v `, params.TaxPolicy, params.RewardPolicy, params.SeigniorageBurdenTarget, - params.MiningIncrement, params.WindowShort, params.WindowLong, - params.OracleClaimShare, params.BudgetClaimShare) + params.MiningIncrement, params.WindowShort, params.WindowLong) } diff --git a/x/treasury/querier.go b/x/treasury/querier.go index 6674c8360..fb4dfd7e9 100644 --- a/x/treasury/querier.go +++ b/x/treasury/querier.go @@ -1,7 +1,6 @@ package treasury import ( - "github.com/terra-project/core/types" "github.com/terra-project/core/types/util" "github.com/cosmos/cosmos-sdk/codec" @@ -37,8 +36,6 @@ func NewQuerier(keeper Keeper) sdk.Querier { return queryTaxProceeds(ctx, path[1:], req, keeper) case QuerySeigniorageProceeds: return querySeigniorageProceeds(ctx, path[1:], req, keeper) - case QueryActiveClaims: - return queryActiveClaims(ctx, req, keeper) case QueryIssuance: return queryIssuance(ctx, path[1:], req, keeper) case QueryCurrentEpoch: @@ -128,7 +125,7 @@ func querySeigniorageProceeds(ctx sdk.Context, path []string, req abci.RequestQu return nil, sdk.ErrInternal("epoch parameter is not correctly formatted") } - pool := keeper.mtk.PeekSeignioragePool(ctx, epoch) + pool := keeper.mtk.PeekEpochSeigniorage(ctx, epoch) bz, err := codec.MarshalJSONIndent(keeper.cdc, pool) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) @@ -145,20 +142,6 @@ func queryCurrentEpoch(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([ return bz, nil } -func queryActiveClaims(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { - claims := types.ClaimPool{} - keeper.IterateClaims(ctx, func(claim types.Claim) (stop bool) { - claims = append(claims, claim) - return false - }) - - bz, err := codec.MarshalJSONIndent(keeper.cdc, claims) - if err != nil { - return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) - } - return bz, nil -} - func queryParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, sdk.Error) { bz, err := codec.MarshalJSONIndent(keeper.cdc, keeper.GetParams(ctx)) if err != nil { diff --git a/x/treasury/querier_test.go b/x/treasury/querier_test.go index 335684dcd..3a0b36f66 100644 --- a/x/treasury/querier_test.go +++ b/x/treasury/querier_test.go @@ -1,11 +1,12 @@ package treasury import ( + "strings" + "testing" + "github.com/terra-project/core/types" "github.com/terra-project/core/types/assets" "github.com/terra-project/core/types/util" - "strings" - "testing" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -246,12 +247,15 @@ func TestQuerySeigniorageProceeds(t *testing.T) { input := createTestInput(t) querier := NewQuerier(input.treasuryKeeper) + getQueriedSeigniorageProceeds(t, input.ctx, input.cdc, querier, util.GetEpoch(input.ctx)) + + input.ctx = input.ctx.WithBlockHeight(util.BlocksPerEpoch) seigniorageProceeds := sdk.NewCoin(assets.MicroLunaDenom, sdk.NewInt(10).MulRaw(assets.MicroUnit)) - input.mintKeeper.AddSeigniorage(input.ctx, seigniorageProceeds.Amount) + input.mintKeeper.Mint(input.ctx, addrs[0], sdk.NewCoin(assets.MicroLunaDenom, seigniorageProceeds.Amount)) queriedSeigniorageProceeds := getQueriedSeigniorageProceeds(t, input.ctx, input.cdc, querier, util.GetEpoch(input.ctx)) - require.Equal(t, queriedSeigniorageProceeds, seigniorageProceeds) + require.Equal(t, seigniorageProceeds, queriedSeigniorageProceeds) } func TestQueryIssuance(t *testing.T) { @@ -264,30 +268,5 @@ func TestQueryIssuance(t *testing.T) { queriedIssuance := getQueriedIssuance(t, input.ctx, input.cdc, querier, assets.MicroSDRDenom) - require.Equal(t, queriedIssuance, issuance) -} - -func TestQueryActiveClaims(t *testing.T) { - input := createTestInput(t) - querier := NewQuerier(input.treasuryKeeper) - - input.treasuryKeeper.AddClaim(input.ctx, types.NewClaim( - types.OracleClaimClass, sdk.NewInt(10), addrs[0], - )) - input.treasuryKeeper.AddClaim(input.ctx, types.NewClaim( - types.BudgetClaimClass, sdk.NewInt(10), addrs[0], - )) - input.treasuryKeeper.AddClaim(input.ctx, types.NewClaim( - types.OracleClaimClass, sdk.NewInt(10), addrs[1], - )) - input.treasuryKeeper.AddClaim(input.ctx, types.NewClaim( - types.BudgetClaimClass, sdk.NewInt(10), addrs[1], - )) - input.treasuryKeeper.AddClaim(input.ctx, types.NewClaim( - types.OracleClaimClass, sdk.NewInt(10), addrs[2], - )) - - queriedActiveClaims := getQueriedActiveClaims(t, input.ctx, input.cdc, querier) - - require.Equal(t, 5, len(queriedActiveClaims)) + require.Equal(t, issuance, queriedIssuance) } diff --git a/x/treasury/test_common.go b/x/treasury/test_common.go index e2199d706..9b622611c 100644 --- a/x/treasury/test_common.go +++ b/x/treasury/test_common.go @@ -89,6 +89,7 @@ func createTestInput(t *testing.T) testInput { keyDistr := sdk.NewKVStoreKey(distr.StoreKey) tKeyDistr := sdk.NewTransientStoreKey(distr.TStoreKey) keyFeeCollection := sdk.NewKVStoreKey(auth.FeeStoreKey) + keyMarket := sdk.NewKVStoreKey(market.StoreKey) cdc := newTestCodec() db := dbm.NewMemDB() @@ -106,6 +107,7 @@ func createTestInput(t *testing.T) testInput { ms.MountStoreWithDB(tKeyStaking, sdk.StoreTypeTransient, db) ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(tKeyDistr, sdk.StoreTypeTransient, db) + ms.MountStoreWithDB(keyMarket, sdk.StoreTypeIAVL, db) require.NoError(t, ms.LoadLatestVersion()) @@ -172,11 +174,21 @@ func createTestInput(t *testing.T) testInput { oracleKeeper := oracle.NewKeeper( cdc, keyOracle, + mintKeeper, + distrKeeper, + feeCollectionKeeper, stakingKeeper.GetValidatorSet(), paramsKeeper.Subspace(oracle.DefaultParamspace), ) - marketKeeper := market.NewKeeper(oracleKeeper, mintKeeper, paramsKeeper.Subspace(market.DefaultParamspace)) + marketKeeper := market.NewKeeper( + cdc, + keyMarket, + oracleKeeper, + mintKeeper, + paramsKeeper.Subspace(market.DefaultParamspace)) + + marketKeeper.SetParams(ctx, market.DefaultParams()) treasuryKeeper := NewKeeper( cdc, @@ -184,8 +196,6 @@ func createTestInput(t *testing.T) testInput { stakingKeeper.GetValidatorSet(), mintKeeper, marketKeeper, - distrKeeper, - feeCollectionKeeper, paramsKeeper.Subspace(DefaultParamspace), )