From 875814c6141bc084a1cb4dbbc264f8454793cfbd Mon Sep 17 00:00:00 2001 From: Kieran R Prasch Date: Sun, 4 Feb 2018 19:42:29 -0800 Subject: [PATCH] [KMS-ETH]- Fixtures for class based API, unit tests, and smart contract tests from @szotov, code reorg. --- install.sh | 2 +- nkms_eth/blockchain.py | 13 +- nkms_eth/miner.py | 18 +- scripts/ursula_flow.py | 10 +- tests/conftest.py | 25 +- tests/contracts/test_escrow_contract.py | 453 ++++++++++++++++++++++++ tests/contracts/test_miner_contract.py | 83 +++++ tests/contracts/test_policy_manager.py | 107 +++--- tests/contracts/test_token.py | 57 --- tests/contracts/test_token_contract.py | 85 +++++ tests/test_blockchain.py | 6 +- tests/test_escrow.py | 4 +- tests/test_miner.py | 65 ++++ tests/test_nucypher_kms_token.py | 2 +- tests/test_populus_project.py | 0 tests/test_ursula.py | 73 ---- 16 files changed, 791 insertions(+), 212 deletions(-) create mode 100644 tests/contracts/test_escrow_contract.py create mode 100644 tests/contracts/test_miner_contract.py delete mode 100644 tests/contracts/test_token.py create mode 100644 tests/contracts/test_token_contract.py create mode 100644 tests/test_miner.py create mode 100644 tests/test_populus_project.py delete mode 100644 tests/test_ursula.py diff --git a/install.sh b/install.sh index 08cf83df5..67beb85e4 100755 --- a/install.sh +++ b/install.sh @@ -7,7 +7,7 @@ sudo pip3 install pipenv pipenv --three VENV="$(pipenv --venv)" -# Sol +# Sol - TODO: integrity wget https://github.com/ethereum/solidity/releases/download/v0.4.19/solc-static-linux -O "${VENV}/bin/solc" chmod +x "${VENV}/bin/solc" diff --git a/nkms_eth/blockchain.py b/nkms_eth/blockchain.py index 3c9140fab..87a198ea6 100644 --- a/nkms_eth/blockchain.py +++ b/nkms_eth/blockchain.py @@ -6,6 +6,17 @@ from os.path import dirname, join, abspath class Blockchain: + """ + http://populus.readthedocs.io/en/latest/config.html#chains + + mainnet: Connects to the public ethereum mainnet via geth. + ropsten: Connects to the public ethereum ropsten testnet via geth. + tester: Uses an ephemeral in-memory chain backed by pyethereum. + testrpc: Uses an ephemeral in-memory chain backed by pyethereum. + temp: Local private chain whos data directory is removed when the chain is shutdown. Runs via geth. + + """ + network = 'mainnetrpc' project_name = 'nucypher-kms' _project = threading.local() @@ -21,7 +32,7 @@ class Blockchain: # Populus project config project_dir = join(dirname(abspath(nkms_eth.__file__)), 'project') project = populus.Project(project_dir) - project.config['chains.mainnetrpc']['contracts']['backends']['JSONFile']['settings']['file_path'] = self.registrar_path + project.config['chains.mainnetrpc.contracts.backends.JSONFile.settings.file_path'] = self.registrar_path self.project_dir = project_dir self._project.project = project diff --git a/nkms_eth/miner.py b/nkms_eth/miner.py index 0c1a99e8b..37d02d138 100644 --- a/nkms_eth/miner.py +++ b/nkms_eth/miner.py @@ -1,20 +1,16 @@ -from nkms_eth.token import NuCypherKMSToken -from nkms_eth.blockchain import Blockchain -from nkms_eth.escrow import Escrow - class Miner: """Practically carrying a pickaxe""" - def __init__(self, blockchain, escrow=None): + def __init__(self, blockchain, token, escrow): self.blockchain = blockchain - if not escrow: - escrow = Escrow.get(blockchain=blockchain) self.escrow = escrow + self.token = token def lock(self, amount: int, locktime: int, address: str=None): """ - Deposit and lock coins for mining. Creating coins starts after it is done + Deposit and lock coins for mining. + Creating coins starts after it is done. :param amount: Amount of coins to lock (in smallest indivisible units) :param locktime: Locktime in periods @@ -36,14 +32,16 @@ class Miner: def mine(self, address: str=None): with self.blockchain as chain: - address = address or chain.web3.eth.accounts[0] + if not address: + address = chain.web3.eth.accounts[0] tx = self.escrow.contract.transact({'from': address}).mint() chain.wait.for_receipt(tx, timeout=self.blockchain.timeout) def withdraw(self, address: str=None): with self.blockchain as chain: - address = address or chain.web3.eth.accounts[0] + if not address: + address = chain.web3.eth.accounts[0] tx = self.escrow.contract.transact({'from': address}).withdrawAll() chain.wait.for_receipt(tx, timeout=self.blockchain.timeout) diff --git a/scripts/ursula_flow.py b/scripts/ursula_flow.py index 7885210ca..18935d2e8 100755 --- a/scripts/ursula_flow.py +++ b/scripts/ursula_flow.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 -"""Deploy contracts in tester. +""" +Deploy contracts in tester. A simple Python script to deploy contracts and then estimate gas for different methods. """ import random -from nkms_eth.blockchain import project +from nkms_eth.blockchain import Blockchain + TIMEOUT = 10 MINING_COEFF = [10 ** 5, 10 ** 7] @@ -29,9 +31,7 @@ def main(): # Create an ERC20 token token, tx = chain.provider.get_or_deploy_contract( 'HumanStandardToken', deploy_args=[ - int(1e9) * M, int(1e10) * M, 'NuCypher KMS', 6, 'KMS'], - deploy_transaction={ - 'from': creator}) + int(1e9) * M, int(1e10) * M, 'NuCypher KMS', 6, 'KMS'],deploy_transaction={'from': creator}) chain.wait.for_receipt(tx, timeout=TIMEOUT) print("Deployed HumanStandardToken, tx hash is", tx) diff --git a/tests/conftest.py b/tests/conftest.py index 7b15b0a31..9ea65d426 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,25 @@ import pytest -from nkms_eth import blockchain +from nkms_eth.blockchain import TesterBlockchain +from nkms_eth.token import NuCypherKMSToken +from nkms_eth.escrow import Escrow +from nkms_eth.miner import Miner @pytest.fixture() -def project(): - return blockchain.project() +def testerchain(): + return TesterBlockchain() @pytest.fixture() -def chain(): - blockchain.DEFAULT_NETWORK = 'tester' - yield blockchain.chain() - blockchain.disconnect() +def token(testerchain): + return NuCypherKMSToken(blockchain=testerchain) + + +@pytest.fixture() +def escrow(testerchain, token): + return Escrow(blockchain=testerchain, token=token) + + +@pytest.fixture() +def miner(testerchain, escrow): + return Miner(blockchain=testerchain, escrow=escrow) diff --git a/tests/contracts/test_escrow_contract.py b/tests/contracts/test_escrow_contract.py new file mode 100644 index 000000000..0844802a6 --- /dev/null +++ b/tests/contracts/test_escrow_contract.py @@ -0,0 +1,453 @@ +import pytest +from ethereum.tester import TransactionFailed + + +@pytest.fixture() +def token(web3, chain): + creator = web3.eth.accounts[0] + # Create an ERC20 token + token, _ = chain.provider.get_or_deploy_contract( + 'NuCypherKMSToken', deploy_args=[10 ** 9, 2 * 10 ** 9], + deploy_transaction={'from': creator}) + return token + + +@pytest.fixture() +def escrow(web3, chain, token): + creator = web3.eth.accounts[0] + # Creator deploys the escrow + escrow, _ = chain.provider.get_or_deploy_contract( + 'Escrow', deploy_args=[token.address, 1, 4 * 2 * 10 ** 7, 4, 4, 2], + deploy_transaction={'from': creator}) + return escrow + + +# TODO extract method +def wait_time(chain, wait_hours): + web3 = chain.web3 + step = 50 + end_timestamp = web3.eth.getBlock(web3.eth.blockNumber).timestamp + wait_hours * 60 * 60 + while web3.eth.getBlock(web3.eth.blockNumber).timestamp < end_timestamp: + chain.wait.for_block(web3.eth.blockNumber + step) + + +def test_escrow(web3, chain, token, escrow): + creator = web3.eth.accounts[0] + ursula = web3.eth.accounts[1] + alice = web3.eth.accounts[2] + + # Give Ursula and Alice some coins + tx = token.transact({'from': creator}).transfer(ursula, 10000) + chain.wait.for_receipt(tx) + tx = token.transact({'from': creator}).transfer(alice, 10000) + chain.wait.for_receipt(tx) + assert 10000 == token.call().balanceOf(ursula) + assert 10000 == token.call().balanceOf(alice) + + # Ursula and Alice give Escrow rights to transfer + tx = token.transact({'from': ursula}).approve(escrow.address, 2500) + chain.wait.for_receipt(tx) + assert 2500 == token.call().allowance(ursula, escrow.address) + tx = token.transact({'from': alice}).approve(escrow.address, 1100) + chain.wait.for_receipt(tx) + assert 1100 == token.call().allowance(alice, escrow.address) + + # Ursula's withdrawal attempt won't succeed because nothing to withdraw + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).withdraw(100) + chain.wait.for_receipt(tx) + + # And can't lock because nothing to lock + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).lock(500, 2) + chain.wait.for_receipt(tx) + + # Check that nothing is locked + assert 0 == escrow.call().getLockedTokens(ursula) + assert 0 == escrow.call().getLockedTokens(alice) + + # Ursula can't lock too low value + # TODO uncomment after completing logic + # with pytest.raises(TransactionFailed): + # tx = escrow.transact({'from': ursula}).deposit(1000, 10) + # chain.wait.for_receipt(tx) + + # Ursula and Alice transfer some tokens to the escrow and lock them + tx = escrow.transact({'from': ursula}).deposit(1000, 1) + chain.wait.for_receipt(tx) + assert 1000 == token.call().balanceOf(escrow.address) + assert 9000 == token.call().balanceOf(ursula) + assert 1000 == escrow.call().getLockedTokens(ursula) + assert 1000 == escrow.call().calculateLockedTokens(ursula, 1) + assert 1000 == escrow.call().calculateLockedTokens(ursula, 2) + tx = escrow.transact({'from': ursula}).switchLock() + chain.wait.for_receipt(tx) + assert 500 == escrow.call().calculateLockedTokens(ursula, 2) + tx = escrow.transact({'from': ursula}).switchLock() + chain.wait.for_receipt(tx) + assert 1000 == escrow.call().calculateLockedTokens(ursula, 2) + tx = escrow.transact({'from': alice}).deposit(500, 2) + chain.wait.for_receipt(tx) + assert 1500 == token.call().balanceOf(escrow.address) + assert 9500 == token.call().balanceOf(alice) + assert 500 == escrow.call().getLockedTokens(alice) + assert 500 == escrow.call().calculateLockedTokens(alice, 1) + + # Checks locked tokens in next period + wait_time(chain, 1) + assert 1000 == escrow.call().getLockedTokens(ursula) + assert 500 == escrow.call().getLockedTokens(alice) + assert 1500 == escrow.call().getAllLockedTokens() + + # Ursula's withdrawal attempt won't succeed + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).withdraw(100) + chain.wait.for_receipt(tx) + assert 1500 == token.call().balanceOf(escrow.address) + assert 9000 == token.call().balanceOf(ursula) + + # Ursula can deposit more tokens + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': ursula}).deposit(500, 0) + chain.wait.for_receipt(tx) + assert 2000 == token.call().balanceOf(escrow.address) + assert 8500 == token.call().balanceOf(ursula) + + # Ursula starts unlocking + tx = escrow.transact({'from': ursula}).switchLock() + chain.wait.for_receipt(tx) + assert 750 == escrow.call().calculateLockedTokens(ursula, 2) + + # Wait 1 period and checks locking + wait_time(chain, 1) + assert 1500 == escrow.call().getLockedTokens(ursula) + + # Confirm activity and wait 1 period + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + wait_time(chain, 1) + assert 750 == escrow.call().getLockedTokens(ursula) + assert 0 == escrow.call().calculateLockedTokens(ursula, 1) + + # And Ursula can withdraw some tokens + tx = escrow.transact({'from': ursula}).withdraw(100) + chain.wait.for_receipt(tx) + assert 1900 == token.call().balanceOf(escrow.address) + assert 8600 == token.call().balanceOf(ursula) + + # But Ursula can't withdraw all without mining for locked value + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).withdrawAll() + chain.wait.for_receipt(tx) + + # Ursula can deposit and lock more tokens + tx = escrow.transact({'from': ursula}).deposit(500, 0) + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': ursula}).lock(100, 0) + chain.wait.for_receipt(tx) + + # Locked tokens will be updated in next period + # Release rate will be updated too because of end of previous locking + assert 750 == escrow.call().getLockedTokens(ursula) + assert 600 == escrow.call().calculateLockedTokens(ursula, 1) + assert 600 == escrow.call().calculateLockedTokens(ursula, 2) + tx = escrow.transact({'from': ursula}).switchLock() + chain.wait.for_receipt(tx) + assert 300 == escrow.call().calculateLockedTokens(ursula, 2) + assert 0 == escrow.call().calculateLockedTokens(ursula, 3) + wait_time(chain, 1) + assert 600 == escrow.call().getLockedTokens(ursula) + assert 300 == escrow.call().calculateLockedTokens(ursula, 1) + assert 0 == escrow.call().calculateLockedTokens(ursula, 2) + + # Ursula can increase lock + tx = escrow.transact({'from': ursula}).lock(500, 2) + chain.wait.for_receipt(tx) + assert 600 == escrow.call().getLockedTokens(ursula) + assert 800 == escrow.call().calculateLockedTokens(ursula, 1) + assert 500 == escrow.call().calculateLockedTokens(ursula, 2) + assert 200 == escrow.call().calculateLockedTokens(ursula, 3) + assert 0 == escrow.call().calculateLockedTokens(ursula, 4) + wait_time(chain, 1) + assert 800 == escrow.call().getLockedTokens(ursula) + + # Alice can't deposit too low value (less then rate) + # TODO uncomment after completing logic + # with pytest.raises(TransactionFailed): + # tx = escrow.transact({'from': ursula}).deposit(100, 100) + # chain.wait.for_receipt(tx) + + # Alice starts unlocking and increases lock by deposit more tokens + tx = escrow.transact({'from': alice}).deposit(500, 0) + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': alice}).switchLock() + chain.wait.for_receipt(tx) + assert 500 == escrow.call().getLockedTokens(alice) + assert 1000 == escrow.call().calculateLockedTokens(alice, 1) + assert 500 == escrow.call().calculateLockedTokens(alice, 2) + assert 0 == escrow.call().calculateLockedTokens(alice, 3) + wait_time(chain, 1) + assert 1000 == escrow.call().getLockedTokens(alice) + + # And increases locked time + tx = escrow.transact({'from': alice}).lock(0, 2) + chain.wait.for_receipt(tx) + assert 1000 == escrow.call().getLockedTokens(alice) + assert 500 == escrow.call().calculateLockedTokens(alice, 1) + assert 0 == escrow.call().calculateLockedTokens(alice, 2) + + # Alice increases lock by small amount of tokens + tx = escrow.transact({'from': alice}).deposit(100, 0) + chain.wait.for_receipt(tx) + assert 600 == escrow.call().calculateLockedTokens(alice, 1) + assert 100 == escrow.call().calculateLockedTokens(alice, 2) + assert 0 == escrow.call().calculateLockedTokens(alice, 3) + + # # Ursula can't destroy contract + # with pytest.raises(TransactionFailed): + # tx = escrow.transact({'from': ursula}).destroy() + # chain.wait.for_receipt(tx) + # + # # Destroy contract from creator and refund all to Ursula and Alice + # tx = escrow.transact({'from': creator}).destroy() + # chain.wait.for_receipt(tx) + # assert 0 == token.call().balanceOf(escrow.address) + # assert 10000 == token.call().balanceOf(ursula) + # assert 10000 == token.call().balanceOf(alice) + + +def test_locked_distribution(web3, chain, token, escrow): + NULL_ADDR = '0x' + '0' * 40 + creator = web3.eth.accounts[0] + miners = web3.eth.accounts[1:] + amount = token.call().balanceOf(creator) // 2 + largest_locked = amount + + # Airdrop + for miner in miners: + tx = token.transact({'from': creator}).transfer(miner, amount) + chain.wait.for_receipt(tx) + amount = amount // 2 + + # Lock + for index, miner in enumerate(miners[::-1]): + balance = token.call().balanceOf(miner) + tx = token.transact({'from': miner}).approve(escrow.address, balance) + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': miner}).deposit(balance, len(miners) - index + 1) + chain.wait.for_receipt(tx) + + # Check current period + address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, 1) + assert NULL_ADDR == address_stop.lower() + assert 0 == shift + + # Wait next period + wait_time(chain, 1) + n_locked = escrow.call().getAllLockedTokens() + assert n_locked > 0 + + # And confirm activity + for miner in miners: + tx = escrow.transact({'from': miner}).confirmActivity() + chain.wait.for_receipt(tx) + + address_stop, shift = escrow.call().findCumSum(NULL_ADDR, n_locked // 3, 1) + assert miners[0].lower() == address_stop.lower() + assert n_locked // 3 == shift + + address_stop, shift = escrow.call().findCumSum(NULL_ADDR, largest_locked, 1) + assert miners[1].lower() == address_stop.lower() + assert 0 == shift + + address_stop, shift = escrow.call().findCumSum( + miners[1], largest_locked // 2 + 1, 1) + assert miners[2].lower() == address_stop.lower() + assert 1 == shift + + address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, 10) + assert NULL_ADDR != address_stop.lower() + assert 0 != shift + address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, 11) + assert NULL_ADDR == address_stop.lower() + assert 0 == shift + + for index, _ in enumerate(miners[:-1]): + address_stop, shift = escrow.call().findCumSum(NULL_ADDR, 1, index + 3) + assert miners[index + 1].lower() == address_stop.lower() + assert 1 == shift + + +def test_mining(web3, chain, token, escrow): + creator = web3.eth.accounts[0] + ursula = web3.eth.accounts[1] + alice = web3.eth.accounts[2] + + policy_manager, _ = chain.provider.get_or_deploy_contract( + 'PolicyManagerTest', deploy_args=[token.address, escrow.address], + deploy_transaction={'from': creator}) + tx = escrow.transact({'from': creator}).setPolicyManager(policy_manager.address) + chain.wait.for_receipt(tx) + + # Give Ursula and Alice some coins + tx = token.transact({'from': creator}).transfer(ursula, 10000) + chain.wait.for_receipt(tx) + tx = token.transact({'from': creator}).transfer(alice, 10000) + chain.wait.for_receipt(tx) + + # Ursula can't confirm and mint because no locked tokens + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).mint() + chain.wait.for_receipt(tx) + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + + # Ursula and Alice give Escrow rights to transfer + tx = token.transact({'from': ursula}).approve(escrow.address, 2000) + chain.wait.for_receipt(tx) + tx = token.transact({'from': alice}).approve(escrow.address, 500) + chain.wait.for_receipt(tx) + + # Ursula and Alice transfer some tokens to the escrow and lock them + tx = escrow.transact({'from': ursula}).deposit(1000, 1) + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': alice}).deposit(500, 2) + chain.wait.for_receipt(tx) + + # Using locked tokens starts from next period + assert 0 == escrow.call().getAllLockedTokens() + + # Give rights for mining + tx = token.transact({'from': creator}).addMiner(escrow.address) + chain.wait.for_receipt(tx) + assert token.call().isMiner(escrow.address) + + # Ursula can't use method from Miner contract + with pytest.raises(TypeError): + tx = escrow.transact({'from': ursula}).mint(ursula, 1, 1, 1, 1, 1) + chain.wait.for_receipt(tx) + + # Only Ursula confirm next period + wait_time(chain, 1) + assert 1500 == escrow.call().getAllLockedTokens() + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + + # Checks that no error + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + + # Ursula and Alice mint tokens for last periods + wait_time(chain, 1) + assert 1000 == escrow.call().getAllLockedTokens() + tx = escrow.transact({'from': ursula}).mint() + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': alice}).mint() + chain.wait.for_receipt(tx) + assert 9050 == token.call().balanceOf(ursula) + assert 9521 == token.call().balanceOf(alice) + + assert 1 == policy_manager.call().getPeriodsLength(ursula) + assert 1 == policy_manager.call().getPeriodsLength(alice) + period = escrow.call().getCurrentPeriod() - 1 + assert period == policy_manager.call().getPeriod(ursula, 0) + assert period == policy_manager.call().getPeriod(alice, 0) + + # Only Ursula confirm activity for next period + tx = escrow.transact({'from': ursula}).switchLock() + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + + # Ursula can't confirm next period because end of locking + wait_time(chain, 1) + assert 500 == escrow.call().getAllLockedTokens() + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + + # But Alice can + tx = escrow.transact({'from': alice}).confirmActivity() + chain.wait.for_receipt(tx) + + # Ursula mint tokens for next period + wait_time(chain, 1) + assert 500 == escrow.call().getAllLockedTokens() + tx = escrow.transact({'from': ursula}).mint() + chain.wait.for_receipt(tx) + # But Alice can't mining because she did not confirmed activity + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': alice}).mint() + chain.wait.for_receipt(tx) + assert 9163 == token.call().balanceOf(ursula) + assert 9521 == token.call().balanceOf(alice) + + assert 3 == policy_manager.call().getPeriodsLength(ursula) + assert 1 == policy_manager.call().getPeriodsLength(alice) + assert period + 1 == policy_manager.call().getPeriod(ursula, 1) + assert period + 2 == policy_manager.call().getPeriod(ursula, 2) + + # Alice confirm next period and mint tokens + tx = escrow.transact({'from': alice}).switchLock() + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': alice}).confirmActivity() + chain.wait.for_receipt(tx) + wait_time(chain, 2) + assert 0 == escrow.call().getAllLockedTokens() + tx = escrow.transact({'from': alice}).mint() + chain.wait.for_receipt(tx) + assert 9163 == token.call().balanceOf(ursula) + assert 9634 == token.call().balanceOf(alice) + + assert 3 == policy_manager.call().getPeriodsLength(ursula) + assert 3 == policy_manager.call().getPeriodsLength(alice) + assert period + 3 == policy_manager.call().getPeriod(alice, 1) + assert period + 4 == policy_manager.call().getPeriod(alice, 2) + + # Ursula can't confirm and mint because no locked tokens + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).mint() + chain.wait.for_receipt(tx) + with pytest.raises(TransactionFailed): + tx = escrow.transact({'from': ursula}).confirmActivity() + chain.wait.for_receipt(tx) + + # Ursula can lock some tokens again + tx = escrow.transact({'from': ursula}).lock(500, 4) + chain.wait.for_receipt(tx) + tx = escrow.transact({'from': ursula}).switchLock() + chain.wait.for_receipt(tx) + assert 500 == escrow.call().getLockedTokens(ursula) + assert 500 == escrow.call().calculateLockedTokens(ursula, 1) + assert 375 == escrow.call().calculateLockedTokens(ursula, 2) + assert 250 == escrow.call().calculateLockedTokens(ursula, 3) + assert 0 == escrow.call().calculateLockedTokens(ursula, 5) + # And can increase lock + tx = escrow.transact({'from': ursula}).lock(100, 0) + chain.wait.for_receipt(tx) + assert 600 == escrow.call().getLockedTokens(ursula) + assert 600 == escrow.call().calculateLockedTokens(ursula, 1) + assert 450 == escrow.call().calculateLockedTokens(ursula, 2) + assert 0 == escrow.call().calculateLockedTokens(ursula, 5) + tx = escrow.transact({'from': ursula}).lock(0, 2) + chain.wait.for_receipt(tx) + assert 600 == escrow.call().getLockedTokens(ursula) + assert 600 == escrow.call().calculateLockedTokens(ursula, 1) + assert 450 == escrow.call().calculateLockedTokens(ursula, 2) + assert 0 == escrow.call().calculateLockedTokens(ursula, 5) + tx = escrow.transact({'from': ursula}).deposit(800, 1) + chain.wait.for_receipt(tx) + assert 1400 == escrow.call().getLockedTokens(ursula) + assert 1400 == escrow.call().calculateLockedTokens(ursula, 1) + assert 1000 == escrow.call().calculateLockedTokens(ursula, 3) + assert 400 == escrow.call().calculateLockedTokens(ursula, 6) + assert 0 == escrow.call().calculateLockedTokens(ursula, 8) + + # Alice can withdraw all + tx = escrow.transact({'from': alice}).withdrawAll() + chain.wait.for_receipt(tx) + assert 10134 == token.call().balanceOf(alice) + + # TODO test max confirmed periods and miners diff --git a/tests/contracts/test_miner_contract.py b/tests/contracts/test_miner_contract.py new file mode 100644 index 000000000..4542a1c19 --- /dev/null +++ b/tests/contracts/test_miner_contract.py @@ -0,0 +1,83 @@ +import pytest +from ethereum.tester import TransactionFailed + + +@pytest.fixture() +def token(web3, chain): + creator = web3.eth.accounts[0] + # Create an ERC20 token + token, _ = chain.provider.get_or_deploy_contract( + 'NuCypherKMSToken', deploy_args=[10 ** 30, 2 * 10 ** 40], + deploy_transaction={'from': creator}) + return token + + +def test_miner(web3, chain, token): + creator = web3.eth.accounts[0] + ursula = web3.eth.accounts[1] + + # Creator deploys the miner + miner, _ = chain.provider.get_or_deploy_contract( + 'MinerTest', deploy_args=[token.address, 1, 10 ** 46, 10 ** 7, 10 ** 7], + deploy_transaction={'from': creator}) + + # Give rights for mining + tx = token.transact({'from': creator}).addMiner(miner.address) + chain.wait.for_receipt(tx) + + # Mint some tokens + tx = miner.transact().testMint(ursula, 0, 1000, 2000, 0, 0) + chain.wait.for_receipt(tx) + assert 10 == token.call().balanceOf(ursula) + assert 10 ** 30 + 10 == token.call().totalSupply() + + # Mint more tokens + tx = miner.transact().testMint(ursula, 0, 500, 500, 0, 0) + chain.wait.for_receipt(tx) + assert 30 == token.call().balanceOf(ursula) + assert 10 ** 30 + 30 == token.call().totalSupply() + + tx = miner.transact().testMint(ursula, 0, 500, 500, 10 ** 7, 0) + chain.wait.for_receipt(tx) + assert 70 == token.call().balanceOf(ursula) + assert 10 ** 30 + 70 == token.call().totalSupply() + + tx = miner.transact().testMint(ursula, 0, 500, 500, 2 * 10 ** 7, 0) + chain.wait.for_receipt(tx) + assert 110 == token.call().balanceOf(ursula) + assert 10 ** 30 + 110 == token.call().totalSupply() + + +def test_inflation_rate(web3, chain, token): + creator = web3.eth.accounts[0] + ursula = web3.eth.accounts[1] + + # Creator deploys the miner + miner, _ = chain.provider.get_or_deploy_contract( + 'MinerTest', deploy_args=[token.address, 1, 2 * 10 ** 19, 1, 1], + deploy_transaction={'from': creator}) + + # Give rights for mining + tx = token.transact({'from': creator}).addMiner(miner.address) + chain.wait.for_receipt(tx) + + # Mint some tokens + tx = miner.transact().testMint(ursula, 1, 1, 1, 0, 0) + chain.wait.for_receipt(tx) + one_period = token.call().balanceOf(ursula) + + # Mint more tokens in the same period + tx = miner.transact().testMint(ursula, 1, 1, 1, 0, 0) + chain.wait.for_receipt(tx) + assert 2 * one_period == token.call().balanceOf(ursula) + + # Mint tokens in the next period + tx = miner.transact().testMint(ursula, 2, 1, 1, 0, 0) + chain.wait.for_receipt(tx) + assert 3 * one_period > token.call().balanceOf(ursula) + minted_amount = token.call().balanceOf(ursula) - 2 * one_period + + # Mint tokens in the next period + tx = miner.transact().testMint(ursula, 3, 1, 1, 0, 0) + chain.wait.for_receipt(tx) + assert 2 * one_period + 2 * minted_amount > token.call().balanceOf(ursula) diff --git a/tests/contracts/test_policy_manager.py b/tests/contracts/test_policy_manager.py index 1fc99710f..4d2012102 100644 --- a/tests/contracts/test_policy_manager.py +++ b/tests/contracts/test_policy_manager.py @@ -3,13 +3,14 @@ from ethereum.tester import TransactionFailed @pytest.fixture() -def token(web3, chain): - creator = web3.eth.accounts[0] - # Create an ERC20 token - token, _ = chain.provider.get_or_deploy_contract( - 'NuCypherKMSToken', deploy_args=[2 * 10 ** 9], - deploy_transaction={'from': creator}) - return token +def token(web3, testerchain): + with testerchain as chain: + creator = web3.eth.accounts[0] + # Create an ERC20 token + token, _ = chain.provider.get_or_deploy_contract( + 'NuCypherKMSToken', deploy_args=[10 ** 9, 2 * 10 ** 9], + deploy_transaction={'from': creator}) + return token @pytest.fixture() @@ -18,7 +19,7 @@ def escrow(web3, chain): node = web3.eth.accounts[1] # Creator deploys the escrow escrow, _ = chain.provider.get_or_deploy_contract( - 'MinersEscrowForPolicyTest', deploy_args=[node, MINUTES_IN_PERIOD], + 'EscrowTest', deploy_args=[node, MINUTES_IN_PERIOD], deploy_transaction={'from': creator}) return escrow @@ -61,58 +62,60 @@ rate = 20 number_of_periods = 10 -def test_create_revoke(web3, chain, token, escrow, policy_manager): - creator = web3.eth.accounts[0] - node = web3.eth.accounts[1] - client = web3.eth.accounts[2] - bad_node = web3.eth.accounts[3] +def test_create_revoke(web3, testerchain, token, escrow, policy_manager): + with testerchain as chain: - # Try create policy for bad node - with pytest.raises(TransactionFailed): - tx = policy_manager.transact({'from': client}).createPolicy(policy_id, bad_node, 1, 1) - chain.wait.for_receipt(tx) + creator = web3.eth.accounts[0] + node = web3.eth.accounts[1] + client = web3.eth.accounts[2] + bad_node = web3.eth.accounts[3] - # Create policy - period = escrow.call().getCurrentPeriod() - tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods) - chain.wait.for_receipt(tx) - policy = policy_manager.call().policies(policy_id) - assert 200 == token.call().balanceOf(policy_manager.address) - assert 9800 == token.call().balanceOf(client) - assert client == policy[0] - assert node == policy[1] - assert rate == policy[2] - assert period + 1 == policy[3] - assert period + 10 == policy[4] + # Try create policy for bad node + with pytest.raises(TransactionFailed): + tx = policy_manager.transact({'from': client}).createPolicy(policy_id, bad_node, 1, 1) + chain.wait.for_receipt(tx) - # Try to create policy again - with pytest.raises(TransactionFailed): + # Create policy + period = escrow.call().getCurrentPeriod() tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods) chain.wait.for_receipt(tx) + policy = policy_manager.call().policies(policy_id) + assert 200 == token.call().balanceOf(policy_manager.address) + assert 9800 == token.call().balanceOf(client) + assert client == policy[0] + assert node == policy[1] + assert rate == policy[2] + assert period + 1 == policy[3] + assert period + 10 == policy[4] - # Not client try to revoke policy - with pytest.raises(TransactionFailed): - tx = policy_manager.transact({'from': creator}).revokePolicy(policy_id) + # Try to create policy again + with pytest.raises(TransactionFailed): + tx = policy_manager.transact({'from': client}).createPolicy(policy_id, node, rate, number_of_periods) + chain.wait.for_receipt(tx) + + # Not client try to revoke policy + with pytest.raises(TransactionFailed): + tx = policy_manager.transact({'from': creator}).revokePolicy(policy_id) + chain.wait.for_receipt(tx) + + # Client try to revoke policy + tx = policy_manager.transact({'from': client}).revokePolicy(policy_id) chain.wait.for_receipt(tx) + policy = policy_manager.call().policies(policy_id) + assert '0x' + '0' * 40 == policy[0] - # Client try to revoke policy - tx = policy_manager.transact({'from': client}).revokePolicy(policy_id) - chain.wait.for_receipt(tx) - policy = policy_manager.call().policies(policy_id) - assert '0x' + '0' * 40 == policy[0] - - # Create another policy - period = escrow.call().getCurrentPeriod() - tx = policy_manager.transact({'from': client}).createPolicy(policy_id_2, node, rate, number_of_periods) - chain.wait.for_receipt(tx) - policy = policy_manager.call().policies(policy_id_2) - assert 200 == token.call().balanceOf(policy_manager.address) - assert 9800 == token.call().balanceOf(client) - assert client == policy[0] - assert node == policy[1] - assert rate == policy[2] - assert period + 1 == policy[3] - assert period + 10 == policy[4] + # Create another policy + period = escrow.call().getCurrentPeriod() + tx = policy_manager.transact({'from': client}).createPolicy(policy_id_2, node, rate, number_of_periods) + chain.wait.for_receipt(tx) + policy = policy_manager.call().policies(policy_id_2) + assert 200 == token.call().balanceOf(policy_manager.address) + assert 9800 == token.call().balanceOf(client) + assert client == policy[0] + assert node == policy[1] + assert rate == policy[2] + assert period + 1 == policy[3] + assert period + 10 == policy[4] def test_reward(web3, chain, token, escrow, policy_manager): diff --git a/tests/contracts/test_token.py b/tests/contracts/test_token.py deleted file mode 100644 index 5f8ee0632..000000000 --- a/tests/contracts/test_token.py +++ /dev/null @@ -1,57 +0,0 @@ -import pytest -from ethereum.tester import TransactionFailed - - -def test_create_token(web3, chain): - """ - These are tests for standard tokens taken from Consensys github: - https://github.com/ConsenSys/Tokens/ - but some of the tests are converted from javascript to python - """ - - creator = web3.eth.accounts[1] - account1 = web3.eth.accounts[0] - account2 = web3.eth.accounts[2] - - # Create an ERC20 token - token, txhash = chain.provider.get_or_deploy_contract( - 'NuCypherKMSToken', deploy_args=[10 ** 9], - deploy_transaction={ - 'from': creator}) - assert txhash is not None - - # Account balances - assert token.call().balanceOf(creator) == 10 ** 9 - assert token.call().balanceOf(account1) == 0 - - # Basic properties - assert token.call().name() == 'NuCypher KMS' - assert token.call().decimals() == 18 - assert token.call().symbol() == 'KMS' - - # Cannot send ethers to the contract - with pytest.raises(TransactionFailed): - tx = web3.eth.sendTransaction({ - 'from': account1, 'to': token.address, 'value': 10 ** 9}) - chain.wait.for_receipt(tx) - - # Can transfer tokens - tx = token.transact({'from': creator}).transfer(account1, 10000) - chain.wait.for_receipt(tx) - assert token.call().balanceOf(account1) == 10000 - assert token.call().balanceOf(creator) == 10 ** 9 - 10000 - - tx = token.transact({'from': account1}).transfer(account2, 10) - chain.wait.for_receipt(tx) - assert token.call().balanceOf(account1) == 10000 - 10 - assert token.call().balanceOf(account2) == 10 - - tx = token.transact({'from': account1}).transfer(token.address, 10) - chain.wait.for_receipt(tx) - assert token.call().balanceOf(token.address) == 10 - - # Can burn own tokens - tx = token.transact({'from': account2}).burn(1) - chain.wait.for_receipt(tx) - assert token.call().balanceOf(account2) == 9 - assert token.call().totalSupply() == 10 ** 9 - 1 diff --git a/tests/contracts/test_token_contract.py b/tests/contracts/test_token_contract.py new file mode 100644 index 000000000..c5b29b222 --- /dev/null +++ b/tests/contracts/test_token_contract.py @@ -0,0 +1,85 @@ +import pytest +from ethereum.tester import TransactionFailed + + +def test_create_token(testerchain): + """ + These are tests for standard tokens taken from Consensys github: + https://github.com/ConsenSys/Tokens/ + but some of the tests are converted from javascript to python + """ + with testerchain as chain: + creator = chain.web3.eth.accounts[1] + account1 = chain.web3.eth.accounts[0] + account2 = chain.web3.eth.accounts[2] + + # Create an ERC20 token + token, txhash = chain.provider.get_or_deploy_contract( + 'NuCypherKMSToken', deploy_args=[10 ** 9, 10 ** 10], + deploy_transaction={'from': creator}) + assert txhash is not None + + # Account balances + assert token.call().balanceOf(creator) == 10 ** 9 + assert token.call().balanceOf(account1) == 0 + + # Basic properties + assert token.call().name() == 'NuCypher KMS' + assert token.call().decimals() == 18 + assert token.call().symbol() == 'KMS' + + # Cannot send ethers to the contract + with pytest.raises(TransactionFailed): + tx = chain.web3.eth.sendTransaction({ + 'from': account1, 'to': token.address, 'value': 10 ** 9}) + chain.wait.for_receipt(tx) + + # Can transfer tokens + tx = token.transact({'from': creator}).transfer(account1, 10000) + chain.wait.for_receipt(tx) + assert token.call().balanceOf(account1) == 10000 + assert token.call().balanceOf(creator) == 10 ** 9 - 10000 + + tx = token.transact({'from': account1}).transfer(account2, 10) + chain.wait.for_receipt(tx) + assert token.call().balanceOf(account1) == 10000 - 10 + assert token.call().balanceOf(account2) == 10 + + tx = token.transact({'from': account1}).transfer(token.address, 10) + chain.wait.for_receipt(tx) + assert token.call().balanceOf(token.address) == 10 + + # Can't mint tokens without rights + with pytest.raises(TransactionFailed): + tx = token.transact({'from': account1}).mint(account2, 10000) + chain.wait.for_receipt(tx) + + # Can't change rights not from owner + with pytest.raises(TransactionFailed): + tx = token.transact({'from': account1}).addMiner(account1) + chain.wait.for_receipt(tx) + with pytest.raises(TransactionFailed): + tx = token.transact({'from': account1}).removeMiner(account1) + chain.wait.for_receipt(tx) + + # Give rights for mining + tx = token.transact({'from': creator}).addMiner(account1) + chain.wait.for_receipt(tx) + assert token.call().isMiner(account1) + + # And try again + tx = token.transact({'from': account1}).mint(account2, 10000) + chain.wait.for_receipt(tx) + assert token.call().balanceOf(account2) == 10010 + assert token.call().totalSupply() == 10 ** 9 + 10000 + + # Remove rights for mining + tx = token.transact({'from': creator}).removeMiner(account1) + chain.wait.for_receipt(tx) + assert not token.call().isMiner(account1) + + # Can burn own tokens + tx = token.transact({'from': account2}).burn(10000) + chain.wait.for_receipt(tx) + assert token.call().balanceOf(account2) == 10 + assert token.call().totalSupply() == 10 ** 9 diff --git a/tests/test_blockchain.py b/tests/test_blockchain.py index 742ca7553..50a8d9fa6 100644 --- a/tests/test_blockchain.py +++ b/tests/test_blockchain.py @@ -1,4 +1,4 @@ -def test_chain_network(testerchain): - with testerchain as blockchain: - assert blockchain.web3.eth.blockNumber >= 0 +def test_testerchain_create(testerchain): + with testerchain as chain: + assert chain.web3.eth.blockNumber >= 0 diff --git a/tests/test_escrow.py b/tests/test_escrow.py index 501da2d82..4549da09a 100644 --- a/tests/test_escrow.py +++ b/tests/test_escrow.py @@ -5,8 +5,8 @@ from nkms_eth.escrow import Escrow from nkms_eth.token import NuCypherKMSToken -def test_create_escrow(testerchain): - token = NuCypherKMSToken(blockchain=testerchain) +def test_create_escrow(testerchain, token): + # token = NuCypherKMSToken(blockchain=testerchain) with raises(NoKnownAddress): Escrow.get(blockchain=testerchain, token=token) diff --git a/tests/test_miner.py b/tests/test_miner.py new file mode 100644 index 000000000..78186f0ef --- /dev/null +++ b/tests/test_miner.py @@ -0,0 +1,65 @@ +import random +import pytest + + +M = 10 ** 6 + + +def airdrop(blockchain, token) -> None: + """ + Airdrops from accounts[0] to others + """ + web3 = blockchain.web3 + # token = Token.get(blockchain=blockchain) + + def txs(): + for account in web3.eth.accounts[1:]: + tx = token.contract.transact({'from': web3.eth.accounts[0]}).transfer(account, 10000*M) + yield tx + + for tx in txs(): + blockchain.chain.wait.for_receipt(tx, timeout=10) + + +def test_deposit(testerchain, miner, token): + airdrop(testerchain, token) + ursula.lock(amount=1000*M, + locktime=100, + address=testerchain.web3.eth.accounts[1]) + + +def test_select_ursulas(testerchain, miner, escrow, token): + airdrop(testerchain, token) + + # Create a random set of miners (we have 9 in total) + for u in testerchain.web3.eth.accounts[1:]: + miner.lock((10 + random.randrange(9000)) * M, 100, u) + testerchain.wait.for_block(testerchain.web3.eth.blockNumber + escrow.BLOCKS_PER_PERIOD) + + miners = escrow.sample(3) + assert len(miners) == 3 + assert len(set(miners)) == 3 + + with pytest.raises(Exception): + escrow.sample(100) # Waay more than we have deployed + + +def test_mine_withdraw(testerchain, miner, token, escrow): + airdrop(testerchain, token) + + addr = testerchain.web3.eth.accounts[1] + initial_balance = token.balance(addr) + + # Create a random set of miners (we have 9 in total) + for u in testerchain.web3.eth.accounts[1:]: + miner.lock(amount=(10 + random.randrange(9000))*M, + locktime=1, + address=u) + + testerchain.chain.wait.for_block(testerchain.web3.eth.blockNumber + 2 * escrow.BLOCKS_PER_PERIOD) + + miner.mine(addr) + miner.withdraw(addr) + final_balance = token.balance(addr) + + assert final_balance > initial_balance \ No newline at end of file diff --git a/tests/test_nucypher_kms_token.py b/tests/test_nucypher_kms_token.py index 2536db78b..6168caa97 100644 --- a/tests/test_nucypher_kms_token.py +++ b/tests/test_nucypher_kms_token.py @@ -3,7 +3,7 @@ from populus.contracts.exceptions import NoKnownAddress from nkms_eth.token import NuCypherKMSToken -def test_get(testerchain): +def test_get_then_create_miner(testerchain): with raises(NoKnownAddress): NuCypherKMSToken.get(blockchain=testerchain) diff --git a/tests/test_populus_project.py b/tests/test_populus_project.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_ursula.py b/tests/test_ursula.py deleted file mode 100644 index 791669187..000000000 --- a/tests/test_ursula.py +++ /dev/null @@ -1,73 +0,0 @@ -from nkms_eth import token -from nkms_eth import escrow -from nkms_eth import ursula -import random -import pytest - -M = 10 ** 6 - - -def airdrop(chain): - """ - Airdrops from accounts[0] to others - """ - web3 = chain.web3 - tok = token.get() - txs = [ - tok.transact({'from': web3.eth.accounts[0]}).transfer(account, 10000 * M) - for account in web3.eth.accounts[1:]] - for tx in txs: - chain.wait.for_receipt(tx, timeout=10) - - -def wait_time(chain, wait_hours): - web3 = chain.web3 - step = 50 - end_timestamp = web3.eth.getBlock(web3.eth.blockNumber).timestamp + wait_hours * 60 * 60 - while web3.eth.getBlock(web3.eth.blockNumber).timestamp < end_timestamp: - chain.wait.for_block(web3.eth.blockNumber + step) - - -def test_deposit(chain): - token.create() - escrow.create() - airdrop(chain) - ursula.lock(1000 * M, 100, chain.web3.eth.accounts[1]) - - -def test_select_ursulas(chain): - token.create() - escrow.create() - airdrop(chain) - - # Create a random set of miners (we have 9 in total) - for u in chain.web3.eth.accounts[1:]: - ursula.lock((10 + random.randrange(9000)) * M, 100, u) - wait_time(chain, escrow.HOURS_PER_PERIOD) - - miners = escrow.sample(3) - assert len(miners) == 3 - assert len(set(miners)) == 3 - - with pytest.raises(Exception): - escrow.sample(100) # Waay more than we have deployed - - -def test_mine_withdraw(chain): - token.create() - escrow.create() - airdrop(chain) - - addr = chain.web3.eth.accounts[1] - initial_balance = token.balance(addr) - - # Create a random set of miners (we have 9 in total) - for u in chain.web3.eth.accounts[1:]: - ursula.lock((10 + random.randrange(9000)) * M, 1, u) - - wait_time(chain, 2 * escrow.HOURS_PER_PERIOD) - ursula.mine(addr) - ursula.withdraw(addr) - final_balance = token.balance(addr) - - assert final_balance > initial_balance