How does ERC20 Token Mining Work?

We are all familar with Bitcoin and most of us are even familiar with how it is mined: using Proof of Work. On one hand, it seems as though mining is wasteful. However, without mining, Bitcoin would not exist. Therefore it is indeed very useful. No worse than using gas to run a car.

Mining is a method for mathematically distributing a token. One can allow mathematics to hold the reigns of a currency instead of giving control to human. This is genius! You will notice that unlike most tokens, Bitcoin has no central owner. Indeed many people proposed ideas for allowing ERC20 tokens to be mined. Here is one example:

Interestingly, a few projects tried this but every single one failed to succeed. Mostly because they forgot one key element: the difficulty target has to be able to adjust. Exhibit A: http://bitcoinereum.com/. Bitcoinereum was a mineable token, but the Proof of Work never adjusts. Accounts are limited to X calls per unit of time, but this is easily cheated since anyone can make thousands of Ethereum accounts. Therefore, it is not a Pure Proof of Work mined token, it has a weird time and accounts component.

Every good technology evolves in small steps. In February of 2018, the 0xBitcoin contract was deployed which has an automatic difficulty adjustment mechanism built into the solidity code of the contract. That way, as the mining hashrate increases, the tokens become harder and harder to mint. Just like Bitcoin. So far this project has been a resounding success and over 1 TH of hashpower is trying to mine these tokens to use them as trustless currency on the Ethereum network. How does it work? Lets pull it apart.

The contract is here:

Inside, the first thing we notice is that no tokens are given to the deployer upon deployment. There is no initial reward or bonus to anyone. All tokens must be mined with this method:

function mint(uint256 nonce, bytes32 challenge_digest) public returns (bool success) {
uint reward_amount = getMiningReward();

bytes32 digest = keccak256(challengeNumber, msg.sender, nonce );

if (digest != challenge_digest) revert();

//the digest must be smaller than the target
if(uint256(digest) > miningTarget) revert();

uint hashFound = rewardHashesFound[digest];
rewardHashesFound[digest] = epochCount;
if(hashFound != 0) revert(); //prevent the same answer from awarding twice

balances[msg.sender] = balances[msg.sender].add(reward_amount);

tokensMinted = tokensMinted.add(reward_amount);

//set readonly diagnostics data
lastRewardTo = msg.sender;
lastRewardAmount = reward_amount;
lastRewardEthBlockNumber = block.number;

//start a new round of mining with a new 'challengeNumber'
_startNewMiningEpoch();

Mint(msg.sender, reward_amount, epochCount, challengeNumber );

return true;
}

Interesting !! The mint() method will only pay out tokens if a rare nonce is found, one which has a sha hash that starts with many zeroes, because it is smaller than the difficulty target. So what the heck does _startNewMiningEpoch() do ? It initializes the next block to be mined.

//a new 'block' to be mined
function _startNewMiningEpoch() internal {
//if max supply for the era will be exceeded next reward round then enter the new era before that happens
//40 is the final reward era, almost all tokens minted
//once the final era is reached, more tokens will not be given out because the assert function
if( tokensMinted.add(getMiningReward()) > maxSupplyForEra && rewardEra < 39)
{
rewardEra = rewardEra + 1;
}
//set the next minted supply at which the era will change
// total supply is 2100000000000000 because of 8 decimal places
maxSupplyForEra = _totalSupply - _totalSupply.div( 2**(rewardEra + 1));
epochCount = epochCount.add(1);
//every so often, readjust difficulty. Dont readjust when deploying
if(epochCount % _BLOCKS_PER_READJUSTMENT == 0)
{
_reAdjustDifficulty();
}
//make the latest ethereum block hash a part of the next challenge for PoW to prevent pre-mining future blocks
//do this last since this is a protection mechanism in the mint() function
challengeNumber = block.blockhash(block.number - 1);
}

The reward era will increase if there are enough tokens minted. The max supply changes as well. The difficulty readjustment method is called every 1024 method calls, or every 1024 mints.

Okay, so how does the difficulty readjust?

function _reAdjustDifficulty() internal {
uint ethBlocksSinceLastDifficultyPeriod = block.number - latestDifficultyPeriodStarted;
//assume 360 ethereum blocks per hour
//we want miners to spend 10 minutes to mine each 'block', about 60 ethereum blocks = one 0xbitcoin epoch
uint epochsMined = _BLOCKS_PER_READJUSTMENT; //256
uint targetEthBlocksPerDiffPeriod = epochsMined * 60; //should be 60 times slower than ethereum
//if there were less eth blocks passed in time than expected
if( ethBlocksSinceLastDifficultyPeriod < targetEthBlocksPerDiffPeriod )
{
uint excess_block_pct = (targetEthBlocksPerDiffPeriod.mul(100)).div( ethBlocksSinceLastDifficultyPeriod );
uint excess_block_pct_extra = excess_block_pct.sub(100).limitLessThan(1000);
// If there were 5% more blocks mined than expected then this is 5. If there were 100% more blocks mined than expected then this is 100.
//make it harder
miningTarget = miningTarget.sub(miningTarget.div(2000).mul(excess_block_pct_extra)); //by up to 50 %
}else{
uint shortage_block_pct = (ethBlocksSinceLastDifficultyPeriod.mul(100)).div( targetEthBlocksPerDiffPeriod );
uint shortage_block_pct_extra = shortage_block_pct.sub(100).limitLessThan(1000); //always between 0 and 1000
//make it easier
miningTarget = miningTarget.add(miningTarget.div(2000).mul(shortage_block_pct_extra)); //by up to 50 %
}
latestDifficultyPeriodStarted = block.number;
if(miningTarget < _MINIMUM_TARGET) //very difficult
{
miningTarget = _MINIMUM_TARGET;
}
if(miningTarget > _MAXIMUM_TARGET) //very easy
{
miningTarget = _MAXIMUM_TARGET;
}
}

Every 1024 mints, this method examines the number of Eth blocks that occured since the last time this method was called. If too few Eth blocks passed (less than 1024 * 60) then the difficulty will increase up to 150% of current. If too many Eth blocks passed, the difficulty will decrease down to 50% of current.

And that is about it! The rest of the contract is just a standard ERC20 token contract.

Now, a currency which is analogous to BTC operates -within- the Ethereum network. It is pure PoW mined, it is not managed by any human, it cannot be upgraded or deleted by anyone. The contract is locked. The 0xBTC tokens are compatible with all ERC20 services and Ethereum accounts and contracts. What can we do with this ?