Pick PR#22 from kms-eth; Further smooths file tree and namespaces.

pull/221/head
Kieran Prasch 2018-04-09 12:47:56 -07:00
parent a972f0e801
commit 1849b282c0
34 changed files with 2873 additions and 91 deletions

View File

@ -1,13 +1,12 @@
import random
from abc import ABC
from enum import Enum
from functools import partial
from typing import Set, Generator, List
from web3.contract import Contract
from functools import partial
from nkms.blockchain.eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer, ContractDeployer
from nkms.blockchain.eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer, \
ContractDeployer
class EthereumContractAgent(ABC):

View File

@ -1,7 +1,10 @@
import random
from abc import ABC
from typing import List
from nkms.blockchain.eth.actors import Miner
from nkms.blockchain.eth.agents import MinerAgent, NuCypherKMSTokenAgent
from nkms.config.configs import EthereumConfig
from nkms.blockchain.eth.config import EthereumConfig
class TheBlockchain(ABC):
@ -77,8 +80,59 @@ class TheBlockchain(ABC):
result = self._chain.wait.for_receipt(txhash, timeout=timeout)
return result
# class TestRpcBlockchain:
class TesterBlockchain(TheBlockchain):
"""Transient, in-memory, local, private chain"""
_network = 'tester'
def wait_time(self, wait_hours, step=50):
"""Wait the specified number of wait_hours by comparing block timestamps."""
end_timestamp = self._chain.web3.eth.getBlock(
self._chain.web3.eth.blockNumber).timestamp + wait_hours * 60 * 60
while self._chain.web3.eth.getBlock(self._chain.web3.eth.blockNumber).timestamp < end_timestamp:
self._chain.wait.for_block(self._chain.web3.eth.blockNumber + step)
def spawn_miners(self, miner_agent: MinerAgent, addresses: list, locktime: int, random_amount=False) -> List[Miner]:
"""
Deposit and lock a random amount of tokens in the miner escrow
from each address, "spawning" new Miners.
"""
miners = list()
for address in addresses:
miner = Miner(miner_agent=miner_agent, address=address)
miners.append(miner)
if random_amount is True:
amount = (10 + random.randrange(9000)) * miner_agent._deployer._M
else:
amount = miner.token_balance() // 2 # stake half
miner.stake(amount=amount, locktime=locktime, auto_switch_lock=True)
return miners
def _global_airdrop(self, token_agent: NuCypherKMSTokenAgent, amount: int):
"""Airdrops from creator address to all other addresses!"""
_creator, *addresses = self._chain.web3.eth.accounts
def txs():
for address in addresses:
txhash = token_agent.transact({'from': token_agent.origin}).transfer(address, amount)
yield txhash
receipts = list()
for tx in txs(): # One at a time
receipt = self.wait_for_receipt(tx)
receipts.append(receipt)
return receipts
#
# class TestRPCBlockchain:
#
# _network = 'testrpc'
# _default_timeout = 60
#

View File

@ -1,74 +0,0 @@
from os.path import dirname, join, abspath
import appdirs
import populus
from nkms.blockchain import eth
class NuCypherTokenConfig:
__subdigits = 18
_M = 10 ** __subdigits
__premine = int(1e9) * _M
__saturation = int(1e10) * _M
_reward = __saturation - __premine
@property
def saturation(self):
return self.__saturation
class NuCypherMinerConfig:
_hours_per_period = 24 # Hours
_min_release_periods = 30 # 720 Hours
__max_awarded_periods = 365
__min_allowed_locked = 10 ** 6
__max_allowed_locked = 10 ** 7 * NuCypherTokenConfig._M
_null_addr = '0x' + '0' * 40
__reward = NuCypherTokenConfig._reward
__mining_coeff = [
_hours_per_period,
2 * 10 ** 7,
__max_awarded_periods,
__max_awarded_periods,
_min_release_periods,
__min_allowed_locked,
__max_allowed_locked
]
@property
def null_address(self):
return self._null_addr
@property
def mining_coefficient(self):
return self.__mining_coeff
@property
def reward(self):
return self.__reward
class EthereumConfig:
__python_project_name = 'nucypher-kms'
# __default_solidity_dir = os.path.join() # TODO: NKMSConfig Classes
def __init__(self, provider, registrar_path=None):
self.provider = provider
# This config is persistent and is created in user's .local directory
if registrar_path is None:
registrar_path = join(appdirs.user_data_dir(self.__python_project_name), 'registrar.json')
self._registrar_path = registrar_path
# Populus project config
self._project_dir = join(dirname(abspath(eth.__file__)), 'project')
self._populus_project = populus.Project(self._project_dir)
self.project.config['chains.mainnetrpc.contracts.backends.JSONFile.settings.file_path'] = self._registrar_path
@property
def project(self):
return self._populus_project

View File

@ -1,8 +1,9 @@
from typing import Tuple, Dict
from nkms.blockchain.eth.constants import NuCypherMinerConfig, NuCypherTokenConfig
from web3.contract import Contract
from nkms.blockchain.eth.config import NuCypherMinerConfig, NuCypherTokenConfig
from .blockchain import TheBlockchain
from .chains import TheBlockchain
class ContractDeployer:
@ -113,8 +114,8 @@ class ContractDeployer:
incorrectly types the arming_word.
"""
# self.check_ready_to_deploy(fail=True)
if self.__armed is True:
raise self.ContractDeploymentError('{} deployer is already armed.'.format(self._contract_name))
# If the blockchain network is public, prompt the user
if self.blockchain._network not in self.blockchain.test_chains:
@ -165,8 +166,8 @@ class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
The contract must be armed before it can be deployed.
Deployment can only ever be executed exactly once!
"""
self.check_ready_to_deploy(fail=True)
is_ready, _disqualifications = self.check_ready_to_deploy(fail=True)
assert is_ready
the_nucypher_token_contract, deployment_txhash = self.blockchain._chain.provider.deploy_contract(
self._contract_name,
@ -194,6 +195,7 @@ class DispatcherDeployer(ContractDeployer):
super().__init__(blockchain=token_agent.blockchain)
def deploy(self) -> str:
dispatcher_contract, txhash = self.blockchain._chain.provider.deploy_contract(
'Dispatcher', deploy_args=[self.target_contract.address],
deploy_transaction={'from': self.token_agent.origin})
@ -231,7 +233,8 @@ class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
"""
# Raise if not all-systems-go
self.check_ready_to_deploy(fail=True)
is_ready, _disqualifications = self.check_ready_to_deploy(fail=True)
assert is_ready
# Build deployment arguments
deploy_args = [self.token_agent.contract_address] + self.mining_coefficient # config
@ -293,7 +296,8 @@ class PolicyManagerDeployer(ContractDeployer):
super().__init__(blockchain=self.miner_escrow_deployer.blockchain)
def deploy(self) -> Dict[str, str]:
self.check_ready_to_deploy(fail=True)
is_ready, _disqualifications = self.check_ready_to_deploy(fail=True)
assert is_ready
# Creator deploys the policy manager
the_policy_manager_contract, deploy_txhash = self.blockchain._chain.provider.deploy_contract(
@ -340,14 +344,18 @@ class UserEscrowDeployer(ContractDeployer):
_contract_name = 'UserEscrow' # TODO
def __init__(self, miner_escrow_deployer, policy_deployer, *args, **kwargs):
def __init__(self, miner_escrow_deployer, policy_deployer):
self.miner_deployer = miner_escrow_deployer
self.policy_deployer = policy_deployer
self.token_deployer = miner_escrow_deployer.token_deployer
super(*args, **kwargs).__init__(blockchain=miner_escrow_deployer.blockchain)
super().__init__(blockchain=miner_escrow_deployer.blockchain)
def deploy(self):
is_ready, _disqualifications = self.check_ready_to_deploy(fail=True)
assert is_ready
deployment_args = [self.token_deployer.contract_address,
self.miner_deployer.contract_address,
self.policy_deployer.contract_address],

View File

@ -0,0 +1,166 @@
pragma solidity ^0.4.18;
import "./NuCypherKMSToken.sol";
import "./zeppelin/math/SafeMath.sol";
import "./proxy/Upgradeable.sol";
/**
* @notice Contract for calculate issued tokens
**/
contract Issuer is Upgradeable {
using SafeMath for uint256;
/// Issuer is initialized with a reserved reward
event Initialized(uint256 reservedReward);
NuCypherKMSToken public token;
uint256 public miningCoefficient;
uint256 public secondsPerPeriod;
uint256 public lockedPeriodsCoefficient;
uint256 public awardedPeriods;
uint256 public lastMintedPeriod;
mapping (byte => uint256) public totalSupply;
byte public currentIndex;
uint256 public futureSupply;
byte constant NEGATION = 0xF0;
/**
* @notice Constructor sets address of token contract and coefficients for mining
* @dev Formula for mining in one period
(futureSupply - currentSupply) * (lockedValue / totalLockedValue) * (k1 + allLockedPeriods) / k2
if allLockedPeriods > awardedPeriods then allLockedPeriods = awardedPeriods
* @param _token Token contract
* @param _hoursPerPeriod Size of period in hours
* @param _miningCoefficient Mining coefficient (k2)
* @param _lockedPeriodsCoefficient Locked blocks coefficient (k1)
* @param _awardedPeriods Max periods that will be additionally awarded
**/
function Issuer(
NuCypherKMSToken _token,
uint256 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint256 _awardedPeriods
)
public
{
require(address(_token) != 0x0 &&
_miningCoefficient != 0 &&
_hoursPerPeriod != 0 &&
_lockedPeriodsCoefficient != 0 &&
_awardedPeriods != 0);
token = _token;
miningCoefficient = _miningCoefficient;
secondsPerPeriod = _hoursPerPeriod.mul(1 hours);
lockedPeriodsCoefficient = _lockedPeriodsCoefficient;
awardedPeriods = _awardedPeriods;
lastMintedPeriod = getCurrentPeriod();
futureSupply = token.totalSupply();
}
/**
* @dev Checks miner initialization
**/
modifier isInitialized()
{
require(currentIndex != 0x00);
_;
}
/**
* @return Number of current period
**/
function getCurrentPeriod() public view returns (uint256) {
return block.timestamp / secondsPerPeriod;
}
/**
* @notice Initialize reserved tokens for reward
**/
function initialize() public {
require(currentIndex == 0x00);
currentIndex = 0x01;
uint256 reservedReward = token.balanceOf(address(this));
uint256 currentTotalSupply = futureSupply.sub(reservedReward);
totalSupply[currentIndex] = currentTotalSupply;
totalSupply[currentIndex ^ NEGATION] = currentTotalSupply;
Initialized(reservedReward);
}
/**
* @notice Function to mint tokens for one period.
* @param _period Period number.
* @param _lockedValue The amount of tokens that were locked by user in specified period.
* @param _totalLockedValue The amount of tokens that were locked by all users in specified period.
* @param _allLockedPeriods The max amount of periods during which tokens will be locked after specified period.
* @param _decimals The amount of locked tokens and blocks in decimals.
* @return Amount of minted tokens.
*/
// TODO decimals
function mint(
uint256 _period,
uint256 _lockedValue,
uint256 _totalLockedValue,
uint256 _allLockedPeriods,
uint256 _decimals
)
internal returns (uint256 amount, uint256 decimals)
{
// TODO end of mining before calculation
uint256 nextTotalSupply = totalSupply[currentIndex ^ NEGATION];
if (_period > lastMintedPeriod) {
currentIndex = currentIndex ^ NEGATION;
lastMintedPeriod = _period;
}
//futureSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2) -
//currentSupply * lockedValue * (k1 + allLockedPeriods) / (totalLockedValue * k2)
uint256 allLockedPeriods = (_allLockedPeriods <= awardedPeriods ?
_allLockedPeriods : awardedPeriods)
.add(lockedPeriodsCoefficient);
uint256 denominator = _totalLockedValue.mul(miningCoefficient);
amount =
futureSupply
.mul(_lockedValue)
.mul(allLockedPeriods)
.div(denominator).sub(
totalSupply[currentIndex]
.mul(_lockedValue)
.mul(allLockedPeriods)
.div(denominator));
decimals = _decimals;
totalSupply[currentIndex ^ NEGATION] = nextTotalSupply.add(amount);
}
function verifyState(address _testTarget) public onlyOwner {
require(address(delegateGet(_testTarget, "token()")) == address(token));
require(uint256(delegateGet(_testTarget, "miningCoefficient()")) == miningCoefficient);
require(uint256(delegateGet(_testTarget, "secondsPerPeriod()")) == secondsPerPeriod);
require(uint256(delegateGet(_testTarget, "lockedPeriodsCoefficient()")) == lockedPeriodsCoefficient);
require(uint256(delegateGet(_testTarget, "awardedPeriods()")) == awardedPeriods);
require(uint256(delegateGet(_testTarget, "lastMintedPeriod()")) == lastMintedPeriod);
require(byte(delegateGet(_testTarget, "currentIndex()")) == currentIndex);
require(uint256(delegateGet(_testTarget, "totalSupply(bytes1)", currentIndex)) == totalSupply[currentIndex]);
require(uint256(delegateGet(_testTarget, "totalSupply(bytes1)", currentIndex ^ NEGATION)) ==
totalSupply[currentIndex ^ NEGATION]);
require(uint256(delegateGet(_testTarget, "futureSupply()")) == futureSupply);
}
function finishUpgrade(address _target) public onlyOwner {
Issuer issuer = Issuer(_target);
token = issuer.token();
miningCoefficient = issuer.miningCoefficient();
secondsPerPeriod = issuer.secondsPerPeriod();
lockedPeriodsCoefficient = issuer.lockedPeriodsCoefficient();
awardedPeriods = issuer.awardedPeriods();
lastMintedPeriod = issuer.lastMintedPeriod();
futureSupply = issuer.futureSupply();
}
}

View File

@ -0,0 +1,669 @@
pragma solidity ^0.4.18;
import "./zeppelin/token/ERC20/SafeERC20.sol";
import "./zeppelin/math/Math.sol";
import "./lib/AdditionalMath.sol";
import "./Issuer.sol";
import "./PolicyManager.sol";
/**
* @notice Contract holds and locks nodes tokens.
Each node that lock its tokens will receive some compensation
**/
contract MinersEscrow is Issuer {
using SafeERC20 for NuCypherKMSToken;
using AdditionalMath for uint256;
event Deposited(address indexed owner, uint256 value, uint256 periods);
event Locked(address indexed owner, uint256 value, uint256 releaseRate);
event LockSwitched(address indexed owner, bool release);
event Withdrawn(address indexed owner, uint256 value);
event ActivityConfirmed(address indexed owner, uint256 indexed period, uint256 value);
event Mined(address indexed owner, uint256 indexed period, uint256 value);
enum MinerInfoField {
MinersLength,
Miner,
Value,
Decimals,
LockedValue,
Release,
MaxReleasePeriods,
ReleaseRate,
ConfirmedPeriodsLength,
ConfirmedPeriod,
ConfirmedPeriodLockedValue,
LastActivePeriod,
DowntimeLength,
DowntimeStartPeriod,
DowntimeEndPeriod,
MinerIdsLength,
MinerId
}
struct ConfirmedPeriodInfo {
uint256 period;
uint256 lockedValue;
}
struct Downtime {
uint256 startPeriod;
uint256 endPeriod;
}
struct MinerInfo {
uint256 value;
uint256 decimals;
uint256 lockedValue;
bool release;
uint256 maxReleasePeriods;
uint256 releaseRate;
// periods that confirmed but not yet mined
ConfirmedPeriodInfo[] confirmedPeriods;
// downtime
uint256 lastActivePeriod;
Downtime[] downtime;
bytes32[] minerIds;
}
uint256 constant MAX_PERIODS = 3;
uint256 constant MAX_OWNERS = 50000;
uint256 constant RESERVED_PERIOD = 0;
mapping (address => MinerInfo) minerInfo;
address[] miners;
mapping (uint256 => uint256) public lockedPerPeriod;
uint256 public minReleasePeriods;
uint256 public minAllowableLockedTokens;
uint256 public maxAllowableLockedTokens;
PolicyManager public policyManager;
/**
* @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 _minReleasePeriods Min amount of periods during which tokens will be released
* @param _lockedPeriodsCoefficient Locked blocks coefficient
* @param _awardedPeriods Max periods that will be additionally awarded
* @param _minAllowableLockedTokens Min amount of tokens that can be locked
* @param _maxAllowableLockedTokens Max amount of tokens that can be locked
**/
function MinersEscrow(
NuCypherKMSToken _token,
uint256 _hoursPerPeriod,
uint256 _miningCoefficient,
uint256 _lockedPeriodsCoefficient,
uint256 _awardedPeriods,
uint256 _minReleasePeriods,
uint256 _minAllowableLockedTokens,
uint256 _maxAllowableLockedTokens
)
public
Issuer(
_token,
_hoursPerPeriod,
_miningCoefficient,
_lockedPeriodsCoefficient,
_awardedPeriods
)
{
require(_minReleasePeriods != 0);
minReleasePeriods = _minReleasePeriods;
minAllowableLockedTokens = _minAllowableLockedTokens;
maxAllowableLockedTokens = _maxAllowableLockedTokens;
}
/**
* @dev Checks that sender exists in contract
**/
modifier onlyTokenOwner()
{
require(minerInfo[msg.sender].value > 0);
_;
}
/**
* @notice Get locked tokens value for owner in current period
* @param _owner Tokens owner
**/
function getLockedTokens(address _owner)
public view returns (uint256)
{
uint256 currentPeriod = getCurrentPeriod();
MinerInfo storage info = minerInfo[_owner];
// no confirmed periods, so current period may be release period
if (info.confirmedPeriods.length == 0) {
uint256 lockedValue = info.lockedValue;
} else {
uint256 i = info.confirmedPeriods.length - 1;
ConfirmedPeriodInfo storage confirmedPeriod = info.confirmedPeriods[i];
// last confirmed period is current period
if (confirmedPeriod.period == currentPeriod) {
return confirmedPeriod.lockedValue;
// last confirmed period is previous periods, so current period may be release period
} else if (confirmedPeriod.period < currentPeriod) {
lockedValue = confirmedPeriod.lockedValue;
// penultimate confirmed period is previous or current period, so get its lockedValue
} else if (info.confirmedPeriods.length > 1) {
return info.confirmedPeriods[info.confirmedPeriods.length - 2].lockedValue;
// no previous periods, so return saved lockedValue
} else {
return info.lockedValue;
}
}
// checks if owner can mine more tokens (before or after release period)
if (calculateLockedTokens(_owner, false, lockedValue, 1) == 0) {
return 0;
} else {
return lockedValue;
}
}
/**
* @notice Get locked tokens value for all owners in current period
**/
function getAllLockedTokens() public view returns (uint256) {
return lockedPerPeriod[getCurrentPeriod()];
}
/**
* @notice Calculate locked tokens value for owner in next period
* @param _owner Tokens owner
* @param _forceRelease Force unlocking period calculation
* @param _lockedTokens Locked tokens in specified period
* @param _periods Number of periods that need to calculate
* @return Calculated locked tokens in next period
**/
function calculateLockedTokens(
address _owner,
bool _forceRelease,
uint256 _lockedTokens,
uint256 _periods
)
internal view returns (uint256)
{
MinerInfo storage info = minerInfo[_owner];
if ((_forceRelease || info.release) && _periods != 0) {
uint256 unlockedTokens = _periods.mul(info.releaseRate);
return unlockedTokens <= _lockedTokens ? _lockedTokens.sub(unlockedTokens) : 0;
} else {
return _lockedTokens;
}
}
/**
* @notice Calculate locked tokens value for owner in next period
* @param _owner Tokens owner
* @param _periods Number of periods after current that need to calculate
* @return Calculated locked tokens in next period
**/
function calculateLockedTokens(address _owner, uint256 _periods)
public view returns (uint256)
{
require(_periods > 0);
uint256 currentPeriod = getCurrentPeriod();
uint256 nextPeriod = currentPeriod.add(_periods);
MinerInfo storage info = minerInfo[_owner];
if (info.confirmedPeriods.length > 0 &&
info.confirmedPeriods[info.confirmedPeriods.length - 1].period >= currentPeriod) {
ConfirmedPeriodInfo storage confirmedPeriod =
info.confirmedPeriods[info.confirmedPeriods.length - 1];
uint256 lockedTokens = confirmedPeriod.lockedValue;
uint256 period = confirmedPeriod.period;
} else {
lockedTokens = getLockedTokens(_owner);
period = currentPeriod;
}
uint256 periods = nextPeriod.sub(period);
return calculateLockedTokens(_owner, false, lockedTokens, periods);
}
/**
* @notice Calculate locked periods for owner from start period
* @param _owner Tokens owner
* @param _lockedTokens Locked tokens in start period
* @return Calculated locked periods
**/
function calculateLockedPeriods(
address _owner,
uint256 _lockedTokens
)
internal view returns (uint256)
{
MinerInfo storage info = minerInfo[_owner];
return _lockedTokens.divCeil(info.releaseRate).sub(uint(1));
}
/**
* @notice Pre-deposit tokens
* @param _owners Tokens owners
* @param _values Amount of token to deposit for each owner
* @param _periods Amount of periods during which tokens will be unlocked for each owner
**/
function preDeposit(address[] _owners, uint256[] _values, uint256[] _periods)
public isInitialized onlyOwner
{
require(_owners.length != 0 &&
miners.length.add(_owners.length) <= MAX_OWNERS &&
_owners.length == _values.length &&
_owners.length == _periods.length);
uint256 currentPeriod = getCurrentPeriod();
uint256 allValue = 0;
for (uint256 i = 0; i < _owners.length; i++) {
address owner = _owners[i];
uint256 value = _values[i];
uint256 periods = _periods[i];
MinerInfo storage info = minerInfo[owner];
require(info.value == 0 &&
value >= minAllowableLockedTokens &&
value <= maxAllowableLockedTokens &&
periods >= minReleasePeriods);
// TODO optimize
miners.push(owner);
info.lastActivePeriod = currentPeriod;
info.value = value;
info.lockedValue = value;
info.maxReleasePeriods = periods;
info.releaseRate = Math.max256(value.divCeil(periods), 1);
info.release = false;
allValue = allValue.add(value);
Deposited(owner, value, periods);
}
token.safeTransferFrom(msg.sender, address(this), allValue);
}
/**
* @notice Deposit tokens
* @param _value Amount of token to deposit
* @param _periods Amount of periods during which tokens will be unlocked
**/
function deposit(uint256 _value, uint256 _periods) public isInitialized {
require(_value != 0);
MinerInfo storage info = minerInfo[msg.sender];
if (minerInfo[msg.sender].value == 0) {
require(miners.length < MAX_OWNERS);
miners.push(msg.sender);
info.lastActivePeriod = getCurrentPeriod();
}
info.value = info.value.add(_value);
token.safeTransferFrom(msg.sender, address(this), _value);
lock(_value, _periods);
Deposited(msg.sender, _value, _periods);
}
/**
* @notice Lock some tokens or increase lock
* @param _value Amount of tokens which should lock
* @param _periods Amount of periods during which tokens will be unlocked
**/
function lock(uint256 _value, uint256 _periods) public onlyTokenOwner {
require(_value != 0 || _periods != 0);
uint256 lockedTokens = calculateLockedTokens(msg.sender, 1);
MinerInfo storage info = minerInfo[msg.sender];
require(_value <= token.balanceOf(address(this)) &&
_value <= info.value.sub(lockedTokens));
if (lockedTokens == 0) {
require(_value >= minAllowableLockedTokens);
info.lockedValue = _value;
info.maxReleasePeriods = Math.max256(_periods, minReleasePeriods);
info.releaseRate = Math.max256(_value.divCeil(info.maxReleasePeriods), 1);
info.release = false;
} else {
info.lockedValue = lockedTokens.add(_value);
info.maxReleasePeriods = info.maxReleasePeriods.add(_periods);
info.releaseRate = Math.max256(
info.lockedValue.divCeil(info.maxReleasePeriods), info.releaseRate);
}
require(info.lockedValue <= maxAllowableLockedTokens);
confirmActivity(info.lockedValue);
Locked(msg.sender, info.lockedValue, info.releaseRate);
mint();
}
/**
* @notice Switch lock
**/
function switchLock() public onlyTokenOwner {
MinerInfo storage info = minerInfo[msg.sender];
info.release = !info.release;
LockSwitched(msg.sender, info.release);
}
/**
* @notice Withdraw available amount of tokens back to owner
* @param _value Amount of token to withdraw
**/
function withdraw(uint256 _value) public onlyTokenOwner {
MinerInfo storage info = minerInfo[msg.sender];
// TODO optimize
uint256 lockedTokens = Math.max256(calculateLockedTokens(msg.sender, 1),
getLockedTokens(msg.sender));
require(_value <= token.balanceOf(address(this)) &&
_value <= info.value.sub(lockedTokens));
info.value -= _value;
token.safeTransfer(msg.sender, _value);
Withdrawn(msg.sender, _value);
}
/**
* @notice Confirm activity for future period
* @param _lockedValue Locked tokens in future period
**/
function confirmActivity(uint256 _lockedValue) internal {
require(_lockedValue > 0);
MinerInfo storage info = minerInfo[msg.sender];
uint256 nextPeriod = getCurrentPeriod() + 1;
if (info.confirmedPeriods.length > 0 &&
info.confirmedPeriods[info.confirmedPeriods.length - 1].period == nextPeriod) {
ConfirmedPeriodInfo storage confirmedPeriod =
info.confirmedPeriods[info.confirmedPeriods.length - 1];
lockedPerPeriod[nextPeriod] = lockedPerPeriod[nextPeriod]
.add(_lockedValue.sub(confirmedPeriod.lockedValue));
confirmedPeriod.lockedValue = _lockedValue;
ActivityConfirmed(msg.sender, nextPeriod, _lockedValue);
return;
}
// require(info.confirmedPeriods.length < MAX_PERIODS);
lockedPerPeriod[nextPeriod] = lockedPerPeriod[nextPeriod]
.add(_lockedValue);
info.confirmedPeriods.push(ConfirmedPeriodInfo(nextPeriod, _lockedValue));
uint256 currentPeriod = nextPeriod - 1;
if (info.lastActivePeriod < currentPeriod) {
info.downtime.push(Downtime(info.lastActivePeriod + 1, currentPeriod));
}
info.lastActivePeriod = nextPeriod;
ActivityConfirmed(msg.sender, nextPeriod, _lockedValue);
}
/**
* @notice Confirm activity for future period and mine for previous period
**/
function confirmActivity() external onlyTokenOwner {
mint();
MinerInfo storage info = minerInfo[msg.sender];
uint256 currentPeriod = getCurrentPeriod();
uint256 nextPeriod = currentPeriod + 1;
if (info.confirmedPeriods.length > 0 &&
info.confirmedPeriods[info.confirmedPeriods.length - 1].period >= nextPeriod) {
return;
}
uint256 lockedTokens = calculateLockedTokens(
msg.sender, false, getLockedTokens(msg.sender), 1);
confirmActivity(lockedTokens);
}
/**
* @notice Mint tokens for sender for previous periods if he locked his tokens and confirmed activity
**/
function mint() public onlyTokenOwner {
uint256 previousPeriod = getCurrentPeriod().sub(uint(1));
MinerInfo storage info = minerInfo[msg.sender];
uint256 numberPeriodsForMinting = info.confirmedPeriods.length;
if (numberPeriodsForMinting == 0 || info.confirmedPeriods[0].period > previousPeriod) {
return;
}
uint256 currentLockedValue = getLockedTokens(msg.sender);
ConfirmedPeriodInfo storage last = info.confirmedPeriods[numberPeriodsForMinting - 1];
uint256 allLockedPeriods = last.lockedValue
.divCeil(info.releaseRate)
.sub(uint(1))
.add(numberPeriodsForMinting);
if (last.period > previousPeriod) {
numberPeriodsForMinting--;
}
if (info.confirmedPeriods[numberPeriodsForMinting - 1].period > previousPeriod) {
numberPeriodsForMinting--;
}
uint256 reward = 0;
for(uint i = 0; i < numberPeriodsForMinting; ++i) {
uint256 amount;
uint256 period = info.confirmedPeriods[i].period;
uint256 lockedValue = info.confirmedPeriods[i].lockedValue;
allLockedPeriods--;
(amount, info.decimals) = mint(
previousPeriod,
lockedValue,
lockedPerPeriod[period],
allLockedPeriods,
info.decimals);
reward = reward.add(amount);
// TODO remove if
if (address(policyManager) != 0x0) {
policyManager.updateReward(msg.sender, period);
}
}
info.value = info.value.add(reward);
// Copy not minted periods
for (i = 0; i < info.confirmedPeriods.length - numberPeriodsForMinting; i++) {
info.confirmedPeriods[i] = info.confirmedPeriods[numberPeriodsForMinting + i];
}
info.confirmedPeriods.length -= numberPeriodsForMinting;
// Update lockedValue for current period
info.lockedValue = currentLockedValue;
Mined(msg.sender, previousPeriod, reward);
}
/**
* @notice Fixed-step in cumulative sum
* @param _startIndex Starting point
* @param _delta How much to step
* @param _periods Amount of periods to get locked tokens
* @dev
_startIndex
v
|-------->*--------------->*---->*------------->|
| ^
| stopIndex
|
| _delta
|---------------------------->|
|
| shift
| |----->|
**/
function findCumSum(uint256 _startIndex, uint256 _delta, uint256 _periods)
external view returns (address stop, uint256 stopIndex, uint256 shift)
{
require(_periods > 0);
uint256 currentPeriod = getCurrentPeriod();
uint256 distance = 0;
for (uint256 i = _startIndex; i < miners.length; i++) {
address current = miners[i];
MinerInfo storage info = minerInfo[current];
if (info.confirmedPeriods.length == 0) {
continue;
}
ConfirmedPeriodInfo storage confirmedPeriod =
info.confirmedPeriods[info.confirmedPeriods.length - 1];
if (confirmedPeriod.period == currentPeriod) {
uint256 lockedTokens = calculateLockedTokens(
current,
true,
confirmedPeriod.lockedValue,
_periods);
} else if (info.confirmedPeriods.length > 1 &&
info.confirmedPeriods[info.confirmedPeriods.length - 2].period == currentPeriod) {
lockedTokens = calculateLockedTokens(
current,
true,
confirmedPeriod.lockedValue,
_periods - 1);
} else {
continue;
}
if (_delta < distance + lockedTokens) {
stop = current;
stopIndex = i;
shift = _delta - distance;
break;
} else {
distance += lockedTokens;
}
}
}
/**
* @notice Set policy manager address
**/
function setPolicyManager(PolicyManager _policyManager) external onlyOwner {
require(address(policyManager) == 0x0 &&
_policyManager.escrow() == address(this));
policyManager = _policyManager;
}
/**
* @notice Set miner id
**/
function setMinerId(bytes32 _minerId) public {
MinerInfo storage info = minerInfo[msg.sender];
info.minerIds.push(_minerId);
}
/**
* @notice Get information about miner
* @dev This get method reduces size of bytecode compared with multiple get methods or public modifiers
* @param _field Field to get
* @param _miner Address of miner
* @param _index Index of array
**/
function getMinerInfo(MinerInfoField _field, address _miner, uint256 _index)
public view returns (bytes32)
{
if (_field == MinerInfoField.MinersLength) {
return bytes32(miners.length);
} else if (_field == MinerInfoField.Miner) {
return bytes32(miners[_index]);
}
MinerInfo storage info = minerInfo[_miner];
if (_field == MinerInfoField.Value) {
return bytes32(info.value);
} else if (_field == MinerInfoField.Decimals) {
return bytes32(info.decimals);
} else if (_field == MinerInfoField.LockedValue) {
return bytes32(info.lockedValue);
} else if (_field == MinerInfoField.Release) {
return info.release ? bytes32(1) : bytes32(0);
} else if (_field == MinerInfoField.MaxReleasePeriods) {
return bytes32(info.maxReleasePeriods);
} else if (_field == MinerInfoField.ReleaseRate) {
return bytes32(info.releaseRate);
} else if (_field == MinerInfoField.ConfirmedPeriodsLength) {
return bytes32(info.confirmedPeriods.length);
} else if (_field == MinerInfoField.ConfirmedPeriod) {
return bytes32(info.confirmedPeriods[_index].period);
} else if (_field == MinerInfoField.ConfirmedPeriodLockedValue) {
return bytes32(info.confirmedPeriods[_index].lockedValue);
} else if (_field == MinerInfoField.LastActivePeriod) {
return bytes32(info.lastActivePeriod);
} else if (_field == MinerInfoField.DowntimeLength) {
return bytes32(info.downtime.length);
} else if (_field == MinerInfoField.DowntimeStartPeriod) {
return bytes32(info.downtime[_index].startPeriod);
} else if (_field == MinerInfoField.DowntimeEndPeriod) {
return bytes32(info.downtime[_index].endPeriod);
} else if (_field == MinerInfoField.MinerIdsLength) {
return bytes32(info.minerIds.length);
} else if (_field == MinerInfoField.MinerId) {
return bytes32(info.minerIds[_index]);
}
}
function verifyState(address _testTarget) public onlyOwner {
super.verifyState(_testTarget);
require(uint256(delegateGet(_testTarget, "minReleasePeriods()")) ==
minReleasePeriods);
require(uint256(delegateGet(_testTarget, "minAllowableLockedTokens()")) ==
minAllowableLockedTokens);
require(uint256(delegateGet(_testTarget, "maxAllowableLockedTokens()")) ==
maxAllowableLockedTokens);
require(address(delegateGet(_testTarget, "policyManager()")) == address(policyManager));
require(uint256(delegateGet(_testTarget, "lockedPerPeriod(uint256)",
bytes32(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint256(MinerInfoField.MinersLength)), 0x0, 0)) == miners.length);
if (miners.length == 0) {
return;
}
address minerAddress = miners[0];
bytes32 miner = bytes32(minerAddress);
require(address(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.Miner)), 0x0, 0)) == minerAddress);
MinerInfo storage info = minerInfo[minerAddress];
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.Value)), miner, 0)) == info.value);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.Decimals)), miner, 0)) == info.decimals);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.LockedValue)), miner, 0)) == info.lockedValue);
require(((delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.Release)), miner, 0)) == bytes32(1)) == info.release);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.MaxReleasePeriods)), miner, 0)) == info.maxReleasePeriods);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.ReleaseRate)), miner, 0)) == info.releaseRate);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.ConfirmedPeriodsLength)), miner, 0)) == info.confirmedPeriods.length);
for (uint256 i = 0; i < info.confirmedPeriods.length; i++) {
ConfirmedPeriodInfo storage confirmedPeriod = info.confirmedPeriods[i];
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.ConfirmedPeriod)), miner, bytes32(i))) == confirmedPeriod.period);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.ConfirmedPeriodLockedValue)), miner, bytes32(i))) ==
confirmedPeriod.lockedValue);
}
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.LastActivePeriod)), miner, 0)) == info.lastActivePeriod);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.DowntimeLength)), miner, 0)) == info.downtime.length);
for (i = 0; i < info.downtime.length && i < 10; i++) {
Downtime storage downtime = info.downtime[i];
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.DowntimeStartPeriod)), miner, bytes32(i))) == downtime.startPeriod);
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.DowntimeEndPeriod)), miner, bytes32(i))) == downtime.endPeriod);
}
require(uint256(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.MinerIdsLength)), miner, 0)) == info.minerIds.length);
for (i = 0; i < info.minerIds.length && i < 10; i++) {
require(delegateGet(_testTarget, "getMinerInfo(uint8,address,uint256)",
bytes32(uint8(MinerInfoField.MinerId)), miner, bytes32(i)) == info.minerIds[i]);
}
}
function finishUpgrade(address _target) public onlyOwner {
super.finishUpgrade(_target);
MinersEscrow escrow = MinersEscrow(_target);
policyManager = escrow.policyManager();
minReleasePeriods = escrow.minReleasePeriods();
minAllowableLockedTokens = escrow.minAllowableLockedTokens();
maxAllowableLockedTokens = escrow.maxAllowableLockedTokens();
// Create fake period
lockedPerPeriod[RESERVED_PERIOD] = 111;
}
}

View File

@ -0,0 +1,47 @@
pragma solidity ^0.4.18;
import "./zeppelin/token/ERC20/BurnableToken.sol";
import "./zeppelin/token/ERC20/StandardToken.sol";
import "./zeppelin/token/ERC20/DetailedERC20.sol";
/**
* @title NuCypher KMS token
* @notice ERC20 token which can be burned by their owners
* @dev Optional approveAndCall() functionality to notify a contract if an approve() has occurred.
**/
contract NuCypherKMSToken is StandardToken, DetailedERC20, BurnableToken {
/**
* @notice Set amount of tokens
* @param _initialAmount Initial amount of tokens
**/
function NuCypherKMSToken (uint256 _initialAmount)
public
DetailedERC20('NuCypher KMS', 'KMS', 18)
{
balances[msg.sender] = _initialAmount;
totalSupply_ = _initialAmount;
Transfer(0x0, msg.sender, _initialAmount);
}
/**
* @notice Approves and then calls the receiving contract
*
* @dev call the receiveApproval function on the contract you want to be notified.
* This crafts the function signature manually so one doesn't have to include a contract in here just for this.
* receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
* it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
**/
function approveAndCall(address _spender, uint256 _value, bytes _extraData)
public returns (bool success)
{
approve(_spender, _value);
require(_spender.call(bytes4(keccak256("receiveApproval(address,uint256,address,bytes)")),
msg.sender, _value, this, _extraData));
return true;
}
}

View File

@ -0,0 +1,483 @@
pragma solidity ^0.4.18;
import "./zeppelin/token/ERC20/SafeERC20.sol";
import "./zeppelin/math/SafeMath.sol";
import "./zeppelin/math/Math.sol";
import "./lib/AdditionalMath.sol";
import "./MinersEscrow.sol";
import "./NuCypherKMSToken.sol";
import "./proxy/Upgradeable.sol";
/**
* @notice Contract holds policy data and locks fees
**/
contract PolicyManager is Upgradeable {
using SafeERC20 for NuCypherKMSToken;
using SafeMath for uint256;
using AdditionalMath for uint256;
using AdditionalMath for int256;
event PolicyCreated(
bytes20 indexed policyId,
address indexed client,
address[] indexed nodes
);
event PolicyRevoked(
bytes20 indexed policyId,
address indexed client,
uint256 value
);
event ArrangementRevoked(
bytes20 indexed policyId,
address indexed client,
address indexed node,
uint256 value
);
event Withdrawn(address indexed node, uint256 value);
event RefundForArrangement(
bytes20 indexed policyId,
address indexed client,
address indexed node,
uint256 value
);
event RefundForPolicy(
bytes20 indexed policyId,
address indexed client,
uint256 value
);
enum PolicyInfoField {
Client,
IndexOfDowntimePeriods,
LastRefundedPeriod,
ArrangementDisabled,
Rate,
StartPeriod,
LastPeriod,
Disabled
}
enum NodeInfoField {
Reward,
RewardRate,
LastMinedPeriod,
RewardDelta
}
struct ArrangementInfo {
uint256 indexOfDowntimePeriods;
uint256 lastRefundedPeriod;
bool active;
}
struct Policy {
address client;
mapping(address => ArrangementInfo) arrangements;
address[] nodes;
// policy for activity periods
uint256 rate;
uint256 startPeriod;
uint256 lastPeriod;
bool disabled;
}
struct NodeInfo {
uint256 reward;
uint256 rewardRate;
uint256 lastMinedPeriod;
mapping (uint256 => int256) rewardDelta;
}
bytes20 constant RESERVED_POLICY_ID = bytes20(0);
address constant RESERVED_NODE = 0x0;
MinersEscrow public escrow;
mapping (bytes20 => Policy) policies;
mapping (address => NodeInfo) nodes;
/**
* @notice Constructor sets address of the escrow contract
* @param _escrow Escrow contract
**/
function PolicyManager(MinersEscrow _escrow) public {
require(address(_escrow) != 0x0);
escrow = _escrow;
}
/**
* @notice Create policy by client
* @dev Generate policy id before creation
* @param _policyId Policy id
* @param _numberOfPeriods Duration of the policy in periods
* @param _nodes Nodes that will handle policy
**/
function createPolicy(
bytes20 _policyId,
uint256 _numberOfPeriods,
address[] _nodes
)
public payable
{
require(
policies[_policyId].rate == 0 &&
_numberOfPeriods != 0 &&
msg.value > 0 &&
msg.value % _numberOfPeriods % _nodes.length == 0 &&
_policyId != RESERVED_POLICY_ID
);
Policy storage policy = policies[_policyId];
policy.client = msg.sender;
policy.nodes = _nodes;
uint256 currentPeriod = escrow.getCurrentPeriod();
policy.startPeriod = currentPeriod.add(uint(1));
policy.lastPeriod = currentPeriod.add(_numberOfPeriods);
uint256 feeByPeriod = msg.value.div(_numberOfPeriods).div(_nodes.length);
policy.rate = feeByPeriod;
uint256 endPeriod = policy.lastPeriod.add(uint(1));
policy.nodes = _nodes;
for (uint256 i = 0; i < _nodes.length; i++) {
require(escrow.getLockedTokens(_nodes[i]) != 0 &&
_nodes[i] != RESERVED_NODE);
NodeInfo storage node = nodes[_nodes[i]];
node.rewardDelta[policy.startPeriod] = node.rewardDelta[policy.startPeriod]
.add(feeByPeriod);
node.rewardDelta[endPeriod] = node.rewardDelta[endPeriod].sub(feeByPeriod);
// TODO node should pay for this
if (node.lastMinedPeriod == 0) {
node.lastMinedPeriod = currentPeriod;
}
ArrangementInfo storage arrangement = policy.arrangements[_nodes[i]];
arrangement.indexOfDowntimePeriods =
uint256(escrow.getMinerInfo(MinersEscrow.MinerInfoField.DowntimeLength, _nodes[i], 0));
arrangement.active = true;
}
PolicyCreated(_policyId, msg.sender, _nodes);
}
/**
* @notice Update node reward
* @param _node Node address
* @param _period Processed period
**/
function updateReward(address _node, uint256 _period) external {
require(msg.sender == address(escrow));
NodeInfo storage node = nodes[_node];
if (node.lastMinedPeriod == 0 || _period <= node.lastMinedPeriod) {
return;
}
for (uint256 i = node.lastMinedPeriod + 1; i <= _period; i++) {
node.rewardRate = node.rewardRate.add(node.rewardDelta[i]);
// delete node.rewardDelta[i];
}
node.lastMinedPeriod = _period;
node.reward = node.reward.add(node.rewardRate);
}
/**
* @notice Withdraw reward by node
**/
function withdraw() public {
NodeInfo storage node = nodes[msg.sender];
uint256 reward = node.reward;
require(reward != 0);
node.reward = 0;
msg.sender.transfer(reward);
Withdrawn(msg.sender, reward);
}
/**
* @notice Revoke policy by client
* @param _policyId Policy id
**/
function revokePolicy(bytes20 _policyId) public {
Policy storage policy = policies[_policyId];
require(policy.client == msg.sender && !policy.disabled);
uint256 refundValue = 0;
uint256 endPeriod = policy.lastPeriod.add(uint(1));
for (uint256 i = 0; i < policy.nodes.length; i++) {
address node = policy.nodes[i];
if (!policy.arrangements[node].active) {
continue;
}
uint256 nodeRefundValue = revokeArrangement(policy, node, endPeriod);
refundValue = refundValue.add(nodeRefundValue);
ArrangementRevoked(_policyId, msg.sender, node, nodeRefundValue);
}
policy.disabled = true;
if (refundValue > 0) {
msg.sender.transfer(refundValue);
}
PolicyRevoked(_policyId, msg.sender, refundValue);
}
/**
* @notice Revoke arrangement by client
* @param _policyId Policy id
* @param _node Node that will be excluded
**/
function revokeArrangement(bytes20 _policyId, address _node)
public returns (uint256 refundValue)
{
Policy storage policy = policies[_policyId];
require(policy.client == msg.sender &&
!policy.disabled &&
policy.arrangements[_node].active);
uint256 endPeriod = policy.lastPeriod.add(uint(1));
refundValue = revokeArrangement(policy, _node, endPeriod);
if (refundValue > 0) {
msg.sender.transfer(refundValue);
}
ArrangementRevoked(_policyId, msg.sender, _node, refundValue);
}
/**
* @notice Revoke arrangement by client
* @param _policy Policy
* @param _node Node that will be excluded
* @param _endPeriod Pre-calculated end of period value
**/
function revokeArrangement(Policy storage _policy, address _node, uint256 _endPeriod)
internal returns (uint256 refundValue)
{
refundValue = calculateRefund(_policy, _node);
NodeInfo storage node = nodes[_node];
ArrangementInfo storage arrangement = _policy.arrangements[_node];
node.rewardDelta[arrangement.lastRefundedPeriod] =
node.rewardDelta[arrangement.lastRefundedPeriod].sub(_policy.rate);
node.rewardDelta[_endPeriod] = node.rewardDelta[_endPeriod].add(_policy.rate);
refundValue = refundValue.add(
_endPeriod.sub(arrangement.lastRefundedPeriod).mul(_policy.rate));
_policy.arrangements[_node].active = false;
}
/**
* @notice Refund part of fee by client
* @param _policyId Policy id
**/
function refund(bytes20 _policyId) public {
Policy storage policy = policies[_policyId];
require(msg.sender == policy.client && !policy.disabled);
uint256 refundValue = 0;
uint256 numberOfActive = policy.nodes.length;
for (uint256 i = 0; i < policy.nodes.length; i++) {
address node = policy.nodes[i];
if (!policy.arrangements[node].active) {
numberOfActive--;
continue;
}
uint256 nodeRefundValue = calculateRefund(policy, node);
if (policy.arrangements[node].lastRefundedPeriod > policy.lastPeriod) {
policy.arrangements[node].active = false;
numberOfActive--;
}
refundValue = refundValue.add(nodeRefundValue);
RefundForArrangement(_policyId, msg.sender, node, nodeRefundValue);
}
if (refundValue > 0) {
msg.sender.transfer(refundValue);
}
if (numberOfActive == 0) {
policy.disabled = true;
}
RefundForPolicy(_policyId, msg.sender, refundValue);
}
/**
* @notice Refund part of one node's fee by client
* @param _policyId Policy id
* @param _node Node address
**/
function refund(bytes20 _policyId, address _node)
public returns (uint256 refundValue)
{
Policy storage policy = policies[_policyId];
require(msg.sender == policy.client &&
!policy.disabled &&
policy.arrangements[_node].active);
refundValue = calculateRefund(policy, _node);
if (policy.arrangements[_node].lastRefundedPeriod > policy.lastPeriod) {
policy.arrangements[_node].active = false;
}
if (refundValue > 0) {
msg.sender.transfer(refundValue);
}
RefundForArrangement(_policyId, msg.sender, _node, refundValue);
}
/**
* @notice Calculate amount of refund
* @param _policy Policy
* @param _node Node for calculation
**/
//TODO extract checkRefund method
function calculateRefund(Policy storage _policy, address _node) internal returns (uint256) {
ArrangementInfo storage arrangement = _policy.arrangements[_node];
uint256 maxPeriod = Math.min256(escrow.getCurrentPeriod(), _policy.lastPeriod);
uint256 minPeriod = Math.max256(_policy.startPeriod, arrangement.lastRefundedPeriod);
uint256 downtimePeriods = 0;
uint256 length = uint256(escrow.getMinerInfo(MinersEscrow.MinerInfoField.DowntimeLength, _node, 0));
for (uint256 i = arrangement.indexOfDowntimePeriods; i < length; i++) {
uint256 startPeriod =
uint256(escrow.getMinerInfo(MinersEscrow.MinerInfoField.DowntimeStartPeriod, _node, i));
uint256 endPeriod =
uint256(escrow.getMinerInfo(MinersEscrow.MinerInfoField.DowntimeEndPeriod, _node, i));
if (startPeriod > maxPeriod) {
break;
} else if (endPeriod < minPeriod) {
continue;
}
downtimePeriods = downtimePeriods.add(
Math.min256(maxPeriod, endPeriod)
.sub(Math.max256(minPeriod, startPeriod))
.add(uint(1)));
if (maxPeriod <= endPeriod) {
break;
}
}
arrangement.indexOfDowntimePeriods = i;
uint256 lastActivePeriod =
uint256(escrow.getMinerInfo(MinersEscrow.MinerInfoField.LastActivePeriod, _node, 0));
if (i == length && lastActivePeriod < maxPeriod) {
downtimePeriods = downtimePeriods.add(
maxPeriod.sub(Math.max256(
minPeriod.sub(uint(1)), lastActivePeriod)));
}
arrangement.lastRefundedPeriod = maxPeriod.add(uint(1));
return _policy.rate.mul(downtimePeriods);
}
/**
* @notice Get number of nodes in policy
* @param _policyId Policy id
**/
function getPolicyNodesLength(bytes20 _policyId)
public view returns (uint256)
{
return policies[_policyId].nodes.length;
}
/**
* @notice Get node from policy
* @param _policyId Policy id
* @param _index Index of node
**/
function getPolicyNode(bytes20 _policyId, uint256 _index)
public view returns (address)
{
return policies[_policyId].nodes[_index];
}
/**
* @notice Get information about policy
* @param _field Field to get
* @param _policyId Policy id
* @param _node Address of node
**/
function getPolicyInfo(PolicyInfoField _field, bytes20 _policyId, address _node)
public view returns (bytes32)
{
Policy storage policy = policies[_policyId];
if (_field == PolicyInfoField.Client) {
return bytes32(policy.client);
} else if (_field == PolicyInfoField.Rate) {
return bytes32(policy.rate);
} else if (_field == PolicyInfoField.StartPeriod) {
return bytes32(policy.startPeriod);
} else if (_field == PolicyInfoField.LastPeriod) {
return bytes32(policy.lastPeriod);
} else if (_field == PolicyInfoField.Disabled) {
return policy.disabled ? bytes32(1) : bytes32(0);
} else if (_field == PolicyInfoField.IndexOfDowntimePeriods) {
return bytes32(policy.arrangements[_node].indexOfDowntimePeriods);
} else if (_field == PolicyInfoField.LastRefundedPeriod) {
return bytes32(policy.arrangements[_node].lastRefundedPeriod);
} else if (_field == PolicyInfoField.ArrangementDisabled) {
return !policy.arrangements[_node].active ? bytes32(1) : bytes32(0);
}
}
/**
* @notice Get information about node
* @param _field Field to get
* @param _node Address of node
* @param _period Period to get reward delta
**/
function getNodeInfo(NodeInfoField _field, address _node, uint256 _period)
public view returns (bytes32)
{
NodeInfo storage nodeInfo = nodes[_node];
if (_field == NodeInfoField.Reward) {
return bytes32(nodeInfo.reward);
} else if (_field == NodeInfoField.RewardRate) {
return bytes32(nodeInfo.rewardRate);
} else if (_field == NodeInfoField.LastMinedPeriod) {
return bytes32(nodeInfo.lastMinedPeriod);
} else if (_field == NodeInfoField.RewardDelta) {
return bytes32(nodeInfo.rewardDelta[_period]);
}
}
function verifyState(address _testTarget) public onlyOwner {
require(address(delegateGet(_testTarget, "escrow()")) == address(escrow));
Policy storage policy = policies[RESERVED_POLICY_ID];
require(address(delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.Client)), RESERVED_POLICY_ID, 0x0)) == policy.client);
require(uint256(delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.Rate)), RESERVED_POLICY_ID, 0x0)) == policy.rate);
require(uint256(delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.StartPeriod)), RESERVED_POLICY_ID, 0x0)) == policy.startPeriod);
require(uint256(delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.LastPeriod)), RESERVED_POLICY_ID, 0x0)) == policy.lastPeriod);
require((delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.Disabled)), RESERVED_POLICY_ID, 0x0) == bytes32(1)) == policy.disabled);
require(uint256(delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.IndexOfDowntimePeriods)), RESERVED_POLICY_ID, bytes32(RESERVED_NODE))) ==
policy.arrangements[RESERVED_NODE].indexOfDowntimePeriods);
require(uint256(delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.LastRefundedPeriod)), RESERVED_POLICY_ID, bytes32(RESERVED_NODE))) ==
policy.arrangements[RESERVED_NODE].lastRefundedPeriod);
require((delegateGet(_testTarget, "getPolicyInfo(uint8,bytes20,address)",
bytes32(uint8(PolicyInfoField.ArrangementDisabled)), RESERVED_POLICY_ID, bytes32(RESERVED_NODE)) == bytes32(1)) ==
!policy.arrangements[RESERVED_NODE].active);
require(uint256(delegateGet(_testTarget, "getPolicyNodesLength(bytes20)",
RESERVED_POLICY_ID)) == policy.nodes.length);
require(address(delegateGet(_testTarget, "getPolicyNode(bytes20,uint256)",
RESERVED_POLICY_ID, 0)) == policy.nodes[0]);
NodeInfo storage nodeInfo = nodes[RESERVED_NODE];
require(uint256(delegateGet(_testTarget, "getNodeInfo(uint8,address,uint256)",
bytes32(uint8(NodeInfoField.Reward)), bytes32(RESERVED_NODE), 0)) == nodeInfo.reward);
require(uint256(delegateGet(_testTarget, "getNodeInfo(uint8,address,uint256)",
bytes32(uint8(NodeInfoField.RewardRate)), bytes32(RESERVED_NODE), 0)) == nodeInfo.rewardRate);
require(uint256(delegateGet(_testTarget, "getNodeInfo(uint8,address,uint256)",
bytes32(uint8(NodeInfoField.LastMinedPeriod)), bytes32(RESERVED_NODE), 0)) == nodeInfo.lastMinedPeriod);
require(int256(delegateGet(_testTarget, "getNodeInfo(uint8,address,uint256)",
bytes32(uint8(NodeInfoField.RewardDelta)), bytes32(RESERVED_NODE), 11)) == nodeInfo.rewardDelta[11]);
}
function finishUpgrade(address _target) public onlyOwner {
PolicyManager policyManager = PolicyManager(_target);
escrow = policyManager.escrow();
// Create fake Policy and NodeInfo to use them in verifyState(address)
Policy storage policy = policies[RESERVED_POLICY_ID];
policy.client = owner;
policy.startPeriod = 1;
policy.lastPeriod = 2;
policy.rate = 3;
policy.disabled = true;
policy.nodes.push(RESERVED_NODE);
policy.arrangements[RESERVED_NODE].indexOfDowntimePeriods = 11;
policy.arrangements[RESERVED_NODE].lastRefundedPeriod = 22;
policy.arrangements[RESERVED_NODE].active = true;
NodeInfo storage nodeInfo = nodes[RESERVED_NODE];
nodeInfo.reward = 100;
nodeInfo.rewardRate = 33;
nodeInfo.lastMinedPeriod = 44;
nodeInfo.rewardDelta[11] = 55;
}
}

View File

@ -0,0 +1,169 @@
pragma solidity ^0.4.18;
import "./zeppelin/token/ERC20/SafeERC20.sol";
import "./zeppelin/ownership/Ownable.sol";
import "./zeppelin/math/SafeMath.sol";
import "./NuCypherKMSToken.sol";
import "./MinersEscrow.sol";
/**
* @notice Contract holds tokens for vesting.
* Also tokens can be send to the miners escrow
**/
contract UserEscrow is Ownable {
using SafeERC20 for NuCypherKMSToken;
using SafeMath for uint256;
event Deposited(address indexed sender, uint256 value, uint256 duration);
event Withdrawn(address indexed owner, uint256 value);
event DepositedAsMiner(address indexed owner, uint256 value, uint256 periods);
event WithdrawnAsMiner(address indexed owner, uint256 value);
event Locked(address indexed owner, uint256 value, uint256 periods);
event LockSwitched(address indexed owner);
event ActivityConfirmed(address indexed owner);
event Mined(address indexed owner);
event RewardWithdrawnAsMiner(address indexed owner, uint256 value);
event RewardWithdrawn(address indexed owner, uint256 value);
NuCypherKMSToken public token;
MinersEscrow public escrow;
PolicyManager public policyManager;
uint256 public lockedValue;
uint256 public endLockTimestamp;
uint256 public lockDuration;
/**
* @notice Constructor sets addresses of the contracts
* @param _token Token contract
* @param _escrow Escrow contract
* @param _policyManager PolicyManager contract
**/
function UserEscrow(
NuCypherKMSToken _token,
MinersEscrow _escrow,
PolicyManager _policyManager
)
public
{
require(address(_token) != 0x0 &&
address(_escrow) != 0x0 &&
address(_policyManager) != 0x0);
token = _token;
escrow = _escrow;
policyManager = _policyManager;
}
function () public payable {}
/**
* @notice Initial tokens deposit
* @param _value Amount of token to deposit
* @param _duration Duration of tokens locking
**/
function initialDeposit(uint256 _value, uint256 _duration) public {
require(lockedValue == 0 && _value > 0);
endLockTimestamp = block.timestamp.add(_duration);
lockDuration = _duration;
lockedValue = _value;
token.safeTransferFrom(msg.sender, address(this), _value);
Deposited(msg.sender, _value, _duration);
}
/**
* @notice Get locked tokens value
**/
function getLockedTokens() public view returns (uint256) {
if (endLockTimestamp <= block.timestamp) {
return 0;
}
return lockedValue.mul(endLockTimestamp.sub(block.timestamp))
.div(lockDuration);
}
/**
* @notice Withdraw available amount of tokens to owner
* @param _value Amount of token to withdraw
**/
function withdraw(uint256 _value) public onlyOwner {
require(token.balanceOf(address(this)).sub(getLockedTokens()) >= _value);
token.safeTransfer(owner, _value);
Withdrawn(owner, _value);
}
/**
* @notice Deposit tokens to the miners escrow
* @param _value Amount of token to deposit
* @param _periods Amount of periods during which tokens will be unlocked
**/
function minerDeposit(uint256 _value, uint256 _periods) public onlyOwner {
require(token.balanceOf(address(this)) > _value);
token.approve(address(escrow), _value);
escrow.deposit(_value, _periods);
DepositedAsMiner(owner, _value, _periods);
}
/**
* @notice Withdraw available amount of tokens from the miners escrow to the user escrow
* @param _value Amount of token to withdraw
**/
function minerWithdraw(uint256 _value) public onlyOwner {
escrow.withdraw(_value);
WithdrawnAsMiner(owner, _value);
}
/**
* @notice Lock some tokens or increase lock in the miners escrow
* @param _value Amount of tokens which should lock
* @param _periods Amount of periods during which tokens will be unlocked
**/
function lock(uint256 _value, uint256 _periods) public onlyOwner {
escrow.lock(_value, _periods);
Locked(owner, _value, _periods);
}
/**
* @notice Switch lock in the miners escrow
**/
function switchLock() public onlyOwner {
escrow.switchLock();
LockSwitched(owner);
}
/**
* @notice Confirm activity for future period in the miners escrow
**/
function confirmActivity() external onlyOwner {
escrow.confirmActivity();
ActivityConfirmed(owner);
}
/**
* @notice Mint tokens in the miners escrow
**/
function mint() external onlyOwner {
escrow.mint();
Mined(owner);
}
/**
* @notice Withdraw available reward from the policy manager to the user escrow
**/
function policyRewardWithdraw() public onlyOwner {
uint256 balance = this.balance;
policyManager.withdraw();
RewardWithdrawnAsMiner(owner, this.balance - balance);
}
/**
* @notice Withdraw available reward to the owner
**/
function rewardWithdraw() public onlyOwner {
uint256 balance = this.balance;
require(balance != 0);
owner.transfer(balance);
RewardWithdrawn(owner, balance);
}
}

View File

@ -0,0 +1,60 @@
pragma solidity ^0.4.18;
import "../zeppelin/math/SafeMath.sol";
/**
* @notice Additional math operations
**/
library AdditionalMath {
using SafeMath for uint256;
/**
* @notice Division and ceil
**/
function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
return (a.add(b) - 1) / b;
}
/**
* @dev Adds unsigned value to signed value, throws on overflow.
*/
function add(int256 a, uint256 b) internal pure returns (int256) {
int256 c = a + int256(b);
assert(c >= a);
return c;
}
/**
* @dev Subtracts two numbers, throws on overflow.
*/
function sub(int256 a, uint256 b) internal pure returns (int256) {
int256 c = a - int256(b);
assert(c <= a);
return c;
}
/**
* @dev Adds signed value to unsigned value, throws on overflow.
*/
function add(uint256 a, int256 b) internal pure returns (uint256) {
if (b >= 0) {
return a.add(uint256(b));
} else {
return a.sub(uint256(-b));
}
}
/**
* @dev Subtracts signed value from unsigned value, throws on overflow.
*/
function sub(uint256 a, int256 b) internal pure returns (uint256) {
if (b >= 0) {
return a.sub(uint256(b));
} else {
return a.add(uint256(-b));
}
}
}

View File

@ -0,0 +1,120 @@
pragma solidity ^0.4.18;
/**
* @notice Doubly linked list for addresses
* @dev see https://github.com/o0ragman0o/LibCLL
* @dev see https://github.com/Majoolr/ethereum-libraries/blob/master/LinkedListLib/truffle/contracts/LinkedListLib.sol
**/
library LinkedList {
address constant NULL = 0x0;
address constant HEAD = NULL;
bool constant PREV = false;
bool constant NEXT = true;
struct Data {
mapping (address => mapping (bool => address)) data;
uint256 count;
}
/// @notice Return existential state of a list.
function exists(Data storage self)
internal view returns (bool)
{
if (self.data[HEAD][PREV] != HEAD ||
self.data[HEAD][NEXT] != HEAD)
return true;
}
/// @notice Returns the number of elements in the list
function sizeOf(Data storage self)
internal view returns (uint result)
{
return self.count;
}
/**
* @notice Check existence of a value
* @param value Value to search for
**/
function valueExists(Data storage self, address value)
internal view returns (bool)
{
if (self.data[value][PREV] == HEAD && self.data[value][NEXT] == HEAD) {
if (self.data[HEAD][NEXT] == value) {
return true;
} else {
return false;
}
} else {
return true;
}
}
/// @notice Returns the links of a value as an array
function getLinks(Data storage self, address value)
internal view returns (address[2])
{
return [self.data[value][PREV], self.data[value][NEXT]];
}
/// @notice Returns the link of a value in specified direction.
function step(Data storage self, address value, bool direction)
internal view returns (address)
{
return self.data[value][direction];
}
/// @notice Creates a bidirectional link between two nodes on specified direction
function createLinks(
Data storage self,
address from,
address to,
bool direction
)
internal
{
self.data[to][!direction] = from;
self.data[from][direction] = to;
}
/// @notice Insert value beside existing value `from` in specified direction.
function insert (
Data storage self,
address from,
address value,
bool direction
)
internal
{
address to = self.data[from][direction];
createLinks(self, from, value, direction);
createLinks(self, value, to, direction);
self.count++;
}
/// @notice Remove value from the list.
function remove(Data storage self, address value) internal returns (address) {
if (value == NULL ||
((self.data[value][NEXT] == HEAD) &&
(self.data[value][PREV] == HEAD) &&
(self.data[self.data[value][PREV]][NEXT] != value))) {
return NULL;
}
createLinks(self, self.data[value][PREV], self.data[value][NEXT], NEXT);
delete self.data[value][PREV];
delete self.data[value][NEXT];
self.count--;
return value;
}
/// @notice Put value to the top of the list in specified direction.
function push(Data storage self, address value, bool direction) internal {
insert(self, HEAD, value, direction);
}
/// @notice Get value from the top of the list in specified direction and remove it.
function pop(Data storage self, bool direction) internal returns (address) {
return remove(self, step(self, HEAD, direction));
}
}

View File

@ -0,0 +1,96 @@
pragma solidity ^0.4.18;
import "./Upgradeable.sol";
/**
* @dev Based on https://github.com/willjgriff/solidity-playground/blob/master/Upgradable/ByzantiumUpgradable/contracts/UpgradableContractProxyOLD.sol
* TODO When python TestRPC will have Byzantium hard fork then should use https://github.com/willjgriff/solidity-playground/blob/master/Upgradable/ByzantiumUpgradable/contracts/UpgradableContractProxy.sol
* @notice Proxying requests to other contracts.
* Client should use ABI of real contract and address of this contract
**/
contract Dispatcher is Upgradeable {
event Upgraded(address indexed from, address indexed to, address owner);
event RolledBack(address indexed from, address indexed to, address owner);
/**
* @param _target Target contract address
**/
function Dispatcher(address _target) public {
target = _target;
require(target.delegatecall(bytes4(keccak256("finishUpgrade(address)")), target));
Upgraded(0x0, _target, msg.sender);
}
/**
* @notice Verify new contract storage and upgrade target
* @param _target New target contract address
**/
function upgrade(address _target) public onlyOwner {
verifyState(_target);
verifyUpgradeableState(target, _target);
previousTarget = target;
target = _target;
require(target.delegatecall(bytes4(keccak256("finishUpgrade(address)")), target));
Upgraded(previousTarget, _target, msg.sender);
}
function verifyState(address _testTarget) public onlyOwner {
//checks equivalence accessing target through new contract and current storage
require(address(delegateGet(_testTarget, "target()")) == target);
require(address(delegateGet(_testTarget, "owner()")) == owner);
}
/**
* @notice Rollback to previous target
* @dev Test storage carefully before upgrade again after rollback
**/
function rollback() public onlyOwner {
require(previousTarget != 0x0);
RolledBack(target, previousTarget, msg.sender);
verifyUpgradeableState(previousTarget, target);
target = previousTarget;
previousTarget = 0x0;
require(target.delegatecall(bytes4(keccak256("finishUpgrade(address)")), target));
}
/**
* @dev Call verifyState method for Upgradeable contract
**/
function verifyUpgradeableState(address _from, address _to) internal {
require(_from.delegatecall(bytes4(keccak256("verifyState(address)")), _to));
}
function finishUpgrade(address) public {}
/**
* @dev Fallback function send all requests to the target contract.
* If contract not exists then result will be unpredictable (see DELEGATECALL)
**/
function () public payable {
assert(target != 0x0);
address upgradableContractMem = target;
uint32 size = 96;
assembly {
let freeMemAddress := mload(0x40)
mstore(0x40, add(freeMemAddress, calldatasize))
calldatacopy(freeMemAddress, 0x0, calldatasize)
// switch delegatecall(gas, upgradableContractMem, freeMemAddress, calldatasize, 0, 0)
switch delegatecall(gas, upgradableContractMem, freeMemAddress, calldatasize, 0, size)
case 0 {
revert(0x0, 0)
}
default {
// returndatacopy(0x0, 0x0, returndatasize)
// return(0x0, returndatasize)
return(0x0, size)
}
}
}
}

View File

@ -0,0 +1,174 @@
pragma solidity ^0.4.18;
import "../zeppelin/math/SafeMath.sol";
import "./Dispatcher.sol";
import "./Upgradeable.sol";
contract MinersEscrowInterface {
function getLockedTokens(address _owner)
public constant returns (uint256);
}
/**
* @notice Contract for version voting
**/
contract Government is Upgradeable {
using SafeMath for uint256;
event VotingCreated(
uint256 indexed votingNumber,
VotingType indexed votingType,
address indexed newAddress
);
event UpgradeCommitted(
uint256 indexed votingNumber,
VotingType indexed votingType,
address indexed newAddress
);
enum VotingState {
Active,
UpgradeWaiting,
Finished
}
enum VotingType {
UpgradeGovernment,
UpgradeEscrow,
UpgradePolicyManager,
RollbackGovernment,
RollbackEscrow,
RollbackPolicyManager
}
Dispatcher public escrow;
Dispatcher public policyManager;
uint256 public votingDurationSeconds;
// last vote for specified voting number
mapping(address => uint256) public lastVote;
uint256 public votingNumber;
uint256 public endVotingTimestamp;
bool public upgradeFinished;
VotingType public votingType;
address public newAddress;
// TODO maybe change to votes "for" only
uint256 public votesFor;
uint256 public votesAgainst;
/**
* @notice Contracts sets address for upgradeable contracts
* @param _escrow The escrow dispatcher
* @param _policyManager The policy manager dispatcher
* @param _votingDurationHours Voting duration in hours
**/
function Government(
Dispatcher _escrow,
Dispatcher _policyManager,
uint256 _votingDurationHours
)
public
{
require(address(_escrow) != 0x0 &&
address(_policyManager) != 0x0 &&
_votingDurationHours != 0);
escrow = _escrow;
policyManager = _policyManager;
votingDurationSeconds = _votingDurationHours.mul(1 hours);
}
/**
* @notice Get voting state
**/
function getVotingState() public view returns (VotingState) {
if (block.timestamp <= endVotingTimestamp) {
return VotingState.Active;
}
if (votesFor > votesAgainst && !upgradeFinished) {
return VotingState.UpgradeWaiting;
}
return VotingState.Finished;
}
/**
* @notice Create voting for upgrade or rollback
* @param _votingType Voting type
* @param _newAddress New address for upgrade. Not used for rollback
**/
function createVoting(
VotingType _votingType,
address _newAddress
) public {
require(getVotingState() == VotingState.Finished &&
MinersEscrowInterface(escrow).getLockedTokens(msg.sender) != 0);
votingNumber = votingNumber.add(1);
endVotingTimestamp = block.timestamp.add(votingDurationSeconds);
upgradeFinished = false;
votesFor = 0;
votesAgainst = 0;
votingType = _votingType;
newAddress = _newAddress;
VotingCreated(votingNumber, votingType, newAddress);
}
/**
* @notice Vote
**/
function vote(bool voteFor) public {
require(getVotingState() == VotingState.Active && lastVote[msg.sender] < votingNumber);
uint256 lockedTokens = MinersEscrowInterface(escrow).getLockedTokens(msg.sender);
require(lockedTokens > 0);
if (voteFor) {
votesFor = votesFor.add(lockedTokens);
} else {
votesAgainst = votesAgainst.add(lockedTokens);
}
lastVote[msg.sender] = votingNumber;
}
/**
* @notice Commit upgrade if voting is successful
**/
function commitUpgrade() public {
require(getVotingState() == VotingState.UpgradeWaiting);
upgradeFinished = true;
if (votingType == VotingType.UpgradeGovernment) {
Dispatcher(address(this)).upgrade(newAddress);
} else if (votingType == VotingType.UpgradeEscrow) {
escrow.upgrade(newAddress);
} else if (votingType == VotingType.UpgradePolicyManager) {
policyManager.upgrade(newAddress);
} else if (votingType == VotingType.RollbackGovernment) {
Dispatcher(address(this)).rollback();
} else if (votingType == VotingType.RollbackEscrow) {
escrow.rollback();
} else if (votingType == VotingType.RollbackPolicyManager) {
policyManager.rollback();
}
UpgradeCommitted(votingNumber, votingType, newAddress);
}
function verifyState(address _testTarget) public onlyOwner {
require(address(delegateGet(_testTarget, "escrow()")) == address(escrow));
require(address(delegateGet(_testTarget, "policyManager()")) == address(policyManager));
require(uint256(delegateGet(_testTarget, "votingDurationSeconds()")) == votingDurationSeconds);
require(uint256(delegateGet(_testTarget, "votingNumber()")) == votingNumber);
require(uint256(delegateGet(_testTarget, "endVotingTimestamp()")) == endVotingTimestamp);
require(delegateGet(_testTarget, "upgradeFinished()") ==
(upgradeFinished ? bytes32(1) : bytes32(0)));
require(uint256(delegateGet(_testTarget, "votingType()")) == uint256(votingType));
require(address(delegateGet(_testTarget, "newAddress()")) == newAddress);
require(uint256(delegateGet(_testTarget, "votesFor()")) == votesFor);
require(uint256(delegateGet(_testTarget, "votesAgainst()")) == votesAgainst);
}
function finishUpgrade(address _target) public onlyOwner {
Government government = Government(_target);
escrow = government.escrow();
policyManager = government.policyManager();
votingDurationSeconds = government.votingDurationSeconds();
}
}

View File

@ -0,0 +1,50 @@
# Upgradeable contracts
Smart contracts in Ethereum are not really changeable. Even it can not be deleted - contract still exists in blockchain after `selfdestruct` (only storage cleared).
So fixing bugs and upgrading logic is to change contract (address) and save previous storage values.
Simple way for this is to create new contract, then copy storage there and destruct (mark as deleted) old contract.
But in this case client should change address for requested contract and also while migration will be active two versions of contract.
More convenient way is to use proxy contract with interface where each method redirect to the target contract.
It's good option because client uses one address most of the time but also have some minus - when we should add some methods then need to change proxy address too.
Another way is using fallback function in proxy contract - this function will execute on any request, redirect request to target and return result value (using some opcodes).
Almost like previous option, but this proxy doesn't have interface methods, only fallback function, so no need to change proxy address if we should change methods.
This way is not ideal and has some restrictions (here only major):
* Sending Ether from client's account to contract uses fallback function. Such transaction could consume only 2300 gas (http://solidity.readthedocs.io/en/develop/contracts.html#fallback-function)
* Proxy contract (Dispatcher) holds storage (not in the contract itself). While upgrading storage values should be the same or equivalent (see below)
# Sources
More examples:
* https://github.com/maraoz/solidity-proxy - good realization of using libraries (not contracts) but too complex and some ideas is obsolete after Byzantium hard fork
* https://github.com/willjgriff/solidity-playground - most of the upgradeable code taken from this repository
* https://github.com/0v1se/contracts-upgradeable - almost the same but also have code for verifying upgrade
# Interaction scheme
![Interaction scheme](pics/Dispatcher.png)
* Dispatcher - proxy contract that redirects requests to the target address.
Also it clearly holds own values (owner and target address) and stores the values of the target contract but not explicitly.
Client should use result contract or interface ABI while sending request to the Dispatcher address.
Owner can change target address by using Dispatcher ABI.
Dispatcher contract uses `delegatecall` for redirecting requests, so msg.sender remains client address and uses storage from dispatcher when executing method in target contract.
If target address is not set or target contract is not exists result may be unpredictable, because `delegatecall` will return true.
* Contract - upgradeable contract, each version should have same order of storage values.
New versions of contract can expand values, but must contain all old values (first of all should contain values from dispatcher).
This contract is like library because it's storage is not used.
If client send request to the contract without using dispatcher then request could be executed without exception
but using wrong target address (should be dispatcher address) and wrong storage (should be dispatcher storage).
* Government - contract that organizes voting for upgrade of contracts and carries out this update.
# Interaction order
* Admin creates contract_v1 and creates dispatcher using contract_v1 address
* Client instantiates contract_v1 using dispatcher address and sends requests
* Admin or any client creates contract_v2 and creates voting in the government contract
* All clients vote for or against upgrade during the voting time
* After successful voting admin or any client commits upgrade in the government contract
* Client uses old instance without changing (if the methods are not changed)
# Development
* Use Upgradeable as base contract for all contracts that will be used with Dispatcher
* Implement `verifyState(address)` method which checks that new version has correct storage
* Implement `finishUpgrade(address)` method which should copy initialization data from library storage to the dispatcher storage
* Each upgrade should include tests which check storage equivalence
# TODO
* Fix return size from `delegatecall` in Dispatcher after eth-testrpc will include Byzantium hard fork.

View File

@ -0,0 +1,47 @@
# Desired properties:
* Nodes decide which update should occur;
* Nodes can rollback contract if new version has bugs.
# Approaches
* "Hard-fork"
![Hard-fork](pics/Hard-fork.png)
Each version is a new contract with separate address and storage.
Nodes should change contract address that they use.
- Advantages:
- Code is simpler, no special requirements;
- Each node can choose which contract to use.
- Disadvantages:
- There are two versions of contract while updating, so contracts should work together.
Also we can add another contract (Government) for voting and migration between versions.
* [Dispatcher](README.MD) (proxy)
![Dispatcher](pics/Dispatcher2.png)
Using proxy contract that holds storage and library address.
Updating is changing only one library address in proxy contract.
- Advantages:
- Instant update without changing address for nodes.
- Disadvantages:
- Certain rules for updating the contract storage,
better to write additional methods for testing contract storage;
- A voting contract (Government) is required for a legitimate upgrade.
# Implementation
* "Hard-fork"
* Soft updating with two contracts
![Hard-fork-impl1](pics/Hard-fork2.png)
Updating contracts should contain methods for transfer data (amount of locked tokens, balance etc.).
For example, change manager address from old to new in Wallet contract.
Also both version should interact for correct mining
(all locked blocks will be sum from old and new versions in the current period).
For rollback will be enough to move data from the new version back to the previous.
In some moment, new version have to disable previous contract and move remaining data to the new version.
* Full update from one contract to another
![Hard-fork-impl2](pics/Hard-fork3.png)
All nodes vote for updating using additional contract.
After the end of voting old contract should be blocked and new version is activated (or created).
And then data will be copied from old version to new, for example, by new contract.
Rollback is almost the same: new version is paused,
data is moved back to the old version and old version is activated.
So main task is the addition of methods for obtaining data for old and new versions.
* Dispatcher
![Dispatcher-impl](pics/Dispatcher3.png)
After voting Government contract changes library address in proxy.
Rollback is changing address back from the new library to the old.
Main goal is create right voting and check storage while setting new address.

View File

@ -0,0 +1,140 @@
pragma solidity ^0.4.18;
import "../zeppelin/ownership/Ownable.sol";
/**
* @notice Base contract for upgradeable contract
* @dev Implementation contract should realize verifyState(address testTarget) method
* by checking storage variables (see verifyState(address testTarget) in Dispatcher)
**/
contract Upgradeable is Ownable {
/**
* @dev Contracts at the target must reserve the first location in storage for this address as
* they will be called through this contract.
* Stored data actually lives in the Dispatcher.
* However the storage layout is specified here in the implementing contracts.
**/
address public target;
/**
* @dev Previous contract address (if available). Used for rollback
**/
address public previousTarget;
/**
* @dev Method for verifying storage state.
* Should check that new target contract returns right storage value
**/
function verifyState(address _testTarget) public /*onlyOwner*/;
/**
* @dev Copy values from the new target to the current storage
* @param _target New target contract address
**/
function finishUpgrade(address _target) public /*onlyOwner*/;
/**
* @dev Simple method for call function without parameters.
* Result should not exceed 32 bytes
**/
//TODO fix return size
function delegateGet(address _testTarget, string _signature)
internal returns (bytes32 result)
{
bytes4 targetCall = bytes4(keccak256(_signature));
assembly {
let free := mload(0x40)
mstore(free, targetCall)
let retVal := delegatecall(gas, _testTarget, free, 4, free, 32)
result := mload(free)
}
}
/**
* @dev Simple method for call function with one parameter.
* Result should not exceed 32 bytes
**/
//TODO fix return size
function delegateGet(address _testTarget, string _signature, bytes32 _argument)
internal returns (bytes32 result)
{
bytes4 targetCall = bytes4(keccak256(_signature));
assembly {
let in_pos := mload(0x40)
mstore(in_pos, targetCall)
mstore(add(in_pos, 0x04), _argument)
switch delegatecall(gas, _testTarget, in_pos, 0x24, in_pos, 32)
case 0 {
revert(0x0, 0)
}
default {
result := mload(in_pos)
mstore(0x40, add(in_pos, 0x24))
}
}
}
/**
* @dev Simple method for call function with two parameters.
* Result should not exceed 32 bytes
**/
//TODO fix return size
function delegateGet(
address _testTarget,
string _signature,
bytes32 _argument1,
bytes32 _argument2
)
internal returns (bytes32 result)
{
bytes4 targetCall = bytes4(keccak256(_signature));
assembly {
let in_pos := mload(0x40)
mstore(in_pos, targetCall)
mstore(add(in_pos, 0x04), _argument1)
mstore(add(in_pos, 0x24), _argument2)
switch delegatecall(gas, _testTarget, in_pos, 0x44, in_pos, 32)
case 0 {
revert(0x0, 0)
}
default {
result := mload(in_pos)
mstore(0x40, add(in_pos, 0x44))
}
}
}
/**
* @dev Simple method for call function with three parameters.
* Result should not exceed 32 bytes
**/
//TODO fix return size
function delegateGet(
address _testTarget,
string _signature,
bytes32 _argument1,
bytes32 _argument2,
bytes32 _argument3
)
internal returns (bytes32 result)
{
bytes4 targetCall = bytes4(keccak256(_signature));
assembly {
let in_pos := mload(0x40)
mstore(in_pos, targetCall)
mstore(add(in_pos, 0x04), _argument1)
mstore(add(in_pos, 0x24), _argument2)
mstore(add(in_pos, 0x44), _argument3)
switch delegatecall(gas, _testTarget, in_pos, 0x64, in_pos, 32)
case 0 {
revert(0x0, 0)
}
default {
result := mload(in_pos)
mstore(0x40, add(in_pos, 0x64))
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,24 @@
pragma solidity ^0.4.18;
/**
* @title Math
* @dev Assorted math operations
*/
library Math {
function max64(uint64 a, uint64 b) internal pure returns (uint64) {
return a >= b ? a : b;
}
function min64(uint64 a, uint64 b) internal pure returns (uint64) {
return a < b ? a : b;
}
function max256(uint256 a, uint256 b) internal pure returns (uint256) {
return a >= b ? a : b;
}
function min256(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}

View File

@ -0,0 +1,48 @@
pragma solidity ^0.4.18;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
/**
* @dev Multiplies two numbers, throws on overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
uint256 c = a * b;
assert(c / a == b);
return c;
}
/**
* @dev Integer division of two numbers, truncating the quotient.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
/**
* @dev Adds two numbers, throws on overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}

View File

@ -0,0 +1,42 @@
pragma solidity ^0.4.18;
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
function Ownable() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}

View File

@ -0,0 +1,51 @@
pragma solidity ^0.4.18;
import "./ERC20Basic.sol";
import "../../math/SafeMath.sol";
/**
* @title Basic token
* @dev Basic version of StandardToken, with no allowances.
*/
contract BasicToken is ERC20Basic {
using SafeMath for uint256;
mapping(address => uint256) balances;
uint256 totalSupply_;
/**
* @dev total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
/**
* @dev transfer token for a specified address
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
// SafeMath.sub will throw if there is not enough balance.
balances[msg.sender] = balances[msg.sender].sub(_value);
balances[_to] = balances[_to].add(_value);
Transfer(msg.sender, _to, _value);
return true;
}
/**
* @dev Gets the balance of the specified address.
* @param _owner The address to query the the balance of.
* @return An uint256 representing the amount owned by the passed address.
*/
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
}

View File

@ -0,0 +1,29 @@
pragma solidity ^0.4.18;
import "./BasicToken.sol";
/**
* @title Burnable Token
* @dev Token that can be irreversibly burned (destroyed).
*/
contract BurnableToken is BasicToken {
event Burn(address indexed burner, uint256 value);
/**
* @dev Burns a specific amount of tokens.
* @param _value The amount of token to be burned.
*/
function burn(uint256 _value) public {
require(_value <= balances[msg.sender]);
// no need to require value <= totalSupply, since that would imply the
// sender's balance is greater than the totalSupply, which *should* be an assertion failure
address burner = msg.sender;
balances[burner] = balances[burner].sub(_value);
totalSupply_ = totalSupply_.sub(_value);
Burn(burner, _value);
Transfer(burner, address(0), _value);
}
}

View File

@ -0,0 +1,16 @@
pragma solidity ^0.4.18;
import "./ERC20.sol";
contract DetailedERC20 is ERC20 {
string public name;
string public symbol;
uint8 public decimals;
function DetailedERC20(string _name, string _symbol, uint8 _decimals) public {
name = _name;
symbol = _symbol;
decimals = _decimals;
}
}

View File

@ -0,0 +1,15 @@
pragma solidity ^0.4.18;
import "./ERC20Basic.sol";
/**
* @title ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/20
*/
contract ERC20 is ERC20Basic {
function allowance(address owner, address spender) public view returns (uint256);
function transferFrom(address from, address to, uint256 value) public returns (bool);
function approve(address spender, uint256 value) public returns (bool);
event Approval(address indexed owner, address indexed spender, uint256 value);
}

View File

@ -0,0 +1,14 @@
pragma solidity ^0.4.18;
/**
* @title ERC20Basic
* @dev Simpler version of ERC20 interface
* @dev see https://github.com/ethereum/EIPs/issues/179
*/
contract ERC20Basic {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}

View File

@ -0,0 +1,25 @@
pragma solidity ^0.4.18;
import "./ERC20Basic.sol";
import "./ERC20.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure.
* To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
function safeTransfer(ERC20Basic token, address to, uint256 value) internal {
assert(token.transfer(to, value));
}
function safeTransferFrom(ERC20 token, address from, address to, uint256 value) internal {
assert(token.transferFrom(from, to, value));
}
function safeApprove(ERC20 token, address spender, uint256 value) internal {
assert(token.approve(spender, value));
}
}

View File

@ -0,0 +1,100 @@
pragma solidity ^0.4.18;
import "./BasicToken.sol";
import "./ERC20.sol";
/**
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* @dev https://github.com/ethereum/EIPs/issues/20
* @dev Based on code by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*/
contract StandardToken is ERC20, BasicToken {
mapping (address => mapping (address => uint256)) internal allowed;
/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[_from]);
require(_value <= allowed[_from][msg.sender]);
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
allowed[_from][msg.sender] = allowed[_from][msg.sender].sub(_value);
Transfer(_from, _to, _value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
*
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param _spender The address which will spend the funds.
* @param _value The amount of tokens to be spent.
*/
function approve(address _spender, uint256 _value) public returns (bool) {
allowed[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param _owner address The address which owns the funds.
* @param _spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address _owner, address _spender) public view returns (uint256) {
return allowed[_owner][_spender];
}
/**
* @dev Increase the amount of tokens that an owner allowed to a spender.
*
* approve should be called when allowed[_spender] == 0. To increment
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* @param _spender The address which will spend the funds.
* @param _addedValue The amount of tokens to increase the allowance by.
*/
function increaseApproval(address _spender, uint _addedValue) public returns (bool) {
allowed[msg.sender][_spender] = allowed[msg.sender][_spender].add(_addedValue);
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
/**
* @dev Decrease the amount of tokens that an owner allowed to a spender.
*
* approve should be called when allowed[_spender] == 0. To decrement
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* @param _spender The address which will spend the funds.
* @param _subtractedValue The amount of tokens to decrease the allowance by.
*/
function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool) {
uint oldValue = allowed[msg.sender][_spender];
if (_subtractedValue > oldValue) {
allowed[msg.sender][_spender] = 0;
} else {
allowed[msg.sender][_spender] = oldValue.sub(_subtractedValue);
}
Approval(msg.sender, _spender, allowed[msg.sender][_spender]);
return true;
}
}

View File

@ -0,0 +1,142 @@
{
"chains": {
"mainnetrpc": {
"chain": {
"class": "populus.chain.external.ExternalChain"
},
"web3": {
"provider": {
"class": "web3.providers.rpc.HTTPProvider",
"settings": {
"endpoint_uri": "http://127.0.0.1:8545",
"request_kwargs": {
"timeout": 10
}
}
}
},
"contracts": {
"backends": {
"JSONFile": {
"$ref": "contracts.backends.JSONFile"
},
"Memory": {
"$ref": "contracts.backends.Memory"
},
"ProjectContracts": {
"$ref": "contracts.backends.ProjectContracts"
},
"TestContracts": {
"$ref": "contracts.backends.TestContracts"
}
}
}
},
"ropsten": {
"chain": {
"class": "populus.chain.geth.TestnetChain"
},
"web3": {
"$ref": "web3.GethIPC"
},
"contracts": {
"backends": {
"JSONFile": {
"$ref": "contracts.backends.JSONFile"
},
"Memory": {
"$ref": "contracts.backends.Memory"
},
"ProjectContracts": {
"$ref": "contracts.backends.ProjectContracts"
},
"TestContracts": {
"$ref": "contracts.backends.TestContracts"
}
}
}
},
"temp": {
"chain": {
"class": "populus.chain.geth.TemporaryGethChain"
},
"web3": {
"$ref": "web3.GethIPC"
},
"contracts": {
"backends": {
"Memory": {
"$ref": "contracts.backends.Memory"
},
"ProjectContracts": {
"$ref": "contracts.backends.ProjectContracts"
},
"TestContracts": {
"$ref": "contracts.backends.TestContracts"
}
}
}
},
"tester": {
"chain": {
"class": "populus.chain.tester.TesterChain"
},
"web3": {
"$ref": "web3.Tester"
},
"contracts": {
"backends": {
"Memory": {
"$ref": "contracts.backends.Memory"
},
"ProjectContracts": {
"$ref": "contracts.backends.ProjectContracts"
},
"TestContracts": {
"$ref": "contracts.backends.TestContracts"
}
}
}
},
"testrpc": {
"chain": {
"class": "populus.chain.testrpc.TestRPCChain"
},
"web3": {
"$ref": "web3.TestRPC"
},
"contracts": {
"backends": {
"Memory": {
"$ref": "contracts.backends.Memory"
},
"ProjectContracts": {
"$ref": "contracts.backends.ProjectContracts"
},
"TestContracts": {
"$ref": "contracts.backends.TestContracts"
}
}
}
}
},
"compilation": {
"contracts_source_dirs": [
"./contracts",
"../../tests/contracts"
],
"import_remappings": [
"contracts=nkms/blockchain/eth/sol_source/contracts"
],
"backend": {
"settings": {
"stdin": {
"optimizer": {
"enabled": true
}
}
}
}
},
"version": "7"
}

View File

@ -1,7 +1,75 @@
from .fixtures import *
from .eth_fixtures import *
<<<<<<< HEAD
from umbral.config import set_default_curve
from cryptography.hazmat.primitives.asymmetric import ec
set_default_curve(ec.SECP256K1())
=======
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent, PolicyAgent
from nkms_eth.blockchain import TheBlockchain
from nkms_eth.config import EthereumConfig
from nkms_eth.deployers import PolicyManagerDeployer
from nkms_eth.utilities import TesterBlockchain, MockNuCypherKMSTokenDeployer, MockMinerEscrowDeployer, MockMinerAgent
from eth_tester import EthereumTester
@pytest.fixture(scope='session')
def testerchain():
tester = EthereumTester()
test_provider = EthereumTesterProvider(ethereum_tester=tester)
web3_provider = Web3(providers=test_provider)
ethconfig = EthereumConfig(provider=web3_provider)
testerchain = TesterBlockchain(eth_config=ethconfig)
yield testerchain
del testerchain
TheBlockchain._TheBlockchain__instance = None
@pytest.fixture()
def mock_token_deployer(testerchain):
token_deployer = MockNuCypherKMSTokenDeployer(blockchain=testerchain)
token_deployer.arm()
token_deployer.deploy()
yield token_deployer
@pytest.fixture()
def mock_miner_escrow_deployer(token_agent):
escrow = MockMinerEscrowDeployer(token_agent=token_agent)
escrow.arm()
escrow.deploy()
yield escrow
@pytest.fixture()
def mock_policy_manager_deployer(mock_token_deployer):
policy_manager_deployer = PolicyManagerDeployer(token_deployer=mock_token_deployer)
policy_manager_deployer.arm()
policy_manager_deployer.deploy()
yield policy_manager_deployer
#
# Unused args preserve fixture dependency order #
#
@pytest.fixture()
def token_agent(testerchain, mock_token_deployer):
token = NuCypherKMSTokenAgent(blockchain=testerchain)
yield token
@pytest.fixture()
def mock_miner_agent(token_agent, mock_token_deployer, mock_miner_escrow_deployer):
miner_agent = MinerAgent(token_agent=token_agent)
yield miner_agent
@pytest.fixture()
def mock_policy_agent(mock_miner_agent, token_agent, mock_token_deployer, mock_miner_escrow_deployer):
policy_agent = PolicyAgent(miner_agent=mock_miner_agent)
yield policy_agent
>>>>>>> 6400353... Removes populus test fixture -> web3 TestProvider; Deprecates IssuerDeployer. Begins reflow of deployers, with readiness assertions.