Dynamic Rewards - Algorithm Design and Code Breakdown
Fellow TechTalk readers,
For the third article of our TechTalk series, it is time we look into our new dynamic rewards calculation.
We will be going through the mathematical framework behind it, the code and algorithmic nuances involved.
Our intention with this article is to provide users with a thorough understanding of how our dynamic rewards system works.
Our approach to dynamic rewards is inspired by Milton Friedman’s vision of a system governed by fixed rules rather than flexible policies. Just as Friedman advocated for replacing central banks with a computer program to ensure steady monetary growth, we use fixed parameters (5% for the total emission rate and 10% for the circulating emission rate) and real-time blockchain metrics to dynamically adjust rewards.
Our dynamic rewards system aims to maintain economic stability by adjusting block rewards in response to changes in total and circulating supply. A key factor in this approach is the use of fixed percentages for calculations and dynamic adjustments based on real blockchain indicators.
Our algorithm ensures accurate coin supply calculations and stable block rewards by:
- Analyzing the UTXO Set
It analyzes the entire Unspent Transaction Output (UTXO) set to determine the current coin supply. - Excluding Specific UTXOs
It excludes burn addresses and UTXOs matching the current and upcoming masternode collateral values. - Age-Based UTXO Weighting
UTXOs are weighted based on their age:
- Fully counting those less than three months old.
- Linearly scaling down those between three to twelve months old.
- Excluding all UTXOs older than twelve months. - Monitoring Supply Dynamics
The algorithm monitors coin supply dynamics and adjusts emission rates based on the total and circulating supplies when needed. Once the exact values of the total and circulating supplies are determined, the system computes the emission rate for the current period. This process also involves anticipating expected minting, determined by specified percentages of the total and circulating supplies. - Damping Function
A damping function moderates changes in the block reward through a linear approach, ranging from 0% to 10% with each adjustment.
# Getting total money supply and masternode collaterals
First thing to do is get the total money supply and masternode collaterals (current and next weeks).
code reference: view code
CMasternode::GetMasternodeNodeCollateral(nHeight)
Is called to determine how much collateral is needed for a masternode at this specific point on the blockchain (nHeight).
Likewise, CMasternode::GetMasternodeNodeCollateral(nHeight + nBlocksPerWeek) is called to determine how much collateral is needed for a masternode exactly one week ahead of the current block.
nBlocksPerWeek
The variable that represents the number of blocks produced in one week and as such, this value is added to the current block height (nHeight) to predict what the collateral amount requirement will be one week later.
# Calculating the current circulating supply
Now that we have both our total money supply and masternode collaterals, we will be calculating the current circulating supply to then be able to calculate the emission rate for the current period.
The circulating supply is obtained by analyzing the entire UTXO set and excluding burn addresses and UTXOs matching the current and upcoming masternode collateral values.
The function initializes the circulating supply to zero and then iterates through the UTXO set using a cursor.
For each UTXO, it checks if the address is a burn address or a masternode collateral and if so, it skips these UTXOs as these are not considered to be unlocked coins and can’t be transacted with.
For UTXOs that are not skipped, it calculates the supply weight ratio based on their age and adjusts the circulating supply accordingly.
UTXOs that are 3 months old or less get a weight of 100% whilst UTXOs that are 12 months old or more get a weight of 0%.
code reference: view code
nCirculatingSupply
Is set at 0. It will accumulate the total value of all UTXOs considered to be part of the circulating supply.
A cursor is created for iterating through the UTXOs and the loop continues as long as the cursor points to a valid entry in the UTXO set.
Scanning burn addresses
code reference: view code
We start by extracting the destination addresses and converting these to a string.
After doing so, we look for these in the mBurnAddresses map which stores all burn addresses.
If the address is found, it means the UTXO is locked to a burn address and as such, we skip to the next UTXO.
Scanning masternode collaterals
code reference: view code
Here we check for UTXOs that match the current and upcoming masternode collateral values and if so, we do not account for them in the circulating supply calculation.
The code checks if the value of the current UTXO ( coin.out.nValue ) is equal to either the current masternode collateral amount ( nCollateralAmount ) or the next week’s collateral amount ( nNextWeekCollateralAmount ) and if so, it advances to the next UTXO.
UTXOs age related scanning
code reference: view code
Here we count UTXOs based on their age, fully counting those less than three months old, linearly scaling down those between three to twelve months, and excluding all UTXOs older than twelve months.
The code starts by calculating the difference in blocks between the current block height and the UTXO’s creation block height which will represent the UTXO age. This is stored in nBlocksDiff.
Depending on the age just calculated, the linear function in the code will decrease the supply weight ratio from 100% for coins that are 3 months old or younger to 0% for coins that are 12 months or older.
nMultiplier
Is set at 100000000 to scale numbers in the calculations and handle calculations with 8-decimal precision.
We use it to improve the precision of intermediate calculations, specifically integer divisions and fractional values.
In Bitcoin-based coins, integer calculations are essential to avoid the imprecision of floating-point operations, which can lead to non-deterministic results and blockchain forks. By using nMultiplier, we ensure precise calculations.
((100LL * nMultiplier)/(9LL * nBlocksPerMonth)
This is the slope of our linear function and we will use it to determine how much the supply weight will decrease for each block beyond 3 months old
- (100LL * nMultiplier)
represents the initial weight (100%) scaled up by the multiplier for calculation purposes - (9LL* nBlocksPerMonth)
represents the number of blocks in 9 months
(nBlocksDiff — 3LL * nBlocksPerMonth)
- nBlocksDiff
represents the UTXO age or number of blocks since the UTXO was created. - 3LL * nBlocksPerMonth
represents the number of blocks in 3 months.
If this subtraction is negative or zero, then the UTXO is less than 3 months old. If not, then the UTXO is older than 3 months and the weight decrease per block is calculated using the slope.
(100LL * nMultiplier — (((100LL * nMultiplier)/(9LL * nBlocksPerMonth)) * (nBlocksDiff — 3LL * nBlocksPerMonth))) / nMultiplier,
This represents our initial weight (100%) minus the weight decrease due to age. The decrease is proportional to the number of blocks the coin has been in circulation beyond 3 months.
nCirculatingSupply += coin.out.nValue * nSupplyWeightRatio / 100LL;
Lastly, we adjust our circulating supply taking into consideration that coins with a higher weight ratio contribute more, while coins with a lower weight ratio contribute less.
# Calculating the epoch’s average staking power
Our estimated circulating supply has now been calculated but we’re still missing one piece: we still need to calculate the epoch’s average staking power to have it subtracted from our circulating supply and then we’ll have the final one.
An epoch represents a specific interval in the blockchain, defined by a fixed number of blocks over which staking activity and reward adjustments are calculated. In our code, an epoch is determined by the nRewardAdjustmentInterval parameter.
code reference: view code
We start by getting our RewardAdjustmentInterval (RAI) that, as the name suggests, defines the period over which staking activity and reward adjustments are calculated.
nTimeSlotLength
Is obtained from the consensus parameters and depends on the current block height (nHeight). It defines the length of time slots used in the calculation of the staked coins
endBlock
Is set to the most recent block.
startBlock
Is set to the block at the beginning of the current reward adjustment interval.
nTimeDiff
Is the difference in time between the start and end blocks, which gives the total time elapsed during the reward adjustment interval.
nWorkDiff
Is the difference in the chain work between the start and end blocks. Chain work typically represents the total number of hashes that are expected to have been necessary to produce the current chain.
nNetworkHashPS
The network hash rate is calculated by dividing the work difference by the time difference. This provides the average hash rate (work per second) over the reward adjustment interval.
nStakedCoins
Is calculated by multiplying the network hash rate by the time slot length and a constant (100 in this case). This gives an estimate of the number of coins that are staked on the network over the time slot.
Removing the staked supply from circulating supply
We have obtained our staked supply and we can now have it subtracted from our circulating supply since it isn’t circulating.
code reference: view code
This calculation adjusts the circulating supply by removing the amount of staked coins from it. This is done because staked coins are not available to transact with and as such, should not be included in the circulating supply.
# Calculating target emissions
Next up, we’ll be calculating our target emissions. By having a target for emissions, the supply growth can be more predictable allowing for a more stable economic environment.
code reference: view code
nTotalEmissionRate
Is the total emission rate (TER) that represents the emission rate of new coins for the entire supply.
nCirculatingEmissionRate
Is the circulating emission rate (CER) that represents the emission rate of new coins to the circulating supply.
Sporks are being used here for testing and development purposes. In the final implementation, fixed percentage values will be used, with the total emission rate set at 5% and the circulating emission rate at 10%.
nActualEmission
Is the actual emission (AE) for the current reward period which is obtained by multiplying the reward per block (nSubsidy) with the number of blocks inside the interval at which rewards are adjusted (RAI).
Next up, we’ll be calculating our supply target emission (STE) and circulating target emission (CTE) which will then be used to calculate the target emission (TE).
nSupplyTargetEmission
Is the supply target emission (STE) and it can be calculated by multiplying our daily average supply with the total emission rate (TER) and the reward adjustment interval (RAI).
Our daily average supply can be calculated by dividing our total money supply by the total number of blocks generated in a year (365LL * nBlocksPerDay).
nCirculatingTargetEmission
Is the circulating target emission (CTE) and it can be calculated by multiplying our daily average circulating supply with the circulating emission rate (CER) and the reward adjustment interval (RAI).
Our daily average circulating supply can be calculated by dividing our circulating supply by the total number of blocks generated in a year (365LL * nBlocksPerDay).
nTargetEmission
Is our target emission (TE) and it is the average of the supply target emission (STE) and the circulating target emission (CTE). It results from the sum of the supply target emission and circulating target emission divided by 2.
# Calculating required delta values
After calculating both our actual emission (AE) and target emission (TE) it is time we use these to find our required delta values.
code reference: view code
nDelta
Represents how much the actual emission deviates from the target emission. It is the difference between the actual emission (AE) and the target emission (TE) over the reward adjustment interval (RAI).
nRatio
The nDelta is converted to a percentage relative to the current reward (nSubsidy) and stored on nRatio.
nWeightRatio
Determine a dampening factor (nWeightRatio) based on the previously calculated nRatio, which is mapped to a range between 1 and 10.
This creates a linear damping function where:
- at 0% deviation, the weight ratio is 1.
- at 100% deviation, the weight ratio is 10.
- for deviations between 0% and 100%, the weight ratio scales linearly from 1 to 10
nDampedDelta
Is calculated by applying the weighted ratio which will represent the dampened adjustment to be applied.
# Adjusting reward for the epoch
code reference: view code
nNewSubsidy
After calculating our adjusted delta value, we can use it to adjust the rewards for the current epoch and we do so by subtracting it from the current block reward.
Since fractional values might result from this operation, we’ll be converting it to the nearest integer, removing any fractional part.
We hope that you’ve enjoyed this read and that it has shed some light on how our dynamic rewards system works.
Stay tuned for our next TechTalk series article where we’ll be exploring the concept of deterministic builds and how these improve security and ensure consistency across different environments.
About DECENOMY TechTalk
DECENOMY TechTalk is an independent hub for developers, tech enthusiasts, and innovators passionate about decentralized technologies. Our focus is on the technical side of the decentralized ecosystem, offering in-depth articles, tutorials, and discussions on blockchain development, smart contracts, decentralized applications (dApps), and more.
This platform is created by and for the tech community, committed to exploring the tools, frameworks, and innovations that drive decentralized systems. We believe in the power of open-source collaboration and strive to provide a space where technical knowledge is freely shared and where new ideas can take root.
Whether you are a seasoned developer or just starting out, DECENOMY TechTalk is your resource for staying up-to-date with the latest developments in decentralized technology. Our mission is to empower developers to innovate, create, and push the boundaries of what’s possible in the decentralized world.
Join us as we explore the future of tech — driven by curiosity, innovation, and a passion for decentralization.