diff --git a/nkms_eth/blockchain.py b/nkms_eth/blockchain.py index f92ca76a4..e1f9fcace 100644 --- a/nkms_eth/blockchain.py +++ b/nkms_eth/blockchain.py @@ -20,6 +20,8 @@ class Blockchain: network = '' # 'mainnetrpc' python_project_name = 'nucypher-kms' _project = threading.local() + + # This config is persistent and is created in user's .local directory registrar_path = join(appdirs.user_data_dir(python_project_name), 'registrar.json') def __init__(self, project_name='nucypher-kms', timeout=60): diff --git a/nkms_eth/escrow.py b/nkms_eth/escrow.py index 426fbbe5a..4c1213359 100644 --- a/nkms_eth/escrow.py +++ b/nkms_eth/escrow.py @@ -1,83 +1,5 @@ import random from typing import List -<<<<<<< HEAD -from nkms_eth import blockchain -from nkms_eth import token - -ESCROW_NAME = 'MinersEscrow' -PREMINE = int(1e9) * token.M -REWARD = token.SATURATION - PREMINE -HOURS_PER_PERIOD = 1 # 24 -MIN_RELEASE_PERIODS = 1 # 30 -MAX_AWARDED_PERIODS = 365 -MIN_ALLOWED_LOCKED = 10 ** 6 -MAX_ALLOWED_LOCKED = 10 ** 7 * token.M -MINING_COEFF = [ - HOURS_PER_PERIOD, - 2 * 10 ** 7, - MAX_AWARDED_PERIODS, - MAX_AWARDED_PERIODS, - MIN_RELEASE_PERIODS, - MIN_ALLOWED_LOCKED, - MAX_ALLOWED_LOCKED -] -NULL_ADDR = '0x' + '0' * 40 - - -def create(): - """ - Creates an escrow which manages mining - """ - chain = blockchain.chain() - creator = chain.web3.eth.accounts[0] # TODO: make it possible to override - tok = token.get() - escrow, tx = chain.provider.get_or_deploy_contract( - ESCROW_NAME, deploy_args=[token.get().address] + MINING_COEFF, - deploy_transaction={'from': creator}) - chain.wait.for_receipt(tx, timeout=blockchain.TIMEOUT) - tx = tok.transact({'from': creator}).transfer(escrow.address, REWARD) - chain.wait.for_receipt(tx, timeout=blockchain.TIMEOUT) - tx = escrow.transact({'from': creator}).initialize() - chain.wait.for_receipt(tx, timeout=blockchain.TIMEOUT) - return escrow - - -def get(): - """ - Returns an escrow object - """ - return token.get(ESCROW_NAME) - - -def sample(n: 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 - """ - escrow = get() - n_select = int(n * 1.7) # Select more ursulas - n_tokens = escrow.call().getAllLockedTokens() - duration = 10 - - for _ in range(5): # number of tries - points = [0] + sorted(random.randrange(n_tokens) for _ in - range(n_select)) - deltas = [i - j for i, j in zip(points[1:], points[:-1])] - addrs = set() - addr = NULL_ADDR - shift = 0 - - for delta in deltas: - addr, shift = escrow.call().findCumSum(addr, delta + shift, duration) - addrs.add(addr) - - if len(addrs) >= n: - addrs = random.sample(addrs, n) - return addrs - - raise Exception('Not enough Ursulas') -======= from populus.contracts.contract import PopulusContract diff --git a/nkms_eth/token.py b/nkms_eth/token.py index 88c8f11ef..f32cd567e 100644 --- a/nkms_eth/token.py +++ b/nkms_eth/token.py @@ -1,37 +1,101 @@ -from nkms_eth import blockchain - -CONTRACT_NAME = 'NuCypherKMSToken' # TODO this should be NuCypher's class -SUBDIGITS = 18 -M = 10 ** SUBDIGITS -SATURATION = int(1e10) * M +from populus.contracts.contract import PopulusContract +from .blockchain import Blockchain -def create(): - """ - Creates a contract with tokens and returns it. - If it was already created, just returns the already existing contract +class NuCypherKMSToken: + _contract_name = 'NuCypherKMSToken' + subdigits = 18 + M = 10 ** subdigits + premine = int(1e9) * M + saturation = int(1e10) * M - :returns: Token contract object - """ - chain = blockchain.chain() - web3 = chain.web3 - creator = web3.eth.accounts[0] # TODO: make it possible to override + class ContractDeploymentError(Exception): + pass - token, tx = chain.provider.get_or_deploy_contract( - CONTRACT_NAME, deploy_args=[SATURATION], - deploy_transaction={'from': creator}) - if tx: - chain.wait.for_receipt(tx, timeout=blockchain.TIMEOUT) + def __init__(self, blockchain: Blockchain, token_contract: PopulusContract=None): + self.creator = blockchain.web3.eth.accounts[0] + self.blockchain = blockchain + self.contract = token_contract + self.armed = False - return token + 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 get(name=CONTRACT_NAME): - """ - Gets an existing contract or returns an error - """ - return blockchain.chain().provider.get_contract(name) + 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 balance(address: str): - return get().call().balanceOf(address) + def arm(self): + """Arm contract for deployment to blockchain.""" + self.armed = True + return self + + def deploy(self): + """Deploy and publish contract to the blockchain.""" + + if not self.armed: + raise self.ContractDeploymentError('use .arm() to arm the contract, then .deploy().') + + if self.contract: + class_name = self.__class__.__name__ + message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name) + raise self.ContractDeploymentError(message) + + token_contract, 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(txhash, timeout=self.blockchain.timeout) + + self.contract = token_contract + return self + + def transact(self, *args): + """Invoke contract -> State change""" + self._check_contract_deployment() + result = self.contract.transact(*args) + return result + + @classmethod + def get(cls, blockchain): + """Gets an existing token contract or returns an error""" + 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.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