Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mana calculator #1158

Merged
merged 45 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
e823ede
Add Delegator Component
Dr-Electron Aug 20, 2023
b38912e
Add Validator Component
Dr-Electron Aug 27, 2023
55edaa6
Add accumulation tab
Dr-Electron Aug 27, 2023
9888b67
Fix react-select styling
Dr-Electron Sep 3, 2023
eacf444
Hide validators behind advanced settings
Dr-Electron Sep 3, 2023
2c9123b
Add 'Add' buttons
Dr-Electron Sep 3, 2023
d17806c
Basic css touch ups
lucas-tortora Sep 4, 2023
1fa3160
restyle validator boxes
lucas-tortora Oct 13, 2023
8ecf3aa
Remove 'epoch' in TPS tab
Dr-Electron Oct 15, 2023
afae7a1
Move the advanced setting to a less obvious place
Dr-Electron Oct 15, 2023
3a4be25
Hide some of the validator’s settings
Dr-Electron Oct 15, 2023
b3d5d4d
Make needed inputs numbers
Dr-Electron Oct 15, 2023
302bad6
Swap 'passiveRewards' and 'manaGeneratedPerEpoch'
Dr-Electron Oct 16, 2023
eaa86c2
Don't use micro
Dr-Electron Oct 16, 2023
5817c2d
add mana calc text
oliviasaa Oct 20, 2023
05b8eef
Merge branch 'mana-calculator' of https://github.com/iotaledger/iota-…
oliviasaa Oct 20, 2023
6fa6a7a
Update src/components/ManaCalculator/utils.ts
Dr-Electron Oct 24, 2023
5c6832c
Update mana-calculator.md (#1283)
DafPhil Oct 26, 2023
ef790cf
Update mana-calculator.md (#1284)
DafPhil Oct 26, 2023
7b7513a
Merge branch 'main' into mana-calculator
Dr-Electron Oct 26, 2023
f8d39eb
feat: Flexible network configuration for Mana calculator (#1289)
marc2332 Oct 27, 2023
1652d1d
Format
Dr-Electron Oct 27, 2023
c6ff575
Lint
Dr-Electron Oct 28, 2023
c36d196
Merge branch 'main' into mana-calculator
Dr-Electron Nov 2, 2023
b47899d
Fix epochDiff
Dr-Electron Nov 2, 2023
a7e46e5
Merge remote-tracking branch 'origin/main' into mana-calculator
begonaalvarezd Nov 3, 2023
b13c9b0
feat: Improved user flow of Mana Calculator (#1314)
marc2332 Nov 5, 2023
493e754
chore: Refactor mana calculator (#1315)
marc2332 Nov 6, 2023
09e1db9
feat: Add user total holding input in Mana Calculator (#1316)
marc2332 Nov 6, 2023
67c2d3b
feat: Mana accumulation graph in Mana Calculator (#1317)
marc2332 Nov 6, 2023
b0f1dc7
feat: Block issuance rate graph in Mana Calculator (#1318)
marc2332 Nov 6, 2023
2003952
Merge branch 'main' into mana-calculator
Dr-Electron Nov 6, 2023
f2182f2
Add time to issue default block (#1323)
Dr-Electron Nov 7, 2023
36f07e6
Format
Dr-Electron Nov 7, 2023
daed1df
fix: remove redundant *1000 in humanized duration
begonaalvarezd Nov 7, 2023
e73ce44
feat: UI polish (#1321)
marc2332 Nov 8, 2023
b7d163f
feat: Mana calculator units (#1324)
marc2332 Nov 8, 2023
9e1f314
Use BPS instead of TPS (#1327)
Dr-Electron Nov 8, 2023
7798427
feat: Final values for networks in Mana Calculator (#1325)
marc2332 Nov 8, 2023
c7e7722
Format
Dr-Electron Nov 8, 2023
5cca6c0
Remove Mana calc from Mana page
Dr-Electron Nov 8, 2023
5adcb2a
Remove unused var
Dr-Electron Nov 8, 2023
4d921bf
Fix linting errors
jlvandenhout Nov 8, 2023
413a470
Apply suggestions from code review
Dr-Electron Nov 8, 2023
5a147f6
Fix typo
Dr-Electron Nov 8, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/learn/protocols/iota2.0/core-concepts/mana-calculator.md
Dr-Electron marked this conversation as resolved.
Show resolved Hide resolved
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.
Dr-Electron marked this conversation as resolved.
Show resolved Hide resolved

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

You're almost there! 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:
Dr-Electron marked this conversation as resolved.
Show resolved Hide resolved

- **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).


4 changes: 3 additions & 1 deletion docs/learn/protocols/iota2.0/core-concepts/mana.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Tokenomics: Mana, Accounts, Staking and _Delegation_
import ManaCalculator from '@site/src/components/ManaCalculator';

# Tokenomics: Mana, Accounts, Staking and Delegation

Dr-Electron marked this conversation as resolved.
Show resolved Hide resolved
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
Loading