mirror of https://github.com/nucypher/nucypher.git
Pick PR#22 from kms-eth; Further smooths file tree and namespaces.
parent
a972f0e801
commit
1849b282c0
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
||||
#
|
|
@ -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
|
|
@ -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],
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||

|
||||
* 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.
|
|
@ -0,0 +1,47 @@
|
|||
# Desired properties:
|
||||
* Nodes decide which update should occur;
|
||||
* Nodes can rollback contract if new version has bugs.
|
||||
# Approaches
|
||||
* "Hard-fork"
|
||||

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

|
||||
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
|
||||

|
||||
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
|
||||

|
||||
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
|
||||

|
||||
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.
|
|
@ -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 |
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue