mirror of https://github.com/nucypher/nucypher.git
[KMS-ETH]- Payment model and client methods updates; Additional shared base class methods for contract agents and deployers
parent
d12572546c
commit
9a4c4ad135
|
@ -53,13 +53,13 @@ class PolicyArrangement:
|
|||
return txhash
|
||||
|
||||
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
|
||||
self._elapsed_periods = periods
|
||||
|
||||
def revoke(self, gas_price: int) -> str:
|
||||
"""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
|
||||
return txhash
|
||||
|
||||
|
@ -148,7 +148,7 @@ class Miner(Actor):
|
|||
|
||||
return txhash
|
||||
|
||||
def collect_policy_reward(self, policy_manager):
|
||||
def collect_policy_reward(self, policy_manager) -> str:
|
||||
"""Collect policy reward in ETH"""
|
||||
|
||||
txhash = policy_manager.transact({'from': self.address}).withdraw()
|
||||
|
@ -191,8 +191,8 @@ class Miner(Actor):
|
|||
def token_balance(self) -> int:
|
||||
"""Check miner's current token balance"""
|
||||
|
||||
self._token_agent._check_contract_deployment()
|
||||
balance = self._token_agent().balanceOf(self.address)
|
||||
# self._token_agent._check_contract_deployment()
|
||||
balance = self._token_agent.call().balanceOf(self.address)
|
||||
|
||||
return balance
|
||||
|
||||
|
@ -261,12 +261,12 @@ class PolicyAuthor(Actor):
|
|||
try:
|
||||
arrangement = self._arrangements[arrangement_id]
|
||||
except KeyError:
|
||||
raise Exception('No such arrangement')
|
||||
raise Exception('No such arrangement') #TODO
|
||||
else:
|
||||
txhash = arrangement.revoke()
|
||||
return txhash
|
||||
|
||||
def select_miners(self, quantity: int) -> List[str]:
|
||||
def recruit(self, quantity: int) -> List[str]:
|
||||
miner_addresses = self.policy_agent.miner_agent.sample(quantity=quantity)
|
||||
return miner_addresses
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ class NuCypherKMSTokenAgent(EthereumContractAgent, deployer=NuCypherKMSTokenDepl
|
|||
all_known_address = self._blockchain._chain.registrar.get_contract_address(self._principal_contract_name)
|
||||
return all_known_address
|
||||
|
||||
def check_balance(self, address: str) -> int:
|
||||
def balance(self, address: str) -> int:
|
||||
"""Get the balance of a token address"""
|
||||
return self.call().balanceOf(address)
|
||||
|
||||
|
@ -83,8 +83,8 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
|
|||
|
||||
system_random = random.SystemRandom()
|
||||
n_select = round(quantity*additional_ursulas) # Select more Ursulas
|
||||
n_tokens = self.call().getAllLockedTokens()
|
||||
|
||||
n_tokens = self.call().getAllLockedTokens() # Check for locked tokens
|
||||
if not n_tokens > 0:
|
||||
raise self.NotEnoughUrsulas('There are no locked tokens.')
|
||||
|
||||
|
@ -92,9 +92,9 @@ class MinerAgent(EthereumContractAgent, deployer=MinerEscrowDeployer):
|
|||
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, shift = set(), MinerEscrowDeployer._null_addr, 0
|
||||
addrs, addr, index, shift = set(), self._deployer._null_addr, 0, 0
|
||||
for delta in deltas:
|
||||
addr, shift = self.call().findCumSum(addr, delta+shift, duration)
|
||||
addr, index, shift = self.call().findCumSum(index, delta+shift, duration)
|
||||
addrs.add(addr)
|
||||
|
||||
if len(addrs) >= quantity:
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
from abc import ABC, abstractmethod
|
||||
|
||||
from nkms_eth.blockchain import TheBlockchain
|
||||
|
||||
|
||||
class Actor(ABC):
|
||||
def __init__(self, address):
|
||||
|
@ -15,6 +17,7 @@ class Actor(ABC):
|
|||
|
||||
|
||||
class ContractDeployer(ABC):
|
||||
|
||||
_contract_name = NotImplemented
|
||||
|
||||
class ContractDeploymentError(Exception):
|
||||
|
@ -22,31 +25,38 @@ class ContractDeployer(ABC):
|
|||
|
||||
def __init__(self, blockchain):
|
||||
self.__armed = False
|
||||
self.__contract = None
|
||||
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
|
||||
return self._contract.address == other.address
|
||||
|
||||
@property
|
||||
def address(self) -> str:
|
||||
return self.__contract.address
|
||||
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)
|
||||
return bool(self._contract is not None)
|
||||
|
||||
@property
|
||||
def is_armed(self) -> bool:
|
||||
return bool(self.__armed is True)
|
||||
|
||||
@classmethod
|
||||
def contract_name(cls) -> str:
|
||||
return cls._contract_name
|
||||
|
||||
def _verify_contract_deployment(self) -> None:
|
||||
"""Raises ContractDeploymentError if the contract has not been armed and deployed."""
|
||||
if not self.__contract:
|
||||
if not self._contract:
|
||||
class_name = self.__class__.__name__
|
||||
message = '{} contract is not deployed. Arm, then deploy.'.format(class_name)
|
||||
raise self.ContractDeploymentError(message)
|
||||
|
@ -65,50 +75,62 @@ class ContractDeployer(ABC):
|
|||
# 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
|
||||
@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(Exception):
|
||||
class ContractNotDeployed(ContractDeployer.ContractDeploymentError):
|
||||
pass
|
||||
|
||||
def __init__(self, agent, *args, **kwargs):
|
||||
if not self._blockchain:
|
||||
self._blockchain = agent._blockchain
|
||||
|
||||
self._blockchain = agent._blockchain
|
||||
|
||||
contract = self._blockchain._chain.provider.get_contract(self._principal_contract_name)
|
||||
self.__contract = contract
|
||||
self._contract = contract
|
||||
|
||||
def __init_subclass__(cls, deployer):
|
||||
@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()
|
||||
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)
|
||||
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()
|
||||
return self._contract.call()
|
||||
|
||||
def transact(self, *args, **kwargs):
|
||||
return self.__contract.transact(*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
|
||||
return self._contract.address
|
||||
|
||||
@property
|
||||
def contract_name(self):
|
||||
|
|
|
@ -36,14 +36,14 @@ class NuCypherKMSTokenDeployer(ContractDeployer):
|
|||
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(
|
||||
the_nucypher_token_contract, deployment_txhash = self._blockchain._chain.provider.deploy_contract(
|
||||
self._contract_name,
|
||||
deploy_args=[self._token_config.saturation],
|
||||
deploy_args=[self.saturation],
|
||||
deploy_transaction={'from': self.origin})
|
||||
|
||||
self._blockchain._chain.wait.for_receipt(deployment_txhash, timeout=self._blockchain._timeout)
|
||||
self._contract = the_nucypher_token_contract
|
||||
|
||||
self._contract = the_nucypherKMS_token_contract
|
||||
return deployment_txhash
|
||||
|
||||
|
||||
|
@ -104,21 +104,21 @@ class MinerEscrowDeployer(ContractDeployer):
|
|||
message = '{} contract already deployed, use .get() to retrieve it.'.format(class_name)
|
||||
raise self.ContractDeploymentError(message)
|
||||
|
||||
deploy_args = [self._token_agent._contract.address] + self._config.mining_coefficient
|
||||
deploy_tx = {'from': self._token_agent._creator}
|
||||
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._chain.wait.for_receipt(deploy_txhash, timeout=self._blockchain._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,
|
||||
self._config.reward)
|
||||
reward_txhash = self._token_agent.transact({'from': self._token_agent.origin}).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_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)
|
||||
|
||||
return deploy_txhash, reward_txhash, init_txhash
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
|
||||
from nkms_eth.agents import NuCypherKMSTokenAgent, MinerAgent
|
||||
from nkms_eth.blockchain import TheBlockchain
|
||||
from nkms_eth.deployers import NuCypherKMSTokenDeployer
|
||||
from tests.utilities import TesterBlockchain, MockMinerEscrowDeployer
|
||||
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
import random
|
||||
|
||||
import pytest
|
||||
from populus.contracts.exceptions import NoKnownAddress
|
||||
from pytest import raises
|
||||
|
||||
from nkms_eth.agents import MinerAgent, NuCypherKMSTokenAgent
|
||||
from nkms_eth.actors import Miner
|
||||
|
||||
M = 10 ** 6
|
||||
|
||||
|
||||
def test_create_escrow(testerchain):
|
||||
with raises(NoKnownAddress):
|
||||
NuCypherKMSTokenAgent.get(blockchain=testerchain)
|
||||
|
||||
token = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||
token.arm()
|
||||
token.deploy()
|
||||
|
||||
same_token = NuCypherKMSTokenAgent.get(blockchain=testerchain)
|
||||
with raises(NuCypherKMSTokenAgent.ContractDeploymentError):
|
||||
same_token.arm()
|
||||
same_token.deploy()
|
||||
|
||||
assert len(token.__contract.address) == 42
|
||||
assert token.__contract.address == same_token._contract.address
|
||||
|
||||
with raises(NoKnownAddress):
|
||||
MinerAgent.get(token=token)
|
||||
|
||||
escrow = MinerAgent(token=token)
|
||||
escrow.arm()
|
||||
escrow.deploy()
|
||||
|
||||
same_escrow = MinerAgent.get(token=token)
|
||||
with raises(MinerAgent.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)
|
||||
creator, *addresses = testerchain._chain.web3.eth.accounts
|
||||
|
||||
# Create 9 Miners
|
||||
for address in addresses:
|
||||
miner = Miner(miner_agent=escrow, address=address)
|
||||
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,92 +0,0 @@
|
|||
import random
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from nkms_eth.agents import MinerAgent, NuCypherKMSTokenAgent
|
||||
from nkms_eth.actors import Miner
|
||||
|
||||
|
||||
M = 10 ** 6
|
||||
|
||||
|
||||
def test_deposit(testerchain, token, escrow):
|
||||
token._airdrop(amount=10000) # weeee
|
||||
|
||||
ursula_address = testerchain._chain.web3.eth.accounts[1]
|
||||
miner = Miner(miner_agent=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(miner_agent=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(miner_agent=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(entire_balance=True)
|
||||
final_balance = token.balance(ursula.address)
|
||||
|
||||
assert final_balance > initial_balance
|
||||
|
||||
|
||||
def test_publish_miner_id(testerchain, token, escrow):
|
||||
token._airdrop(amount=10000) # weeee
|
||||
|
||||
miner_addr = testerchain._chain.web3.eth.accounts[1]
|
||||
miner = Miner(miner_agent=escrow, 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)
|
||||
stored_miner_ids = miner.fetch_miner_ids()
|
||||
|
||||
assert len(stored_miner_ids) == 1
|
||||
assert mock_miner_id == stored_miner_ids[0]
|
||||
|
||||
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 when v4 of web3.py is released
|
||||
assert another_mock_miner_id == escrow().getMinerId(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(miner_agent=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(MinerAgent.NotEnoughUrsulas):
|
||||
escrow.sample(quantity=100) # Waay more than we have deployed
|
|
@ -25,9 +25,8 @@ def test_deploy_and_fetch_nucypherkms_token(testerchain):
|
|||
token_agent = NuCypherKMSTokenAgent(blockchain=testerchain)
|
||||
|
||||
# Make sure we got the name right
|
||||
deployer_contract_identifier = NuCypherKMSTokenDeployer.contract_name
|
||||
contract_identifier = NuCypherKMSTokenAgent._contract_name
|
||||
assert'NuCypherKMSToken' == contract_identifier == deployer_contract_identifier
|
||||
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
|
|
@ -1,7 +1,6 @@
|
|||
from os.path import join, dirname, abspath
|
||||
|
||||
import nkms_eth
|
||||
from nkms_eth.agents import NuCypherKMSTokenAgent
|
||||
from nkms_eth.deployers import NuCypherKMSTokenDeployer
|
||||
|
||||
|
||||
|
@ -26,5 +25,5 @@ def test_nucypher_populus_project(testerchain):
|
|||
assert testerchain._project.project_dir == populus_project_dir
|
||||
|
||||
# Ensure that solidity smart contacts are available, post-compile.
|
||||
token_contract_identifier = NuCypherKMSTokenDeployer(blockchain=testerchain).contract_name()
|
||||
token_contract_identifier = NuCypherKMSTokenDeployer(blockchain=testerchain)._contract_name
|
||||
assert token_contract_identifier in testerchain._project.compiled_contract_data
|
Loading…
Reference in New Issue