# Treasury

The Treasury module acts as the "central bank" of the Terra economy, measuring macroeconomic activity by observing indicators and adjusting monetary policy levers to modulate miner incentives toward stable, long-term growth.

Note:

While the Treasury stabilizes miner demand by adjusting rewards, the Market module is responsible for Terra price-stability through arbitrage and the market maker.

# Concepts

# Observed Indicators

The Treasury observes three macroeconomic indicators for each epoch and keeps indicators of their values during previous epochs:

  • Tax Rewards: , the income generated from transaction and stability fees during an epoch.
  • Seigniorage Rewards*: , the amount of seigniorage generated from Luna swaps to Terra during an epoch which is destined for ballot rewards inside the Oracle rewards. As of Columbus-5, all seigniorage is burned.
  • Total Staked Luna: , the total amount of Luna staked by users and bonded to their delegated validators.

These indicators are used to derive two other values:

Note:

As of Columbus-5, all seigniorage is burned and no longer funds community or reward pools.

  • Seigniorage Rewards:: , The amount of seigniorage generated from Luna swaps to Terra during each epoch.

Note:

As of Columbus-5, all seigniorage is burned.

These indicators can be used to derive two other values, the Tax Reward per unit Luna represented by , used in Updating Tax Rate, and total mining rewards : the sum of the Tax Rewards and the Seigniorage Rewards, used in Updating Reward Weight.

The protocol can compute and compare the short-term (WindowShort) and the long-term (WindowLong) rolling averages of the above indicators to determine the relative direction and velocity of the Terra economy.

# Monetary Policy Levers

  • Tax Rate: , adjusts the amount of income gained from Terra transactions, limited by tax cap.

  • Reward Weight: , the portion of seigniorage allocated to the reward pool for Oracle vote winners. This is given to validtors who vote within the reward band of the weighted median exchange rate.

Tip

As of Columbus-5, all seigniorage is burned and no longer funds the community pool or the oracle reward pool. Validators are rewarded for faithful oracle votes through swap fees.

# Updating Policies

Both Tax Rate and Reward Weight are stored as values in the KVStore and can have their values updated through governance proposals after they have passed. The Treasury recalibrates each lever once per epoch to stabilize unit returns for Luna, ensuring predictable mining rewards from staking:

  • For Tax Rate, in order to make sure that unit mining rewards do not stay stagnant, the treasury adds a MiningIncrement so mining rewards increase steadily over time, described here.

  • For Reward Weight, the Treasury observes the portion of seigniorage needed to bear the overall reward profile, SeigniorageBurdenTarget, and raises rates accordingly, as described here. The current Reward Weight is 1.

# Probation

A probationary period specified by the WindowProbation prevents the network from performing Tax Rate and Reward Weight updates during the first epochs after genesis to allow the blockchain to first obtain a critical mass of transactions and a mature, reliable history of indicators.

# Data

# PolicyConstraints

Policy updates from governance proposals and automatic calibration are constrained by the TaxPolicy and RewardPolicy parameters, respectively. PolicyConstraints specifies the floor, ceiling, and max periodic changes for each variable.

// PolicyConstraints defines constraints around updating a key Treasury variable
type PolicyConstraints struct {
    RateMin       sdk.Dec  `json:"rate_min"`
    RateMax       sdk.Dec  `json:"rate_max"`
    Cap           sdk.Coin `json:"cap"`
    ChangeRateMax sdk.Dec  `json:"change_max"`
}

The logic for constraining a policy lever update is performed by pc.Clamp().

// Clamp constrains a policy variable update within the policy constraints
func (pc PolicyConstraints) Clamp(prevRate sdk.Dec, newRate sdk.Dec) (clampedRate sdk.Dec) {
	if newRate.LT(pc.RateMin) {
		newRate = pc.RateMin
	} else if newRate.GT(pc.RateMax) {
		newRate = pc.RateMax
	}

	delta := newRate.Sub(prevRate)
	if newRate.GT(prevRate) {
		if delta.GT(pc.ChangeRateMax) {
			newRate = prevRate.Add(pc.ChangeRateMax)
		}
	} else {
		if delta.Abs().GT(pc.ChangeRateMax) {
			newRate = prevRate.Sub(pc.ChangeRateMax)
		}
	}
	return newRate
}

# Proposals

The Treasury module defines special proposals which allow the Tax Rate and Reward Weight values in the KVStore to be voted on and changed accordingly, subject to the policy constraints imposed by pc.Clamp().

# TaxRateUpdateProposal

type TaxRateUpdateProposal struct {
	Title       string  `json:"title" yaml:"title"`             // Title of the Proposal
	Description string  `json:"description" yaml:"description"` // Description of the Proposal
	TaxRate     sdk.Dec `json:"tax_rate" yaml:"tax_rate"`       // target TaxRate
}

Note:

As of Columbus-5, all seigniorage is burned. The Reward Weight is now set to 1.

# State

# Tax Rate

  • type: Dec
  • min: .1%
  • max: 1%

The value of the Tax Rate policy lever for the current epoch.

# Reward Weight

  • type: Dec
  • default: 1

The value of the Reward Weight policy lever for the current epoch. As of Columbus-5, the reward weight is set to 1.

# Tax Caps

  • type: map[string]Int

The Treasury keeps a KVStore that maps a denomination denom to an sdk.Int which represents the maximum income that can be generated from taxes on a transaction in that same denomination. This is updated every epoch with the equivalent value of TaxPolicy.Cap at the current exchange rate.

For example, if a transaction's value were 100 SDT with a tax rate of 5% and a tax cap of 1 SDT, the income generated would be 1 SDT, not 5 SDT.

# Tax Proceeds

  • type: Coins

The Tax Rewards for the current epoch.

# Epoch Initial Issuance

  • type: Coins

The total supply of Luna at the beginning of the current epoch. This value is used in k.SettleSeigniorage() to calculate the seigniorage distributed at the end of each epoch. As of Columbus 5, all seigniorage is burned.

Recording the initial issuance will automatically use the Supply module to determine the total issuance of Luna. Peeking will return the epoch's initial issuance of ┬ÁLuna as sdk.Int instead of sdk.Coins for clarity.

# Indicators

The Treasury keeps track of following indicators for the present and previous epochs:

# Tax Rewards

  • type: Dec

The Tax Rewards for each epoch.

# Seigniorage Rewards

  • type: Dec

The Seigniorage Rewards for each epoch.

# Total Staked Luna

  • type: Int

The Total Staked Luna for each epoch.

# Functions

# k.UpdateIndicators()

func (k Keeper) UpdateIndicators(ctx sdk.Context)

At the end of each epoch , this function records the current values of tax rewards , seigniorage rewards , and total staked Luna before moving to the next epoch .

  • is the current value in TaxProceeds
  • , with epoch seigniorage and reward weight .
  • is the result of staking.TotalBondedTokens()

# k.UpdateTaxPolicy()

func (k Keeper) UpdateTaxPolicy(ctx sdk.Context) (newTaxRate sdk.Dec)

At the end of each epoch, this funtion calculates the next value of the Tax Rate monetary lever.

Using as the current Tax Rate and as the MiningIncrement parameter:

  1. Calculate the rolling average of Tax Rewards per unit Luna over the last year WindowLong.

  2. Calculate the rolling average of Tax Rewards per unit Luna over the last month WindowShort.

  3. If , there was no tax revenue in the last month. The Tax Rate should be set to the maximum permitted by the Tax Policy, subject to the rules of pc.Clamp() (see constraints).

  4. If , the new Tax Rate is , subject to the rules of pc.Clamp(). See constraints for more details.

When monthly tax revenues dip below the yearly average, the Treasury raises the Tax Rate. When monthly tax revenues go above the yearly average, the Treasury lowers the Tax Rate.

# k.UpdateRewardPolicy()

func (k Keeper) UpdateRewardPolicy(ctx sdk.Context) (newRewardWeight sdk.Dec)

At the end of each epoch, this funtion calculates the next value of the Reward Weight monetary lever.

Using as the current reward weight, and as the SeigniorageBurdenTarget parameter:

  1. Calculate the sum of seigniorage rewards over the last month WindowShort.

  2. Calculate the sum of total mining rewards over the last month WindowShort.

  3. If and , there were no mining or seigniorage rewards in the last month. The Reward Weight should be set to the maximum permitted by the Reward Policy, subject to the rules of pc.Clamp(). See constraints for more details.

  4. If or , the new Reward Weight is , subject to the rules of pc.Clamp(). See constraints for more details.

Note:

As of Columbus-5, all seigniorage is burned and no longer funds the community or reward pools.

# k.UpdateTaxCap()

func (k Keeper) UpdateTaxCap(ctx sdk.Context) sdk.Coins

This function is called at the end of an epoch to compute the Tax Caps for every denomination for the next epoch.

For every denomination in circulation, the new Tax Cap for each denomination is set to be the global Tax Cap defined in the TaxPolicy parameter, at current exchange rates.

# k.SettleSeigniorage()

func (k Keeper) SettleSeigniorage(ctx sdk.Context)

This function is called at the end of an epoch to compute seigniorage and forwards the funds to the Oracle module for ballot rewards, and the Distribution for the community pool.

  1. The seigniorage of the current epoch is calculated by taking the difference between the Luna supply at the start of the epoch (Epoch Initial Issuance) and the Luna supply at the time of calling.

    Note that when the current Luna supply is lower than at the start of the epoch, because the Luna had been burned from Luna swaps into Terra. See here.

  2. The Reward Weight is the percentage of the seigniorage designated for ballot rewards. Amount of new Luna is minted, and the Oracle module receives of the seigniorage.

  3. The remainder of the coins is sent to the Distribution module, where it is allocated into the community pool.

Note:

As of Columbus-5, all seigniorage is burned and no longer funds the community pool or the oracle reward pool. Validators are rewarded for faithful oracle votes through swap fees.

# Transitions

# End-Block

If the blockchain is at the final block of the epoch, the following procedure is run:

  1. Update all the indicators with k.UpdateIndicators()

  2. If the this current block is under probation, skip to step 6.

  3. Settle seigniorage accrued during the epoch and make funds available to ballot rewards and the community pool during the next epoch. As of Columbus-5, all seigniorage is burned.

  4. Calculate the Tax Rate, Reward Weight, and Tax Cap for the next epoch. As of Columbus-5, all seigniorage is burned, and the reward weight is set to 1.

  5. Emit the policy_update event, recording the new policy lever values.

  6. Finally, record the Luna issuance with k.RecordEpochInitialIssuance(). This will be used in calculating the seigniorage for the next epoch.

Events
Type Attribute Key Attribute Value
policy_update tax_rate {taxRate}
policy_update reward_weight {rewardWeight}
policy_update tax_cap {taxCap}

# Parameters

The subspace for the Treasury module is treasury.

type Params struct {
	TaxPolicy               PolicyConstraints `json:"tax_policy" yaml:"tax_policy"`
	RewardPolicy            PolicyConstraints `json:"reward_policy" yaml:"reward_policy"`
	SeigniorageBurdenTarget sdk.Dec           `json:"seigniorage_burden_target" yaml:"seigniorage_burden_target"`
	MiningIncrement         sdk.Dec           `json:"mining_increment" yaml:"mining_increment"`
	WindowShort             int64             `json:"window_short" yaml:"window_short"`
	WindowLong              int64             `json:"window_long" yaml:"window_long"`
	WindowProbation         int64             `json:"window_probation" yaml:"window_probation"`
}

# TaxPolicy

  • type: PolicyConstraints
  • default:
DefaultTaxPolicy = PolicyConstraints{
    RateMin:       sdk.NewDecWithPrec(5, 4), // 0.05%
    RateMax:       sdk.NewDecWithPrec(1, 2), // 1%
    Cap:           sdk.NewCoin(core.MicroSDRDenom, sdk.OneInt().MulRaw(core.MicroUnit)), // 1 SDR Tax cap
    ChangeRateMax: sdk.NewDecWithPrec(25, 5), // 0.025%
}

Constraints / rules for updating the Tax Rate monetary policy lever.

# RewardPolicy

  • type: PolicyConstraints
  • default:
DefaultRewardPolicy = PolicyConstraints{
    RateMin:       sdk.NewDecWithPrec(5, 2), // 5%
    RateMax:       sdk.NewDecWithPrec(90, 2), // 90%
    ChangeRateMax: sdk.NewDecWithPrec(25, 3), // 2.5%
    Cap:           sdk.NewCoin("unused", sdk.ZeroInt()), // UNUSED
}

Constraints / rules for updating the Reward Weight monetary policy lever.

# SeigniorageBurdenTarget

  • type: sdk.Dec
  • default: 67%

Multiplier specifying portion of burden seigniorage needed to bear the overall reward profile for Reward Weight updates during epoch transition.

# MiningIncrement

  • type: sdk.Dec
  • default: 1.07 growth rate, 15% CAGR of

Multiplier determining an annual growth rate for Tax Rate policy updates during epoch transition.

# WindowShort

  • type: int64
  • default: 4 (month = 4 weeks)

A number of epochs that specifies a time interval for calculating short-term moving average.

# WindowLong

  • type: int64
  • default: 52 (year = 52 weeks)

A number of epochs that specifies a time interval for calculating long-term moving average.

# WindowProbation

  • type: int64
  • default: 12 (3 months = 12 weeks)

A number of epochs that specifies a time interval for the probationary period.