Issuer: two-phase issuance

pull/1884/head
vzotova 2020-04-03 17:02:33 +03:00
parent aac486afbb
commit c958b52570
5 changed files with 398 additions and 145 deletions

View File

@ -10,7 +10,7 @@ import "zeppelin/token/ERC20/SafeERC20.sol";
/**
* @notice Contract for calculation of issued tokens
* @dev |v3.1.1|
* @dev |v3.2.1|
*/
abstract contract Issuer is Upgradeable {
using SafeERC20 for NuCypherToken;
@ -25,13 +25,20 @@ abstract contract Issuer is Upgradeable {
NuCypherToken public immutable token;
uint128 public immutable totalSupply;
uint256 public immutable miningCoefficient;
uint256 public immutable lockedPeriodsCoefficient;
// k2 * k3
uint256 public immutable mintingCoefficient;
// k1
uint256 public immutable lockingDurationCoefficient1;
// k3
uint256 public immutable lockingDurationCoefficient2;
uint32 public immutable secondsPerPeriod;
uint16 public immutable rewardedPeriods;
uint16 public immutable maxRewardedPeriods;
uint256 public immutable maxFirstPhaseReward;
uint256 public immutable firstPhaseTotalSupply;
/**
* Current supply is used in the mining formula and is stored to prevent different calculation
* Current supply is used in the minting formula and is stored to prevent different calculation
* for stakers which get reward in the same period. There are two values -
* supply for previous period (used in formula) and supply for current period which accumulates value
* before end of period.
@ -41,47 +48,68 @@ abstract contract Issuer is Upgradeable {
uint16 public currentMintingPeriod;
/**
* @notice Constructor sets address of token contract and coefficients for mining
* @dev Mining formula for one stake in one period
(totalSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2
if allLockedPeriods > rewardedPeriods then allLockedPeriods = rewardedPeriods
* @notice Constructor sets address of token contract and coefficients for minting
* @dev Minting formula for one sub-stake in one period for the first phase
maxFirstPhaseReward * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k3
* @dev Minting formula for one sub-stake in one period for the seconds phase
(totalSupply - currentSupply) / k2 * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k3
if allLockedPeriods > maxRewardedPeriods then allLockedPeriods = maxRewardedPeriods
* @param _token Token contract
* @param _hoursPerPeriod Size of period in hours
* @param _miningCoefficient Mining coefficient (k2)
* @param _lockedPeriodsCoefficient Locked periods coefficient (k1)
* @param _rewardedPeriods Max periods that will be additionally rewarded
* @param _secondPhaseMintingCoefficient Minting coefficient for the second phase (k2)
* @param _lockingDurationCoefficient1 Numerator of he locking duration coefficient (k1)
* @param _lockingDurationCoefficient2 Denominator of the locking duration coefficient (k3)
* @param _maxRewardedPeriods Max periods that will be additionally rewarded
* @param _firstPhaseTotalSupply Total supply for the first phase
* @param _maxFirstPhaseReward Max possible reward for one period for all stakers in the first phase
*/
constructor(
NuCypherToken _token,
uint32 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint16 _rewardedPeriods
uint256 _secondPhaseMintingCoefficient,
uint256 _lockingDurationCoefficient1,
uint256 _lockingDurationCoefficient2,
uint16 _maxRewardedPeriods,
uint256 _firstPhaseTotalSupply,
uint256 _maxFirstPhaseReward
)
public
{
uint256 localTotalSupply = _token.totalSupply();
require(localTotalSupply > 0 &&
_miningCoefficient != 0 &&
_secondPhaseMintingCoefficient != 0 &&
_hoursPerPeriod != 0 &&
_lockedPeriodsCoefficient != 0 &&
_rewardedPeriods != 0);
_lockingDurationCoefficient1 != 0 &&
_lockingDurationCoefficient2 != 0 &&
_maxRewardedPeriods != 0);
require(localTotalSupply <= uint256(MAX_UINT128), "Token contract has supply more than supported");
uint256 maxLockedPeriods = _rewardedPeriods + _lockedPeriodsCoefficient;
require(maxLockedPeriods > _rewardedPeriods &&
_miningCoefficient >= maxLockedPeriods &&
// worst case for `totalLockedValue * k2`, when totalLockedValue == totalSupply
localTotalSupply * _miningCoefficient / localTotalSupply == _miningCoefficient &&
uint256 maxLockingDurationCoefficient = _maxRewardedPeriods + _lockingDurationCoefficient1;
uint256 localMintingCoefficient = _secondPhaseMintingCoefficient * _lockingDurationCoefficient2;
require(maxLockingDurationCoefficient > _maxRewardedPeriods &&
localMintingCoefficient / _secondPhaseMintingCoefficient == _lockingDurationCoefficient2 &&
// worst case for `totalLockedValue * k2 * k3`, when totalLockedValue == totalSupply
localTotalSupply * localMintingCoefficient / localTotalSupply == localMintingCoefficient &&
// worst case for `(totalSupply - currentSupply) * lockedValue * (k1 + allLockedPeriods)`,
// when currentSupply == 0, lockedValue == totalSupply
localTotalSupply * localTotalSupply * maxLockedPeriods / localTotalSupply / localTotalSupply == maxLockedPeriods,
localTotalSupply * localTotalSupply * maxLockingDurationCoefficient / localTotalSupply / localTotalSupply ==
maxLockingDurationCoefficient,
"Specified parameters cause overflow");
require(maxLockingDurationCoefficient <= _lockingDurationCoefficient2,
"Resulting locking duration coefficient must be less than 1");
require(_firstPhaseTotalSupply <= localTotalSupply, "Too many tokens for the first phase");
require(_maxFirstPhaseReward <= _firstPhaseTotalSupply, "Reward for the first phase is too high");
token = _token;
miningCoefficient = _miningCoefficient;
secondsPerPeriod = _hoursPerPeriod.mul32(1 hours);
lockedPeriodsCoefficient = _lockedPeriodsCoefficient;
rewardedPeriods = _rewardedPeriods;
lockingDurationCoefficient1 = _lockingDurationCoefficient1;
lockingDurationCoefficient2 = _lockingDurationCoefficient2;
maxRewardedPeriods = _maxRewardedPeriods;
firstPhaseTotalSupply = _firstPhaseTotalSupply;
maxFirstPhaseReward = _maxFirstPhaseReward;
totalSupply = uint128(localTotalSupply);
mintingCoefficient = localMintingCoefficient;
}
/**
@ -105,10 +133,12 @@ abstract contract Issuer is Upgradeable {
*/
function initialize(uint256 _reservedReward) external onlyOwner {
require(currentMintingPeriod == 0);
token.safeTransferFrom(msg.sender, address(this), _reservedReward);
// Reserved reward must be sufficient for at least one period of the first phase
require(maxFirstPhaseReward <= _reservedReward);
currentMintingPeriod = getCurrentPeriod();
currentPeriodSupply = totalSupply - uint128(_reservedReward);
previousPeriodSupply = currentPeriodSupply;
token.safeTransferFrom(msg.sender, address(this), _reservedReward);
emit Initialized(_reservedReward);
}
@ -136,15 +166,29 @@ abstract contract Issuer is Upgradeable {
previousPeriodSupply = currentPeriodSupply;
currentMintingPeriod = _currentPeriod;
}
uint128 currentReward = totalSupply - previousPeriodSupply;
//(totalSupply - currentSupply) * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
uint256 currentReward;
uint256 coefficient;
// first phase
// maxFirstPhaseReward * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k3)
if (previousPeriodSupply + maxFirstPhaseReward <= firstPhaseTotalSupply) {
currentReward = maxFirstPhaseReward;
coefficient = lockingDurationCoefficient2;
// second phase
// (totalSupply - currentSupply) * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2 * k3)
} else {
currentReward = totalSupply - previousPeriodSupply;
coefficient = mintingCoefficient;
}
uint256 allLockedPeriods =
AdditionalMath.min16(_allLockedPeriods, rewardedPeriods) + lockedPeriodsCoefficient;
AdditionalMath.min16(_allLockedPeriods, maxRewardedPeriods) + lockingDurationCoefficient1;
amount = (uint256(currentReward) * _lockedValue * allLockedPeriods) /
(_totalLockedValue * miningCoefficient);
(_totalLockedValue * coefficient);
// rounding the last reward
// TODO optimize
uint256 maxReward = getReservedReward();
if (amount == 0) {
amount = 1;

View File

@ -37,7 +37,7 @@ interface WorkLockInterface {
/**
* @notice Contract holds and locks stakers tokens.
* Each staker that locks their tokens will receive some compensation
* @dev |v4.1.1|
* @dev |v4.2.1|
*/
contract StakingEscrow is Issuer, IERC900History {
@ -143,10 +143,13 @@ contract StakingEscrow is Issuer, IERC900History {
* @notice Constructor sets address of token contract and coefficients for mining
* @param _token Token contract
* @param _hoursPerPeriod Size of period in hours
* @param _miningCoefficient Mining coefficient
* @param _secondPhaseMintingCoefficient Minting coefficient for the second phase (k2)
* @param _lockingDurationCoefficient1 Numerator of he locking duration coefficient (k1)
* @param _lockingDurationCoefficient2 Denominator of the locking duration coefficient (k3)
* @param _maxRewardedPeriods Max periods that will be additionally rewarded
* @param _firstPhaseTotalSupply Total supply for the first phase
* @param _maxFirstPhaseReward Max possible reward for one period for all stakers in the first phase
* @param _minLockedPeriods Min amount of periods during which tokens can be locked
* @param _lockedPeriodsCoefficient Locked blocks coefficient
* @param _rewardedPeriods Max periods that will be additionally rewarded
* @param _minAllowableLockedTokens Min amount of tokens that can be locked
* @param _maxAllowableLockedTokens Max amount of tokens that can be locked
* @param _minWorkerPeriods Min amount of periods while a worker can't be changed
@ -155,9 +158,12 @@ contract StakingEscrow is Issuer, IERC900History {
constructor(
NuCypherToken _token,
uint32 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint16 _rewardedPeriods,
uint256 _secondPhaseMintingCoefficient,
uint256 _lockingDurationCoefficient1,
uint256 _lockingDurationCoefficient2,
uint16 _maxRewardedPeriods,
uint256 _firstPhaseTotalSupply,
uint256 _maxFirstPhaseReward,
uint16 _minLockedPeriods,
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens,
@ -168,9 +174,12 @@ contract StakingEscrow is Issuer, IERC900History {
Issuer(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_rewardedPeriods
_secondPhaseMintingCoefficient,
_lockingDurationCoefficient1,
_lockingDurationCoefficient2,
_maxRewardedPeriods,
_firstPhaseTotalSupply,
_maxFirstPhaseReward
)
{
// constant `1` in the expression `_minLockedPeriods > 1` uses to simplify the `lock` method

View File

@ -40,35 +40,41 @@ def test_issuer(testerchain, token, deploy_contract):
locked_periods_coefficient=10 ** 4,
maximum_rewarded_periods=10 ** 4,
hours_per_period=1)
locking_duration_coefficient_1 = economics.maximum_rewarded_periods
locking_duration_coefficient_2 = 2 * economics.maximum_rewarded_periods
first_phase_total_supply = INITIAL_SUPPLY + 1000
max_first_phase_reward = (first_phase_total_supply - INITIAL_SUPPLY) // 3
def calculate_reward(locked, total_locked, locked_periods):
return economics.erc20_reward_supply * locked * \
(locked_periods + economics.locked_periods_coefficient) // \
def calculate_first_phase_reward(locked, total_locked, locked_periods):
return max_first_phase_reward * locked * \
(locked_periods + locking_duration_coefficient_1) // \
(total_locked * locking_duration_coefficient_2)
def calculate_second_phase_reward(locked, total_locked, locked_periods):
return (economics.erc20_reward_supply - INITIAL_SUPPLY) * locked * \
(locked_periods + locking_duration_coefficient_1) // \
(total_locked * economics.staking_coefficient)
creator = testerchain.client.accounts[0]
ursula = testerchain.client.accounts[1]
staker = testerchain.client.accounts[1]
# Only token contract is allowed in Issuer constructor
# TODO update base economics
bad_args = dict(_token=staker,
_hoursPerPeriod=economics.hours_per_period,
_secondPhaseMintingCoefficient=economics.staking_coefficient // locking_duration_coefficient_2,
_lockingDurationCoefficient1=locking_duration_coefficient_1,
_lockingDurationCoefficient2=locking_duration_coefficient_2,
_maxRewardedPeriods=economics.maximum_rewarded_periods,
_firstPhaseTotalSupply=first_phase_total_supply,
_maxFirstPhaseReward=max_first_phase_reward)
with pytest.raises((TransactionFailed, ValueError)):
deploy_contract(
contract_name='IssuerMock',
_token=ursula,
_hoursPerPeriod=economics.hours_per_period,
_miningCoefficient=economics.staking_coefficient,
_lockedPeriodsCoefficient=economics.locked_periods_coefficient,
_rewardedPeriods=economics.maximum_rewarded_periods
)
deploy_contract(contract_name='IssuerMock', **bad_args)
# Creator deploys the issuer
issuer, _ = deploy_contract(
contract_name='IssuerMock',
_token=token.address,
_hoursPerPeriod=economics.hours_per_period,
_miningCoefficient=economics.staking_coefficient,
_lockedPeriodsCoefficient=economics.locked_periods_coefficient,
_rewardedPeriods=economics.maximum_rewarded_periods
)
args = bad_args
args.update(_token=token.address)
issuer, _ = deploy_contract(contract_name='IssuerMock', **args)
events = issuer.events.Initialized.createFilter(fromBlock='latest')
# Give staker tokens for reward and initialize contract
@ -82,7 +88,7 @@ def test_issuer(testerchain, token, deploy_contract):
# Only owner can initialize
with pytest.raises((TransactionFailed, ValueError)):
tx = issuer.functions.initialize(0).transact({'from': ursula})
tx = issuer.functions.initialize(0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.initialize(economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
@ -97,39 +103,194 @@ def test_issuer(testerchain, token, deploy_contract):
tx = issuer.functions.initialize(0).transact({'from': creator})
testerchain.wait_for_receipt(tx)
# First phase
# Check result of minting tokens
tx = issuer.functions.testMint(0, 1000, 2000, 0).transact({'from': ursula})
tx = issuer.functions.testMint(0, 1000, 2000, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward = calculate_reward(1000, 2000, 0)
assert reward == token.functions.balanceOf(ursula).call()
reward = calculate_first_phase_reward(1000, 2000, 0)
assert calculate_second_phase_reward(1000, 2000, 0) != reward
assert token.functions.balanceOf(staker).call() == reward
assert token.functions.balanceOf(issuer.address).call() == balance - reward
# The result must be more because of a different proportion of lockedValue and totalLockedValue
tx = issuer.functions.testMint(0, 500, 500, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_first_phase_reward(500, 500, 0)
assert token.functions.balanceOf(staker).call() == reward
assert token.functions.balanceOf(issuer.address).call() == balance - reward
# The result must be more because of bigger value of allLockedPeriods
tx = issuer.functions.testMint(0, 500, 500, 10 ** 4).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_first_phase_reward(500, 500, 10 ** 4)
assert token.functions.balanceOf(staker).call() == reward
assert token.functions.balanceOf(issuer.address).call() == balance - reward
# The result is the same because allLockedPeriods more then specified coefficient _rewardedPeriods
period = issuer.functions.getCurrentPeriod().call()
tx = issuer.functions.testMint(period, 500, 500, 2 * 10 ** 4).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_first_phase_reward(500, 500, 10 ** 4)
assert token.functions.balanceOf(staker).call() == reward
assert token.functions.balanceOf(issuer.address).call() == balance - reward
# Still the first phase because minting period didn't change
assert issuer.functions.previousPeriodSupply().call() + max_first_phase_reward < first_phase_total_supply
assert issuer.functions.currentPeriodSupply().call() < first_phase_total_supply
assert issuer.functions.currentPeriodSupply().call() + max_first_phase_reward >= first_phase_total_supply
tx = issuer.functions.testMint(0, 100, 500, 10 ** 4).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_first_phase_reward(100, 500, 10 ** 4)
assert token.functions.balanceOf(staker).call() == reward
assert token.functions.balanceOf(issuer.address).call() == balance - reward
# Second phase
assert issuer.functions.previousPeriodSupply().call() + max_first_phase_reward < first_phase_total_supply
assert issuer.functions.currentPeriodSupply().call() < first_phase_total_supply
assert issuer.functions.currentPeriodSupply().call() + max_first_phase_reward >= first_phase_total_supply
# Check result of minting tokens
tx = issuer.functions.testMint(period + 1, 1000, 2000, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
current_reward = calculate_second_phase_reward(1000, 2000, 0)
assert calculate_first_phase_reward(500, 500, 10 ** 4) != current_reward
reward += current_reward
assert reward == token.functions.balanceOf(staker).call()
assert balance - reward == token.functions.balanceOf(issuer.address).call()
# The result must be more because of a different proportion of lockedValue and totalLockedValue
tx = issuer.functions.testMint(0, 500, 500, 0).transact({'from': ursula})
tx = issuer.functions.testMint(1, 500, 500, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_reward(500, 500, 0)
assert reward == token.functions.balanceOf(ursula).call()
reward += calculate_second_phase_reward(500, 500, 0)
assert reward == token.functions.balanceOf(staker).call()
assert balance - reward == token.functions.balanceOf(issuer.address).call()
# The result must be more because of bigger value of allLockedPeriods
tx = issuer.functions.testMint(0, 500, 500, 10 ** 4).transact({'from': ursula})
tx = issuer.functions.testMint(1, 500, 500, 10 ** 4).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_reward(500, 500, 10 ** 4)
assert reward == token.functions.balanceOf(ursula).call()
reward += calculate_second_phase_reward(500, 500, 10 ** 4)
assert reward == token.functions.balanceOf(staker).call()
assert balance - reward == token.functions.balanceOf(issuer.address).call()
# The result is the same because allLockedPeriods more then specified coefficient _rewardedPeriods
tx = issuer.functions.testMint(0, 500, 500, 2 * 10 ** 4).transact({'from': ursula})
tx = issuer.functions.testMint(1, 500, 500, 2 * 10 ** 4).transact({'from': staker})
testerchain.wait_for_receipt(tx)
reward += calculate_reward(500, 500, 10 ** 4)
assert reward == token.functions.balanceOf(ursula).call()
reward += calculate_second_phase_reward(500, 500, 10 ** 4)
assert reward == token.functions.balanceOf(staker).call()
assert balance - reward == token.functions.balanceOf(issuer.address).call()
@pytest.mark.slow
def test_inflation_rate(testerchain, token, deploy_contract):
def test_issuance_first_phase(testerchain, token, deploy_contract):
"""
Check decreasing of inflation rate after minting.
Checks stable issuance in the first phase
"""
economics = BaseEconomics(initial_supply=INITIAL_SUPPLY,
total_supply=TOTAL_SUPPLY,
staking_coefficient=2 * 10 ** 35,
locked_periods_coefficient=1,
maximum_rewarded_periods=1,
hours_per_period=1)
creator = testerchain.client.accounts[0]
staker = testerchain.client.accounts[1]
# Creator deploys the contract
# TODO update base economics
first_phase_total_supply = INITIAL_SUPPLY + 1000
max_first_phase_reward = (first_phase_total_supply - INITIAL_SUPPLY) // 5
args = dict(_token=token.address,
_hoursPerPeriod=economics.hours_per_period,
_secondPhaseMintingCoefficient=economics.staking_coefficient // (
2 * economics.maximum_rewarded_periods),
_lockingDurationCoefficient1=economics.maximum_rewarded_periods,
_lockingDurationCoefficient2=2 * economics.maximum_rewarded_periods,
_maxRewardedPeriods=economics.maximum_rewarded_periods,
_firstPhaseTotalSupply=first_phase_total_supply,
_maxFirstPhaseReward=max_first_phase_reward)
issuer, _ = deploy_contract(contract_name='IssuerMock', **args)
# Give staker tokens for reward and initialize contract
tx = token.functions.approve(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.initialize(economics.erc20_reward_supply).transact({'from': creator})
testerchain.wait_for_receipt(tx)
reward = issuer.functions.getReservedReward().call()
# Mint some tokens and save result of minting
period = issuer.functions.getCurrentPeriod().call()
tx = issuer.functions.testMint(period + 1, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
one_period = token.functions.balanceOf(staker).call()
assert one_period == max_first_phase_reward
# Inflation rate must be the same in all periods of the first phase
# Mint more tokens in the same period
tx = issuer.functions.testMint(period + 1, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 2 * one_period == token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Mint tokens in the next period
tx = issuer.functions.testMint(period + 2, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 3 * one_period == token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Mint tokens in the first period again
tx = issuer.functions.testMint(period + 1, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 4 * one_period == token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Mint tokens in the next period
tx = issuer.functions.testMint(period + 3, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 5 * one_period == token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Switch to the second phase
assert issuer.functions.previousPeriodSupply().call() < first_phase_total_supply
assert issuer.functions.currentPeriodSupply().call() == first_phase_total_supply
tx = issuer.functions.testMint(period + 4, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 6 * one_period > token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
minted_amount_second_phase = token.functions.balanceOf(staker).call() - 5 * one_period
# Return some tokens as a reward
# balance = token.functions.balanceOf(staker).call() TODO
reward = issuer.functions.getReservedReward().call()
amount_to_burn = 4 * one_period + minted_amount_second_phase
tx = token.functions.approve(issuer.address, amount_to_burn).transact({'from': staker})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.burn(amount_to_burn).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert reward + amount_to_burn == issuer.functions.getReservedReward().call()
assert one_period == token.functions.balanceOf(staker).call()
events = issuer.events.Burnt.createFilter(fromBlock=0).get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert staker == event_args['sender']
assert amount_to_burn == event_args['value']
# Switch back to the first phase
reward += amount_to_burn
tx = issuer.functions.testMint(period + 5, 1, 1, economics.maximum_rewarded_periods).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 2 * one_period == token.functions.balanceOf(staker).call()
assert reward - one_period == issuer.functions.getReservedReward().call()
@pytest.mark.slow
def test_issuance_second_phase(testerchain, token, deploy_contract):
"""
Check for decreasing of issuance after minting in the second phase.
During one period inflation rate must be the same
"""
@ -141,17 +302,20 @@ def test_inflation_rate(testerchain, token, deploy_contract):
hours_per_period=1)
creator = testerchain.client.accounts[0]
ursula = testerchain.client.accounts[1]
staker = testerchain.client.accounts[1]
# Creator deploys the contract
issuer, _ = deploy_contract(
contract_name='IssuerMock',
_token=token.address,
_hoursPerPeriod=economics.hours_per_period,
_miningCoefficient=economics.staking_coefficient,
_lockedPeriodsCoefficient=economics.locked_periods_coefficient,
_rewardedPeriods=economics.maximum_rewarded_periods
)
# TODO update base economics
args = dict(_token=token.address,
_hoursPerPeriod=economics.hours_per_period,
_secondPhaseMintingCoefficient=economics.staking_coefficient // (
2 * economics.maximum_rewarded_periods),
_lockingDurationCoefficient1=economics.maximum_rewarded_periods,
_lockingDurationCoefficient2=2 * economics.maximum_rewarded_periods,
_maxRewardedPeriods=economics.maximum_rewarded_periods,
_firstPhaseTotalSupply=0,
_maxFirstPhaseReward=0)
issuer, _ = deploy_contract(contract_name='IssuerMock', **args)
# Give staker tokens for reward and initialize contract
tx = token.functions.approve(issuer.address, economics.erc20_reward_supply).transact({'from': creator})
@ -162,58 +326,58 @@ def test_inflation_rate(testerchain, token, deploy_contract):
# Mint some tokens and save result of minting
period = issuer.functions.getCurrentPeriod().call()
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula})
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
one_period = token.functions.balanceOf(ursula).call()
one_period = token.functions.balanceOf(staker).call()
# Mint more tokens in the same period, inflation rate must be the same as in previous minting
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula})
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 2 * one_period == token.functions.balanceOf(ursula).call()
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
assert 2 * one_period == token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Mint tokens in the next period, inflation rate must be lower than in previous minting
tx = issuer.functions.testMint(period + 2, 1, 1, 0).transact({'from': ursula})
tx = issuer.functions.testMint(period + 2, 1, 1, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 3 * one_period > token.functions.balanceOf(ursula).call()
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
minted_amount = token.functions.balanceOf(ursula).call() - 2 * one_period
assert 3 * one_period > token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
minted_amount = token.functions.balanceOf(staker).call() - 2 * one_period
# Mint tokens in the first period again, inflation rate must be the same as in previous minting
# but can't be equals as in first minting because rate can't be increased
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': ursula})
tx = issuer.functions.testMint(period + 1, 1, 1, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 2 * one_period + 2 * minted_amount == token.functions.balanceOf(ursula).call()
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
assert 2 * one_period + 2 * minted_amount == token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Mint tokens in the next period, inflation rate must be lower than in previous minting
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula})
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert 2 * one_period + 3 * minted_amount > token.functions.balanceOf(ursula).call()
assert reward - token.functions.balanceOf(ursula).call() == issuer.functions.getReservedReward().call()
assert 2 * one_period + 3 * minted_amount > token.functions.balanceOf(staker).call()
assert reward - token.functions.balanceOf(staker).call() == issuer.functions.getReservedReward().call()
# Return some tokens as a reward
balance = token.functions.balanceOf(ursula).call()
balance = token.functions.balanceOf(staker).call()
reward = issuer.functions.getReservedReward().call()
amount_to_burn = 2 * one_period + 2 * minted_amount
tx = token.functions.transfer(ursula, amount_to_burn).transact({'from': creator})
tx = token.functions.transfer(staker, amount_to_burn).transact({'from': creator})
testerchain.wait_for_receipt(tx)
tx = token.functions.approve(issuer.address, amount_to_burn).transact({'from': ursula})
tx = token.functions.approve(issuer.address, amount_to_burn).transact({'from': staker})
testerchain.wait_for_receipt(tx)
tx = issuer.functions.burn(amount_to_burn).transact({'from': ursula})
tx = issuer.functions.burn(amount_to_burn).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert reward + amount_to_burn == issuer.functions.getReservedReward().call()
events = issuer.events.Burnt.createFilter(fromBlock=0).get_all_entries()
assert 1 == len(events)
event_args = events[0]['args']
assert ursula == event_args['sender']
assert staker == event_args['sender']
assert amount_to_burn == event_args['value']
# Rate will be increased because some tokens were returned
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': ursula})
tx = issuer.functions.testMint(period + 3, 1, 1, 0).transact({'from': staker})
testerchain.wait_for_receipt(tx)
assert balance + one_period == token.functions.balanceOf(ursula).call()
assert balance + one_period == token.functions.balanceOf(staker).call()
assert reward + one_period + 2 * minted_amount == issuer.functions.getReservedReward().call()
@ -226,9 +390,12 @@ def test_upgrading(testerchain, token, deploy_contract):
contract_name='IssuerMock',
_token=token.address,
_hoursPerPeriod=1,
_miningCoefficient=2,
_lockedPeriodsCoefficient=1,
_rewardedPeriods=1
_secondPhaseMintingCoefficient=1,
_lockingDurationCoefficient1=1,
_lockingDurationCoefficient2=2,
_maxRewardedPeriods=1,
_firstPhaseTotalSupply=1,
_maxFirstPhaseReward=1
)
dispatcher, _ = deploy_contract('Dispatcher', contract_library_v1.address)
@ -237,9 +404,12 @@ def test_upgrading(testerchain, token, deploy_contract):
contract_name='IssuerV2Mock',
_token=token.address,
_hoursPerPeriod=2,
_miningCoefficient=4,
_lockedPeriodsCoefficient=2,
_rewardedPeriods=2
_secondPhaseMintingCoefficient=2,
_lockingDurationCoefficient1=2,
_lockingDurationCoefficient2=4,
_maxRewardedPeriods=2,
_firstPhaseTotalSupply=2,
_maxFirstPhaseReward=2
)
contract = testerchain.client.get_contract(
abi=contract_library_v2.abi,
@ -262,14 +432,17 @@ def test_upgrading(testerchain, token, deploy_contract):
# Upgrade to the second version, check new and old values of variables
period = contract.functions.currentMintingPeriod().call()
assert 2 == contract.functions.miningCoefficient().call()
assert 2 == contract.functions.mintingCoefficient().call()
tx = dispatcher.functions.upgrade(contract_library_v2.address).transact({'from': creator})
testerchain.wait_for_receipt(tx)
assert contract_library_v2.address == dispatcher.functions.target().call()
assert 4 == contract.functions.miningCoefficient().call()
assert 8 == contract.functions.mintingCoefficient().call()
assert 2 * 3600 == contract.functions.secondsPerPeriod().call()
assert 2 == contract.functions.lockedPeriodsCoefficient().call()
assert 2 == contract.functions.rewardedPeriods().call()
assert 2 == contract.functions.lockingDurationCoefficient1().call()
assert 4 == contract.functions.lockingDurationCoefficient2().call()
assert 2 == contract.functions.maxRewardedPeriods().call()
assert 2 == contract.functions.firstPhaseTotalSupply().call()
assert 2 == contract.functions.maxFirstPhaseReward().call()
assert period == contract.functions.currentMintingPeriod().call()
assert TOTAL_SUPPLY == contract.functions.totalSupply().call()
# Check method from new ABI
@ -298,10 +471,13 @@ def test_upgrading(testerchain, token, deploy_contract):
testerchain.wait_for_receipt(tx)
# Check old values
assert contract_library_v1.address == dispatcher.functions.target().call()
assert 2 == contract.functions.miningCoefficient().call()
assert 2 == contract.functions.mintingCoefficient().call()
assert 3600 == contract.functions.secondsPerPeriod().call()
assert 1 == contract.functions.lockedPeriodsCoefficient().call()
assert 1 == contract.functions.rewardedPeriods().call()
assert 1 == contract.functions.lockingDurationCoefficient1().call()
assert 2 == contract.functions.lockingDurationCoefficient2().call()
assert 1 == contract.functions.maxRewardedPeriods().call()
assert 1 == contract.functions.firstPhaseTotalSupply().call()
assert 1 == contract.functions.maxFirstPhaseReward().call()
assert period == contract.functions.currentMintingPeriod().call()
assert TOTAL_SUPPLY == contract.functions.totalSupply().call()
# After rollback can't use new ABI

View File

@ -14,17 +14,23 @@ contract IssuerMock is Issuer {
constructor(
NuCypherToken _token,
uint32 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint16 _rewardedPeriods
uint256 _secondPhaseMintingCoefficient,
uint256 _lockingDurationCoefficient1,
uint256 _lockingDurationCoefficient2,
uint16 _maxRewardedPeriods,
uint256 _firstPhaseTotalSupply,
uint256 _maxFirstPhaseReward
)
public
Issuer(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_rewardedPeriods
_secondPhaseMintingCoefficient,
_lockingDurationCoefficient1,
_lockingDurationCoefficient2,
_maxRewardedPeriods,
_firstPhaseTotalSupply,
_maxFirstPhaseReward
)
{
}
@ -70,17 +76,23 @@ contract IssuerV2Mock is Issuer {
constructor(
NuCypherToken _token,
uint32 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint16 _rewardedPeriods
uint256 _secondPhaseMintingCoefficient,
uint256 _lockingDurationCoefficient1,
uint256 _lockingDurationCoefficient2,
uint16 _maxRewardedPeriods,
uint256 _firstPhaseTotalSupply,
uint256 _maxFirstPhaseReward
)
public
Issuer(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_rewardedPeriods
_secondPhaseMintingCoefficient,
_lockingDurationCoefficient1,
_lockingDurationCoefficient2,
_maxRewardedPeriods,
_firstPhaseTotalSupply,
_maxFirstPhaseReward
)
{
}

View File

@ -13,9 +13,12 @@ contract StakingEscrowBad is StakingEscrow {
constructor(
NuCypherToken _token,
uint32 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint16 _rewardedPeriods,
uint256 _secondPhaseMintingCoefficient,
uint256 _lockingDurationCoefficient1,
uint256 _lockingDurationCoefficient2,
uint16 _maxRewardedPeriods,
uint256 _firstPhaseTotalSupply,
uint256 _maxFirstPhaseReward,
uint16 _minLockedPeriods,
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens,
@ -26,9 +29,12 @@ contract StakingEscrowBad is StakingEscrow {
StakingEscrow(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_rewardedPeriods,
_secondPhaseMintingCoefficient,
_lockingDurationCoefficient1,
_lockingDurationCoefficient2,
_maxRewardedPeriods,
_firstPhaseTotalSupply,
_maxFirstPhaseReward,
_minLockedPeriods,
_minAllowableLockedTokens,
_maxAllowableLockedTokens,
@ -53,9 +59,12 @@ contract StakingEscrowV2Mock is StakingEscrow {
constructor(
NuCypherToken _token,
uint32 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint16 _rewardedPeriods,
uint256 _secondPhaseMintingCoefficient,
uint256 _lockingDurationCoefficient1,
uint256 _lockingDurationCoefficient2,
uint16 _maxRewardedPeriods,
uint256 _firstPhaseTotalSupply,
uint256 _maxFirstPhaseReward,
uint16 _minLockedPeriods,
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens,
@ -67,9 +76,12 @@ contract StakingEscrowV2Mock is StakingEscrow {
StakingEscrow(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_rewardedPeriods,
_secondPhaseMintingCoefficient,
_lockingDurationCoefficient1,
_lockingDurationCoefficient2,
_maxRewardedPeriods,
_firstPhaseTotalSupply,
_maxFirstPhaseReward,
_minLockedPeriods,
_minAllowableLockedTokens,
_maxAllowableLockedTokens,