# 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 through adjusting rewards, the Market is responsible for Terra price-stability through arbitrage and market maker.

# Concepts

# Observed Indicators

The Treasury observes three macroeconomic indicators for each epoch (set to 1 week) and keeps historical records of their values during previous epochs.

  • Tax Rewards: , Income generated from transaction fees (stability fee) in a during the epoch.
  • Seigniorage Rewards: , Amount of seignorage generated from Luna swaps to Terra during the epoch that is destined for ballot rewards inside the Oracle rewards.
  • Total Staked Luna: , total Luna that has been staked by users and bonded by their delegated validators.

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 , simply 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 long-term (WindowLong) rolling averages of the above indicators to determine the relative direction and velocity of the Terra economy.

# Monetary Policy Levers

NOTE

From Columbus-3, the Reward Weight lever replaces the previous lever for controlling the rate of Luna burn in seigniorage. Now, miners are compensated through burning from swap fees, and ballot rewards in the oracle.

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

  • Reward Weight which is the portion of seigniorage allocated for the reward pool for the ballot winners for correctly voting within the reward band of the weighted median of exchange rate in the Oracle module.

# Updating Policies

Both Tax Rate and Reward Weight are stored as values in the KVStore, and can have their values updated through governance proposals once passed. The Treasury will also re-calibrate each lever once per epoch to stabilize unit returns for Luna, thereby 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 burden seigniorage needed to bear the overall reward profile, SeigniorageBurdenTarget, and hikes up rates accordingly, described here.

# Probation

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

# Data

# PolicyConstraints

Policy updates from both governance proposals and automatic calibration are constrained by the TaxPolicy and RewardPolicy parameters, respectively. The type PolicyConstraints specifies the floor, ceiling, and the 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(), shown below.

// 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
}
JSON Example
{
  "type": "treasury/TaxRateUpdateProposal",
  "value": {
    "title": "proposal title",
    "description": "proposal description",
    "tax_rate": "0.001000000000000000"
  }
}
Events
Type Attribute Key Attribute Value
tax_rate_update tax_rate {taxRate}

# RewardWeightUpdateProposal

type RewardWeightUpdateProposal struct {
	Title        string  `json:"title" yaml:"title"`                 // Title of the Proposal
	Description  string  `json:"description" yaml:"description"`     // Description of the Proposal
	RewardWeight sdk.Dec `json:"reward_weight" yaml:"reward_weight"` // target RewardWeight
}
JSON Example
{
  "type": "treasury/RewardWeightUpdateProposal",
  "value": {
    "title": "proposal title",
    "description": "proposal description",
    "reward_weight": "0.001000000000000000"
  }
}
Events
Type Attribute Key Attribute Value
reward_weight_update reward_weight {rewardWeight}

# State

# Tax Rate

  • type: Dec
  • default: 0.1%

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

# Reward Weight

  • type: Dec
  • default: 5%

The value of the Reward Weight policy lever for the current epoch.

# Tax Caps

  • type: map[string]Int

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

For instance, if a transaction's value were 100 SDT, and tax rate and tax cap 5% and 1 SDT respectively, the income generated from the transaction would be 1 SDT instead of 5 SDT, as it exceeds the tax cap.

# 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 to distribute at the end of the epoch.

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 convenience.

# Indicators

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

# Tax Rewards

  • type: Dec

The Tax Rewards for the epoch.

# Seigniorage Rewards

  • type: Dec

The Seigniorage Rewards for the epoch.

# Total Staked Luna

  • type: Int

The Total Staked Luna for the epoch.

# Functions

# k.UpdateIndicators()

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

This function gets run at the end of an epoch and records the current values of tax rewards , seigniorage rewards , and total staked Luna as the historic indicators for epoch before moving to the next epoch .

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

# k.UpdateTaxPolicy()

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

This function gets called at the end of an epoch to calculate the next value of the Tax Rate monetary lever.

Consider to be the current Tax Rate, and to be 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 thus be set to the maximum permitted by the Tax Policy, subject to the rules of pc.Clamp() (see constraints).

  4. Otherwise, the new Tax Rate is , subject to the rules of pc.Clamp() (see constraints).

As such, the Treasury hikes up Tax Rate when tax revenues in a shorter time window is performing poorly in comparison to the longer term tax revenue average. It lowers Tax Rate when short term tax revenues are outperforming the longer term index.

# k.UpdateRewardPolicy()

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

This function gets called at the end of an epoch to calculate the next value of the Reward Weight monetary lever.

Consider to be the current reward weight, and to be the SeigniorageBurdenTarget parameter.

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

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

  3. If either or there was no mining and seigniorage rewards in the last month. The Rewards Weight should thus be set to the maximum permitted by the Reward Policy, subject to the rules of pc.Clamp() (see constraints).

  4. Otherwise, the new Reward Weight is , subject to the rules of pc.Clamp() (see constraints).

# 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 each denomination in circulation, the new Tax Cap for that 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.

# 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.

  4. Calculate the Tax Rate, Reward Weight, and Tax Cap for the next epoch.

  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.