mirror of https://github.com/nucypher/nucypher.git
[KMS-ETH]- Extracts policies module, API updates, further collapse contract wrapper logic up inheritance tree. Enhances docstrings.
parent
84f9454436
commit
e8f44e611e
|
@ -1,73 +1,43 @@
|
||||||
|
from abc import ABC
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Tuple, List
|
from datetime import datetime
|
||||||
|
from typing import Tuple, List, Union
|
||||||
|
|
||||||
# from nkms_eth.agents import MinerAgent, PolicyAgent
|
from nkms_eth.agents import NuCypherKMSTokenAgent
|
||||||
from nkms_eth.base import Actor
|
from nkms_eth.policies import BlockchainArrangement
|
||||||
|
|
||||||
|
|
||||||
class PolicyArrangement:
|
class TokenActor(ABC):
|
||||||
def __init__(self, author: 'PolicyAuthor', miner: 'Miner', value: int,
|
|
||||||
periods: int, arrangement_id: bytes=None):
|
|
||||||
|
|
||||||
if arrangement_id is None:
|
def __init__(self, token_agent: NuCypherKMSTokenAgent, address: Union[bytes, str]):
|
||||||
self.id = self.__class__._generate_arrangement_id() # TODO: Generate policy ID
|
self.token_agent = token_agent
|
||||||
|
|
||||||
# The relationship exists between two addresses
|
if isinstance(address, bytes):
|
||||||
self.author = author
|
address = address.hex()
|
||||||
self.policy_agent = author.policy_agent
|
self.address = address
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _generate_arrangement_id(policy_hrac: bytes) -> bytes:
|
|
||||||
pass # TODO
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
class_name = self.__class__.__name__
|
class_name = self.__class__.__name__
|
||||||
r = "{}(client={}, node={})"
|
r = "{}(address='{}')"
|
||||||
r = r.format(class_name, self.author, self.miner)
|
r.format(class_name, self.address)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def publish(self, gas_price: int) -> str:
|
def eth_balance(self):
|
||||||
|
"""Return this actors's current ETH balance"""
|
||||||
|
|
||||||
payload = {'from': self.author.address,
|
balance = self.token_agent._blockchain._chain.web3.eth.getBalance(self.address)
|
||||||
'value': self.value,
|
return balance
|
||||||
'gas_price': gas_price}
|
|
||||||
|
|
||||||
txhash = self.policy_agent.transact(payload).createPolicy(self.id,
|
def token_balance(self):
|
||||||
self.miner.address,
|
"""Return this actors's current token balance"""
|
||||||
self.periods)
|
|
||||||
|
|
||||||
self.policy_agent._blockchain._chain.wait.for_receipt(txhash)
|
balance = self.token_agent.get_balance(address=self.address)
|
||||||
self.publish_transaction = txhash
|
return balance
|
||||||
self.is_published = True
|
|
||||||
return txhash
|
|
||||||
|
|
||||||
def __update_periods(self) -> None:
|
|
||||||
blockchain_record = self.policy_agent.fetch_arrangement_data(self.id)
|
|
||||||
client, delegate, rate, *periods = blockchain_record
|
|
||||||
self._elapsed_periods = periods
|
|
||||||
|
|
||||||
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 Miner(Actor):
|
class Miner(TokenActor):
|
||||||
"""
|
"""
|
||||||
Practically carrying a pickaxe.
|
Ursula - practically carrying a pickaxe.
|
||||||
Intended for use as an Ursula mixin.
|
|
||||||
|
|
||||||
Accepts a running blockchain, deployed token contract, and deployed escrow contract.
|
Accepts a running blockchain, deployed token contract, and deployed escrow contract.
|
||||||
If the provided token and escrow contracts are not deployed,
|
If the provided token and escrow contracts are not deployed,
|
||||||
|
@ -76,28 +46,30 @@ class Miner(Actor):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, miner_agent, address):
|
def __init__(self, miner_agent, address):
|
||||||
super().__init__(address)
|
super().__init__(token_agent=miner_agent.token_agent, address=address)
|
||||||
|
|
||||||
self.miner_agent = miner_agent
|
self.miner_agent = miner_agent
|
||||||
miner_agent.miners.append(self) # Track Miners
|
miner_agent.miners.append(self) # Track Miners
|
||||||
|
|
||||||
self._token_agent = miner_agent._token
|
self.token_agent = miner_agent.token_agent
|
||||||
self._blockchain = self._token_agent._blockchain
|
self._blockchain = self.token_agent._blockchain
|
||||||
|
|
||||||
self._transactions = list()
|
self._transactions = OrderedDict()
|
||||||
self._locked_tokens = self._update_locked_tokens()
|
self._locked_tokens = self._update_locked_tokens()
|
||||||
|
|
||||||
def _update_locked_tokens(self) -> None:
|
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.call().getLockedTokens(self.address)
|
self._locked_tokens = self.miner_agent.call().getLockedTokens(self.address)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _approve_escrow(self, amount: int) -> str:
|
def _approve_escrow(self, amount: int) -> str:
|
||||||
"""Approve the transfer of token from the miner's address to the escrow contract."""
|
"""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)
|
txhash = self.token_agent.transact({'from': self.address}).approve(self.miner_agent._contract.address, amount)
|
||||||
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
||||||
|
|
||||||
self._transactions.append(txhash)
|
self._transactions[datetime.now()] = txhash
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
|
@ -107,12 +79,16 @@ class Miner(Actor):
|
||||||
deposit_txhash = self.miner_agent.transact({'from': self.address}).deposit(amount, locktime)
|
deposit_txhash = self.miner_agent.transact({'from': self.address}).deposit(amount, locktime)
|
||||||
self._blockchain._chain.wait.for_receipt(deposit_txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(deposit_txhash, timeout=self._blockchain._timeout)
|
||||||
|
|
||||||
self._transactions.append(deposit_txhash)
|
self._transactions[datetime.now()] = deposit_txhash
|
||||||
|
|
||||||
return deposit_txhash
|
return deposit_txhash
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_staking(self):
|
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)
|
return bool(self._locked_tokens > 0)
|
||||||
|
|
||||||
def lock(self, amount: int, locktime: int) -> Tuple[str, str, str]:
|
def lock(self, amount: int, locktime: int) -> Tuple[str, str, str]:
|
||||||
|
@ -124,7 +100,7 @@ class Miner(Actor):
|
||||||
lock_txhash = self.miner_agent.transact({'from': self.address}).switchLock()
|
lock_txhash = self.miner_agent.transact({'from': self.address}).switchLock()
|
||||||
self._blockchain._chain.wait.for_receipt(lock_txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(lock_txhash, timeout=self._blockchain._timeout)
|
||||||
|
|
||||||
self._transactions.extend([approve_txhash, deposit_txhash, lock_txhash])
|
self._transactions[datetime.now()] = (approve_txhash, deposit_txhash, lock_txhash)
|
||||||
|
|
||||||
return approve_txhash, deposit_txhash, lock_txhash
|
return approve_txhash, deposit_txhash, lock_txhash
|
||||||
|
|
||||||
|
@ -134,7 +110,7 @@ class Miner(Actor):
|
||||||
txhash = self.miner_agent.transact({'from': self.address}).confirmActivity()
|
txhash = self.miner_agent.transact({'from': self.address}).confirmActivity()
|
||||||
self._blockchain._chain.wait.for_receipt(txhash)
|
self._blockchain._chain.wait.for_receipt(txhash)
|
||||||
|
|
||||||
self._transactions.append(txhash)
|
self._transactions[datetime.now()] = txhash
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
|
@ -144,7 +120,7 @@ class Miner(Actor):
|
||||||
txhash = self.miner_agent.transact({'from': self.address}).mint()
|
txhash = self.miner_agent.transact({'from': self.address}).mint()
|
||||||
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
||||||
|
|
||||||
self._transactions.append(txhash)
|
self._transactions[datetime.now()] = txhash
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
|
@ -154,7 +130,7 @@ class Miner(Actor):
|
||||||
txhash = policy_manager.transact({'from': self.address}).withdraw()
|
txhash = policy_manager.transact({'from': self.address}).withdraw()
|
||||||
self._blockchain._chain.wait.for_receipt(txhash)
|
self._blockchain._chain.wait.for_receipt(txhash)
|
||||||
|
|
||||||
self._transactions.append(txhash)
|
self._transactions[datetime.now()] = txhash
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
|
@ -164,45 +140,34 @@ class Miner(Actor):
|
||||||
txhash = self.miner_agent.transact({'from': self.address}).setMinerId(miner_id)
|
txhash = self.miner_agent.transact({'from': self.address}).setMinerId(miner_id)
|
||||||
self._blockchain._chain.wait.for_receipt(txhash)
|
self._blockchain._chain.wait.for_receipt(txhash)
|
||||||
|
|
||||||
self._transactions.append(txhash)
|
self._transactions[datetime.now()] = txhash
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
def fetch_miner_ids(self) -> tuple:
|
def fetch_miner_ids(self) -> tuple:
|
||||||
"""Retrieve all stored Miner IDs on this miner"""
|
"""Retrieve all stored Miner IDs on this miner"""
|
||||||
|
|
||||||
count = self.escrow().getMinerInfo(self.escrow.MinerInfoField.MINER_IDS_LENGTH.value,
|
count = self.miner_agent.call().getMinerInfo(self.miner_agent.MinerInfoField.MINER_IDS_LENGTH.value,
|
||||||
self.address,
|
self.address,
|
||||||
0).encode('latin-1')
|
0).encode('latin-1')
|
||||||
|
|
||||||
count = self._blockchain._chain.web3.toInt(count)
|
count = self._blockchain._chain.web3.toInt(count)
|
||||||
|
|
||||||
miner_ids = list()
|
miner_ids = list()
|
||||||
for index in range(count):
|
for index in range(count):
|
||||||
miner_id = self.miner_agent.call().getMinerInfo(self.escrow.MinerInfoField.MINER_ID.value, self.address, index)
|
miner_id = self.miner_agent.call().getMinerInfo(self.miner_agent.MinerInfoField.MINER_ID.value, self.address, index)
|
||||||
encoded_miner_id = miner_id.encode('latin-1') # TODO change when v4 of web3.py is released
|
encoded_miner_id = miner_id.encode('latin-1') # TODO change when v4 of web3.py is released
|
||||||
miner_ids.append(encoded_miner_id)
|
miner_ids.append(encoded_miner_id)
|
||||||
|
|
||||||
return tuple(miner_ids)
|
return tuple(miner_ids)
|
||||||
|
|
||||||
def eth_balance(self):
|
|
||||||
return self._blockchain._chain.web3.eth.getBalance(self.address)
|
|
||||||
|
|
||||||
def token_balance(self) -> int:
|
|
||||||
"""Check miner's current token balance"""
|
|
||||||
|
|
||||||
# self._token_agent._check_contract_deployment()
|
|
||||||
balance = self._token_agent.call().balanceOf(self.address)
|
|
||||||
|
|
||||||
return balance
|
|
||||||
|
|
||||||
def withdraw(self, amount: int=0, entire_balance=False) -> str:
|
def withdraw(self, amount: int=0, entire_balance=False) -> str:
|
||||||
"""Withdraw tokens"""
|
"""Withdraw tokens"""
|
||||||
|
|
||||||
tokens_amount = self._blockchain._chain.web3.toInt(
|
tokens_amount = self._blockchain._chain.web3.toInt(
|
||||||
self.escrow().getMinerInfo(self.escrow.MinerInfoField.VALUE.value, self.address, 0).encode('latin-1'))
|
self.miner_agent.call().getMinerInfo(self.miner_agent.MinerInfoField.VALUE.value, self.address, 0).encode('latin-1'))
|
||||||
|
|
||||||
txhash = self.escrow.transact({'from': self.address}).withdraw(tokens_amount)
|
txhash = self.miner_agent.call().transact({'from': self.address}).withdraw(tokens_amount)
|
||||||
|
|
||||||
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
||||||
|
|
||||||
|
@ -210,39 +175,39 @@ class Miner(Actor):
|
||||||
raise Exception("Specify an amount or entire balance, not both")
|
raise Exception("Specify an amount or entire balance, not both")
|
||||||
|
|
||||||
if entire_balance:
|
if entire_balance:
|
||||||
txhash = self.escrow.transact({'from': self.address}).withdraw(tokens_amount)
|
txhash = self.miner_agent.call().transact({'from': self.address}).withdraw(tokens_amount)
|
||||||
else:
|
else:
|
||||||
txhash = self.escrow.transact({'from': self.address}).withdraw(amount)
|
txhash = self.miner_agent.call().transact({'from': self.address}).withdraw(amount)
|
||||||
|
|
||||||
self._transactions.append(txhash)
|
|
||||||
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(txhash, timeout=self._blockchain._timeout)
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
|
|
||||||
class PolicyAuthor(Actor):
|
class PolicyAuthor(TokenActor):
|
||||||
"""Alice"""
|
"""Alice"""
|
||||||
|
|
||||||
def __init__(self, address: bytes, policy_agent):
|
def __init__(self, address: bytes, policy_agent):
|
||||||
|
super().__init__(token_agent=policy_agent._token, address=address)
|
||||||
self.policy_agent = policy_agent
|
self.policy_agent = policy_agent
|
||||||
super().__init__(address)
|
|
||||||
self._arrangements = OrderedDict() # Track authored policies by id
|
self._arrangements = OrderedDict() # Track authored policies by id
|
||||||
|
|
||||||
def make_arrangement(self, miner: Miner, periods: int, rate: int, arrangement_id: bytes=None) -> PolicyArrangement:
|
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.
|
Create a new arrangement to carry out a blockchain policy for the specified rate and time.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
value = rate * periods
|
value = rate * periods
|
||||||
arrangement = PolicyArrangement(author=self,
|
arrangement = BlockchainArrangement(author=self,
|
||||||
miner=miner,
|
miner=miner,
|
||||||
value=value,
|
value=value,
|
||||||
periods=periods)
|
periods=periods)
|
||||||
|
|
||||||
self._arrangements[arrangement.id] = {arrangement_id: arrangement}
|
self._arrangements[arrangement.id] = {arrangement_id: arrangement}
|
||||||
return arrangement
|
return arrangement
|
||||||
|
|
||||||
def get_arrangement(self, arrangement_id: bytes) -> PolicyArrangement:
|
def get_arrangement(self, arrangement_id: bytes) -> BlockchainArrangement:
|
||||||
"""Fetch a published arrangement from the blockchain"""
|
"""Fetch a published arrangement from the blockchain"""
|
||||||
|
|
||||||
blockchain_record = self.policy_agent.call().policies(arrangement_id)
|
blockchain_record = self.policy_agent.call().policies(arrangement_id)
|
||||||
|
@ -251,13 +216,13 @@ class PolicyAuthor(Actor):
|
||||||
duration = end_block - start_block
|
duration = end_block - start_block
|
||||||
|
|
||||||
miner = Miner(address=miner_address, miner_agent=self.policy_agent.miner_agent)
|
miner = Miner(address=miner_address, miner_agent=self.policy_agent.miner_agent)
|
||||||
arrangement = PolicyArrangement(author=self, miner=miner, periods=duration)
|
arrangement = BlockchainArrangement(author=self, miner=miner, periods=duration)
|
||||||
|
|
||||||
arrangement.is_published = True
|
arrangement.is_published = True
|
||||||
return arrangement
|
return arrangement
|
||||||
|
|
||||||
def revoke_arrangement(self, arrangement_id):
|
def revoke_arrangement(self, arrangement_id):
|
||||||
"""Lookup the arrangement in the cache and revoke it on the blockchain"""
|
"""Get the arrangement from the cache and revoke it on the blockchain"""
|
||||||
try:
|
try:
|
||||||
arrangement = self._arrangements[arrangement_id]
|
arrangement = self._arrangements[arrangement_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -267,9 +232,12 @@ class PolicyAuthor(Actor):
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
def recruit(self, quantity: int) -> List[str]:
|
def recruit(self, quantity: int) -> List[str]:
|
||||||
|
"""Uses sampling logic to gather"""
|
||||||
|
|
||||||
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
|
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
|
||||||
return miner_addresses
|
return miner_addresses
|
||||||
|
|
||||||
def balance(self):
|
def balance(self):
|
||||||
|
"""Get the balance of this actor's address"""
|
||||||
return self.policy_agent.miner_agent.call().balanceOf(self.address)
|
return self.policy_agent.miner_agent.call().balanceOf(self.address)
|
||||||
|
|
||||||
|
|
|
@ -1,27 +1,80 @@
|
||||||
import random
|
import random
|
||||||
|
from abc import ABC
|
||||||
from typing import Set, Generator, List
|
from typing import Set, Generator, List
|
||||||
|
|
||||||
from nkms_eth.actors import PolicyAuthor
|
from nkms_eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer, ContractDeployer
|
||||||
from nkms_eth.base import EthereumContractAgent
|
|
||||||
from nkms_eth.blockchain import TheBlockchain
|
|
||||||
from nkms_eth.deployers import MinerEscrowDeployer, NuCypherKMSTokenDeployer, PolicyManagerDeployer
|
class EthereumContractAgent(ABC):
|
||||||
|
_principal_contract_name = NotImplemented
|
||||||
|
|
||||||
|
_contract_subclasses = list()
|
||||||
|
|
||||||
|
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, blockchain, *args, **kwargs):
|
||||||
|
|
||||||
|
self._blockchain = blockchain
|
||||||
|
self._contract = self.__fetch_contract()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __init_subclass__(cls, deployer, **kwargs):
|
||||||
|
"""
|
||||||
|
https://www.python.org/dev/peps/pep-0487/#proposal
|
||||||
|
"""
|
||||||
|
super().__init_subclass__(**kwargs)
|
||||||
|
cls._deployer = deployer
|
||||||
|
cls._principal_contract_name = deployer._contract_name
|
||||||
|
cls._contract_subclasses.append(cls)
|
||||||
|
|
||||||
|
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 __fetch_contract(self):
|
||||||
|
contract = self._blockchain._chain.provider.get_contract(self._principal_contract_name)
|
||||||
|
return contract
|
||||||
|
|
||||||
|
def call(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.call().balanceOf(address)
|
||||||
|
|
||||||
|
|
||||||
class NuCypherKMSTokenAgent(EthereumContractAgent, deployer=NuCypherKMSTokenDeployer):
|
class NuCypherKMSTokenAgent(EthereumContractAgent, deployer=NuCypherKMSTokenDeployer):
|
||||||
|
|
||||||
def __init__(self, blockchain: TheBlockchain):
|
_principal_contract_name = NotImplemented
|
||||||
self._blockchain = blockchain
|
|
||||||
super().__init__(self)
|
|
||||||
|
|
||||||
def registrar(self):
|
def registrar(self):
|
||||||
"""Retrieve all known addresses for this contract"""
|
"""Retrieve all known addresses for this contract"""
|
||||||
all_known_address = self._blockchain._chain.registrar.get_contract_address(self._principal_contract_name)
|
all_known_address = self._blockchain._chain.registrar.get_contract_address(self._principal_contract_name)
|
||||||
return all_known_address
|
return all_known_address
|
||||||
|
|
||||||
def balance(self, address: str) -> int:
|
|
||||||
"""Get the balance of a token address"""
|
|
||||||
return self.call().balanceOf(address)
|
|
||||||
|
|
||||||
|
|
||||||
class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
|
class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
|
||||||
"""
|
"""
|
||||||
|
@ -36,20 +89,21 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
|
||||||
class NotEnoughUrsulas(Exception):
|
class NotEnoughUrsulas(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __init__(self, token: NuCypherKMSTokenAgent):
|
def __init__(self, token_agent: NuCypherKMSTokenAgent):
|
||||||
super().__init__(agent=token)
|
super().__init__(blockchain=token_agent._blockchain)
|
||||||
self._token = token
|
self.token_agent = token_agent
|
||||||
self.miners = list()
|
self.miners = list()
|
||||||
|
|
||||||
def get_miner_ids(self) -> Set[str]:
|
def get_miner_ids(self) -> Set[str]:
|
||||||
"""
|
"""
|
||||||
Fetch all miner IDs from the local cache and return them in a set
|
Fetch all miner IDs from the local cache and return them in a set
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return {miner.get_id() for miner in self.miners}
|
return {miner.get_id() for miner in self.miners}
|
||||||
|
|
||||||
def swarm(self) -> Generator[str, None, None]:
|
def swarm(self) -> Generator[str, None, None]:
|
||||||
"""
|
"""
|
||||||
Generates all miner addresses via cumulative sum on-network.
|
Returns an iterator of all miner addresses via cumulative sum, on-network.
|
||||||
"""
|
"""
|
||||||
count = self.call().getMinerInfo(self._deployer.MinerInfoField.MINERS_LENGTH.value, self._deployer.null_address, 0).encode('latin-1')
|
count = self.call().getMinerInfo(self._deployer.MinerInfoField.MINERS_LENGTH.value, self._deployer.null_address, 0).encode('latin-1')
|
||||||
count = self._blockchain._chain.web3.toInt(count)
|
count = self._blockchain._chain.web3.toInt(count)
|
||||||
|
@ -106,17 +160,19 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
|
||||||
class PolicyAgent(EthereumContractAgent, deployer=PolicyManagerDeployer):
|
class PolicyAgent(EthereumContractAgent, deployer=PolicyManagerDeployer):
|
||||||
|
|
||||||
def __init__(self, miner_agent):
|
def __init__(self, miner_agent):
|
||||||
super().__init__(miner_agent)
|
super().__init__(blockchain=miner_agent._blockchain)
|
||||||
self.miner_agent = miner_agent
|
self.miner_agent = miner_agent
|
||||||
|
|
||||||
def fetch_arrangement_data(self, arrangement_id: bytes) -> list:
|
def fetch_arrangement_data(self, arrangement_id: bytes) -> list:
|
||||||
blockchain_record = self.call().policies(arrangement_id)
|
blockchain_record = self.call().policies(arrangement_id)
|
||||||
return blockchain_record
|
return blockchain_record
|
||||||
|
|
||||||
def revoke_arrangement(self, arrangement_id: bytes, author: 'PolicyAuthor', gas_price: int):
|
def revoke_arrangement(self, arrangement_id: bytes, author, gas_price: int):
|
||||||
"""
|
"""
|
||||||
Revoke by arrangement ID; Only the policy author can revoke the policy
|
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)
|
txhash = self.transact({'from': author.address, 'gas_price': gas_price}).revokePolicy(arrangement_id)
|
||||||
self._blockchain._chain.wait.for_receipt(txhash)
|
self._blockchain._chain.wait.for_receipt(txhash)
|
||||||
|
|
||||||
return txhash
|
return txhash
|
||||||
|
|
137
nkms_eth/base.py
137
nkms_eth/base.py
|
@ -1,137 +0,0 @@
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
from nkms_eth.blockchain import TheBlockchain
|
|
||||||
|
|
||||||
|
|
||||||
class Actor(ABC):
|
|
||||||
def __init__(self, address):
|
|
||||||
if isinstance(address, bytes):
|
|
||||||
address = address.hex()
|
|
||||||
self.address = address
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
class_name = self.__class__.__name__
|
|
||||||
r = "{}(address='{}')"
|
|
||||||
r.format(class_name, self.address)
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
class ContractDeployer(ABC):
|
|
||||||
|
|
||||||
_contract_name = NotImplemented
|
|
||||||
|
|
||||||
class ContractDeploymentError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, blockchain):
|
|
||||||
self.__armed = False
|
|
||||||
self._contract = 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
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self._contract.address == other.address
|
|
||||||
|
|
||||||
@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 _verify_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)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def arm(self) -> None:
|
|
||||||
self.__armed = True
|
|
||||||
return None
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def deploy(self) -> str:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# @abstractmethod
|
|
||||||
# def make_agent(self) -> 'EthereumContractAgent':
|
|
||||||
# raise NotImplementedError
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_blockchain(cls, blockchain: TheBlockchain) -> 'ContractDeployer':
|
|
||||||
"""
|
|
||||||
Returns the NuCypherKMSToken object,
|
|
||||||
or raises UnknownContract if the contract has not been deployed.
|
|
||||||
"""
|
|
||||||
contract = blockchain._chain.provider.get_contract(cls._contract_name)
|
|
||||||
instance = cls(blockchain=blockchain)
|
|
||||||
instance._contract = contract
|
|
||||||
return instance
|
|
||||||
|
|
||||||
|
|
||||||
class EthereumContractAgent(ABC):
|
|
||||||
_deployer = NotImplemented
|
|
||||||
_principal_contract_name = NotImplemented
|
|
||||||
|
|
||||||
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def __init__(self, agent, *args, **kwargs):
|
|
||||||
|
|
||||||
self._blockchain = agent._blockchain
|
|
||||||
|
|
||||||
contract = self._blockchain._chain.provider.get_contract(self._principal_contract_name)
|
|
||||||
self._contract = contract
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __init_subclass__(cls, deployer, **kwargs):
|
|
||||||
"""
|
|
||||||
https://www.python.org/dev/peps/pep-0487/#proposal
|
|
||||||
"""
|
|
||||||
cls._deployer = deployer
|
|
||||||
cls._principal_contract_name = deployer._contract_name
|
|
||||||
super().__init_subclass__(**kwargs)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def call(self):
|
|
||||||
return self._contract.call()
|
|
||||||
|
|
||||||
def transact(self, *args, **kwargs):
|
|
||||||
return self._contract.transact(*args, **kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def origin(self):
|
|
||||||
return self._blockchain._chain.web3.eth.accounts[0] # TODO
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contract_address(self):
|
|
||||||
return self._contract.address
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contract_name(self):
|
|
||||||
return self._principal_contract_name
|
|
|
@ -83,4 +83,4 @@ class TheBlockchain:
|
||||||
while not_time_yet:
|
while not_time_yet:
|
||||||
self._chain.wait.for_block(self._chain.web3.eth.blockNumber+step)
|
self._chain.wait.for_block(self._chain.web3.eth.blockNumber+step)
|
||||||
current_block = self._chain.web3.eth.getBlock(self._chain.web3.eth.blockNumber)
|
current_block = self._chain.web3.eth.getBlock(self._chain.web3.eth.blockNumber)
|
||||||
not_time_yet = current_block.timestamp < end_timestamp
|
not_time_yet = current_block.timestamp < end_timestamp
|
|
@ -1,23 +1,86 @@
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
from nkms_eth.base import ContractDeployer
|
|
||||||
from nkms_eth.config import NuCypherMinerConfig, NuCypherTokenConfig
|
from nkms_eth.config import NuCypherMinerConfig, NuCypherTokenConfig
|
||||||
from .blockchain import TheBlockchain
|
from .blockchain import TheBlockchain
|
||||||
|
|
||||||
addr = str
|
|
||||||
|
class ContractDeployer(ABC):
|
||||||
|
|
||||||
|
_contract_name = NotImplemented
|
||||||
|
_agency = NotImplemented
|
||||||
|
|
||||||
|
class ContractDeploymentError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __init__(self, blockchain: TheBlockchain):
|
||||||
|
self.__armed = False
|
||||||
|
self._contract = 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
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self._contract.address == other.address
|
||||||
|
|
||||||
|
@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)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def arm(self) -> None:
|
||||||
|
if self.__armed is True:
|
||||||
|
raise self.ContractDeploymentError("Deployer already armed, use .deploy() to deploy.")
|
||||||
|
self.__armed = True
|
||||||
|
return None
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def deploy(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_blockchain(cls, blockchain: TheBlockchain) -> 'ContractDeployer':
|
||||||
|
"""Returns the NuCypherKMSToken object, or raises UnknownContract if the contract has not been deployed."""
|
||||||
|
|
||||||
|
contract = blockchain._chain.provider.get_contract(cls._contract_name)
|
||||||
|
instance = cls(blockchain=blockchain)
|
||||||
|
instance._contract = contract
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
|
class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
|
||||||
|
|
||||||
_contract_name = 'NuCypherKMSToken'
|
_contract_name = 'NuCypherKMSToken'
|
||||||
|
|
||||||
def __init__(self, blockchain: TheBlockchain):
|
def __init__(self, blockchain):
|
||||||
super().__init__(blockchain=blockchain)
|
super().__init__(blockchain=blockchain)
|
||||||
self.__creator = self._blockchain._chain.web3.eth.accounts[0]
|
self._creator = self._blockchain._chain.web3.eth.accounts[0]
|
||||||
|
|
||||||
@property
|
|
||||||
def origin(self):
|
|
||||||
return self.__creator
|
|
||||||
|
|
||||||
def deploy(self) -> str:
|
def deploy(self) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -39,7 +102,7 @@ class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
|
||||||
the_nucypher_token_contract, deployment_txhash = self._blockchain._chain.provider.deploy_contract(
|
the_nucypher_token_contract, deployment_txhash = self._blockchain._chain.provider.deploy_contract(
|
||||||
self._contract_name,
|
self._contract_name,
|
||||||
deploy_args=[self.saturation],
|
deploy_args=[self.saturation],
|
||||||
deploy_transaction={'from': self.origin})
|
deploy_transaction={'from': self._creator})
|
||||||
|
|
||||||
self._blockchain._chain.wait.for_receipt(deployment_txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(deployment_txhash, timeout=self._blockchain._timeout)
|
||||||
self._contract = the_nucypher_token_contract
|
self._contract = the_nucypher_token_contract
|
||||||
|
@ -47,42 +110,16 @@ class NuCypherKMSTokenDeployer(ContractDeployer, NuCypherTokenConfig):
|
||||||
return deployment_txhash
|
return deployment_txhash
|
||||||
|
|
||||||
|
|
||||||
class PolicyManagerDeployer(ContractDeployer):
|
|
||||||
|
|
||||||
_contract_name = 'PolicyManager'
|
|
||||||
|
|
||||||
def __init__(self, miner_agent):
|
|
||||||
super().__init__(miner_agent)
|
|
||||||
self.miner_agent = miner_agent
|
|
||||||
self.token_agent = miner_agent._token_agent
|
|
||||||
|
|
||||||
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.creator})
|
|
||||||
|
|
||||||
self._contract = the_policy_manager_contract
|
|
||||||
|
|
||||||
set_txhash = self.miner_agent.transact({'from': self.token_agent.creator}).setPolicyManager(the_policy_manager_contract.address)
|
|
||||||
self._blockchain._chain.wait.for_receipt(set_txhash)
|
|
||||||
|
|
||||||
return deploy_txhash, set_txhash
|
|
||||||
|
|
||||||
|
|
||||||
class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
|
class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
|
||||||
|
"""
|
||||||
|
Depends on NuCypherTokenAgent
|
||||||
|
"""
|
||||||
|
|
||||||
_contract_name = 'MinersEscrow'
|
_contract_name = 'MinersEscrow'
|
||||||
|
|
||||||
def __init__(self, token_agent):
|
def __init__(self, token_agent):
|
||||||
self._token_agent = token_agent
|
|
||||||
super().__init__(blockchain=token_agent._blockchain)
|
super().__init__(blockchain=token_agent._blockchain)
|
||||||
|
self.token_agent = token_agent
|
||||||
|
|
||||||
def deploy(self) -> Tuple[str, str, str]:
|
def deploy(self) -> Tuple[str, str, str]:
|
||||||
"""
|
"""
|
||||||
|
@ -103,21 +140,54 @@ class MinerEscrowDeployer(ContractDeployer, NuCypherMinerConfig):
|
||||||
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
||||||
raise self.ContractDeploymentError(message)
|
raise self.ContractDeploymentError(message)
|
||||||
|
|
||||||
deploy_args = [self._token_agent._contract.address] + self.mining_coefficient
|
deploy_args = [self.token_agent._contract.address] + self.mining_coefficient
|
||||||
deploy_tx = {'from': self._token_agent.origin}
|
deploy_tx = {'from': self.token_agent.origin}
|
||||||
|
|
||||||
the_escrow_contract, deploy_txhash = self._blockchain._chain.provider.deploy_contract(self._contract_name,
|
the_escrow_contract, deploy_txhash = self._blockchain._chain.provider.deploy_contract(self._contract_name,
|
||||||
deploy_args=deploy_args,
|
deploy_args=deploy_args,
|
||||||
deploy_transaction=deploy_tx)
|
deploy_transaction=deploy_tx)
|
||||||
|
|
||||||
self._blockchain._chain.wait.for_receipt(deploy_txhash, timeout=self._blockchain._timeout)
|
timeout = self._blockchain._timeout
|
||||||
|
self._blockchain._chain.wait.for_receipt(deploy_txhash, timeout=timeout)
|
||||||
self._contract = the_escrow_contract
|
self._contract = the_escrow_contract
|
||||||
|
|
||||||
reward_txhash = self._token_agent.transact({'from': self._token_agent.origin}).transfer(self.contract_address,
|
reward_txhash = self.token_agent.transact({'from': self.token_agent.origin}).transfer(self.contract_address,
|
||||||
self.reward)
|
self.reward)
|
||||||
self._blockchain._chain.wait.for_receipt(reward_txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(reward_txhash, timeout=timeout)
|
||||||
|
|
||||||
init_txhash = self._contract.transact({'from': self._token_agent.origin}).initialize()
|
init_txhash = self._contract.transact({'from': self.token_agent.origin}).initialize()
|
||||||
self._blockchain._chain.wait.for_receipt(init_txhash, timeout=self._blockchain._timeout)
|
self._blockchain._chain.wait.for_receipt(init_txhash, timeout=timeout)
|
||||||
|
|
||||||
return deploy_txhash, reward_txhash, init_txhash
|
return deploy_txhash, reward_txhash, init_txhash
|
||||||
|
|
||||||
|
|
||||||
|
class PolicyManagerDeployer(ContractDeployer):
|
||||||
|
"""
|
||||||
|
Depends on MinerAgent and NuCypherTokenAgent
|
||||||
|
"""
|
||||||
|
|
||||||
|
_contract_name = 'PolicyManager'
|
||||||
|
|
||||||
|
def __init__(self, miner_agent):
|
||||||
|
super().__init__(blockchain=token_agent._blockchain)
|
||||||
|
self.token_agent = miner_agent._token_agent
|
||||||
|
self.miner_agent = miner_agent
|
||||||
|
|
||||||
|
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.creator})
|
||||||
|
|
||||||
|
self._contract = the_policy_manager_contract
|
||||||
|
|
||||||
|
set_txhash = self.miner_agent.transact({'from': self.token_agent.creator}).setPolicyManager(the_policy_manager_contract.address)
|
||||||
|
self._blockchain._chain.wait.for_receipt(set_txhash)
|
||||||
|
|
||||||
|
return deploy_txhash, set_txhash
|
||||||
|
|
|
@ -7,15 +7,19 @@ from nkms_eth.token import NuCypherKMSToken
|
||||||
|
|
||||||
|
|
||||||
class PolicyArrangement:
|
class PolicyArrangement:
|
||||||
def __init__(self, author: 'PolicyAuthor', miner: 'Miner', value: int,
|
"""
|
||||||
periods: int, arrangement_id: bytes=None):
|
A relationship between Alice and a single Ursula as part of BlockchainPolicy
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, author, miner, value: int, periods: int, arrangement_id: bytes=None):
|
||||||
|
|
||||||
if arrangement_id is None:
|
if arrangement_id is None:
|
||||||
self.id = self.__class__._generate_arrangement_id() # TODO: Generate policy ID
|
self.id = self.__class__._generate_arrangement_id() # TODO: Generate policy ID
|
||||||
|
|
||||||
# The relationship between two addresses
|
# The relationship exists between two addresses
|
||||||
self.author = author
|
self.author = author
|
||||||
self.policy_manager = author.policy_manager
|
self.policy_agent = author.policy_agent
|
||||||
|
|
||||||
|
|
||||||
self.miner = miner
|
self.miner = miner
|
||||||
|
|
||||||
|
@ -28,7 +32,6 @@ class PolicyArrangement:
|
||||||
|
|
||||||
self.is_published = False
|
self.is_published = False
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _generate_arrangement_id(policy_hrac: bytes) -> bytes:
|
def _generate_arrangement_id(policy_hrac: bytes) -> bytes:
|
||||||
pass # TODO
|
pass # TODO
|
||||||
|
@ -45,23 +48,25 @@ class PolicyArrangement:
|
||||||
'value': self.value,
|
'value': self.value,
|
||||||
'gas_price': gas_price}
|
'gas_price': gas_price}
|
||||||
|
|
||||||
txhash = self.policy_manager.transact(payload).createPolicy(self.id,
|
|
||||||
self.miner.address,
|
|
||||||
self.periods)
|
|
||||||
|
|
||||||
self.policy_manager.blockchain._chain.wait.for_receipt(txhash)
|
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.publish_transaction = txhash
|
||||||
self.is_published = True
|
self.is_published = True
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
def __update_periods(self) -> None:
|
def __update_periods(self) -> None:
|
||||||
blockchain_record = self.policy_manager.fetch_arrangement_data(self.id)
|
blockchain_record = self.policy_agent.fetch_arrangement_data(self.id)
|
||||||
client, delegate, rate, *periods = blockchain_record
|
client, delegate, rate, *periods = blockchain_record
|
||||||
self._elapsed_periods = periods
|
self._elapsed_periods = periods
|
||||||
|
|
||||||
def revoke(self, gas_price: int) -> str:
|
def revoke(self, gas_price: int) -> str:
|
||||||
"""Revoke this arrangement and return the transaction hash as hex."""
|
"""Revoke this arrangement and return the transaction hash as hex."""
|
||||||
txhash = self.policy_manager.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
|
txhash = self.policy_agent.revoke_arrangement(self.id, author=self.author, gas_price=gas_price)
|
||||||
self.revoke_transaction = txhash
|
self.revoke_transaction = txhash
|
||||||
return txhash
|
return txhash
|
||||||
|
|
||||||
|
@ -195,3 +200,18 @@ class PolicyAuthor:
|
||||||
|
|
||||||
def balance(self):
|
def balance(self):
|
||||||
return self.policy_manager.token().balanceOf(self.address)
|
return self.policy_manager.token().balanceOf(self.address)
|
||||||
|
|
||||||
|
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"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._arrangements = list()
|
||||||
|
|
|
@ -2,11 +2,10 @@ import pytest
|
||||||
|
|
||||||
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
|
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
|
||||||
from nkms_eth.blockchain import TheBlockchain
|
from nkms_eth.blockchain import TheBlockchain
|
||||||
from nkms_eth.deployers import NuCypherKMSTokenDeployer
|
from tests.utilities import TesterBlockchain, MockNuCypherKMSTokenDeployer, MockMinerEscrowDeployer
|
||||||
from tests.utilities import TesterBlockchain, MockMinerEscrowDeployer
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture()
|
||||||
def testerchain():
|
def testerchain():
|
||||||
chain = TesterBlockchain()
|
chain = TesterBlockchain()
|
||||||
yield chain
|
yield chain
|
||||||
|
@ -14,7 +13,7 @@ def testerchain():
|
||||||
TheBlockchain._TheBlockchain__instance = None
|
TheBlockchain._TheBlockchain__instance = None
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture()
|
||||||
def mock_token_deployer(testerchain):
|
def mock_token_deployer(testerchain):
|
||||||
token_deployer = MockNuCypherKMSTokenDeployer(blockchain=testerchain)
|
token_deployer = MockNuCypherKMSTokenDeployer(blockchain=testerchain)
|
||||||
token_deployer.arm()
|
token_deployer.arm()
|
||||||
|
@ -22,12 +21,6 @@ def mock_token_deployer(testerchain):
|
||||||
yield token_deployer
|
yield token_deployer
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def token_agent(testerchain):
|
|
||||||
token = NuCypherKMSTokenAgent(blockchain=testerchain)
|
|
||||||
yield token
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def mock_miner_escrow_deployer(token_agent):
|
def mock_miner_escrow_deployer(token_agent):
|
||||||
escrow = MockMinerEscrowDeployer(token_agent)
|
escrow = MockMinerEscrowDeployer(token_agent)
|
||||||
|
@ -38,8 +31,13 @@ def mock_miner_escrow_deployer(token_agent):
|
||||||
|
|
||||||
# Unused args preserve fixture dependency order #
|
# Unused args preserve fixture dependency order #
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def token_agent(testerchain, mock_token_deployer):
|
||||||
|
token = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||||
|
yield token
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
|
@pytest.fixture()
|
||||||
def miner_agent(token_agent, mock_token_deployer, mock_miner_escrow_deployer):
|
def miner_agent(token_agent, mock_token_deployer, mock_miner_escrow_deployer):
|
||||||
miner_agent = MinerAgent(token_agent)
|
miner_agent = MinerAgent(token_agent)
|
||||||
yield miner_agent
|
yield miner_agent
|
||||||
|
|
|
@ -18,32 +18,34 @@ def test_deposit(testerchain, mock_token_deployer, token_agent, miner_agent):
|
||||||
miner.lock(amount=1000*M, locktime=100)
|
miner.lock(amount=1000*M, locktime=100)
|
||||||
|
|
||||||
|
|
||||||
class MockNucypherMinerConfig(object):
|
def test_mine_withdraw(testerchain, mock_token_deployer, token_agent, miner_agent, mock_miner_escrow_deployer):
|
||||||
pass
|
"""
|
||||||
|
- Airdrop tokens to everyone
|
||||||
|
- Create an Ursula (Miner)
|
||||||
|
- Ursula locks tokens
|
||||||
|
- Spawn additional miners
|
||||||
|
- Wait
|
||||||
|
- Ursula mints new tokens
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def test_mine_withdraw(testerchain, mock_token_deployer, token_agent, miner_agent):
|
|
||||||
mock_token_deployer._global_airdrop(amount=10000)
|
mock_token_deployer._global_airdrop(amount=10000)
|
||||||
|
|
||||||
ursula_address = testerchain._chain.web3.eth.accounts[1]
|
_origin, ursula_address, *everyone_else = testerchain._chain.web3.eth.accounts
|
||||||
miner = Miner(miner_agent=miner_agent, address=ursula_address)
|
|
||||||
|
|
||||||
ursula = miner
|
ursula = Miner(miner_agent=miner_agent, address=ursula_address)
|
||||||
initial_balance = token_agent.balance(address=ursula.address)
|
initial_balance = ursula.token_balance()
|
||||||
|
|
||||||
# Create a random set of miners (we have 9 in total)
|
|
||||||
for address in testerchain._chain.web3.eth.accounts[1:]:
|
|
||||||
miner = Miner(miner_agent=miner_agent, address=address)
|
|
||||||
|
|
||||||
amount = (10+random.randrange(9000)) * M
|
amount = (10 + random.randrange(9000)) * M
|
||||||
miner.lock(amount=amount, locktime=1)
|
ursula.lock(amount=amount, locktime=1)
|
||||||
|
|
||||||
testerchain.wait_time(MockNuCypherMinerConfig._hours_per_period*2)
|
spawn_miners(miner_agent=miner_agent, addresses=everyone_else, locktime=1, m=M)
|
||||||
|
testerchain.wait_time(wait_hours=miner_agent._deployer._hours_per_period*2)
|
||||||
|
|
||||||
ursula.mint()
|
ursula.mint()
|
||||||
ursula.withdraw(entire_balance=True)
|
ursula.withdraw(entire_balance=True)
|
||||||
final_balance = token_agent.balance(ursula.address)
|
|
||||||
|
|
||||||
|
final_balance = token_agent.balance(ursula.address)
|
||||||
assert final_balance > initial_balance
|
assert final_balance > initial_balance
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ class MockNuCypherKMSTokenDeployer(NuCypherKMSTokenDeployer):
|
||||||
|
|
||||||
def txs():
|
def txs():
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
yield self._contract.transact({'from': self.origin}).transfer(address, amount * (10 ** 6))
|
yield self._contract.transact({'from': self._creator}).transfer(address, amount * (10 ** 6))
|
||||||
|
|
||||||
for tx in txs():
|
for tx in txs():
|
||||||
self._blockchain._chain.wait.for_receipt(tx, timeout=10)
|
self._blockchain._chain.wait.for_receipt(tx, timeout=10)
|
||||||
|
|
Loading…
Reference in New Issue