mirror of https://github.com/nucypher/nucypher.git
[KMS-ETH]- Merge pull request #11 from KPrasch/python_api
Contract agent and deployer Interfacepull/195/head^2
commit
64a0558e0a
|
@ -0,0 +1,271 @@
|
||||||
|
from abc import ABC
|
||||||
|
from collections import OrderedDict
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Tuple, List, Union
|
||||||
|
|
||||||
|
from nkms_eth.agents import NuCypherKMSTokenAgent
|
||||||
|
from nkms_eth.policies import BlockchainArrangement
|
||||||
|
|
||||||
|
|
||||||
|
class TokenActor(ABC):
|
||||||
|
|
||||||
|
class ActorError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, token_agent: NuCypherKMSTokenAgent, address: Union[bytes, str]):
|
||||||
|
self.token_agent = token_agent
|
||||||
|
|
||||||
|
if isinstance(address, bytes):
|
||||||
|
address = address.hex()
|
||||||
|
self.address = address
|
||||||
|
|
||||||
|
self._transactions = OrderedDict() # Tracks
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
r = "{}(address='{}')"
|
||||||
|
r = r.format(class_name, self.address)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def eth_balance(self):
|
||||||
|
"""Return this actors's current ETH balance"""
|
||||||
|
|
||||||
|
balance = self.token_agent.blockchain._chain.web3.eth.getBalance(self.address)
|
||||||
|
return balance
|
||||||
|
|
||||||
|
def token_balance(self):
|
||||||
|
"""Return this actors's current token balance"""
|
||||||
|
|
||||||
|
balance = self.token_agent.get_balance(address=self.address)
|
||||||
|
return balance
|
||||||
|
|
||||||
|
|
||||||
|
class Miner(TokenActor):
|
||||||
|
"""
|
||||||
|
Ursula - practically carrying a pickaxe.
|
||||||
|
|
||||||
|
Accepts a running blockchain, deployed token contract, and deployed escrow contract.
|
||||||
|
If the provided token and escrow contracts are not deployed,
|
||||||
|
ContractDeploymentError will be raised.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
class StakingError(TokenActor.ActorError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, miner_agent, address):
|
||||||
|
super().__init__(token_agent=miner_agent.token_agent, address=address)
|
||||||
|
|
||||||
|
self.miner_agent = miner_agent
|
||||||
|
miner_agent.miners.append(self) # Track Miners
|
||||||
|
|
||||||
|
self.token_agent = miner_agent.token_agent
|
||||||
|
self.blockchain = self.token_agent.blockchain
|
||||||
|
|
||||||
|
self._locked_tokens = None
|
||||||
|
self._update_locked_tokens()
|
||||||
|
|
||||||
|
def _update_locked_tokens(self) -> None:
|
||||||
|
"""Query the contract for the amount of locked tokens on this miner's eth address and cache it"""
|
||||||
|
|
||||||
|
self._locked_tokens = self.miner_agent.read().getLockedTokens(self.address)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_staking(self, query=True):
|
||||||
|
"""Checks if this Miner currently has locked tokens."""
|
||||||
|
|
||||||
|
if query:
|
||||||
|
self._update_locked_tokens()
|
||||||
|
return bool(self._locked_tokens > 0)
|
||||||
|
|
||||||
|
def _approve_escrow(self, amount: int) -> str:
|
||||||
|
"""Approve the transfer of token from the miner's address to the escrow contract."""
|
||||||
|
|
||||||
|
txhash = self.token_agent.transact({'from': self.address}).approve(self.miner_agent.contract_address, amount)
|
||||||
|
self.blockchain.wait_for_receipt(txhash)
|
||||||
|
|
||||||
|
self._transactions[datetime.utcnow()] = txhash
|
||||||
|
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
def _send_tokens_to_escrow(self, amount, locktime) -> str:
|
||||||
|
"""Send tokes to the escrow from the miner's address"""
|
||||||
|
|
||||||
|
deposit_txhash = self.miner_agent.transact({'from': self.address}).deposit(amount, locktime)
|
||||||
|
self.blockchain.wait_for_receipt(deposit_txhash)
|
||||||
|
|
||||||
|
self._transactions[datetime.utcnow()] = deposit_txhash
|
||||||
|
|
||||||
|
return deposit_txhash
|
||||||
|
|
||||||
|
def deposit(self, amount: int, locktime: int) -> Tuple[str, str]:
|
||||||
|
"""Public facing method for token locking."""
|
||||||
|
|
||||||
|
approve_txhash = self._approve_escrow(amount=amount)
|
||||||
|
deposit_txhash = self._send_tokens_to_escrow(amount=amount, locktime=locktime)
|
||||||
|
|
||||||
|
return approve_txhash, deposit_txhash
|
||||||
|
|
||||||
|
def switch_lock(self):
|
||||||
|
lock_txhash = self.miner_agent.transact({'from': self.address}).switchLock()
|
||||||
|
self.blockchain.wait_for_receipt(lock_txhash)
|
||||||
|
|
||||||
|
self._transactions[datetime.utcnow()] = lock_txhash
|
||||||
|
return lock_txhash
|
||||||
|
|
||||||
|
def lock(self, amount: int, locktime: int) -> Tuple[str, str, str]:
|
||||||
|
"""Public facing method for token locking."""
|
||||||
|
|
||||||
|
approve_txhash, deposit_txhash = self.deposit(amount=amount, locktime=locktime)
|
||||||
|
lock_txhash = self.switch_lock()
|
||||||
|
|
||||||
|
return approve_txhash, deposit_txhash, lock_txhash
|
||||||
|
|
||||||
|
def confirm_activity(self) -> str:
|
||||||
|
"""Miner rewarded for every confirmed period"""
|
||||||
|
|
||||||
|
txhash = self.miner_agent.transact({'from': self.address}).confirmActivity()
|
||||||
|
self.blockchain.wait_for_receipt(txhash)
|
||||||
|
|
||||||
|
self._transactions[datetime.utcnow()] = txhash
|
||||||
|
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
def mint(self) -> str:
|
||||||
|
"""Computes and transfers tokens to the miner's account"""
|
||||||
|
|
||||||
|
txhash = self.miner_agent.transact({'from': self.address}).mint()
|
||||||
|
|
||||||
|
self.blockchain.wait_for_receipt(txhash)
|
||||||
|
self._transactions[datetime.utcnow()] = txhash
|
||||||
|
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
def collect_reward(self, policy_manager) -> str:
|
||||||
|
"""Withdraw policy reward in ETH"""
|
||||||
|
|
||||||
|
token_reward_bytes = self.miner_agent().getMinerInfo(self.miner_agent.MinerInfoField.VALUE.value, self.address, 0).encode('latin-1')
|
||||||
|
reward_amount = self.blockchain._chain.web3.toInt(token_reward_bytes)
|
||||||
|
|
||||||
|
txhash = policy_manager.transact({'from': self.address}).withdraw(reward_amount) # TODO: Calculate reward
|
||||||
|
|
||||||
|
self.blockchain.wait_for_receipt(txhash)
|
||||||
|
self._transactions[datetime.utcnow()] = txhash
|
||||||
|
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
def stake(self, amount, locktime, entire_balance=False, auto_switch_lock=False):
|
||||||
|
"""
|
||||||
|
High level staking method for Miners.
|
||||||
|
"""
|
||||||
|
staking_transactions = OrderedDict() # Time series of txhases
|
||||||
|
|
||||||
|
if entire_balance and amount:
|
||||||
|
raise self.StakingError("Specify an amount or entire balance, not both")
|
||||||
|
|
||||||
|
if not locktime >= self.miner_agent._deployer._min_release_periods:
|
||||||
|
raise self.StakingError('Locktime must be at least {}'.format(self.miner_agent._deployer._min_release_periods))
|
||||||
|
|
||||||
|
if entire_balance is True:
|
||||||
|
balance_bytes = self.miner_agent.read().getMinerInfo(self.miner_agent._deployer.MinerInfoField.VALUE.value,
|
||||||
|
self.address,
|
||||||
|
0).encode('latin-1')
|
||||||
|
|
||||||
|
amount = self.blockchain._chain.web3.toInt(balance_bytes)
|
||||||
|
else:
|
||||||
|
if not amount > 0:
|
||||||
|
raise self.StakingError('Staking amount must be greater than zero.')
|
||||||
|
|
||||||
|
approve_txhash, initial_deposit_txhash = self.deposit(amount=amount, locktime=locktime)
|
||||||
|
staking_transactions[datetime.utcnow()] = initial_deposit_txhash
|
||||||
|
|
||||||
|
if auto_switch_lock is True:
|
||||||
|
lock_txhash = self.switch_lock()
|
||||||
|
staking_transactions[datetime.utcnow()] = lock_txhash
|
||||||
|
|
||||||
|
return staking_transactions
|
||||||
|
|
||||||
|
# TODO: Sidechain datastore
|
||||||
|
# def publish_data(self, miner_id) -> str:
|
||||||
|
# """Store a new Miner ID"""
|
||||||
|
#
|
||||||
|
# txhash = self.miner_agent.transact({'from': self.address}).setMinerId(miner_id)
|
||||||
|
# self._blockchain.wait_for_receipt(txhash)
|
||||||
|
# self._transactions[datetime.utcnow()] = txhash
|
||||||
|
#
|
||||||
|
# return txhash
|
||||||
|
#
|
||||||
|
# def fetch_miner_data(self) -> tuple:
|
||||||
|
# """Retrieve all stored Miner IDs on this miner"""
|
||||||
|
#
|
||||||
|
# count = self.miner_agent.read().getMinerInfo(self.miner_agent._deployer.MinerInfoField.MINER_IDS_LENGTH.value,
|
||||||
|
# self.address,
|
||||||
|
# 0).encode('latin-1')
|
||||||
|
#
|
||||||
|
# count = self._blockchain._chain.web3.toInt(count)
|
||||||
|
#
|
||||||
|
# miner_ids = list()
|
||||||
|
# for index in range(count):
|
||||||
|
# miner_id = self.miner_agent.read().getMinerInfo(self.miner_agent._deployer.MinerInfoField.MINER_ID.value,
|
||||||
|
# self.address,
|
||||||
|
# index)
|
||||||
|
# encoded_miner_id = miner_id.encode('latin-1') # TODO change when v4 of web3.py is released
|
||||||
|
# miner_ids.append(encoded_miner_id)
|
||||||
|
#
|
||||||
|
# return tuple(miner_ids)
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyAuthor(TokenActor):
|
||||||
|
"""Alice"""
|
||||||
|
|
||||||
|
def __init__(self, address: bytes, policy_agent):
|
||||||
|
super().__init__(token_agent=policy_agent._token, address=address)
|
||||||
|
self.policy_agent = policy_agent
|
||||||
|
|
||||||
|
self._arrangements = OrderedDict() # Track authored policies by id
|
||||||
|
|
||||||
|
def make_arrangement(self, miner: Miner, periods: int, rate: int, arrangement_id: bytes=None) -> 'BlockchainArrangement':
|
||||||
|
"""
|
||||||
|
Create a new arrangement to carry out a blockchain policy for the specified rate and time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = rate * periods
|
||||||
|
arrangement = BlockchainArrangement(author=self,
|
||||||
|
miner=miner,
|
||||||
|
value=value,
|
||||||
|
periods=periods)
|
||||||
|
|
||||||
|
self._arrangements[arrangement.id] = {arrangement_id: arrangement}
|
||||||
|
return arrangement
|
||||||
|
|
||||||
|
def get_arrangement(self, arrangement_id: bytes) -> BlockchainArrangement:
|
||||||
|
"""Fetch a published arrangement from the blockchain"""
|
||||||
|
|
||||||
|
blockchain_record = self.policy_agent.read().policies(arrangement_id)
|
||||||
|
author_address, miner_address, rate, start_block, end_block, downtime_index = blockchain_record
|
||||||
|
|
||||||
|
duration = end_block - start_block
|
||||||
|
|
||||||
|
miner = Miner(address=miner_address, miner_agent=self.policy_agent.miner_agent)
|
||||||
|
arrangement = BlockchainArrangement(author=self, miner=miner, periods=duration)
|
||||||
|
|
||||||
|
arrangement.is_published = True
|
||||||
|
return arrangement
|
||||||
|
|
||||||
|
def revoke_arrangement(self, arrangement_id):
|
||||||
|
"""Get the arrangement from the cache and revoke it on the blockchain"""
|
||||||
|
try:
|
||||||
|
arrangement = self._arrangements[arrangement_id]
|
||||||
|
except KeyError:
|
||||||
|
raise Exception('No such arrangement') #TODO
|
||||||
|
else:
|
||||||
|
txhash = arrangement.revoke()
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
def recruit(self, quantity: int) -> List[str]:
|
||||||
|
"""Uses sampling logic to gather miner address from the blockchain"""
|
||||||
|
|
||||||
|
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
|
||||||
|
return miner_addresses
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
import random
|
||||||
|
from abc import ABC
|
||||||
|
from functools import partial
|
||||||
|
from typing import Set, Generator, List
|
||||||
|
|
||||||
|
from nkms_eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer, ContractDeployer
|
||||||
|
|
||||||
|
|
||||||
|
class EthereumContractAgent(ABC):
|
||||||
|
_principal_contract_name = NotImplemented
|
||||||
|
|
||||||
|
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, blockchain, *args, **kwargs):
|
||||||
|
|
||||||
|
self.blockchain = blockchain
|
||||||
|
self._contract = self.blockchain._chain.provider.get_contract(self._principal_contract_name)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
r = "{}(blockchain={}, contract={})"
|
||||||
|
return r.format(class_name, self.blockchain, self._contract)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return bool(self.contract_address == other.contract_address)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contract_address(self):
|
||||||
|
return self._contract.address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contract_name(self) -> str:
|
||||||
|
return self._principal_contract_name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def origin(self) -> str:
|
||||||
|
return self.blockchain._chain.web3.eth.accounts[0] # TODO: make swappable
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return self._contract.call()
|
||||||
|
|
||||||
|
def transact(self, payload: dict):
|
||||||
|
"""Packs kwargs into payload dictionary and transmits an eth contract transaction"""
|
||||||
|
return self._contract.transact(payload)
|
||||||
|
|
||||||
|
def get_balance(self, address: str=None) -> int:
|
||||||
|
"""Get the balance of a token address, or of this contract address"""
|
||||||
|
if address is None:
|
||||||
|
address = self.contract_address
|
||||||
|
return self.read().balanceOf(address)
|
||||||
|
|
||||||
|
|
||||||
|
class NuCypherKMSTokenAgent(EthereumContractAgent):
|
||||||
|
|
||||||
|
_deployer = NuCypherKMSTokenDeployer
|
||||||
|
_principal_contract_name = NuCypherKMSTokenDeployer._contract_name
|
||||||
|
|
||||||
|
def registrar(self):
|
||||||
|
"""Retrieve all known addresses for this contract"""
|
||||||
|
all_known_address = self.blockchain._chain.registrar.get_contract_address(self._principal_contract_name)
|
||||||
|
return all_known_address
|
||||||
|
|
||||||
|
|
||||||
|
class MinerAgent(EthereumContractAgent):
|
||||||
|
"""
|
||||||
|
Wraps NuCypher's Escrow solidity smart contract, and manages a PopulusContract.
|
||||||
|
|
||||||
|
In order to become a participant of the network,
|
||||||
|
a miner locks tokens by depositing to the Escrow contract address
|
||||||
|
for a duration measured in periods.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
_deployer = MinerEscrowDeployer
|
||||||
|
_principal_contract_name = MinerEscrowDeployer._contract_name
|
||||||
|
|
||||||
|
class NotEnoughUrsulas(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, token_agent: NuCypherKMSTokenAgent):
|
||||||
|
super().__init__(blockchain=token_agent.blockchain) # TODO: public
|
||||||
|
self.token_agent = token_agent
|
||||||
|
self.miners = list() # Tracks per client
|
||||||
|
|
||||||
|
def get_miner_ids(self) -> Set[str]:
|
||||||
|
"""
|
||||||
|
Fetch all miner IDs from the local cache and return them in a set
|
||||||
|
"""
|
||||||
|
|
||||||
|
return {miner.get_id() for miner in self.miners}
|
||||||
|
|
||||||
|
def swarm(self) -> Generator[str, None, None]:
|
||||||
|
"""
|
||||||
|
Returns an iterator of all miner addresses via cumulative sum, on-network.
|
||||||
|
|
||||||
|
Miner addresses will be returned in the order in which they were added to the MinersEscrow's ledger
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO - Partial;
|
||||||
|
info_reader = partial(self.read().getMinerInfo, self._deployer.MinerInfoField.MINERS_LENGTH.value, self._deployer._null_addr)
|
||||||
|
count = info_reader(0).encode('latin-1')
|
||||||
|
count = self.blockchain._chain.web3.toInt(count)
|
||||||
|
|
||||||
|
for index in range(count):
|
||||||
|
addr = info_reader(index).encode('latin-1')
|
||||||
|
yield self.blockchain._chain.web3.toChecksumAddress(addr)
|
||||||
|
|
||||||
|
def sample(self, quantity: int=10, additional_ursulas: float=1.7, attempts: int=5, duration: int=10) -> List[str]:
|
||||||
|
"""
|
||||||
|
Select n random staking Ursulas, according to their stake distribution.
|
||||||
|
The returned addresses are shuffled, so one can request more than needed and
|
||||||
|
throw away those which do not respond.
|
||||||
|
|
||||||
|
_startIndex
|
||||||
|
v
|
||||||
|
|-------->*--------------->*---->*------------->|
|
||||||
|
| ^
|
||||||
|
| stopIndex
|
||||||
|
|
|
||||||
|
| _delta
|
||||||
|
|---------------------------->|
|
||||||
|
|
|
||||||
|
| shift
|
||||||
|
| |----->|
|
||||||
|
|
||||||
|
|
||||||
|
See full diagram here: https://github.com/nucypher/kms-whitepaper/blob/master/pdf/miners-ruler.pdf
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
system_random = random.SystemRandom()
|
||||||
|
n_select = round(quantity*additional_ursulas) # Select more Ursulas
|
||||||
|
n_tokens = self.read().getAllLockedTokens()
|
||||||
|
|
||||||
|
if not n_tokens > 0:
|
||||||
|
raise self.NotEnoughUrsulas('There are no locked tokens.')
|
||||||
|
|
||||||
|
for _ in range(attempts):
|
||||||
|
points = [0] + sorted(system_random.randrange(n_tokens) for _ in range(n_select))
|
||||||
|
deltas = [i-j for i, j in zip(points[1:], points[:-1])]
|
||||||
|
|
||||||
|
addrs, addr, index, shift = set(), self._deployer._null_addr, 0, 0
|
||||||
|
for delta in deltas:
|
||||||
|
addr, index, shift = self.read().findCumSum(index, delta + shift, duration)
|
||||||
|
addrs.add(addr)
|
||||||
|
|
||||||
|
if len(addrs) >= quantity:
|
||||||
|
return system_random.sample(addrs, quantity)
|
||||||
|
|
||||||
|
raise self.NotEnoughUrsulas('Selection failed after {} attempts'.format(attempts))
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyAgent(EthereumContractAgent):
|
||||||
|
|
||||||
|
_deployer = PolicyManagerDeployer
|
||||||
|
_principal_contract_name = PolicyManagerDeployer._contract_name
|
||||||
|
|
||||||
|
def __init__(self, miner_agent):
|
||||||
|
super().__init__(blockchain=miner_agent.blockchain)
|
||||||
|
self.miner_agent = miner_agent
|
||||||
|
|
||||||
|
def fetch_arrangement_data(self, arrangement_id: bytes) -> list:
|
||||||
|
blockchain_record = self.read().policies(arrangement_id)
|
||||||
|
return blockchain_record
|
||||||
|
|
||||||
|
def revoke_arrangement(self, arrangement_id: bytes, author, gas_price: int):
|
||||||
|
"""
|
||||||
|
Revoke by arrangement ID; Only the policy author can revoke the policy
|
||||||
|
"""
|
||||||
|
|
||||||
|
txhash = self.transact({'from': author.address, 'gas_price': gas_price}).revokePolicy(arrangement_id)
|
||||||
|
self.blockchain.wait_for_receipt(txhash)
|
||||||
|
return txhash
|
|
@ -1,7 +1,9 @@
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
from nkms_eth.config import PopulusConfig
|
from nkms_eth.config import PopulusConfig
|
||||||
|
|
||||||
|
|
||||||
class Blockchain:
|
class TheBlockchain(ABC):
|
||||||
"""
|
"""
|
||||||
http://populus.readthedocs.io/en/latest/config.html#chains
|
http://populus.readthedocs.io/en/latest/config.html#chains
|
||||||
|
|
||||||
|
@ -12,13 +14,18 @@ class Blockchain:
|
||||||
temp: Local private chain whos data directory is removed when the chain is shutdown. Runs via geth.
|
temp: Local private chain whos data directory is removed when the chain is shutdown. Runs via geth.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_network = ''
|
_network = NotImplemented
|
||||||
_instance = False
|
_default_timeout = NotImplemented
|
||||||
|
__instance = None
|
||||||
|
|
||||||
class AlreadyRunning(Exception):
|
test_chains = ('tester', )
|
||||||
|
transient_chains = test_chains + ('testrpc', 'temp')
|
||||||
|
public_chains = ('mainnet', 'ropsten')
|
||||||
|
|
||||||
|
class IsAlreadyRunning(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, populus_config: PopulusConfig=None, timeout=60):
|
def __init__(self, populus_config: PopulusConfig=None):
|
||||||
"""
|
"""
|
||||||
Configures a populus project and connects to blockchain.network.
|
Configures a populus project and connects to blockchain.network.
|
||||||
Transaction timeouts specified measured in seconds.
|
Transaction timeouts specified measured in seconds.
|
||||||
|
@ -28,21 +35,26 @@ class Blockchain:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Singleton
|
# Singleton
|
||||||
if Blockchain._instance is True:
|
if TheBlockchain.__instance is not None:
|
||||||
class_name = self.__class__.__name__
|
message = '{} is already running. Use .get() to retrieve'.format(self._network)
|
||||||
raise Blockchain.AlreadyRunning('{} is already running. Use .get() to retrieve'.format(class_name))
|
raise TheBlockchain.IsAlreadyRunning(message)
|
||||||
Blockchain._instance = True
|
TheBlockchain.__instance = self
|
||||||
|
|
||||||
if populus_config is None:
|
if populus_config is None:
|
||||||
populus_config = PopulusConfig()
|
populus_config = PopulusConfig()
|
||||||
|
|
||||||
self._populus_config = populus_config
|
self._populus_config = populus_config
|
||||||
self._timeout = timeout
|
|
||||||
self._project = populus_config.project
|
self._project = populus_config.project
|
||||||
|
|
||||||
# Opens and preserves connection to a running populus blockchain
|
# Opens and preserves connection to a running populus blockchain
|
||||||
self._chain = self._project.get_chain(self._network).__enter__()
|
self._chain = self._project.get_chain(self._network).__enter__()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls):
|
||||||
|
if cls.__instance is None:
|
||||||
|
class_name = cls.__name__
|
||||||
|
raise Exception('{} has not been created.'.format(class_name))
|
||||||
|
return cls.__instance
|
||||||
|
|
||||||
def disconnect(self):
|
def disconnect(self):
|
||||||
self._chain.__exit__(None, None, None)
|
self._chain.__exit__(None, None, None)
|
||||||
|
|
||||||
|
@ -51,8 +63,8 @@ class Blockchain:
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
class_name = self.__class__.__name__
|
class_name = self.__class__.__name__
|
||||||
r = "{}(network={}, timeout={})"
|
r = "{}(network={})"
|
||||||
return r.format(class_name, self._network, self._timeout)
|
return r.format(class_name, self._network)
|
||||||
|
|
||||||
def get_contract(self, name):
|
def get_contract(self, name):
|
||||||
"""
|
"""
|
||||||
|
@ -62,19 +74,14 @@ class Blockchain:
|
||||||
"""
|
"""
|
||||||
return self._chain.provider.get_contract(name)
|
return self._chain.provider.get_contract(name)
|
||||||
|
|
||||||
def wait_time(self, wait_hours, step=50):
|
def wait_for_receipt(self, txhash, timeout=None) -> None:
|
||||||
"""Wait the specified number of wait_hours by comparing block timestamps."""
|
if timeout is None:
|
||||||
|
timeout = self._default_timeout
|
||||||
|
|
||||||
wait_seconds = wait_hours * 60 * 60
|
self._chain.wait.for_receipt(txhash, timeout=timeout)
|
||||||
current_block = self._chain.web3.eth.getBlock(self._chain.web3.eth.blockNumber)
|
|
||||||
end_timestamp = current_block.timestamp + wait_seconds
|
|
||||||
|
|
||||||
not_time_yet = True
|
# class TestRpcBlockchain:
|
||||||
while not_time_yet:
|
#
|
||||||
self._chain.wait.for_block(self._chain.web3.eth.blockNumber+step)
|
# _network = 'testrpc'
|
||||||
current_block = self._chain.web3.eth.getBlock(self._chain.web3.eth.blockNumber)
|
# _default_timeout = 60
|
||||||
not_time_yet = current_block.timestamp < end_timestamp
|
|
||||||
|
|
||||||
|
|
||||||
class TesterBlockchain(Blockchain):
|
|
||||||
_network = 'tester'
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from enum import Enum
|
||||||
from os.path import dirname, join, abspath
|
from os.path import dirname, join, abspath
|
||||||
|
|
||||||
import appdirs
|
import appdirs
|
||||||
|
@ -6,13 +7,81 @@ import populus
|
||||||
import nkms_eth
|
import nkms_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
|
||||||
|
]
|
||||||
|
|
||||||
|
class MinerInfoField(Enum):
|
||||||
|
MINERS_LENGTH = 0
|
||||||
|
MINER = 1
|
||||||
|
VALUE = 2
|
||||||
|
DECIMALS = 3
|
||||||
|
LOCKED_VALUE = 4
|
||||||
|
RELEASE = 5
|
||||||
|
MAX_RELEASE_PERIODS = 6
|
||||||
|
RELEASE_RATE = 7
|
||||||
|
CONFIRMED_PERIODS_LENGTH = 8
|
||||||
|
CONFIRMED_PERIOD = 9
|
||||||
|
CONFIRMED_PERIOD_LOCKED_VALUE = 10
|
||||||
|
LAST_ACTIVE_PERIOD_F = 11
|
||||||
|
DOWNTIME_LENGTH = 12
|
||||||
|
DOWNTIME_START_PERIOD = 13
|
||||||
|
DOWNTIME_END_PERIOD = 14
|
||||||
|
MINER_IDS_LENGTH = 15
|
||||||
|
MINER_ID = 16
|
||||||
|
|
||||||
|
@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 PopulusConfig:
|
class PopulusConfig:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, project_name='nucypher-kms', registrar_path=None):
|
||||||
self._python_project_name = 'nucypher-kms'
|
self._python_project_name = project_name
|
||||||
|
|
||||||
# This config is persistent and is created in user's .local directory
|
# This config is persistent and is created in user's .local directory
|
||||||
self._registrar_path = join(appdirs.user_data_dir(self._python_project_name), 'registrar.json')
|
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
|
# Populus project config
|
||||||
self._project_dir = join(dirname(abspath(nkms_eth.__file__)), 'project')
|
self._project_dir = join(dirname(abspath(nkms_eth.__file__)), 'project')
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Tuple
|
||||||
|
|
||||||
|
from nkms_eth.config import NuCypherMinerConfig, NuCypherTokenConfig
|
||||||
|
from .blockchain import TheBlockchain
|
||||||
|
|
||||||
|
|
||||||
|
class ContractDeployer(ABC):
|
||||||
|
|
||||||
|
_contract_name = NotImplemented
|
||||||
|
|
||||||
|
class ContractDeploymentError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, blockchain: TheBlockchain):
|
||||||
|
self.__armed = False
|
||||||
|
self._contract = None
|
||||||
|
self.deployment_receipt = None
|
||||||
|
|
||||||
|
# Sanity check
|
||||||
|
if not isinstance(blockchain, TheBlockchain):
|
||||||
|
error = 'Only TheBlockchain can be used to create a deployer, got {}.'
|
||||||
|
raise ValueError(error.format(type(blockchain)))
|
||||||
|
self.blockchain = blockchain
|
||||||
|
|
||||||
|
@property
|
||||||
|
def contract_address(self) -> str:
|
||||||
|
try:
|
||||||
|
address = self._contract.address
|
||||||
|
except AttributeError:
|
||||||
|
cls = self.__class__
|
||||||
|
raise cls.ContractDeploymentError('Contract not deployed')
|
||||||
|
else:
|
||||||
|
return address
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_deployed(self) -> bool:
|
||||||
|
return bool(self._contract is not None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_armed(self) -> bool:
|
||||||
|
return bool(self.__armed is True)
|
||||||
|
|
||||||
|
def _ensure_contract_deployment(self) -> None:
|
||||||
|
"""Raises ContractDeploymentError if the contract has not been armed and deployed."""
|
||||||
|
|
||||||
|
if self._contract is None:
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
|
||||||
|
raise self.ContractDeploymentError(message)
|
||||||
|
|
||||||
|
# http: // populus.readthedocs.io / en / latest / chain.contracts.html # checking-availability-of-contracts
|
||||||
|
available = bool(self.blockchain._chain.provider.are_contract_dependencies_available(self._contract_name))
|
||||||
|
if not available:
|
||||||
|
raise self.ContractDeploymentError('Contract is not available')
|
||||||
|
|
||||||
|
def arm(self) -> None:
|
||||||
|
"""Safety mechanism for ethereum contract deployment"""
|
||||||
|
|
||||||
|
if self.__armed is True:
|
||||||
|
raise self.ContractDeploymentError("Deployer already armed, use .deploy() to deploy.")
|
||||||
|
|
||||||
|
# Check that the contract can be deployed
|
||||||
|
is_ready = bool(self.blockchain._chain.provider.are_contract_dependencies_available(self._contract_name))
|
||||||
|
|
||||||
|
# If the blockchain network is public, prompt the user
|
||||||
|
if self.blockchain._network not in self.blockchain.test_chains:
|
||||||
|
message = """
|
||||||
|
Are you sure you want to deploy {} on the {} network?
|
||||||
|
|
||||||
|
Type "I UNDERSTAND" to arm the deployer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
answer = input(message.format(self._contract_name, self.blockchain._network))
|
||||||
|
if answer == "I UNDERSTAND":
|
||||||
|
arm = True
|
||||||
|
outcome_message = '{} is armed!'.format(self.__class__.__name__)
|
||||||
|
else:
|
||||||
|
arm = False
|
||||||
|
outcome_message = '{} was not armed.'.format(self.__class__.__name__)
|
||||||
|
|
||||||
|
print(outcome_message)
|
||||||
|
else:
|
||||||
|
# If this is a private chain, just arm the deployer without interaction.
|
||||||
|
arm = True
|
||||||
|
|
||||||
|
self.__armed = arm
|
||||||
|
return
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def deploy(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
|
||||||
|
|
||||||
|
_contract_name = 'NuCypherKMSToken'
|
||||||
|
|
||||||
|
def __init__(self, blockchain):
|
||||||
|
super().__init__(blockchain=blockchain)
|
||||||
|
self._creator = self.blockchain._chain.web3.eth.accounts[0]
|
||||||
|
|
||||||
|
def deploy(self) -> str:
|
||||||
|
"""
|
||||||
|
Deploy and publish the NuCypherKMS Token contract
|
||||||
|
to the blockchain network specified in self.blockchain.network.
|
||||||
|
|
||||||
|
The contract must be armed before it can be deployed.
|
||||||
|
Deployment can only ever be executed exactly once!
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.is_armed is False:
|
||||||
|
raise self.ContractDeploymentError('use .arm() to arm the contract, then .deploy().')
|
||||||
|
|
||||||
|
if self.is_deployed is True:
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
||||||
|
raise self.ContractDeploymentError(message)
|
||||||
|
|
||||||
|
the_nucypher_token_contract, deployment_txhash = self.blockchain._chain.provider.deploy_contract(
|
||||||
|
self._contract_name,
|
||||||
|
deploy_args=[self.saturation],
|
||||||
|
deploy_transaction={'from': self._creator})
|
||||||
|
|
||||||
|
self.blockchain.wait_for_receipt(deployment_txhash)
|
||||||
|
self._contract = the_nucypher_token_contract
|
||||||
|
self.deployment_receipt = deployment_txhash
|
||||||
|
|
||||||
|
return self.deployment_receipt
|
||||||
|
|
||||||
|
|
||||||
|
class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
|
||||||
|
"""
|
||||||
|
Deploys the MinerEscrow ethereum contract to the blockchain. Depends on NuCypherTokenAgent
|
||||||
|
"""
|
||||||
|
|
||||||
|
_contract_name = 'MinersEscrow'
|
||||||
|
|
||||||
|
def __init__(self, token_agent):
|
||||||
|
super().__init__(blockchain=token_agent.blockchain)
|
||||||
|
self.token_agent = token_agent
|
||||||
|
|
||||||
|
def deploy(self) -> Tuple[str, str, str]:
|
||||||
|
"""
|
||||||
|
Deploy and publish the NuCypherKMS Token contract
|
||||||
|
to the blockchain network specified in self.blockchain.network.
|
||||||
|
|
||||||
|
The contract must be armed before it can be deployed.
|
||||||
|
Deployment can only ever be executed exactly once!
|
||||||
|
|
||||||
|
Returns transaction hashes in a tuple: deploy, reward, and initialize.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if self.is_armed is False:
|
||||||
|
raise self.ContractDeploymentError('use .arm() to arm the contract, then .deploy().')
|
||||||
|
|
||||||
|
if self.is_deployed is True:
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
||||||
|
raise self.ContractDeploymentError(message)
|
||||||
|
|
||||||
|
deploy_args = [self.token_agent.contract_address] + self.mining_coefficient
|
||||||
|
deploy_tx = {'from': self.token_agent.origin}
|
||||||
|
|
||||||
|
the_escrow_contract, deploy_txhash = self.blockchain._chain.provider.deploy_contract(self._contract_name,
|
||||||
|
deploy_args=deploy_args,
|
||||||
|
deploy_transaction=deploy_tx)
|
||||||
|
|
||||||
|
self.blockchain.wait_for_receipt(deploy_txhash)
|
||||||
|
self._contract = the_escrow_contract
|
||||||
|
|
||||||
|
reward_txhash = self.token_agent.transact({'from': self.token_agent.origin}).transfer(self.contract_address,
|
||||||
|
self.reward)
|
||||||
|
self.blockchain.wait_for_receipt(reward_txhash)
|
||||||
|
|
||||||
|
init_txhash = self._contract.transact({'from': self.token_agent.origin}).initialize()
|
||||||
|
self.blockchain.wait_for_receipt(init_txhash)
|
||||||
|
|
||||||
|
self.deployment_receipt = deploy_txhash
|
||||||
|
|
||||||
|
return deploy_txhash, reward_txhash, init_txhash
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyManagerDeployer(ContractDeployer):
|
||||||
|
"""
|
||||||
|
Depends on MinerAgent and NuCypherTokenAgent
|
||||||
|
"""
|
||||||
|
|
||||||
|
_contract_name = 'PolicyManager'
|
||||||
|
|
||||||
|
def __init__(self, miner_agent):
|
||||||
|
self.token_agent = miner_agent.token_agent
|
||||||
|
self.miner_agent = miner_agent
|
||||||
|
super().__init__(blockchain=self.token_agent.blockchain)
|
||||||
|
|
||||||
|
def deploy(self) -> Tuple[str, str]:
|
||||||
|
if self.is_armed is False:
|
||||||
|
raise self.ContractDeploymentError('PolicyManager contract not armed')
|
||||||
|
if self.is_deployed is True:
|
||||||
|
raise self.ContractDeploymentError('PolicyManager contract already deployed')
|
||||||
|
|
||||||
|
# Creator deploys the policy manager
|
||||||
|
the_policy_manager_contract, deploy_txhash = self.blockchain._chain.provider.deploy_contract(
|
||||||
|
self._contract_name,
|
||||||
|
deploy_args=[self.miner_agent.contract_address],
|
||||||
|
deploy_transaction={'from': self.token_agent.origin})
|
||||||
|
|
||||||
|
self._contract = the_policy_manager_contract
|
||||||
|
|
||||||
|
policy_setter_txhash = self.miner_agent.transact({'from': self.token_agent.origin}).setPolicyManager(the_policy_manager_contract.address)
|
||||||
|
self.blockchain.wait_for_receipt(policy_setter_txhash)
|
||||||
|
self.deployment_receipt = deploy_txhash
|
||||||
|
|
||||||
|
return deploy_txhash, policy_setter_txhash
|
|
@ -1,190 +0,0 @@
|
||||||
import random
|
|
||||||
from typing import List, Tuple, Set, Generator
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
from populus.contracts.contract import PopulusContract
|
|
||||||
|
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
|
||||||
from .blockchain import Blockchain
|
|
||||||
|
|
||||||
addr = str
|
|
||||||
|
|
||||||
|
|
||||||
class Escrow:
|
|
||||||
"""
|
|
||||||
Wraps NuCypher's Escrow solidity smart contract, and manages a PopulusContract.
|
|
||||||
|
|
||||||
In order to become a participant of the network,
|
|
||||||
a miner locks tokens by depositing to the Escrow contract address
|
|
||||||
for a duration measured in periods.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
_contract_name = 'MinersEscrow'
|
|
||||||
hours_per_period = 1 # 24 Hours
|
|
||||||
min_release_periods = 1 # 30 Periods
|
|
||||||
max_awarded_periods = 365 # Periods
|
|
||||||
min_allowed_locked = 10 ** 6
|
|
||||||
max_allowed_locked = 10 ** 7 * NuCypherKMSToken.M
|
|
||||||
reward = NuCypherKMSToken.saturation - NuCypherKMSToken.premine
|
|
||||||
null_addr = '0x' + '0' * 40
|
|
||||||
|
|
||||||
mining_coeff = [
|
|
||||||
hours_per_period,
|
|
||||||
2 * 10 ** 7,
|
|
||||||
max_awarded_periods,
|
|
||||||
max_awarded_periods,
|
|
||||||
min_release_periods,
|
|
||||||
min_allowed_locked,
|
|
||||||
max_allowed_locked
|
|
||||||
]
|
|
||||||
|
|
||||||
class MinerInfoField(Enum):
|
|
||||||
MINERS_LENGTH = 0
|
|
||||||
MINER = 1
|
|
||||||
VALUE = 2
|
|
||||||
DECIMALS = 3
|
|
||||||
LOCKED_VALUE = 4
|
|
||||||
RELEASE = 5
|
|
||||||
MAX_RELEASE_PERIODS = 6
|
|
||||||
RELEASE_RATE = 7
|
|
||||||
CONFIRMED_PERIODS_LENGTH = 8
|
|
||||||
CONFIRMED_PERIOD = 9
|
|
||||||
CONFIRMED_PERIOD_LOCKED_VALUE = 10
|
|
||||||
LAST_ACTIVE_PERIOD_F = 11
|
|
||||||
DOWNTIME_LENGTH = 12
|
|
||||||
DOWNTIME_START_PERIOD = 13
|
|
||||||
DOWNTIME_END_PERIOD = 14
|
|
||||||
MINER_IDS_LENGTH = 15
|
|
||||||
MINER_ID = 16
|
|
||||||
|
|
||||||
class ContractDeploymentError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class NotEnoughUrsulas(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, blockchain: Blockchain, token: NuCypherKMSToken, contract: PopulusContract=None):
|
|
||||||
self.blockchain = blockchain
|
|
||||||
self.contract = contract
|
|
||||||
self.token = token
|
|
||||||
self.armed = False
|
|
||||||
self.miners = list()
|
|
||||||
|
|
||||||
def __call__(self):
|
|
||||||
"""Gateway to contract function calls without state change."""
|
|
||||||
return self.contract.call()
|
|
||||||
|
|
||||||
def __eq__(self, other: 'Escrow'):
|
|
||||||
"""If two deployed escrows have the same contract address, they are equal."""
|
|
||||||
return self.contract.address == other.contract.address
|
|
||||||
|
|
||||||
def arm(self) -> None:
|
|
||||||
self.armed = True
|
|
||||||
|
|
||||||
def deploy(self) -> Tuple[str, str, str]:
|
|
||||||
"""
|
|
||||||
Deploy and publish the NuCypherKMS Token contract
|
|
||||||
to the blockchain network specified in self.blockchain.network.
|
|
||||||
|
|
||||||
The contract must be armed before it can be deployed.
|
|
||||||
Deployment can only ever be executed exactly once!
|
|
||||||
|
|
||||||
Returns transaction hashes in a tuple: deploy, reward, and initialize.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.armed is False:
|
|
||||||
raise self.ContractDeploymentError('use .arm() to arm the contract, then .deploy().')
|
|
||||||
|
|
||||||
if self.contract is not None:
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
|
||||||
raise self.ContractDeploymentError(message)
|
|
||||||
|
|
||||||
the_escrow_contract, deploy_txhash = self.blockchain._chain.provider.deploy_contract(self._contract_name,
|
|
||||||
deploy_args=[self.token.contract.address] + self.mining_coeff,
|
|
||||||
deploy_transaction={'from': self.token.creator})
|
|
||||||
|
|
||||||
self.blockchain._chain.wait.for_receipt(deploy_txhash, timeout=self.blockchain._timeout)
|
|
||||||
self.contract = the_escrow_contract
|
|
||||||
|
|
||||||
reward_txhash = self.token.transact({'from': self.token.creator}).transfer(self.contract.address, self.reward)
|
|
||||||
self.blockchain._chain.wait.for_receipt(reward_txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
init_txhash = self.contract.transact({'from': self.token.creator}).initialize()
|
|
||||||
self.blockchain._chain.wait.for_receipt(init_txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
return deploy_txhash, reward_txhash, init_txhash
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, blockchain, token) -> 'Escrow':
|
|
||||||
"""
|
|
||||||
Returns the Escrow object,
|
|
||||||
or raises UnknownContract if the contract has not been deployed.
|
|
||||||
"""
|
|
||||||
contract = blockchain._chain.provider.get_contract(cls._contract_name)
|
|
||||||
return cls(blockchain=blockchain, token=token, contract=contract)
|
|
||||||
|
|
||||||
def transact(self, *args, **kwargs):
|
|
||||||
if self.contract is None:
|
|
||||||
raise self.ContractDeploymentError('Contract must be deployed before executing transactions.')
|
|
||||||
return self.contract.transact(*args, **kwargs)
|
|
||||||
|
|
||||||
def get_dht(self) -> Set[str]:
|
|
||||||
"""Fetch all miner IDs and return them in a set"""
|
|
||||||
return {miner.get_id() for miner in self.miners}
|
|
||||||
|
|
||||||
def swarm(self) -> Generator[str, None, None]:
|
|
||||||
"""
|
|
||||||
Generates all miner addresses via cumulative sum.
|
|
||||||
"""
|
|
||||||
count = self.blockchain._chain.web3.toInt(
|
|
||||||
self.__call__().getMinerInfo(self.MinerInfoField.MINERS_LENGTH.value, self.null_addr, 0)
|
|
||||||
.encode('latin-1'))
|
|
||||||
for index in range(count):
|
|
||||||
yield self.blockchain._chain.web3.toChecksumAddress(
|
|
||||||
self.__call__().getMinerInfo(self.MinerInfoField.MINER.value, self.null_addr, index).encode('latin-1'))
|
|
||||||
|
|
||||||
def sample(self, quantity: int=10, additional_ursulas: float=1.7, attempts: int=5, duration: int=10) -> List[addr]:
|
|
||||||
"""
|
|
||||||
Select n random staking Ursulas, according to their stake distribution.
|
|
||||||
The returned addresses are shuffled, so one can request more than needed and
|
|
||||||
throw away those which do not respond.
|
|
||||||
|
|
||||||
_startIndex
|
|
||||||
v
|
|
||||||
|-------->*--------------->*---->*------------->|
|
|
||||||
| ^
|
|
||||||
| stopIndex
|
|
||||||
|
|
|
||||||
| _delta
|
|
||||||
|---------------------------->|
|
|
||||||
|
|
|
||||||
| shift
|
|
||||||
| |----->|
|
|
||||||
|
|
||||||
See full diagram here: https://github.com/nucypher/kms-whitepaper/blob/master/pdf/miners-ruler.pdf
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
system_random = random.SystemRandom()
|
|
||||||
n_select = round(quantity*additional_ursulas) # Select more Ursulas
|
|
||||||
n_tokens = self.__call__().getAllLockedTokens()
|
|
||||||
|
|
||||||
if not n_tokens > 0:
|
|
||||||
raise self.NotEnoughUrsulas('There are no locked tokens.')
|
|
||||||
|
|
||||||
for _ in range(attempts):
|
|
||||||
points = [0] + sorted(system_random.randrange(n_tokens) for _ in range(n_select))
|
|
||||||
deltas = [i-j for i, j in zip(points[1:], points[:-1])]
|
|
||||||
|
|
||||||
addrs, addr, index, shift = set(), self.null_addr, 0, 0
|
|
||||||
for delta in deltas:
|
|
||||||
addr, index, shift = self.__call__().findCumSum(index, delta+shift, duration)
|
|
||||||
addrs.add(addr)
|
|
||||||
|
|
||||||
if len(addrs) >= quantity:
|
|
||||||
return system_random.sample(addrs, quantity)
|
|
||||||
|
|
||||||
raise self.NotEnoughUrsulas('Selection failed after {} attempts'.format(attempts))
|
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
from typing import Tuple
|
|
||||||
|
|
||||||
from .blockchain import Blockchain
|
|
||||||
from .escrow import Escrow
|
|
||||||
from .token import NuCypherKMSToken
|
|
||||||
|
|
||||||
|
|
||||||
class Miner:
|
|
||||||
"""
|
|
||||||
Practically carrying a pickaxe.
|
|
||||||
Intended for use as an Ursula mixin.
|
|
||||||
|
|
||||||
Accepts a running blockchain, deployed token contract, and deployed escrow contract.
|
|
||||||
If the provided token and escrow contracts are not deployed,
|
|
||||||
ContractDeploymentError will be raised.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, blockchain: Blockchain, token: NuCypherKMSToken, escrow: Escrow, address=None):
|
|
||||||
self.blockchain = blockchain
|
|
||||||
self.token = token
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
self.escrow = escrow
|
|
||||||
if not escrow.contract:
|
|
||||||
raise Escrow.ContractDeploymentError('Escrow contract not deployed. Arm then deploy.')
|
|
||||||
else:
|
|
||||||
escrow.miners.append(self)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
r = "{}(address='{}')"
|
|
||||||
r.format(class_name, self.address)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
"""Removes this miner from the escrow's list of miners on delete."""
|
|
||||||
self.escrow.miners.remove(self)
|
|
||||||
|
|
||||||
def _approve_escrow(self, amount: int) -> str:
|
|
||||||
"""Approve the transfer of token from the miner's address to the escrow contract."""
|
|
||||||
|
|
||||||
txhash = self.token.transact({'from': self.address}).approve(self.escrow.contract.address, amount)
|
|
||||||
self.blockchain._chain.wait.for_receipt(txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
return txhash
|
|
||||||
|
|
||||||
def _send_tokens_to_escrow(self, amount, locktime) -> str:
|
|
||||||
"""Send tokes to the escrow from the miner's address"""
|
|
||||||
|
|
||||||
deposit_txhash = self.escrow.transact({'from': self.address}).deposit(amount, locktime)
|
|
||||||
self.blockchain._chain.wait.for_receipt(deposit_txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
return deposit_txhash
|
|
||||||
|
|
||||||
def lock(self, amount: int, locktime: int) -> Tuple[str, str, str]:
|
|
||||||
"""Deposit and lock tokens for mining."""
|
|
||||||
|
|
||||||
approve_txhash = self._approve_escrow(amount=amount)
|
|
||||||
deposit_txhash = self._send_tokens_to_escrow(amount=amount, locktime=locktime)
|
|
||||||
|
|
||||||
lock_txhash = self.escrow.transact({'from': self.address}).switchLock()
|
|
||||||
self.blockchain._chain.wait.for_receipt(lock_txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
return approve_txhash, deposit_txhash, lock_txhash
|
|
||||||
|
|
||||||
def mint(self) -> str:
|
|
||||||
"""Computes and transfers tokens to the miner's account"""
|
|
||||||
|
|
||||||
txhash = self.escrow.transact({'from': self.address}).mint()
|
|
||||||
self.blockchain._chain.wait.for_receipt(txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
return txhash
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# def collect_reward(self):
|
|
||||||
# tx = policy_manager.transact({'from': self.address}).withdraw()
|
|
||||||
# chain.wait.for_receipt(tx)
|
|
||||||
|
|
||||||
def publish_dht_key(self, dht_id) -> str:
|
|
||||||
"""Store a new DHT key"""
|
|
||||||
|
|
||||||
txhash = self.escrow.transact({'from': self.address}).setMinerId(dht_id)
|
|
||||||
self.blockchain._chain.wait.for_receipt(txhash)
|
|
||||||
|
|
||||||
return txhash
|
|
||||||
|
|
||||||
def get_dht_key(self) -> tuple:
|
|
||||||
"""Retrieve all stored DHT keys for this miner"""
|
|
||||||
|
|
||||||
count = self.blockchain._chain.web3.toInt(
|
|
||||||
self.escrow().getMinerInfo(self.escrow.MinerInfoField.MINER_IDS_LENGTH.value, self.address, 0)
|
|
||||||
.encode('latin-1'))
|
|
||||||
# TODO change when v4 web3.py will released
|
|
||||||
dht_keys = tuple(self.escrow().getMinerInfo(self.escrow.MinerInfoField.MINER_ID.value, self.address, index)
|
|
||||||
.encode('latin-1') for index in range(count))
|
|
||||||
|
|
||||||
return dht_keys
|
|
||||||
|
|
||||||
def confirm_activity(self) -> str:
|
|
||||||
"""Miner rewarded for every confirmed period"""
|
|
||||||
|
|
||||||
txhash = self.escrow.contract.transact({'from': self.address}).confirmActivity()
|
|
||||||
self.blockchain._chain.wait.for_receipt(txhash)
|
|
||||||
|
|
||||||
return txhash
|
|
||||||
|
|
||||||
def balance(self) -> int:
|
|
||||||
"""Check miner's current balance"""
|
|
||||||
|
|
||||||
self.token._check_contract_deployment()
|
|
||||||
balance = self.token().balanceOf(self.address)
|
|
||||||
|
|
||||||
return balance
|
|
||||||
|
|
||||||
def withdraw(self) -> str:
|
|
||||||
"""withdraw rewarded tokens"""
|
|
||||||
tokens_amount = self.blockchain._chain.web3.toInt(
|
|
||||||
self.escrow().getMinerInfo(self.escrow.MinerInfoField.VALUE.value, self.address, 0).encode('latin-1'))
|
|
||||||
txhash = self.escrow.transact({'from': self.address}).withdraw(tokens_amount)
|
|
||||||
self.blockchain._chain.wait.for_receipt(txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
return txhash
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
class BlockchainArrangement:
|
||||||
|
"""
|
||||||
|
A relationship between Alice and a single Ursula as part of Blockchain Policy
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, author, miner, value: int, periods: int, arrangement_id: bytes=None):
|
||||||
|
|
||||||
|
self.id = arrangement_id
|
||||||
|
|
||||||
|
# The relationship exists between two addresses
|
||||||
|
self.author = author
|
||||||
|
self.policy_agent = author.policy_agent
|
||||||
|
|
||||||
|
self.miner = miner
|
||||||
|
|
||||||
|
# Arrangement value, rate, and duration
|
||||||
|
rate = value // periods
|
||||||
|
self._rate = rate
|
||||||
|
|
||||||
|
self.value = value
|
||||||
|
self.periods = periods # TODO: datetime -> duration in blocks
|
||||||
|
|
||||||
|
self.is_published = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
class_name = self.__class__.__name__
|
||||||
|
r = "{}(client={}, node={})"
|
||||||
|
r = r.format(class_name, self.author, self.miner)
|
||||||
|
return r
|
||||||
|
|
||||||
|
def publish(self, gas_price: int) -> str:
|
||||||
|
|
||||||
|
payload = {'from': self.author.address,
|
||||||
|
'value': self.value,
|
||||||
|
'gas_price': gas_price}
|
||||||
|
|
||||||
|
|
||||||
|
txhash = self.policy_agent.transact(payload).createPolicy(self.id,
|
||||||
|
self.miner.address,
|
||||||
|
self.periods)
|
||||||
|
|
||||||
|
self.policy_agent._blockchain._chain.wait.for_receipt(txhash)
|
||||||
|
|
||||||
|
self.publish_transaction = txhash
|
||||||
|
self.is_published = True
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
def revoke(self, gas_price: int) -> str:
|
||||||
|
"""Revoke this arrangement and return the transaction hash as hex."""
|
||||||
|
txhash = self.policy_agent.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
|
||||||
|
self.revoke_transaction = txhash
|
||||||
|
return txhash
|
||||||
|
|
||||||
|
|
||||||
|
class BlockchainPolicy:
|
||||||
|
"""A collection of n BlockchainArrangements representing a single Policy"""
|
||||||
|
|
||||||
|
class NoSuchPolicy(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._arrangements = list()
|
|
@ -226,6 +226,23 @@ contract MinersEscrow is Issuer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @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
|
* @notice Pre-deposit tokens
|
||||||
* @param _owners Tokens owners
|
* @param _owners Tokens owners
|
||||||
* @param _values Amount of token to deposit for each owner
|
* @param _values Amount of token to deposit for each owner
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
from populus.contracts.contract import PopulusContract
|
|
||||||
from .blockchain import Blockchain
|
|
||||||
|
|
||||||
|
|
||||||
class NuCypherKMSToken:
|
|
||||||
_contract_name = 'NuCypherKMSToken'
|
|
||||||
subdigits = 18
|
|
||||||
M = 10 ** subdigits
|
|
||||||
premine = int(1e9) * M
|
|
||||||
saturation = int(1e10) * M
|
|
||||||
|
|
||||||
class ContractDeploymentError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, blockchain: Blockchain, token_contract: PopulusContract=None):
|
|
||||||
self.creator = blockchain._chain.web3.eth.accounts[0]
|
|
||||||
self.blockchain = blockchain
|
|
||||||
self.contract = token_contract
|
|
||||||
self.armed = False
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
r = "{}(blockchain={}, contract={})"
|
|
||||||
return r.format(class_name, self.blockchain, self.contract)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
"""Two token objects are equal if they have the same contract address"""
|
|
||||||
return self.contract.address == other.contract.address
|
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
|
||||||
"""Invoke contract -> No state change"""
|
|
||||||
return self.contract.call(*args, **kwargs)
|
|
||||||
|
|
||||||
def _check_contract_deployment(self) -> None:
|
|
||||||
"""Raises ContractDeploymentError if the contract has not been armed and deployed."""
|
|
||||||
if not self.contract:
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
|
|
||||||
raise self.ContractDeploymentError(message)
|
|
||||||
|
|
||||||
def arm(self) -> None:
|
|
||||||
"""Arm contract for deployment to blockchain."""
|
|
||||||
self.armed = True
|
|
||||||
|
|
||||||
def deploy(self) -> str:
|
|
||||||
"""
|
|
||||||
Deploy and publish the NuCypherKMS Token contract
|
|
||||||
to the blockchain network specified in self.blockchain.network.
|
|
||||||
|
|
||||||
The contract must be armed before it can be deployed.
|
|
||||||
Deployment can only ever be executed exactly once!
|
|
||||||
"""
|
|
||||||
|
|
||||||
if self.armed is False:
|
|
||||||
raise self.ContractDeploymentError('use .arm() to arm the contract, then .deploy().')
|
|
||||||
|
|
||||||
if self.contract is not None:
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
|
||||||
raise self.ContractDeploymentError(message)
|
|
||||||
|
|
||||||
the_nucypherKMS_token_contract, deployment_txhash = self.blockchain._chain.provider.deploy_contract(
|
|
||||||
self._contract_name,
|
|
||||||
deploy_args=[self.saturation],
|
|
||||||
deploy_transaction={'from': self.creator})
|
|
||||||
|
|
||||||
self.blockchain._chain.wait.for_receipt(deployment_txhash, timeout=self.blockchain._timeout)
|
|
||||||
|
|
||||||
self.contract = the_nucypherKMS_token_contract
|
|
||||||
return deployment_txhash
|
|
||||||
|
|
||||||
def transact(self, *args):
|
|
||||||
"""Invoke contract -> State change"""
|
|
||||||
self._check_contract_deployment()
|
|
||||||
result = self.contract.transact(*args)
|
|
||||||
return result
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get(cls, blockchain):
|
|
||||||
"""
|
|
||||||
Returns the NuCypherKMSToken object,
|
|
||||||
or raises UnknownContract if the contract has not been deployed.
|
|
||||||
"""
|
|
||||||
contract = blockchain._chain.provider.get_contract(cls._contract_name)
|
|
||||||
return cls(blockchain=blockchain, token_contract=contract)
|
|
||||||
|
|
||||||
def registrar(self):
|
|
||||||
"""Retrieve all known addresses for this contract"""
|
|
||||||
self._check_contract_deployment()
|
|
||||||
return self.blockchain._chain.registrar.get_contract_address(self._contract_name)
|
|
||||||
|
|
||||||
def balance(self, address: str):
|
|
||||||
"""Get the balance of a token address"""
|
|
||||||
self._check_contract_deployment()
|
|
||||||
return self.__call__().balanceOf(address)
|
|
||||||
|
|
||||||
def _airdrop(self, amount: int):
|
|
||||||
"""Airdrops from creator address to all other addresses!"""
|
|
||||||
self._check_contract_deployment()
|
|
||||||
_, *addresses = self.blockchain._chain.web3.eth.accounts
|
|
||||||
|
|
||||||
def txs():
|
|
||||||
for address in addresses:
|
|
||||||
yield self.transact({'from': self.creator}).transfer(address, amount*(10**6))
|
|
||||||
|
|
||||||
for tx in txs():
|
|
||||||
self.blockchain._chain.wait.for_receipt(tx, timeout=10)
|
|
||||||
|
|
||||||
return self
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
import random
|
||||||
|
|
||||||
|
from nkms_eth.actors import Miner
|
||||||
|
from nkms_eth.agents import MinerAgent, EthereumContractAgent
|
||||||
|
from nkms_eth.blockchain import TheBlockchain
|
||||||
|
from nkms_eth.config import NuCypherMinerConfig
|
||||||
|
from nkms_eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class MockNuCypherKMSTokenDeployer(NuCypherKMSTokenDeployer):
|
||||||
|
_M = 10 ** 6 #TODO
|
||||||
|
|
||||||
|
def _global_airdrop(self, amount: int):
|
||||||
|
"""Airdrops from creator address to all other addresses!"""
|
||||||
|
|
||||||
|
_creator, *addresses = self.blockchain._chain.web3.eth.accounts
|
||||||
|
|
||||||
|
def txs():
|
||||||
|
for address in addresses:
|
||||||
|
yield self._contract.transact({'from': self._creator}).transfer(address, amount * (10 ** 6))
|
||||||
|
|
||||||
|
for tx in txs():
|
||||||
|
self.blockchain._chain.wait.for_receipt(tx, timeout=10)
|
||||||
|
|
||||||
|
return self # for method chaining
|
||||||
|
|
||||||
|
|
||||||
|
class MockNuCypherMinerConfig(NuCypherMinerConfig):
|
||||||
|
"""Speed things up a bit"""
|
||||||
|
_hours_per_period = 1 # Hours
|
||||||
|
_min_release_periods = 1 # Minimum switchlock periods
|
||||||
|
|
||||||
|
|
||||||
|
class MockMinerEscrowDeployer(MinerEscrowDeployer, MockNuCypherMinerConfig):
|
||||||
|
"""Helper class for MockMinerAgent"""
|
||||||
|
|
||||||
|
|
||||||
|
class MockMinerAgent(MinerAgent):
|
||||||
|
"""MinerAgent with faked config subclass"""
|
||||||
|
_deployer = MockMinerEscrowDeployer
|
||||||
|
|
||||||
|
|
||||||
|
def spawn_miners(addresses: list, miner_agent: MinerAgent, m: int, locktime: int) -> None:
|
||||||
|
"""
|
||||||
|
Deposit and lock a random amount of tokens in the miner escrow
|
||||||
|
from each address, "spawning" new Miners.
|
||||||
|
"""
|
||||||
|
# Create n Miners
|
||||||
|
for address in addresses:
|
||||||
|
miner = Miner(miner_agent=miner_agent, address=address)
|
||||||
|
amount = (10+random.randrange(9000)) * m
|
||||||
|
miner.lock(amount=amount, locktime=locktime)
|
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Deploy contracts in tester.
|
Deploy contracts in tester.
|
||||||
|
|
||||||
A simple Python script to deploy contracts and then estimate gas for different methods.
|
A simple Python script to deploy contracts and then estimate gas for different methods.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from nkms_eth.blockchain import TesterBlockchain
|
|
||||||
from nkms_eth.escrow import Escrow
|
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from tests.utilities import TesterBlockchain
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
testerchain = TesterBlockchain()
|
testerchain = TesterBlockchain()
|
||||||
|
@ -19,6 +19,7 @@ def main():
|
||||||
|
|
||||||
print("Web3 providers are", web3.providers)
|
print("Web3 providers are", web3.providers)
|
||||||
|
|
||||||
|
# TODO: Updatae to agents and deployers
|
||||||
# Create an ERC20 token
|
# Create an ERC20 token
|
||||||
token = NuCypherKMSToken(blockchain=testerchain)
|
token = NuCypherKMSToken(blockchain=testerchain)
|
||||||
token.arm()
|
token.arm()
|
||||||
|
@ -289,4 +290,4 @@ def main():
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
|
@ -1,8 +1,8 @@
|
||||||
import pytest
|
import pytest
|
||||||
from nkms_eth.blockchain import TesterBlockchain, Blockchain
|
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
|
||||||
from nkms_eth.escrow import Escrow
|
from nkms_eth.blockchain import TheBlockchain
|
||||||
from nkms_eth.miner import Miner
|
from nkms_eth.utilities import TesterBlockchain, MockNuCypherKMSTokenDeployer, MockMinerEscrowDeployer
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
|
@ -10,20 +10,34 @@ def testerchain():
|
||||||
chain = TesterBlockchain()
|
chain = TesterBlockchain()
|
||||||
yield chain
|
yield chain
|
||||||
del chain
|
del chain
|
||||||
Blockchain._instance = False
|
TheBlockchain._TheBlockchain__instance = None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def token(testerchain):
|
def mock_token_deployer(testerchain):
|
||||||
token = NuCypherKMSToken(blockchain=testerchain)
|
token_deployer = MockNuCypherKMSTokenDeployer(blockchain=testerchain)
|
||||||
token.arm()
|
token_deployer.arm()
|
||||||
token.deploy()
|
token_deployer.deploy()
|
||||||
|
yield token_deployer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def mock_miner_escrow_deployer(token_agent):
|
||||||
|
escrow = MockMinerEscrowDeployer(token_agent)
|
||||||
|
escrow.arm()
|
||||||
|
escrow.deploy()
|
||||||
|
yield escrow
|
||||||
|
|
||||||
|
|
||||||
|
# Unused args preserve fixture dependency order #
|
||||||
|
|
||||||
|
@pytest.fixture(scope='function')
|
||||||
|
def token_agent(testerchain, mock_token_deployer):
|
||||||
|
token = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||||
yield token
|
yield token
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def escrow(testerchain, token):
|
def mock_miner_agent(token_agent, mock_token_deployer, mock_miner_escrow_deployer):
|
||||||
escrow = Escrow(blockchain=testerchain, token=token)
|
miner_agent = MinerAgent(token_agent)
|
||||||
escrow.arm()
|
yield miner_agent
|
||||||
escrow.deploy()
|
|
||||||
yield escrow
|
|
||||||
|
|
|
@ -620,7 +620,7 @@ def test_verifying_state(web3, chain):
|
||||||
|
|
||||||
# Can't upgrade to the previous version or to the bad version
|
# Can't upgrade to the previous version or to the bad version
|
||||||
contract_library_bad, _ = chain.provider.deploy_contract(
|
contract_library_bad, _ = chain.provider.deploy_contract(
|
||||||
'PolicyManagerBad', deploy_args=[address2],
|
'PolicyManagerBad', deploy_args=[address2],
|
||||||
deploy_transaction={'from': creator})
|
deploy_transaction={'from': creator})
|
||||||
with pytest.raises(TransactionFailed):
|
with pytest.raises(TransactionFailed):
|
||||||
tx = dispatcher.transact({'from': creator}).upgrade(contract_library_v1.address)
|
tx = dispatcher.transact({'from': creator}).upgrade(contract_library_v1.address)
|
||||||
|
@ -641,4 +641,4 @@ def test_verifying_state(web3, chain):
|
||||||
# Try to upgrade to the bad version
|
# Try to upgrade to the bad version
|
||||||
with pytest.raises(TransactionFailed):
|
with pytest.raises(TransactionFailed):
|
||||||
tx = dispatcher.transact({'from': creator}).upgrade(contract_library_bad.address)
|
tx = dispatcher.transact({'from': creator}).upgrade(contract_library_bad.address)
|
||||||
chain.wait.for_receipt(tx)
|
chain.wait.for_receipt(tx)
|
|
@ -0,0 +1,125 @@
|
||||||
|
import random
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from nkms_eth.actors import Miner
|
||||||
|
from nkms_eth.agents import MinerAgent
|
||||||
|
from nkms_eth.utilities import spawn_miners
|
||||||
|
|
||||||
|
|
||||||
|
def test_miner_locking_tokens(testerchain, mock_token_deployer, mock_miner_agent):
|
||||||
|
|
||||||
|
mock_token_deployer._global_airdrop(amount=10000) # weeee
|
||||||
|
|
||||||
|
miner = Miner(miner_agent=mock_miner_agent, address=testerchain._chain.web3.eth.accounts[1])
|
||||||
|
|
||||||
|
an_amount_of_tokens = 1000 * mock_token_deployer._M
|
||||||
|
miner.stake(amount=an_amount_of_tokens, locktime=mock_miner_agent._deployer._min_release_periods, auto_switch_lock=False)
|
||||||
|
|
||||||
|
# Verify that the escrow is allowed to receive tokens
|
||||||
|
# assert mock_miner_agent.token_agent.read().allowance(miner.address, mock_miner_agent.contract_address) == 0
|
||||||
|
|
||||||
|
# Stake starts after one period
|
||||||
|
# assert miner.token_balance() == 0
|
||||||
|
# assert mock_miner_agent.read().getLockedTokens(miner.address) == 0
|
||||||
|
|
||||||
|
# Wait for it...
|
||||||
|
testerchain.wait_time(mock_miner_agent._deployer._hours_per_period)
|
||||||
|
|
||||||
|
assert mock_miner_agent.read().getLockedTokens(miner.address) == an_amount_of_tokens
|
||||||
|
|
||||||
|
|
||||||
|
def test_mine_then_withdraw_tokens(testerchain, mock_token_deployer, token_agent, mock_miner_agent, mock_miner_escrow_deployer):
|
||||||
|
"""
|
||||||
|
- Airdrop tokens to everyone
|
||||||
|
- Create a Miner (Ursula)
|
||||||
|
- Spawn additional miners
|
||||||
|
- All miners lock tokens
|
||||||
|
- Wait (with time)
|
||||||
|
- Miner (Ursula) mints new tokens
|
||||||
|
"""
|
||||||
|
|
||||||
|
mock_token_deployer._global_airdrop(amount=10000)
|
||||||
|
|
||||||
|
_origin, *everybody = testerchain._chain.web3.eth.accounts
|
||||||
|
ursula_address, *everyone_else = everybody
|
||||||
|
|
||||||
|
miner = Miner(miner_agent=mock_miner_agent, address=ursula_address)
|
||||||
|
|
||||||
|
initial_balance = miner.token_balance()
|
||||||
|
assert token_agent.get_balance(miner.address) == miner.token_balance()
|
||||||
|
|
||||||
|
stake_amount = (10 + random.randrange(9000)) * mock_token_deployer._M
|
||||||
|
miner.stake(amount=stake_amount, locktime=30, auto_switch_lock=False)
|
||||||
|
|
||||||
|
# Stake starts after one period
|
||||||
|
assert miner.token_balance() == 0
|
||||||
|
assert mock_miner_agent.read().getLockedTokens(ursula_address) == 0
|
||||||
|
|
||||||
|
# Wait for it...
|
||||||
|
testerchain.wait_time(mock_miner_agent._deployer._hours_per_period)
|
||||||
|
|
||||||
|
# Have other address lock tokens, and wait...
|
||||||
|
spawn_miners(miner_agent=mock_miner_agent, addresses=everyone_else, locktime=30, m=mock_token_deployer._M)
|
||||||
|
testerchain.wait_time(mock_miner_agent._deployer._hours_per_period*2)
|
||||||
|
|
||||||
|
# miner.confirm_activity()
|
||||||
|
miner.mint()
|
||||||
|
miner.collect_reward()
|
||||||
|
|
||||||
|
final_balance = token_agent.get_balance(miner.address)
|
||||||
|
assert final_balance > initial_balance
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_miners(testerchain, mock_token_deployer, mock_miner_agent):
|
||||||
|
mock_token_deployer._global_airdrop(amount=10000)
|
||||||
|
|
||||||
|
_origin, *everyone_else = testerchain._chain.web3.eth.accounts[1:]
|
||||||
|
spawn_miners(addresses=everyone_else, locktime=100, miner_agent=mock_miner_agent, m=mock_token_deployer._M)
|
||||||
|
|
||||||
|
testerchain.wait_time(mock_miner_agent._deployer._hours_per_period)
|
||||||
|
|
||||||
|
with pytest.raises(MinerAgent.NotEnoughUrsulas):
|
||||||
|
mock_miner_agent.sample(quantity=100) # Waay more than we have deployed
|
||||||
|
|
||||||
|
miners = mock_miner_agent.sample(quantity=3)
|
||||||
|
assert len(miners) == 3
|
||||||
|
assert len(set(miners)) == 3
|
||||||
|
|
||||||
|
|
||||||
|
# def test_publish_miner_ids(testerchain, mock_token_deployer, mock_miner_agent):
|
||||||
|
# mock_token_deployer._global_airdrop(amount=10000) # weeee
|
||||||
|
#
|
||||||
|
# miner_addr = testerchain._chain.web3.eth.accounts[1]
|
||||||
|
# miner = Miner(miner_agent=mock_miner_agent, address=miner_addr)
|
||||||
|
#
|
||||||
|
# balance = miner.token_balance()
|
||||||
|
# miner.lock(amount=balance, locktime=1)
|
||||||
|
#
|
||||||
|
# # Publish Miner IDs to the DHT
|
||||||
|
# mock_miner_id = os.urandom(32)
|
||||||
|
# _txhash = miner.publish_miner_id(mock_miner_id)
|
||||||
|
#
|
||||||
|
# # Fetch the miner Ids
|
||||||
|
# stored_miner_ids = miner.fetch_miner_ids()
|
||||||
|
#
|
||||||
|
# assert len(stored_miner_ids) == 1
|
||||||
|
# assert mock_miner_id == stored_miner_ids[0]
|
||||||
|
#
|
||||||
|
# # Repeat, with another miner ID
|
||||||
|
# another_mock_miner_id = os.urandom(32)
|
||||||
|
# _txhash = miner.publish_miner_id(another_mock_miner_id)
|
||||||
|
#
|
||||||
|
# stored_miner_ids = miner.fetch_miner_ids()
|
||||||
|
#
|
||||||
|
# assert len(stored_miner_ids) == 2
|
||||||
|
# assert another_mock_miner_id == stored_miner_ids[1]
|
||||||
|
#
|
||||||
|
# # TODO change encoding when v4 of web3.py is released
|
||||||
|
# supposedly_the_same_miner_id = mock_miner_agent.call() \
|
||||||
|
# .getMinerInfo(mock_miner_agent._deployer.MinerInfoField.MINER_ID.value,
|
||||||
|
# miner_addr,
|
||||||
|
# 1).encode('latin-1')
|
||||||
|
#
|
||||||
|
# assert another_mock_miner_id == supposedly_the_same_miner_id
|
||||||
|
#
|
|
@ -0,0 +1,31 @@
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from nkms_eth.utilities import spawn_miners, MockNuCypherMinerConfig
|
||||||
|
|
||||||
|
M = 10 ** 6
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_swarm(testerchain, mock_token_deployer, mock_miner_agent):
|
||||||
|
|
||||||
|
mock_token_deployer._global_airdrop(amount=10000)
|
||||||
|
|
||||||
|
creator, *addresses = testerchain._chain.web3.eth.accounts
|
||||||
|
spawn_miners(addresses=addresses, miner_agent=mock_miner_agent, locktime=1, m=M)
|
||||||
|
|
||||||
|
default_period_duration = MockNuCypherMinerConfig._hours_per_period
|
||||||
|
testerchain.wait_time(default_period_duration)
|
||||||
|
|
||||||
|
swarm = mock_miner_agent.swarm()
|
||||||
|
swarm_addresses = list(swarm)
|
||||||
|
assert len(swarm_addresses) == 9
|
||||||
|
|
||||||
|
# Grab a miner address from the swarm
|
||||||
|
miner_addr = swarm_addresses[0]
|
||||||
|
assert isinstance(miner_addr, str)
|
||||||
|
|
||||||
|
# Verify the address is hex
|
||||||
|
try:
|
||||||
|
int(miner_addr, 16)
|
||||||
|
except ValueError:
|
||||||
|
pytest.fail()
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
import random
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from populus.contracts.exceptions import NoKnownAddress
|
|
||||||
from pytest import raises
|
|
||||||
|
|
||||||
from nkms_eth.escrow import Escrow
|
|
||||||
from nkms_eth.miner import Miner
|
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
|
||||||
|
|
||||||
M = 10 ** 6
|
|
||||||
|
|
||||||
def test_create_escrow(testerchain):
|
|
||||||
with raises(NoKnownAddress):
|
|
||||||
NuCypherKMSToken.get(blockchain=testerchain)
|
|
||||||
|
|
||||||
token = NuCypherKMSToken(blockchain=testerchain)
|
|
||||||
token.arm()
|
|
||||||
token.deploy()
|
|
||||||
|
|
||||||
same_token = NuCypherKMSToken.get(blockchain=testerchain)
|
|
||||||
with raises(NuCypherKMSToken.ContractDeploymentError):
|
|
||||||
same_token.arm()
|
|
||||||
same_token.deploy()
|
|
||||||
|
|
||||||
assert len(token.contract.address) == 42
|
|
||||||
assert token.contract.address == same_token.contract.address
|
|
||||||
|
|
||||||
with raises(NoKnownAddress):
|
|
||||||
Escrow.get(blockchain=testerchain, token=token)
|
|
||||||
|
|
||||||
escrow = Escrow(blockchain=testerchain, token=token)
|
|
||||||
escrow.arm()
|
|
||||||
escrow.deploy()
|
|
||||||
|
|
||||||
same_escrow = Escrow.get(blockchain=testerchain, token=token)
|
|
||||||
with raises(Escrow.ContractDeploymentError):
|
|
||||||
same_escrow.arm()
|
|
||||||
same_escrow.deploy()
|
|
||||||
|
|
||||||
assert len(escrow.contract.address) == 42
|
|
||||||
assert escrow.contract.address == same_escrow.contract.address
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_swarm(testerchain, token, escrow):
|
|
||||||
token._airdrop(amount=10000)
|
|
||||||
|
|
||||||
# Create 9 Miners
|
|
||||||
for u in testerchain._chain.web3.eth.accounts[1:]:
|
|
||||||
miner = Miner(blockchain=testerchain, token=token, escrow=escrow, address=u)
|
|
||||||
amount = (10+random.randrange(9000)) * M
|
|
||||||
miner.lock(amount=amount, locktime=1)
|
|
||||||
|
|
||||||
testerchain.wait_time(escrow.hours_per_period)
|
|
||||||
|
|
||||||
swarm = escrow.swarm()
|
|
||||||
swarm_addresses = list(swarm)
|
|
||||||
assert len(swarm_addresses) == 9
|
|
||||||
|
|
||||||
# Grab a miner address from the swarm
|
|
||||||
miner_addr = swarm_addresses[0]
|
|
||||||
assert isinstance(miner_addr, str)
|
|
||||||
|
|
||||||
# Verify the address is hex
|
|
||||||
try:
|
|
||||||
int(miner_addr, 16)
|
|
||||||
except ValueError:
|
|
||||||
pytest.fail()
|
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
import random
|
|
||||||
|
|
||||||
import os
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
from nkms_eth.escrow import Escrow
|
|
||||||
from nkms_eth.miner import Miner
|
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
|
||||||
|
|
||||||
|
|
||||||
M = 10 ** 6
|
|
||||||
|
|
||||||
|
|
||||||
def test_deposit(testerchain, token, escrow):
|
|
||||||
token._airdrop(amount=10000) # weeee
|
|
||||||
|
|
||||||
ursula_address = testerchain._chain.web3.eth.accounts[1]
|
|
||||||
miner = Miner(blockchain=testerchain, token=token, escrow=escrow, address=ursula_address)
|
|
||||||
miner.lock(amount=1000*M, locktime=100)
|
|
||||||
|
|
||||||
|
|
||||||
def test_mine_withdraw(testerchain, token, escrow):
|
|
||||||
token._airdrop(amount=10000)
|
|
||||||
|
|
||||||
ursula_address = testerchain._chain.web3.eth.accounts[1]
|
|
||||||
miner = Miner(blockchain=testerchain, token=token, escrow=escrow, address=ursula_address)
|
|
||||||
|
|
||||||
ursula = miner
|
|
||||||
initial_balance = token.balance(address=ursula.address)
|
|
||||||
|
|
||||||
# Create a random set of miners (we have 9 in total)
|
|
||||||
for address in testerchain._chain.web3.eth.accounts[1:]:
|
|
||||||
miner = Miner(blockchain=testerchain, token=token,
|
|
||||||
escrow=escrow, address=address)
|
|
||||||
|
|
||||||
amount = (10+random.randrange(9000)) * M
|
|
||||||
miner.lock(amount=amount, locktime=1)
|
|
||||||
|
|
||||||
testerchain.wait_time(escrow.hours_per_period*2)
|
|
||||||
|
|
||||||
ursula.mint()
|
|
||||||
ursula.withdraw()
|
|
||||||
final_balance = token.balance(ursula.address)
|
|
||||||
|
|
||||||
assert final_balance > initial_balance
|
|
||||||
|
|
||||||
|
|
||||||
def test_publish_dht_key(testerchain, token, escrow):
|
|
||||||
token._airdrop(amount=10000) # weeee
|
|
||||||
|
|
||||||
miner_addr = testerchain._chain.web3.eth.accounts[1]
|
|
||||||
miner = Miner(blockchain=testerchain, token=token,
|
|
||||||
escrow=escrow, address=miner_addr)
|
|
||||||
|
|
||||||
balance = miner.balance()
|
|
||||||
miner.lock(amount=balance, locktime=1)
|
|
||||||
|
|
||||||
# Publish DHT keys
|
|
||||||
mock_dht_key = os.urandom(32)
|
|
||||||
|
|
||||||
txhash = miner.publish_dht_key(mock_dht_key)
|
|
||||||
stored_miner_dht_keys = miner.get_dht_key()
|
|
||||||
|
|
||||||
assert len(stored_miner_dht_keys) == 1
|
|
||||||
assert mock_dht_key == stored_miner_dht_keys[0]
|
|
||||||
|
|
||||||
another_mock_dht_key = os.urandom(32)
|
|
||||||
txhash = miner.publish_dht_key(another_mock_dht_key)
|
|
||||||
|
|
||||||
stored_miner_dht_keys = miner.get_dht_key()
|
|
||||||
|
|
||||||
assert len(stored_miner_dht_keys) == 2
|
|
||||||
assert another_mock_dht_key == stored_miner_dht_keys[1]
|
|
||||||
# TODO change when v4 web3.py will released
|
|
||||||
assert another_mock_dht_key == escrow().getMinerInfo(escrow.MinerInfoField.MINER_ID.value, miner_addr, 1)\
|
|
||||||
.encode('latin-1')
|
|
||||||
|
|
||||||
|
|
||||||
def test_select_ursulas(testerchain, token, escrow):
|
|
||||||
token._airdrop(amount=10000)
|
|
||||||
|
|
||||||
# Create a random set of miners (we have 9 in total)
|
|
||||||
for u in testerchain._chain.web3.eth.accounts[1:]:
|
|
||||||
miner = Miner(blockchain=testerchain, token=token, escrow=escrow, address=u)
|
|
||||||
amount = (10 + random.randrange(9000))*M
|
|
||||||
miner.lock(amount=amount, locktime=100)
|
|
||||||
|
|
||||||
testerchain.wait_time(escrow.hours_per_period)
|
|
||||||
|
|
||||||
miners = escrow.sample(quantity=3)
|
|
||||||
assert len(miners) == 3
|
|
||||||
assert len(set(miners)) == 3
|
|
||||||
|
|
||||||
with pytest.raises(Escrow.NotEnoughUrsulas):
|
|
||||||
escrow.sample(quantity=100) # Waay more than we have deployed
|
|
|
@ -1,25 +0,0 @@
|
||||||
from pytest import raises
|
|
||||||
from populus.contracts.exceptions import NoKnownAddress
|
|
||||||
|
|
||||||
from nkms_eth.blockchain import TesterBlockchain
|
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_and_get_nucypherkms_token(testerchain):
|
|
||||||
with raises(NoKnownAddress):
|
|
||||||
NuCypherKMSToken.get(blockchain=testerchain)
|
|
||||||
|
|
||||||
token = NuCypherKMSToken(blockchain=testerchain)
|
|
||||||
token.arm()
|
|
||||||
token.deploy()
|
|
||||||
|
|
||||||
assert len(token.contract.address) == 42
|
|
||||||
assert token.contract.call().totalSupply() != 0
|
|
||||||
# assert token.contract.call().totalSupply() == 10 ** 9 - 1
|
|
||||||
|
|
||||||
same_token = NuCypherKMSToken.get(blockchain=testerchain)
|
|
||||||
|
|
||||||
assert token.contract.address == same_token.contract.address
|
|
||||||
assert token == same_token
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
from populus.contracts.exceptions import NoKnownAddress
|
||||||
|
from pytest import raises
|
||||||
|
|
||||||
|
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
|
||||||
|
from nkms_eth.deployers import NuCypherKMSTokenDeployer, MinerEscrowDeployer, PolicyManagerDeployer
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_deployer_and_agent(testerchain):
|
||||||
|
|
||||||
|
# Trying to get token from blockchain before it's been published fails
|
||||||
|
with raises(NoKnownAddress):
|
||||||
|
NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||||
|
|
||||||
|
# The big day...
|
||||||
|
deployer = NuCypherKMSTokenDeployer(blockchain=testerchain)
|
||||||
|
|
||||||
|
with raises(NuCypherKMSTokenDeployer.ContractDeploymentError):
|
||||||
|
deployer.deploy()
|
||||||
|
|
||||||
|
# Token must be armed before deploying to the blockchain
|
||||||
|
deployer.arm()
|
||||||
|
deployer.deploy()
|
||||||
|
|
||||||
|
# Create a token instance
|
||||||
|
token_agent = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||||
|
|
||||||
|
# Make sure we got the name right
|
||||||
|
deployer_contract_identifier = NuCypherKMSTokenDeployer._contract_name
|
||||||
|
assert'NuCypherKMSToken' == deployer_contract_identifier
|
||||||
|
|
||||||
|
# Ensure the contract is deployed and has a valid blockchain address
|
||||||
|
assert len(token_agent.contract_address) == 42
|
||||||
|
|
||||||
|
# Check that the token contract has tokens
|
||||||
|
assert token_agent.read().totalSupply() != 0
|
||||||
|
# assert token().totalSupply() == 10 ** 9 - 1 # TODO
|
||||||
|
|
||||||
|
# Retrieve the token from the blockchain
|
||||||
|
same_token_agent = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||||
|
|
||||||
|
# Compare the contract address for equality
|
||||||
|
assert token_agent.contract_address == same_token_agent.contract_address
|
||||||
|
assert token_agent == same_token_agent # __eq__
|
||||||
|
|
||||||
|
|
||||||
|
def test_deploy_ethereum_contracts(testerchain):
|
||||||
|
"""
|
||||||
|
Launch all ethereum contracts:
|
||||||
|
- NuCypherKMSToken
|
||||||
|
- PolicyManager
|
||||||
|
- MinersEscrow
|
||||||
|
- UserEscrow
|
||||||
|
- Issuer
|
||||||
|
"""
|
||||||
|
|
||||||
|
token_deployer = NuCypherKMSTokenDeployer(blockchain=testerchain)
|
||||||
|
token_deployer.arm()
|
||||||
|
token_deployer.deploy()
|
||||||
|
|
||||||
|
token_agent = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||||
|
|
||||||
|
miner_escrow_deployer = MinerEscrowDeployer(token_agent=token_agent)
|
||||||
|
miner_escrow_deployer.arm()
|
||||||
|
miner_escrow_deployer.deploy()
|
||||||
|
|
||||||
|
miner_agent = MinerAgent(token_agent=token_agent)
|
||||||
|
|
||||||
|
policy_manager_contract = PolicyManagerDeployer(miner_agent=miner_agent)
|
||||||
|
policy_manager_contract.arm()
|
||||||
|
policy_manager_contract.deploy()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from os.path import join, dirname, abspath
|
from os.path import join, dirname, abspath
|
||||||
|
|
||||||
import nkms_eth
|
import nkms_eth
|
||||||
from nkms_eth.token import NuCypherKMSToken
|
from nkms_eth.deployers import NuCypherKMSTokenDeployer
|
||||||
|
|
||||||
|
|
||||||
def test_testerchain_create(testerchain):
|
def test_testerchain_creation(testerchain):
|
||||||
# Ensure we are testing on the correct network...
|
# Ensure we are testing on the correct network...
|
||||||
assert testerchain._network == 'tester'
|
assert testerchain._network == 'tester'
|
||||||
|
|
||||||
|
@ -24,6 +24,6 @@ def test_nucypher_populus_project(testerchain):
|
||||||
# ...and on the testerchain/blockchain class itself
|
# ...and on the testerchain/blockchain class itself
|
||||||
assert testerchain._project.project_dir == populus_project_dir
|
assert testerchain._project.project_dir == populus_project_dir
|
||||||
|
|
||||||
# Ensure that smart contacts are available, post solidity compile.
|
# Ensure that solidity smart contacts are available, post-compile.
|
||||||
token_contract_identifier = NuCypherKMSToken._contract_name
|
token_contract_identifier = NuCypherKMSTokenDeployer(blockchain=testerchain)._contract_name
|
||||||
assert token_contract_identifier in testerchain._project.compiled_contract_data
|
assert token_contract_identifier in testerchain._project.compiled_contract_data
|
Loading…
Reference in New Issue