Skip to content

Commit

Permalink
Add Mana calculator (#1158)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dr-Electron authored Nov 8, 2023
1 parent 57a81f5 commit 268b453
Show file tree
Hide file tree
Showing 42 changed files with 2,071 additions and 5 deletions.
40 changes: 40 additions & 0 deletions docs/learn/protocols/iota2.0/core-concepts/mana-calculator.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import ManaCalculator from '@site/src/components/ManaCalculator';

# Mana Calculator

Mana is a reward resource generated by holding IOTA tokens. It is utilized for block issuance, access to network throughput, protection against Sybil attacks, and various other services. Our incentives scheme allows you to stake or delegate IOTA tokens to receive Mana rewards, in addition to the Mana generated simply by holding your IOTA tokens, without having to participate in any consensus-related activity.

Here, we introduce the Mana calculator: a tool to help you make decisions about the type of participation that will suit you best, to help you predict the Mana generation you’ll be granted, and to decide the number of IOTA tokens you should hold to guarantee your desired level of access in the future.

## How to use the Calculator: Step-by-step instructions

- **Choose your network:** This calculator works for both Shimmer and the future IOTA 2.0 network. Both networks will have their own type of Mana (IOTA Mana and Shimmer Mana), with their own parameters and, consequently, their own behavior. Be sure you select the correct scenario you want to simulate!

- **Choose your role:** Will you delegate to a validator, become a validator yourself, or just hold tokens? To be a validator, you must run a node and lock your tokens while performing validation services, which will give you higher rewards. If you want to delegate to a validator, your tokens will not be locked and you won’t need to run a node, but this will give you fewer rewards than validating. Finally, just holding tokens grants you a certain amount of Mana, which will be less than by validating or delegating.

- **Input the number of tokens you own:** No matter which network and role you choose, your Mana generation will depend on how many available tokens you have. Pay attention to the units! The input should be done in IOTA or Shimmer (not microIota or Glow).

With the inputs above, you can already estimate how much Mana you’ll be granted per epoch. But what does it mean in TPS? How many blocks per second does your Mana grant you? The answer depends on the size of your block and the network congestion levels. However, we can use a default block size, which will be enough for most applications, and assume certain levels of congestion. This takes us to the last step in this calculator:

- **Choose a congestion level:** Given a certain Mana generation per epoch, this can be translated into a number of blocks issued per second that depends on the congestion level. Choose one of the given congestion levels (low, stable, or extreme) to estimate how many blocks per second your tokens and participation grant you! This metric is also shown in another way: time until block issuance, which tells you in which periodicity you’ll be able to issue a block.

<ManaCalculator/>


## Advanced settings

The steps outlined above can give you a rough estimate of your Mana generation and granted TPS. However, playing with the advanced settings will give you more precise results.

- **Change the state of the system:** The calculator's default settings assume a certain level of participation in the consensus (i.e., locked stake, delegated stake, and performance factor of validators). Those settings can be changed under the “Advanced Settings - State of the System” tab. You can also add or delete validators and change their fixed costs.

- If you choose to delegate:
- **Change the validator you delegate to:** Under the default settings, you'll delegate to Validator 1 (which is pool that, technically speaking, is under the "equilibrium state", see the WP for more details). However, you can change this setting and know your rewards if you participate in other pools, with different share of delegated to locked stake, and different performance factors.

- If you choose to validate:
- **Change the amount of stake delegated to you:** The calculator's default settings automatically assign you a certain share of the delegated stake when you start validating. However, you can change this setting to know your rewards should you manage to attract more (or less) delegated stake than the default setting.
- **Change your performance factor:** Your performance factor under the calculator's default settings is 0.95. However, you can change this to simulate larger or smaller shares of online time.
- **Change your fixed costs:** By the default settings of the calculator, your fixed costs are zero. This setting can be changed so you spare part of the pool rowards just for yourself. However, notice that the fixed cost is a public parameter, that you define during registration. This means that the delegator knows how you set this value and might consider it when choosing a validator to delegate. Furthermore, if you set this value too high, you'll be punished and won't get any rewards.

- **Simulate your Mana accumulation**: the default setting of the calculator assumes a stationary regime and calculates the block creation rate you guarantee depending on your stake and level of participation. However, especially in the early stages of the network, you might want to save your Mana to sell later. This tab simulates how much Mana you'll accumulate in a certain time period. For that (besides the other already defined inputs), you just need to choose an initial and final epoch, and the tool will plot a graph of your accumulation during that period. In the same graph, you can also have an idea of how many blocks can be issued with this accumulated amount of Mana, for the different congestion levels (already defined in the non-advanced settings).


2 changes: 1 addition & 1 deletion docs/learn/protocols/iota2.0/core-concepts/mana.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Tokenomics: Mana, Accounts, Staking and _Delegation_
# Tokenomics: Mana, Accounts, Staking and Delegation

Mana is a scarce resource used to access the IOTA ledger and update its state through block creation. It is a spendable asset tracked in the _ledger state_, powering smart contracts, DeFi_applications, block creation, and various other services, and is linked to [accounts](#accounts), which allow you to [stake or delegate](#staking-and-delegation-1) IOTA to receive [Mana rewards](#mana-rewards).

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"flickity-fade": "^2.0.0",
"globby": "^13.1.4",
"hast-util-is-element": "1.1.0",
"humanize-duration": "^3.30.0",
"plugin-image-zoom": "flexanalytics/plugin-image-zoom",
"raw-loader": "^4.0.2",
"react": "18.2.0",
Expand All @@ -62,6 +63,7 @@
"react-player": "^2.11.2",
"react-popper": "^2.3.0",
"react-select": "^5.7.7",
"recharts": "^2.9.2",
"rehype-jargon": "3.0.0",
"rehype-katex": "4",
"rehype-lodash-template": "^0.2.1",
Expand Down
5 changes: 5 additions & 0 deletions src/components/ManaCalculator/actions/calculateBPS.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EPOCH_DURATION } from '../constants';

export function calculateBPS(mana: number, congestion: number): number {
return mana / congestion / EPOCH_DURATION;
}
145 changes: 145 additions & 0 deletions src/components/ManaCalculator/actions/calculateManaRewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { getNetworkSupply } from '../utils';
import type { ValidatorParameters, ValidatorProps } from '../types';
import { NetworkType, UserType } from '../enums';
import { decay } from './decay';
import { targetReward } from './targetReward';

export function calculateManaRewards(
stake: number,
yourPool: number,
validatorParameters: ValidatorParameters,
validators: ValidatorProps[],
initialEpoch: number,
finalEpoch: number,
userType: UserType,
networkType: NetworkType,
generationPerSlot: number,
): number {
const supply = getNetworkSupply(networkType);
let totalTargetReward = 0;
let epochDiff = finalEpoch - initialEpoch;

if (finalEpoch) {
for (let i = 0; i < epochDiff; i++) {
totalTargetReward += decay(
targetReward(initialEpoch + i, supply, generationPerSlot),
epochDiff - i,
);
}
} else {
epochDiff = 1;
finalEpoch = initialEpoch + 1;
totalTargetReward = targetReward(initialEpoch, supply, generationPerSlot);
}

const lockedStake: number[] = validators.map(
(validator) => validator.lockedStake,
);
const fixedCosts: number[] = validators.map(
(validator) => validator.fixedCost,
);
const performance: number[] = validators.map(
(validator) => validator.performanceFactor,
);
const delegatedStake: number[] = validators.map(
(validator) => validator.delegatedStake,
);

if (userType == UserType.VALIDATOR) {
lockedStake.push(stake * validatorParameters.shareOfYourStakeLocked);
fixedCosts.push(validatorParameters.fixedCost);
performance.push(validatorParameters.performanceFactor);
delegatedStake.push(
(1 - validatorParameters.shareOfYourStakeLocked) * stake +
validatorParameters.attractedNewDelegatedStake +
validatorParameters.attractedDelegatedStakeFromOtherPools *
delegatedStake.reduce((a, b) => a + b, 0),
);
for (let i = 0; i < validators.length; i++) {
delegatedStake[i] *=
1 - validatorParameters.attractedDelegatedStakeFromOtherPools;
}
}

if (userType == UserType.DELEGATOR) {
delegatedStake[yourPool] += stake;
}

const totalValidatorsStake = lockedStake.reduce((a, b) => a + b, 0);
const totalDelegatedStake = delegatedStake.reduce((a, b) => a + b, 0);
const totalStake = totalDelegatedStake + totalValidatorsStake;
const restOfTokenHoldings = supply - totalStake;
if (restOfTokenHoldings < 0) {
throw new Error('Pools must have (collectively) at most iotaSupply tokens');
}

// Calculates profit margin of the epoch (only when there are tokens stake, otherwise, it's set to None)
let profitMargin: number | null;
if (totalStake > 0) {
profitMargin = totalValidatorsStake / (totalValidatorsStake + totalStake);
} else {
profitMargin = null;
}

// Calculates the total rewards for each pool, already discounting the validator fixed cost
const poolRewards: number[] = new Array(lockedStake.length).fill(0);
if (totalStake > 0) {
if (totalValidatorsStake > 0) {
for (let i = 0; i < lockedStake.length; i++) {
poolRewards[i] =
((lockedStake[i] + delegatedStake[i]) / totalStake +
lockedStake[i] / totalValidatorsStake) *
totalTargetReward *
(performance[i] / 2.0) -
epochDiff * fixedCosts[i];
}
} else {
for (let i = 0; i < lockedStake.length; i++) {
poolRewards[i] =
((lockedStake[i] + delegatedStake[i]) / totalStake) *
totalTargetReward *
(performance[i] / 2.0) -
epochDiff * fixedCosts[i];
}
}
}

// Calculates the rewards for each validator
const validatorRewards: number[] = new Array(lockedStake.length).fill(0);
for (let i = 0; i < lockedStake.length; i++) {
if (poolRewards[i] < 0) {
validatorRewards[i] = 0;
poolRewards[i] = 0;
} else if (poolRewards[i] === 0) {
validatorRewards[i] = epochDiff * fixedCosts[i];
} else {
validatorRewards[i] =
epochDiff * fixedCosts[i] +
poolRewards[i] * profitMargin +
((1 - profitMargin) * poolRewards[i] * lockedStake[i]) /
(delegatedStake[i] + lockedStake[i]);
}
}

const delegatorRewards: number[] = new Array(lockedStake.length).fill(0);
for (let i = 0; i < lockedStake.length; i++) {
if (poolRewards[i] > 0) {
delegatorRewards[i] =
(poolRewards[i] * (1 - profitMargin) * delegatedStake[i]) /
(delegatedStake[i] + lockedStake[i]);
}
}

let rewards = 0;
if (userType == UserType.DELEGATOR) {
if (delegatorRewards[yourPool] > 0) {
rewards = (delegatorRewards[yourPool] * stake) / delegatedStake[yourPool];
}
}

if (userType == UserType.VALIDATOR) {
rewards = validatorRewards[lockedStake.length - 1];
}

return rewards;
}
16 changes: 16 additions & 0 deletions src/components/ManaCalculator/actions/calculatePassiveRewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { getFirstSlotOfEpoch } from '../utils';
import { getPotentialMana } from './getPotentialMana';

export function calculatePassiveRewards(
tokens: number,
initialEpoch: number,
finalEpoch: number,
generationPerSlot: number,
): number {
return getPotentialMana(
tokens,
getFirstSlotOfEpoch(initialEpoch) - 1,
getFirstSlotOfEpoch(finalEpoch) - 1,
generationPerSlot,
);
}
12 changes: 12 additions & 0 deletions src/components/ManaCalculator/actions/decay.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { BETA_PER_YEAR, EPOCH_DURATION_IN_YEARS } from '../constants';

// Returns the decayed value of value by the amount of epochs
export function decay(value: number, epochsAmount: number): number {
if (value != 0 && epochsAmount != 0) {
const decay = Math.exp(
-BETA_PER_YEAR * EPOCH_DURATION_IN_YEARS * epochsAmount,
);
value = Math.floor(value * decay);
}
return value;
}
47 changes: 47 additions & 0 deletions src/components/ManaCalculator/actions/getPotentialMana.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
BETA_PER_YEAR,
EPOCH_DURATION_IN_YEARS,
SLOTS_IN_EPOCH,
} from '../constants';
import { getFirstSlotOfEpoch, slotToEpoch } from '../utils';
import { decay } from './decay';

// Returns the potential mana generated by holding value tokens from creationSlot to consumptionSlot
export function getPotentialMana(
value: number,
creationSlot: number,
consumptionSlot: number,
generationRate: number,
): number {
const creationEpoch = slotToEpoch(creationSlot);
const consumptionEpoch = slotToEpoch(consumptionSlot);

const epochsLeftToConsumption = consumptionEpoch - creationEpoch;

const slotAfterCreationEpoch = getFirstSlotOfEpoch(creationEpoch + 1);
const firstSlotConsumptionEpoch = getFirstSlotOfEpoch(consumptionEpoch);

const d1 = slotAfterCreationEpoch - creationSlot;
const d2 = consumptionSlot - firstSlotConsumptionEpoch;

let potentialMana = 0;
if (epochsLeftToConsumption == 0) {
potentialMana = value * (consumptionSlot - creationSlot) * generationRate;
} else if (epochsLeftToConsumption == 1) {
potentialMana =
value * d2 * generationRate + decay(value * d1 * generationRate, 1);
} else {
const c =
Math.exp(-BETA_PER_YEAR * EPOCH_DURATION_IN_YEARS) /
(1 - Math.exp(-BETA_PER_YEAR * EPOCH_DURATION_IN_YEARS));
const aux = value * generationRate * c * SLOTS_IN_EPOCH;
const potentialMana_n = decay(
value * d1 * generationRate,
epochsLeftToConsumption,
);
const potentialMana_n_1 = decay(aux, epochsLeftToConsumption - 1);
const potentialMana_0 = value * d2 * generationRate + aux;
potentialMana = potentialMana_n - potentialMana_n_1 + potentialMana_0;
}
return potentialMana;
}
5 changes: 5 additions & 0 deletions src/components/ManaCalculator/actions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './calculateManaRewards';
export * from './calculatePassiveRewards';
export * from './calculateBPS';
export * from './getPotentialMana';
export * from './decay';
34 changes: 34 additions & 0 deletions src/components/ManaCalculator/actions/targetReward.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import {
BETA_PER_YEAR,
BOOTSTRAPPING_DURATION,
EPOCH_DURATION_IN_YEARS,
REWARDS_MANA_SHARE_COEFFICIENT,
SLOTS_IN_EPOCH,
} from '../constants';
import { decay } from './decay';

// Returns the target reward for the given epoch
export function targetReward(
epoch: number,
supply: number,
generationPerSlot: number,
): number {
const finalReward =
supply *
REWARDS_MANA_SHARE_COEFFICIENT *
generationPerSlot *
SLOTS_IN_EPOCH;
const decayBalancingConstant =
Math.exp(1) /
(BOOTSTRAPPING_DURATION *
(1 - Math.exp(-EPOCH_DURATION_IN_YEARS * BETA_PER_YEAR)));
const initialReward = finalReward * decayBalancingConstant;

let reward = 0;
if (epoch <= BOOTSTRAPPING_DURATION) {
reward = decay(initialReward, epoch);
} else {
reward = finalReward;
}
return reward;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Details } from '@docusaurus/theme-common/Details';
import React from 'react';
import { useManaState } from '../hooks';
import { ValidatorCard } from './ValidatorCard';

export function AdvancedSettingsValidator() {
const { handleAddValidator, state } = useManaState();

function onAddValidator() {
handleAddValidator({
lockedStake: 100,
delegatedStake: 0,
performanceFactor: 1.0,
fixedCost: 0.0,
});
}

return (
<Details
summary='Advanced Settings - Validators'
className='mana_calculator__card mana_calculator_inner__card table'
>
<div className='table'>
<div className='row small-row-head'>
<div className='col col--2 text--center'>ID</div>
<div className='col col--2 horizontal-spaced text--center'>
Stake ({state.network})
</div>
<div className='col col--2 horizontal-space text--center'>
Delegated ({state.network})
</div>
<div className='col col--2 horizontal-spaced text--center'>
Performance factor
</div>
<div className='col col--2 horizontal-spaced text--center'>
Fixed costs
</div>
<div className='col col--1'></div>
</div>
{state.validators.map((validator, i) => (
<div className='row row--centered' key={i}>
<ValidatorCard validator={validator} id={i} />
</div>
))}
</div>
<button
className='button button--block mana-calculator__button '
onClick={onAddValidator}
>
New Validator
</button>
</Details>
);
}
Loading

0 comments on commit 268b453

Please sign in to comment.