From d09aafbe072eb68ffc1c71e0b3e273d8133d9173 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2020 06:40:44 +0000 Subject: [PATCH 1/5] Bump npm-registry-fetch from 4.0.4 to 4.0.5 Bumps [npm-registry-fetch](https://github.com/npm/registry-fetch) from 4.0.4 to 4.0.5. - [Release notes](https://github.com/npm/registry-fetch/releases) - [Changelog](https://github.com/npm/npm-registry-fetch/blob/latest/CHANGELOG.md) - [Commits](https://github.com/npm/registry-fetch/commits) Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index bff60159..6c4ad0bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5208,9 +5208,9 @@ } }, "npm-registry-fetch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.4.tgz", - "integrity": "sha512-6jb34hX/iYNQebqWUHtU8YF6Cjb1H6ouTFPClYsyiW6lpFkljTpdeftm53rRojtja1rKAvKNIIiTS5Sjpw4wsA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-4.0.5.tgz", + "integrity": "sha512-yQ0/U4fYpCCqmueB2g8sc+89ckQ3eXpmU4+Yi2j5o/r0WkKvE2+Y0tK3DEILAtn2UaQTkjTHxIXe2/CSdit+/Q==", "dev": true, "requires": { "JSONStream": "^1.3.4", From f5ca65bde5095a87b63763961e82db3f9344e4e3 Mon Sep 17 00:00:00 2001 From: uivlis Date: Thu, 9 Jul 2020 12:00:10 +0300 Subject: [PATCH 2/5] First batch of doc-to-unit tests --- hmt_escrow/eth_bridge.py | 128 +++++++---------- hmt_escrow/job.py | 301 ++++++++++++++++----------------------- hmt_escrow/storage.py | 1 + 3 files changed, 175 insertions(+), 255 deletions(-) diff --git a/hmt_escrow/eth_bridge.py b/hmt_escrow/eth_bridge.py index 4d18527a..f3dd2ac3 100644 --- a/hmt_escrow/eth_bridge.py +++ b/hmt_escrow/eth_bridge.py @@ -1,6 +1,7 @@ import logging import os import time +import unittest from solcx import compile_files from web3 import Web3, HTTPProvider, EthereumTesterProvider @@ -63,29 +64,6 @@ def handle_transaction(txn_func, *args, **kwargs) -> AttributeDict: signing, building, sending the transaction and returning a transaction receipt. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - >>> job.launch(rep_oracle_pub_key) - True - - >>> gas = 4712388 - >>> hmt_amount = int(job.amount * 10**18) - >>> hmtoken_contract = get_hmtoken() - >>> txn_func = hmtoken_contract.functions.transfer - >>> func_args = [job.job_contract.address, hmt_amount] - >>> txn_info = { - ... "gas_payer": job.gas_payer, - ... "gas_payer_priv": job.gas_payer_priv, - ... "gas": gas - ... } - >>> txn_receipt = handle_transaction(txn_func, *func_args, **txn_info) - >>> type(txn_receipt) - - Args: txn_func: the transaction function to be handled. @@ -158,20 +136,6 @@ def get_hmtoken(hmtoken_addr=HMTOKEN_ADDR) -> Contract: def get_escrow(escrow_addr: str) -> Contract: """Retrieve the Escrow contract from a given address. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - - Deploying a new Job to the ethereum network succeeds. - - >>> job.launch(rep_oracle_pub_key) - True - >>> type(get_escrow(job.job_contract.address)) - - Args: escrow_addr (str): an ethereum address of the escrow contract. @@ -191,14 +155,6 @@ def get_escrow(escrow_addr: str) -> Contract: def get_factory(factory_addr: Optional[str]) -> Contract: """Retrieve the EscrowFactory contract from a given address. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - >>> type(get_factory(job.factory_contract.address)) - - Args: factory_addr (str): the ethereum address of the Escrow contract. @@ -260,25 +216,6 @@ def get_pub_key_from_addr(wallet_addr: str) -> bytes: Returns: bytes: the public key in bytes form - >>> import os - >>> from web3 import Web3 - >>> get_pub_key_from_addr('badaddress') - Traceback (most recent call last): - File "/usr/lib/python3.6/doctest.py", line 1330, in __run - compileflags, 1), test.globs) - File "", line 1, in - get_pub_key_from_addr('blah') - File "hmt_escrow/eth_bridge.py", line 268, in get_pub_key_from_addr - raise ValueError('environment variable GAS_PAYER required') - ValueError: environment variable GAS_PAYER required - >>> os.environ['GAS_PAYER'] = "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92" - >>> os.environ['GAS_PAYER_PRIV'] = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> set_pub_key_at_addr(pub_key) #doctest: +ELLIPSIS - AttributeDict({'transactionHash': ...}) - >>> get_pub_key_from_addr(os.environ['GAS_PAYER']) - b'2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d' - """ # TODO: Should we try to get the checksum address here instead of assuming user will do that? GAS_PAYER = os.getenv("GAS_PAYER") @@ -310,15 +247,6 @@ def set_pub_key_at_addr(pub_key: str) -> Dict[str, Any]: Returns: AttributeDict: receipt of the set transaction on the blockchain - - >>> from web3 import Web3 - >>> import os - >>> os.environ['GAS_PAYER'] = "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92" - >>> os.environ['GAS_PAYER_PRIV'] = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - >>> pub_key_to_set = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> set_pub_key_at_addr(pub_key_to_set) #doctest: +ELLIPSIS - AttributeDict({'transactionHash': ...}) - """ GAS_PAYER = os.getenv("GAS_PAYER") GAS_PAYER_PRIV = os.getenv("GAS_PAYER_PRIV") @@ -340,9 +268,63 @@ def set_pub_key_at_addr(pub_key: str) -> Dict[str, Any]: return handle_transaction(txn_func, *func_args, **txn_info) +class EthBridgeTestCase(unittest.TestCase): + + def setUp(self): + from job import Job + + self.credentials = { + 'gas_payer': '0x1413862C2B7054CDbfdc181B83962CB0FC11fD92', + 'gas_payer_priv': '28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5' + } + self.rep_oracle_pub_key = b'2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d' + self.job = Job(credentials=self.credentials, escrow_manifest=manifest) + + + def test_handle_transaction(self): + from web3.datastructures import AttributeDict as Web3AttributeDict + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + gas = 4712388 + hmt_amount = int(self.job.amount * 10**18) + hmtoken_contract = get_hmtoken() + txn_func = hmtoken_contract.functions.transfer + func_args = [self.job.job_contract.address, hmt_amount] + txn_info = { + "gas_payer": self.job.gas_payer, + "gas_payer_priv": self.job.gas_payer_priv, + "gas": gas + } + txn_receipt = handle_transaction(txn_func, *func_args, **txn_info) + self.assertIs(type(txn_receipt), Web3AttributeDict) + + def test_get_escrow(self): + self.job.launch(self.rep_oracle_pub_key) + self.assertIsNotNone(get_escrow(self.job.job_contract.address)) + + def test_get_factory(self): + self.assertIsNotNone(get_factory(self.job.factory_contract.address)) + + def test_get_pub_key_from_address(self): + with self.assertRaises(ValueError): + get_pub_key_from_addr('badaddress') + os.environ['GAS_PAYER'] = self.credentials['gas_payer'] + os.environ['GAS_PAYER_PRIV'] = self.credentials['gas_payer_priv'] + set_pub_key_at_addr(self.rep_oracle_pub_key) + self.assertEqual( + get_pub_key_from_addr(os.environ['GAS_PAYER']), + self.rep_oracle_pub_key + ) + + def test_set_pub_key_at_address(self): + os.environ['GAS_PAYER'] = self.credentials['gas_payer'] + os.environ['GAS_PAYER_PRIV'] = self.credentials['gas_payer_priv'] + self.assertIsNotNone(set_pub_key_at_addr(self.rep_oracle_pub_key).transactionHash) + + if __name__ == "__main__": import doctest from test_manifest import manifest from job import Job doctest.testmod(raise_on_error=True) + unittest.main(exit=False) diff --git a/hmt_escrow/job.py b/hmt_escrow/job.py index 4186a72b..6055f90c 100644 --- a/hmt_escrow/job.py +++ b/hmt_escrow/job.py @@ -2,6 +2,7 @@ import os import sys import logging +import unittest from decimal import Decimal from enum import Enum @@ -35,21 +36,6 @@ def status(escrow_contract: Contract, gas_payer: str, gas: int = GAS_LIMIT) -> Enum: """Returns the status of the Job. - >>> from test_manifest import manifest - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials, manifest) - - After deployment status is "Launched". - - >>> job.launch(rep_oracle_pub_key) - True - >>> status(job.job_contract, job.gas_payer) - - Args: escrow_contract (Contract): the escrow contract of the Job. gas_payer (str): an ethereum address paying for the gas costs. @@ -70,20 +56,6 @@ def manifest_url( ) -> str: """Retrieves the deployed manifest url uploaded on Job initialization. - >>> from test_manifest import manifest - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials, manifest) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> manifest_hash(job.job_contract, job.gas_payer) == job.manifest_hash - True - Args: escrow_contract (Contract): the escrow contract of the Job. gas_payer (str): an ethereum address paying for the gas costs. @@ -103,20 +75,6 @@ def manifest_hash( ) -> str: """Retrieves the deployed manifest hash uploaded on Job initialization. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> from test_manifest import manifest - >>> job = Job(credentials, manifest) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> manifest_hash(job.job_contract, job.gas_payer) == job.manifest_hash - True - Args: escrow_contract (Contract): the escrow contract of the Job. gas_payer (str): an ethereum address paying for the gas costs. @@ -228,56 +186,6 @@ def __init__( address is used to initialize the factory of the Job. Alternatively a new factory is created if no factory address is provided. - Creating a new Job instance initializes the critical attributes correctly. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> from test_manifest import manifest - >>> job = Job(credentials, manifest) - >>> job.gas_payer == credentials["gas_payer"] - True - >>> job.gas_payer_priv == credentials["gas_payer_priv"] - True - >>> job.serialized_manifest["oracle_stake"] - '0.05' - >>> job.amount - Decimal('100.0') - - Initializing a new Job instance with a factory address succeeds. - >>> factory_addr = deploy_factory(**credentials) - >>> job = Job(credentials, manifest, factory_addr) - >>> job.factory_contract.address == factory_addr - True - - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> launcher(job.job_contract, credentials['gas_payer']).lower() == job.factory_contract.address.lower() - True - - Initializing an existing Job instance with a factory and escrow address succeeds. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", - ... "rep_oracle_priv_key": b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> escrow_addr = job.job_contract.address - >>> factory_addr = job.factory_contract.address - >>> manifest_url = job.manifest_url - >>> new_job = Job(credentials=credentials, factory_addr=factory_addr, escrow_addr=escrow_addr) - >>> new_job.manifest_url == manifest_url - True - >>> new_job.job_contract.address == escrow_addr - True - >>> new_job.factory_contract.address == factory_addr - True - >>> new_job.launch(rep_oracle_pub_key) - Traceback (most recent call last): - AttributeError: The escrow has been already deployed. - Creating a new Job instance with falsy credentials fails. >>> credentials = { ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", @@ -345,25 +253,6 @@ def launch(self, pub_key: bytes) -> bool: >>> job.status() - >>> multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] - >>> job = Job(credentials, manifest, multi_credentials=multi_credentials) - - Inject wrong credentials on purpose to test out raffling - - >>> job.gas_payer_priv = "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" - >>> job.multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] - >>> job.launch(rep_oracle_pub_key) - True - >>> job.status() - - - Make sure we launched with raffled credentials - - >>> job.gas_payer - '0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809' - >>> job.gas_payer_priv - 'f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1' - Args: pub_key (bytes): the public key of the Reputation Oracle. Returns: @@ -389,28 +278,6 @@ def setup(self, gas: int = GAS_LIMIT) -> bool: """Sets the escrow contract to be ready to receive answers from the Recording Oracle. The contract needs to be deployed and funded first. - >>> from test_manifest import manifest - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials, manifest) - - A Job can't be setup without deploying it first. - - >>> job.setup() - False - - >>> multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] - >>> job = Job(credentials, manifest, multi_credentials=multi_credentials) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.gas_payer_priv = "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" - >>> job.multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] - >>> job.setup() - True - Returns: bool: returns True if Job is in Pending state. @@ -507,19 +374,6 @@ def add_trusted_handlers(self, handlers: List[str], gas: int = GAS_LIMIT) -> boo >>> job.launch(rep_oracle_pub_key) True - Make sure we se set our gas payer as a trusted handler by default. - - >>> is_trusted_handler(job.job_contract, job.gas_payer, job.gas_payer) - True - - >>> trusted_handlers = ['0x61F9F0B31eacB420553da8BCC59DC617279731Ac', '0xD979105297fB0eee83F7433fC09279cb5B94fFC6'] - >>> job.add_trusted_handlers(trusted_handlers) - True - >>> is_trusted_handler(job.job_contract, '0x61F9F0B31eacB420553da8BCC59DC617279731Ac', job.gas_payer) - True - >>> is_trusted_handler(job.job_contract, '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', job.gas_payer) - True - Args: handlers (List[str]): a list of trusted handlers. @@ -578,39 +432,6 @@ def bulk_payout( >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) True - The escrow contract is still in Partial state as there's still balance left. - - >>> job.balance() - 30000000000000000000 - >>> job.status() - - - Trying to pay more than the contract balance results in failure. - - >>> payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('40.0'))] - >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) - False - - Paying the remaining amount empties the escrow and updates the status correctly. - - >>> payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('30.0'))] - >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) - True - >>> job.balance() - 0 - >>> job.status() - - - >>> multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] - >>> job = Job(credentials, manifest, multi_credentials=multi_credentials) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))] - >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) - True - Args: payouts (List[Tuple[str, int]]): a list of tuples with ethereum addresses and amounts. results (Dict): the final answer results stored by the Reputation Oracle. @@ -1249,7 +1070,6 @@ def _validate_credentials( bool: returns True if the calculated and the given address match. """ - addr_valid = False gas_payer_addr = credentials["gas_payer"] gas_payer_priv = credentials["gas_payer_priv"] @@ -1501,9 +1321,126 @@ def _raffle_txn( return txn_succeeded +class JobTestCase(unittest.TestCase): + + def setUp(self): + self.credentials = { + "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + } + self.rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + self.job = Job(self.credentials, manifest) + + def test_status(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertEqual(status(self.job.job_contract, self.job.gas_payer), Status(1)) + + def test_manifest_url(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + self.assertEqual(manifest_hash(self.job.job_contract, self.job.gas_payer), self.job.manifest_hash) + + def test_job_init(self): + + # Creating a new Job instance initializes the critical attributes correctly. + self.assertEqual(self.job.gas_payer, self.credentials["gas_payer"]) + self.assertEqual(self.job.gas_payer_priv, self.credentials["gas_payer_priv"]) + self.assertEqual(self.job.serialized_manifest["oracle_stake"], '0.05') + self.assertEqual(self.job.amount, Decimal('100.0')) + + # Initializing a new Job instance with a factory address succeeds. + factory_addr = deploy_factory(**(self.credentials)) + self.job = Job(self.credentials, manifest, factory_addr) + self.assertTrue(self.job.factory_contract.address, factory_addr) + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + self.assertTrue(launcher(self.job.job_contract, self.credentials['gas_payer']).lower(), self.job.factory_contract.address.lower()) + + # Initializing an existing Job instance with a factory and escrow address succeeds. + self.credentials["rep_oracle_priv_key"] = b'28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5' + escrow_addr = self.job.job_contract.address + factory_addr = self.job.factory_contract.address + manifest_url = self.job.manifest_url + new_job = Job(credentials=self.credentials, factory_addr=factory_addr, escrow_addr=escrow_addr) + self.assertEqual(new_job.manifest_url, manifest_url) + self.assertEqual(new_job.job_contract.address, escrow_addr) + self.assertEqual(new_job.factory_contract.address, factory_addr) + with self.assertRaises(AttributeError): + new_job.launch(self.rep_oracle_pub_key) + + def test_job_launch(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertEqual(self.job.status(), Status(1)) + multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + self.job = Job(self.credentials, manifest, multi_credentials=multi_credentials) + + # Inject wrong credentials on purpose to test out raffling + + self.job.gas_payer_priv = "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" + self.job.multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertEqual(self.job.status(), Status(1)) + + # Make sure we launched with raffled credentials + + self.assertEqual(self.job.gas_payer, '0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809') + self.assertEqual(self.job.gas_payer_priv, 'f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1') + + def test_setup(self): + + # A Job can't be setup without deploying it first. + + self.assertFalse(self.job.setup()) + multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + self.job = Job(self.credentials, manifest, multi_credentials=multi_credentials) + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + + def test_add_trusted_handlers(self): + + # Make sure we se set our gas payer as a trusted handler by default. + + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(is_trusted_handler(self.job.job_contract, self.job.gas_payer, self.job.gas_payer)) + trusted_handlers = ['0x61F9F0B31eacB420553da8BCC59DC617279731Ac', '0xD979105297fB0eee83F7433fC09279cb5B94fFC6'] + self.assertTrue(self.job.add_trusted_handlers(trusted_handlers)) + self.assertTrue(is_trusted_handler(self.job.job_contract, '0x61F9F0B31eacB420553da8BCC59DC617279731Ac', self.job.gas_payer)) + self.assertTrue(is_trusted_handler(self.job.job_contract, '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', self.job.gas_payer)) + + def test_bulk_payout(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))] + self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) + + # The escrow contract is still in Partial state as there's still balance left. + + self.assertEqual(self.job.balance(), 30000000000000000000) + self.assertEqual(self.job.status(), Status(3)) + + # Trying to pay more than the contract balance results in failure. + + payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('40.0'))] + self.assertFalse(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) + + # Paying the remaining amount empties the escrow and updates the status correctly. + + payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('30.0'))] + self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) + self.assertEqual(self.job.balance(), 0) + self.assertEqual(self.job.status(), Status(4)) + + multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + self.job = Job(self.credentials, manifest, multi_credentials=multi_credentials) + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))] + self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) + if __name__ == "__main__": import doctest - + from test_manifest import manifest # IMPORTANT, don't modify this so CI catches the doctest errors. doctest.testmod() + unittest.main(exit=False) diff --git a/hmt_escrow/storage.py b/hmt_escrow/storage.py index 23ba62ca..559e690f 100644 --- a/hmt_escrow/storage.py +++ b/hmt_escrow/storage.py @@ -3,6 +3,7 @@ import codecs import hashlib import json +import unittest from typing import Dict, Tuple from eth_keys import keys From d3444e376e1ed818fac0fc4b4bf7f081b41e30e0 Mon Sep 17 00:00:00 2001 From: uivlis Date: Thu, 9 Jul 2020 12:32:47 +0300 Subject: [PATCH 3/5] Refactored files with black --- hmt_escrow/eth_bridge.py | 38 +++++------ hmt_escrow/job.py | 135 +++++++++++++++++++++++++++++++-------- 2 files changed, 127 insertions(+), 46 deletions(-) diff --git a/hmt_escrow/eth_bridge.py b/hmt_escrow/eth_bridge.py index f3dd2ac3..965aba33 100644 --- a/hmt_escrow/eth_bridge.py +++ b/hmt_escrow/eth_bridge.py @@ -269,31 +269,30 @@ def set_pub_key_at_addr(pub_key: str) -> Dict[str, Any]: class EthBridgeTestCase(unittest.TestCase): - def setUp(self): from job import Job self.credentials = { - 'gas_payer': '0x1413862C2B7054CDbfdc181B83962CB0FC11fD92', - 'gas_payer_priv': '28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5' - } - self.rep_oracle_pub_key = b'2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d' + "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + } + self.rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" self.job = Job(credentials=self.credentials, escrow_manifest=manifest) - def test_handle_transaction(self): from web3.datastructures import AttributeDict as Web3AttributeDict + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) gas = 4712388 - hmt_amount = int(self.job.amount * 10**18) + hmt_amount = int(self.job.amount * 10 ** 18) hmtoken_contract = get_hmtoken() txn_func = hmtoken_contract.functions.transfer func_args = [self.job.job_contract.address, hmt_amount] txn_info = { - "gas_payer": self.job.gas_payer, - "gas_payer_priv": self.job.gas_payer_priv, - "gas": gas - } + "gas_payer": self.job.gas_payer, + "gas_payer_priv": self.job.gas_payer_priv, + "gas": gas, + } txn_receipt = handle_transaction(txn_func, *func_args, **txn_info) self.assertIs(type(txn_receipt), Web3AttributeDict) @@ -306,19 +305,20 @@ def test_get_factory(self): def test_get_pub_key_from_address(self): with self.assertRaises(ValueError): - get_pub_key_from_addr('badaddress') - os.environ['GAS_PAYER'] = self.credentials['gas_payer'] - os.environ['GAS_PAYER_PRIV'] = self.credentials['gas_payer_priv'] + get_pub_key_from_addr("badaddress") + os.environ["GAS_PAYER"] = self.credentials["gas_payer"] + os.environ["GAS_PAYER_PRIV"] = self.credentials["gas_payer_priv"] set_pub_key_at_addr(self.rep_oracle_pub_key) self.assertEqual( - get_pub_key_from_addr(os.environ['GAS_PAYER']), - self.rep_oracle_pub_key + get_pub_key_from_addr(os.environ["GAS_PAYER"]), self.rep_oracle_pub_key ) def test_set_pub_key_at_address(self): - os.environ['GAS_PAYER'] = self.credentials['gas_payer'] - os.environ['GAS_PAYER_PRIV'] = self.credentials['gas_payer_priv'] - self.assertIsNotNone(set_pub_key_at_addr(self.rep_oracle_pub_key).transactionHash) + os.environ["GAS_PAYER"] = self.credentials["gas_payer"] + os.environ["GAS_PAYER_PRIV"] = self.credentials["gas_payer_priv"] + self.assertIsNotNone( + set_pub_key_at_addr(self.rep_oracle_pub_key).transactionHash + ) if __name__ == "__main__": diff --git a/hmt_escrow/job.py b/hmt_escrow/job.py index 6055f90c..4f6c4c78 100644 --- a/hmt_escrow/job.py +++ b/hmt_escrow/job.py @@ -1321,12 +1321,12 @@ def _raffle_txn( return txn_succeeded -class JobTestCase(unittest.TestCase): +class JobTestCase(unittest.TestCase): def setUp(self): self.credentials = { - "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", } self.rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" self.job = Job(self.credentials, manifest) @@ -1338,15 +1338,18 @@ def test_status(self): def test_manifest_url(self): self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) - self.assertEqual(manifest_hash(self.job.job_contract, self.job.gas_payer), self.job.manifest_hash) + self.assertEqual( + manifest_hash(self.job.job_contract, self.job.gas_payer), + self.job.manifest_hash, + ) def test_job_init(self): # Creating a new Job instance initializes the critical attributes correctly. self.assertEqual(self.job.gas_payer, self.credentials["gas_payer"]) self.assertEqual(self.job.gas_payer_priv, self.credentials["gas_payer_priv"]) - self.assertEqual(self.job.serialized_manifest["oracle_stake"], '0.05') - self.assertEqual(self.job.amount, Decimal('100.0')) + self.assertEqual(self.job.serialized_manifest["oracle_stake"], "0.05") + self.assertEqual(self.job.amount, Decimal("100.0")) # Initializing a new Job instance with a factory address succeeds. factory_addr = deploy_factory(**(self.credentials)) @@ -1354,14 +1357,23 @@ def test_job_init(self): self.assertTrue(self.job.factory_contract.address, factory_addr) self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) - self.assertTrue(launcher(self.job.job_contract, self.credentials['gas_payer']).lower(), self.job.factory_contract.address.lower()) + self.assertTrue( + launcher(self.job.job_contract, self.credentials["gas_payer"]).lower(), + self.job.factory_contract.address.lower(), + ) # Initializing an existing Job instance with a factory and escrow address succeeds. - self.credentials["rep_oracle_priv_key"] = b'28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5' + self.credentials[ + "rep_oracle_priv_key" + ] = b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" escrow_addr = self.job.job_contract.address factory_addr = self.job.factory_contract.address manifest_url = self.job.manifest_url - new_job = Job(credentials=self.credentials, factory_addr=factory_addr, escrow_addr=escrow_addr) + new_job = Job( + credentials=self.credentials, + factory_addr=factory_addr, + escrow_addr=escrow_addr, + ) self.assertEqual(new_job.manifest_url, manifest_url) self.assertEqual(new_job.job_contract.address, escrow_addr) self.assertEqual(new_job.factory_contract.address, factory_addr) @@ -1371,48 +1383,104 @@ def test_job_init(self): def test_job_launch(self): self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertEqual(self.job.status(), Status(1)) - multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + multi_credentials = [ + ( + "0x61F9F0B31eacB420553da8BCC59DC617279731Ac", + "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e", + ), + ( + "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", + "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + ), + ] self.job = Job(self.credentials, manifest, multi_credentials=multi_credentials) # Inject wrong credentials on purpose to test out raffling - self.job.gas_payer_priv = "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" - self.job.multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + self.job.gas_payer_priv = ( + "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" + ) + self.job.multi_credentials = [ + ( + "0x61F9F0B31eacB420553da8BCC59DC617279731Ac", + "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + ), + ( + "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", + "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + ), + ] self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertEqual(self.job.status(), Status(1)) # Make sure we launched with raffled credentials - self.assertEqual(self.job.gas_payer, '0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809') - self.assertEqual(self.job.gas_payer_priv, 'f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1') + self.assertEqual( + self.job.gas_payer, "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809" + ) + self.assertEqual( + self.job.gas_payer_priv, + "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + ) def test_setup(self): # A Job can't be setup without deploying it first. self.assertFalse(self.job.setup()) - multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + multi_credentials = [ + ( + "0x61F9F0B31eacB420553da8BCC59DC617279731Ac", + "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + ), + ( + "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", + "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + ), + ] self.job = Job(self.credentials, manifest, multi_credentials=multi_credentials) self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) def test_add_trusted_handlers(self): - + # Make sure we se set our gas payer as a trusted handler by default. - + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) - self.assertTrue(is_trusted_handler(self.job.job_contract, self.job.gas_payer, self.job.gas_payer)) - trusted_handlers = ['0x61F9F0B31eacB420553da8BCC59DC617279731Ac', '0xD979105297fB0eee83F7433fC09279cb5B94fFC6'] + self.assertTrue( + is_trusted_handler( + self.job.job_contract, self.job.gas_payer, self.job.gas_payer + ) + ) + trusted_handlers = [ + "0x61F9F0B31eacB420553da8BCC59DC617279731Ac", + "0xD979105297fB0eee83F7433fC09279cb5B94fFC6", + ] self.assertTrue(self.job.add_trusted_handlers(trusted_handlers)) - self.assertTrue(is_trusted_handler(self.job.job_contract, '0x61F9F0B31eacB420553da8BCC59DC617279731Ac', self.job.gas_payer)) - self.assertTrue(is_trusted_handler(self.job.job_contract, '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', self.job.gas_payer)) + self.assertTrue( + is_trusted_handler( + self.job.job_contract, + "0x61F9F0B31eacB420553da8BCC59DC617279731Ac", + self.job.gas_payer, + ) + ) + self.assertTrue( + is_trusted_handler( + self.job.job_contract, + "0xD979105297fB0eee83F7433fC09279cb5B94fFC6", + self.job.gas_payer, + ) + ) def test_bulk_payout(self): self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) - payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))] + payouts = [ + ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal("20.0")), + ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal("50.0")), + ] self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) - + # The escrow contract is still in Partial state as there's still balance left. self.assertEqual(self.job.balance(), 30000000000000000000) @@ -1420,27 +1488,40 @@ def test_bulk_payout(self): # Trying to pay more than the contract balance results in failure. - payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('40.0'))] + payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal("40.0"))] self.assertFalse(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) # Paying the remaining amount empties the escrow and updates the status correctly. - payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('30.0'))] + payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal("30.0"))] self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) self.assertEqual(self.job.balance(), 0) self.assertEqual(self.job.status(), Status(4)) - multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + multi_credentials = [ + ( + "0x61F9F0B31eacB420553da8BCC59DC617279731Ac", + "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e", + ), + ( + "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", + "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + ), + ] self.job = Job(self.credentials, manifest, multi_credentials=multi_credentials) self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) - payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))] + payouts = [ + ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal("20.0")), + ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal("50.0")), + ] self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) if __name__ == "__main__": import doctest from test_manifest import manifest + # IMPORTANT, don't modify this so CI catches the doctest errors. doctest.testmod() unittest.main(exit=False) From bb4833413b1c7065fedb32e98585a16856e5ea0e Mon Sep 17 00:00:00 2001 From: uivlis Date: Thu, 9 Jul 2020 13:30:38 +0300 Subject: [PATCH 4/5] Second batch of doc-to-unit tests --- hmt_escrow/job.py | 156 +++++++++++++++++------------------------- hmt_escrow/storage.py | 117 +++++++++++++++---------------- 2 files changed, 117 insertions(+), 156 deletions(-) diff --git a/hmt_escrow/job.py b/hmt_escrow/job.py index 4f6c4c78..977e6dfa 100644 --- a/hmt_escrow/job.py +++ b/hmt_escrow/job.py @@ -502,46 +502,6 @@ def abort(self, gas: int = GAS_LIMIT) -> bool: >>> job.abort() True - The escrow contract is in Partial state after the first payout and it can't be aborted. - - - The escrow contract is in Paid state after the a full bulk payout and it can't be aborted. - - >>> job = Job(credentials, manifest) - >>> job.launch(rep_oracle_pub_key) - True - - >>> job.setup() - True - >>> payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('100.0'))] - >>> job.bulk_payout(payouts, {'results': 0}, rep_oracle_pub_key) - True - >>> job.abort() - False - >>> job.status() - - - - Trusted handler should be able to abort an existing contract - - >>> trusted_handler = "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809" - >>> job = Job(credentials, manifest) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> job.add_trusted_handlers([trusted_handler]) - True - - >>> handler_credentials = { - ... "gas_payer": "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", - ... "gas_payer_priv": "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", - ... "rep_oracle_priv_key": b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> access_job = Job(credentials=handler_credentials, factory_addr=job.factory_contract.address, escrow_addr=job.job_contract.address) - >>> access_job.abort() - True - Returns: bool: returns True if contract has been destroyed successfully. @@ -598,29 +558,6 @@ def cancel(self, gas: int = GAS_LIMIT) -> bool: >>> job.status() - The escrow contract is in Partial state after the first payout and it can't be cancelled. - - >>> job = Job(credentials, manifest) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0'))] - >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) - True - >>> job.status() - - - The escrow contract is in Paid state after the second payout and it can't be cancelled. - - >>> payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('80.0'))] - >>> job.bulk_payout(payouts, {'results': 0}, rep_oracle_pub_key) - True - >>> job.cancel() - False - >>> job.status() - - Returns: bool: returns True if gas payer has been paid back and contract is in "Cancelled" state. @@ -803,21 +740,6 @@ def complete(self, gas: int = GAS_LIMIT) -> bool: def status(self, gas: int = GAS_LIMIT) -> Enum: """Returns the status of the Job. - >>> from test_manifest import manifest - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials, manifest) - - After deployment status is "Launched". - - >>> job.launch(rep_oracle_pub_key) - True - >>> job.status() - - Returns: Enum: returns the status as an enumeration. @@ -827,20 +749,6 @@ def status(self, gas: int = GAS_LIMIT) -> Enum: def balance(self, gas: int = GAS_LIMIT) -> int: """Retrieve the balance of a Job in HMT. - >>> from test_manifest import manifest - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials, manifest) - >>> job.launch(rep_oracle_pub_key) - True - >>> job.setup() - True - >>> job.balance() - 100000000000000000000 - Args: escrow_contract (Contract): the contract to be read. gas_payer (str): an ethereum address calling the contract. @@ -1423,7 +1331,7 @@ def test_job_launch(self): "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", ) - def test_setup(self): + def test_job_setup(self): # A Job can't be setup without deploying it first. @@ -1442,7 +1350,7 @@ def test_setup(self): self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) - def test_add_trusted_handlers(self): + def test_job_add_trusted_handlers(self): # Make sure we se set our gas payer as a trusted handler by default. @@ -1472,7 +1380,7 @@ def test_add_trusted_handlers(self): ) ) - def test_bulk_payout(self): + def test_job_bulk_payout(self): self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) self.assertTrue(self.job.setup()) payouts = [ @@ -1517,6 +1425,64 @@ def test_bulk_payout(self): ] self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) + def test_job_abort(self): + + # The escrow contract is in Paid state after the a full bulk payout and it can't be aborted. + + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal("100.0"))] + self.assertTrue( + self.job.bulk_payout(payouts, {"results": 0}, self.rep_oracle_pub_key) + ) + self.assertFalse(self.job.abort()) + self.assertEqual(self.job.status(), Status(4)) + + # Trusted handler should be able to abort an existing contract + + self.job = Job(self.credentials, manifest) + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + trusted_handler = "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809" + self.assertTrue(self.job.add_trusted_handlers([trusted_handler])) + + handler_credentials = { + "gas_payer": "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", + "gas_payer_priv": "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + "rep_oracle_priv_key": b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + } + access_job = Job( + credentials=handler_credentials, + factory_addr=self.job.factory_contract.address, + escrow_addr=self.job.job_contract.address, + ) + self.assertTrue(access_job.abort()) + + def test_job_cancel(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal("20.0"))] + self.assertTrue(self.job.bulk_payout(payouts, {}, self.rep_oracle_pub_key)) + self.assertEqual(self.job.status(), Status(3)) + + # The escrow contract is in Paid state after the second payout and it can't be cancelled. + + payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal("80.0"))] + self.assertTrue( + self.job.bulk_payout(payouts, {"results": 0}, self.rep_oracle_pub_key) + ) + self.assertFalse(self.job.cancel()) + self.assertEqual(self.job.status(), Status(4)) + + def test_job_status(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertEqual(self.job.status(), Status(1)) + + def test_job_balance(self): + self.assertTrue(self.job.launch(self.rep_oracle_pub_key)) + self.assertTrue(self.job.setup()) + self.assertEqual(self.job.balance(), 100000000000000000000) + if __name__ == "__main__": import doctest diff --git a/hmt_escrow/storage.py b/hmt_escrow/storage.py index 559e690f..aab6b019 100644 --- a/hmt_escrow/storage.py +++ b/hmt_escrow/storage.py @@ -48,25 +48,6 @@ def _connect_s3(): def download(key: str, private_key: bytes) -> Dict: """Download a key, decrypt it, and output it as a binary string. - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> from test_manifest import manifest - >>> from job import Job - >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) - >>> manifest_dict = download(manifest_url, job.gas_payer_priv) - >>> manifest_dict == job.serialized_manifest - True - - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) - >>> manifest_dict = download(manifest_url, job.gas_payer_priv) - >>> manifest_dict == job.serialized_manifest - True - Args: key (str): This is the hash code returned when uploading. private_key (str): The private_key to decrypt this string with. @@ -99,27 +80,6 @@ def upload(msg: Dict, public_key: bytes) -> Tuple[str, str]: This can be manifest files, results, or anything that's been already encrypted. - >>> from test_manifest import manifest - >>> from job import Job - >>> credentials = { - ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", - ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - ... } - >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) - >>> manifest_dict = download(manifest_url, job.gas_payer_priv) - >>> manifest_dict == job.serialized_manifest - True - - >>> job = Job(credentials=credentials, escrow_manifest=manifest) - >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) - >>> manifest_url.startswith('s3') - True - >>> manifest_dict = download(manifest_url, job.gas_payer_priv) - >>> manifest_dict == job.serialized_manifest - True - Args: msg (Dict): The message to upload and encrypt. public_key (bytes): The public_key to encrypt the file for. @@ -150,20 +110,6 @@ def upload(msg: Dict, public_key: bytes) -> Tuple[str, str]: def _decrypt(private_key: bytes, msg: bytes) -> str: """Use ECIES to decrypt a message with a given private key and an optional MAC. - >>> from test_manifest import manifest - >>> from job import Job - >>> priv_key = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> msg = "test" - >>> _decrypt(priv_key, _encrypt(pub_key, msg)) == msg - True - - Using a wrong public key to decrypt a message results in failure. - >>> false_pub_key = b"74c81fe41b30f741b31185052664a10c3256e2f08bcfb20c8f54e733bef58972adcf84e4f5d70a979681fd39d7f7847d2c0d3b5d4aead806c4fec4d8534be114" - >>> _decrypt(priv_key, _encrypt(false_pub_key, msg)) == msg - Traceback (most recent call last): - p2p.exceptions.DecryptionError: Failed to verify tag - Args: private_key (bytes): The private_key to decrypt the message with. msg (bytes): The message to be decrypted. @@ -179,13 +125,6 @@ def _decrypt(private_key: bytes, msg: bytes) -> str: def _encrypt(public_key: bytes, msg: str) -> bytes: """Use ECIES to encrypt a message with a given public key and optional MAC. - >>> from test_manifest import manifest - >>> from job import Job - >>> priv_key = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" - >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" - >>> msg = "test" - >>> _decrypt(priv_key, _encrypt(pub_key, msg)) == msg - True Args: public_key (bytes): The public_key to encrypt the message with. @@ -200,7 +139,63 @@ def _encrypt(public_key: bytes, msg: str) -> bytes: return ecies.encrypt(msg_bytes, pub_key, shared_mac_data=SHARED_MAC_DATA) +class StorageTest(unittest.TestCase): + def test_download(self): + credentials = { + "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + } + pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + job = Job(credentials=credentials, escrow_manifest=manifest) + (_, manifest_url) = upload(job.serialized_manifest, pub_key) + manifest_dict = download(manifest_url, job.gas_payer_priv) + self.assertEqual(manifest_dict, job.serialized_manifest) + + job = Job(credentials=credentials, escrow_manifest=manifest) + (_, manifest_url) = upload(job.serialized_manifest, pub_key) + manifest_dict = download(manifest_url, job.gas_payer_priv) + self.assertEqual(manifest_dict, job.serialized_manifest) + + def test_upload(self): + credentials = { + "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + } + pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + job = Job(credentials=credentials, escrow_manifest=manifest) + (_, manifest_url) = upload(job.serialized_manifest, pub_key) + manifest_dict = download(manifest_url, job.gas_payer_priv) + self.assertEqual(manifest_dict, job.serialized_manifest) + + job = Job(credentials=credentials, escrow_manifest=manifest) + (_, manifest_url) = upload(job.serialized_manifest, pub_key) + self.assertTrue(manifest_url.startswith("s3")) + manifest_dict = download(manifest_url, job.gas_payer_priv) + self.assertEqual(manifest_dict, job.serialized_manifest) + + def test_decrypt(self): + from p2p.exceptions import DecryptionError + + priv_key = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + msg = "test" + self.assertEqual(_decrypt(priv_key, _encrypt(pub_key, msg)), msg) + # Using a wrong public key to decrypt a message results in failure. + false_pub_key = b"74c81fe41b30f741b31185052664a10c3256e2f08bcfb20c8f54e733bef58972adcf84e4f5d70a979681fd39d7f7847d2c0d3b5d4aead806c4fec4d8534be114" + with self.assertRaises(DecryptionError): + _decrypt(priv_key, _encrypt(false_pub_key, msg)) == msg + + def test_encrypt(self): + priv_key = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + msg = "test" + self.assertEqual(_decrypt(priv_key, _encrypt(pub_key, msg)), msg) + + if __name__ == "__main__": import doctest + from test_manifest import manifest + from job import Job doctest.testmod(raise_on_error=True) + unittest.main(exit=False) From b42ba807f67a3b8b3ab6c06005adc51d25930d50 Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Thu, 9 Jul 2020 12:04:13 -0400 Subject: [PATCH 5/5] return docs --- hmt_escrow/eth_bridge.py | 55 +++++++- hmt_escrow/job.py | 277 ++++++++++++++++++++++++++++++++++++++- hmt_escrow/storage.py | 66 +++++++++- 3 files changed, 383 insertions(+), 15 deletions(-) diff --git a/hmt_escrow/eth_bridge.py b/hmt_escrow/eth_bridge.py index 965aba33..08309bd8 100644 --- a/hmt_escrow/eth_bridge.py +++ b/hmt_escrow/eth_bridge.py @@ -136,6 +136,20 @@ def get_hmtoken(hmtoken_addr=HMTOKEN_ADDR) -> Contract: def get_escrow(escrow_addr: str) -> Contract: """Retrieve the Escrow contract from a given address. + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials=credentials, escrow_manifest=manifest) + + Deploying a new Job to the ethereum network succeeds. + + >>> job.launch(rep_oracle_pub_key) + True + >>> type(get_escrow(job.job_contract.address)) + + Args: escrow_addr (str): an ethereum address of the escrow contract. @@ -155,6 +169,14 @@ def get_escrow(escrow_addr: str) -> Contract: def get_factory(factory_addr: Optional[str]) -> Contract: """Retrieve the EscrowFactory contract from a given address. + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> job = Job(credentials=credentials, escrow_manifest=manifest) + >>> type(get_factory(job.factory_contract.address)) + + Args: factory_addr (str): the ethereum address of the Escrow contract. @@ -216,6 +238,25 @@ def get_pub_key_from_addr(wallet_addr: str) -> bytes: Returns: bytes: the public key in bytes form + >>> import os + >>> from web3 import Web3 + >>> get_pub_key_from_addr('badaddress') + Traceback (most recent call last): + File "/usr/lib/python3.6/doctest.py", line 1330, in __run + compileflags, 1), test.globs) + File "", line 1, in + get_pub_key_from_addr('blah') + File "hmt_escrow/eth_bridge.py", line 268, in get_pub_key_from_addr + raise ValueError('environment variable GAS_PAYER required') + ValueError: environment variable GAS_PAYER required + >>> os.environ['GAS_PAYER'] = "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92" + >>> os.environ['GAS_PAYER_PRIV'] = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> set_pub_key_at_addr(pub_key) #doctest: +ELLIPSIS + AttributeDict({'transactionHash': ...}) + >>> get_pub_key_from_addr(os.environ['GAS_PAYER']) + b'2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d' + """ # TODO: Should we try to get the checksum address here instead of assuming user will do that? GAS_PAYER = os.getenv("GAS_PAYER") @@ -247,6 +288,15 @@ def set_pub_key_at_addr(pub_key: str) -> Dict[str, Any]: Returns: AttributeDict: receipt of the set transaction on the blockchain + + >>> from web3 import Web3 + >>> import os + >>> os.environ['GAS_PAYER'] = "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92" + >>> os.environ['GAS_PAYER_PRIV'] = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + >>> pub_key_to_set = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> set_pub_key_at_addr(pub_key_to_set) #doctest: +ELLIPSIS + AttributeDict({'transactionHash': ...}) + """ GAS_PAYER = os.getenv("GAS_PAYER") GAS_PAYER_PRIV = os.getenv("GAS_PAYER_PRIV") @@ -322,9 +372,4 @@ def test_set_pub_key_at_address(self): if __name__ == "__main__": - import doctest - from test_manifest import manifest - from job import Job - - doctest.testmod(raise_on_error=True) unittest.main(exit=False) diff --git a/hmt_escrow/job.py b/hmt_escrow/job.py index 977e6dfa..18445572 100644 --- a/hmt_escrow/job.py +++ b/hmt_escrow/job.py @@ -36,6 +36,21 @@ def status(escrow_contract: Contract, gas_payer: str, gas: int = GAS_LIMIT) -> Enum: """Returns the status of the Job. + >>> from test_manifest import manifest + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials, manifest) + + After deployment status is "Launched". + + >>> job.launch(rep_oracle_pub_key) + True + >>> status(job.job_contract, job.gas_payer) + + Args: escrow_contract (Contract): the escrow contract of the Job. gas_payer (str): an ethereum address paying for the gas costs. @@ -56,6 +71,20 @@ def manifest_url( ) -> str: """Retrieves the deployed manifest url uploaded on Job initialization. + >>> from test_manifest import manifest + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials, manifest) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> manifest_hash(job.job_contract, job.gas_payer) == job.manifest_hash + True + Args: escrow_contract (Contract): the escrow contract of the Job. gas_payer (str): an ethereum address paying for the gas costs. @@ -75,6 +104,20 @@ def manifest_hash( ) -> str: """Retrieves the deployed manifest hash uploaded on Job initialization. + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> from test_manifest import manifest + >>> job = Job(credentials, manifest) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> manifest_hash(job.job_contract, job.gas_payer) == job.manifest_hash + True + Args: escrow_contract (Contract): the escrow contract of the Job. gas_payer (str): an ethereum address paying for the gas costs. @@ -186,6 +229,56 @@ def __init__( address is used to initialize the factory of the Job. Alternatively a new factory is created if no factory address is provided. + Creating a new Job instance initializes the critical attributes correctly. + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> from test_manifest import manifest + >>> job = Job(credentials, manifest) + >>> job.gas_payer == credentials["gas_payer"] + True + >>> job.gas_payer_priv == credentials["gas_payer_priv"] + True + >>> job.serialized_manifest["oracle_stake"] + '0.05' + >>> job.amount + Decimal('100.0') + + Initializing a new Job instance with a factory address succeeds. + >>> factory_addr = deploy_factory(**credentials) + >>> job = Job(credentials, manifest, factory_addr) + >>> job.factory_contract.address == factory_addr + True + + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> launcher(job.job_contract, credentials['gas_payer']).lower() == job.factory_contract.address.lower() + True + + Initializing an existing Job instance with a factory and escrow address succeeds. + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5", + ... "rep_oracle_priv_key": b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> escrow_addr = job.job_contract.address + >>> factory_addr = job.factory_contract.address + >>> manifest_url = job.manifest_url + >>> new_job = Job(credentials=credentials, factory_addr=factory_addr, escrow_addr=escrow_addr) + >>> new_job.manifest_url == manifest_url + True + >>> new_job.job_contract.address == escrow_addr + True + >>> new_job.factory_contract.address == factory_addr + True + >>> new_job.launch(rep_oracle_pub_key) + Traceback (most recent call last): + AttributeError: The escrow has been already deployed. + Creating a new Job instance with falsy credentials fails. >>> credentials = { ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", @@ -253,6 +346,25 @@ def launch(self, pub_key: bytes) -> bool: >>> job.status() + >>> multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + >>> job = Job(credentials, manifest, multi_credentials=multi_credentials) + + Inject wrong credentials on purpose to test out raffling + + >>> job.gas_payer_priv = "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" + >>> job.multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + >>> job.launch(rep_oracle_pub_key) + True + >>> job.status() + + + Make sure we launched with raffled credentials + + >>> job.gas_payer + '0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809' + >>> job.gas_payer_priv + 'f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1' + Args: pub_key (bytes): the public key of the Reputation Oracle. Returns: @@ -278,6 +390,28 @@ def setup(self, gas: int = GAS_LIMIT) -> bool: """Sets the escrow contract to be ready to receive answers from the Recording Oracle. The contract needs to be deployed and funded first. + >>> from test_manifest import manifest + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials, manifest) + + A Job can't be setup without deploying it first. + + >>> job.setup() + False + + >>> multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + >>> job = Job(credentials, manifest, multi_credentials=multi_credentials) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.gas_payer_priv = "657b6497a355a3982928d5515d48a84870f057c4d16923eb1d104c0afada9aa8" + >>> job.multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + >>> job.setup() + True + Returns: bool: returns True if Job is in Pending state. @@ -374,6 +508,19 @@ def add_trusted_handlers(self, handlers: List[str], gas: int = GAS_LIMIT) -> boo >>> job.launch(rep_oracle_pub_key) True + Make sure we se set our gas payer as a trusted handler by default. + + >>> is_trusted_handler(job.job_contract, job.gas_payer, job.gas_payer) + True + + >>> trusted_handlers = ['0x61F9F0B31eacB420553da8BCC59DC617279731Ac', '0xD979105297fB0eee83F7433fC09279cb5B94fFC6'] + >>> job.add_trusted_handlers(trusted_handlers) + True + >>> is_trusted_handler(job.job_contract, '0x61F9F0B31eacB420553da8BCC59DC617279731Ac', job.gas_payer) + True + >>> is_trusted_handler(job.job_contract, '0xD979105297fB0eee83F7433fC09279cb5B94fFC6', job.gas_payer) + True + Args: handlers (List[str]): a list of trusted handlers. @@ -432,6 +579,39 @@ def bulk_payout( >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) True + The escrow contract is still in Partial state as there's still balance left. + + >>> job.balance() + 30000000000000000000 + >>> job.status() + + + Trying to pay more than the contract balance results in failure. + + >>> payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('40.0'))] + >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) + False + + Paying the remaining amount empties the escrow and updates the status correctly. + + >>> payouts = [("0x9d689b8f50Fd2CAec716Cc5220bEd66E03F07B5f", Decimal('30.0'))] + >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) + True + >>> job.balance() + 0 + >>> job.status() + + + >>> multi_credentials = [("0x61F9F0B31eacB420553da8BCC59DC617279731Ac", "486a0621e595dd7fcbe5608cbbeec8f5a8b5cabe7637f11eccfc7acd408c3a0e"), ("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1")] + >>> job = Job(credentials, manifest, multi_credentials=multi_credentials) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0')), ("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('50.0'))] + >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) + True + Args: payouts (List[Tuple[str, int]]): a list of tuples with ethereum addresses and amounts. results (Dict): the final answer results stored by the Reputation Oracle. @@ -502,6 +682,46 @@ def abort(self, gas: int = GAS_LIMIT) -> bool: >>> job.abort() True + The escrow contract is in Partial state after the first payout and it can't be aborted. + + + The escrow contract is in Paid state after the a full bulk payout and it can't be aborted. + + >>> job = Job(credentials, manifest) + >>> job.launch(rep_oracle_pub_key) + True + + >>> job.setup() + True + >>> payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('100.0'))] + >>> job.bulk_payout(payouts, {'results': 0}, rep_oracle_pub_key) + True + >>> job.abort() + False + >>> job.status() + + + + Trusted handler should be able to abort an existing contract + + >>> trusted_handler = "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809" + >>> job = Job(credentials, manifest) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> job.add_trusted_handlers([trusted_handler]) + True + + >>> handler_credentials = { + ... "gas_payer": "0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", + ... "gas_payer_priv": "f22d4fc42da79aa5ba839998a0a9f2c2c45f5e55ee7f1504e464d2c71ca199e1", + ... "rep_oracle_priv_key": b"28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> access_job = Job(credentials=handler_credentials, factory_addr=job.factory_contract.address, escrow_addr=job.job_contract.address) + >>> access_job.abort() + True + Returns: bool: returns True if contract has been destroyed successfully. @@ -558,6 +778,29 @@ def cancel(self, gas: int = GAS_LIMIT) -> bool: >>> job.status() + The escrow contract is in Partial state after the first payout and it can't be cancelled. + + >>> job = Job(credentials, manifest) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> payouts = [("0x6b7E3C31F34cF38d1DFC1D9A8A59482028395809", Decimal('20.0'))] + >>> job.bulk_payout(payouts, {}, rep_oracle_pub_key) + True + >>> job.status() + + + The escrow contract is in Paid state after the second payout and it can't be cancelled. + + >>> payouts = [("0x852023fbb19050B8291a335E5A83Ac9701E7B4E6", Decimal('80.0'))] + >>> job.bulk_payout(payouts, {'results': 0}, rep_oracle_pub_key) + True + >>> job.cancel() + False + >>> job.status() + + Returns: bool: returns True if gas payer has been paid back and contract is in "Cancelled" state. @@ -740,6 +983,21 @@ def complete(self, gas: int = GAS_LIMIT) -> bool: def status(self, gas: int = GAS_LIMIT) -> Enum: """Returns the status of the Job. + >>> from test_manifest import manifest + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials, manifest) + + After deployment status is "Launched". + + >>> job.launch(rep_oracle_pub_key) + True + >>> job.status() + + Returns: Enum: returns the status as an enumeration. @@ -749,6 +1007,20 @@ def status(self, gas: int = GAS_LIMIT) -> Enum: def balance(self, gas: int = GAS_LIMIT) -> int: """Retrieve the balance of a Job in HMT. + >>> from test_manifest import manifest + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> rep_oracle_pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials, manifest) + >>> job.launch(rep_oracle_pub_key) + True + >>> job.setup() + True + >>> job.balance() + 100000000000000000000 + Args: escrow_contract (Contract): the contract to be read. gas_payer (str): an ethereum address calling the contract. @@ -1485,9 +1757,4 @@ def test_job_balance(self): if __name__ == "__main__": - import doctest - from test_manifest import manifest - - # IMPORTANT, don't modify this so CI catches the doctest errors. - doctest.testmod() unittest.main(exit=False) diff --git a/hmt_escrow/storage.py b/hmt_escrow/storage.py index aab6b019..acd1f0d0 100644 --- a/hmt_escrow/storage.py +++ b/hmt_escrow/storage.py @@ -48,6 +48,25 @@ def _connect_s3(): def download(key: str, private_key: bytes) -> Dict: """Download a key, decrypt it, and output it as a binary string. + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> from test_manifest import manifest + >>> from job import Job + >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials=credentials, escrow_manifest=manifest) + >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) + >>> manifest_dict = download(manifest_url, job.gas_payer_priv) + >>> manifest_dict == job.serialized_manifest + True + + >>> job = Job(credentials=credentials, escrow_manifest=manifest) + >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) + >>> manifest_dict = download(manifest_url, job.gas_payer_priv) + >>> manifest_dict == job.serialized_manifest + True + Args: key (str): This is the hash code returned when uploading. private_key (str): The private_key to decrypt this string with. @@ -80,6 +99,27 @@ def upload(msg: Dict, public_key: bytes) -> Tuple[str, str]: This can be manifest files, results, or anything that's been already encrypted. + >>> from test_manifest import manifest + >>> from job import Job + >>> credentials = { + ... "gas_payer": "0x1413862C2B7054CDbfdc181B83962CB0FC11fD92", + ... "gas_payer_priv": "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + ... } + >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> job = Job(credentials=credentials, escrow_manifest=manifest) + >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) + >>> manifest_dict = download(manifest_url, job.gas_payer_priv) + >>> manifest_dict == job.serialized_manifest + True + + >>> job = Job(credentials=credentials, escrow_manifest=manifest) + >>> (hash_, manifest_url) = upload(job.serialized_manifest, pub_key) + >>> manifest_url.startswith('s3') + True + >>> manifest_dict = download(manifest_url, job.gas_payer_priv) + >>> manifest_dict == job.serialized_manifest + True + Args: msg (Dict): The message to upload and encrypt. public_key (bytes): The public_key to encrypt the file for. @@ -110,6 +150,20 @@ def upload(msg: Dict, public_key: bytes) -> Tuple[str, str]: def _decrypt(private_key: bytes, msg: bytes) -> str: """Use ECIES to decrypt a message with a given private key and an optional MAC. + >>> from test_manifest import manifest + >>> from job import Job + >>> priv_key = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> msg = "test" + >>> _decrypt(priv_key, _encrypt(pub_key, msg)) == msg + True + + Using a wrong public key to decrypt a message results in failure. + >>> false_pub_key = b"74c81fe41b30f741b31185052664a10c3256e2f08bcfb20c8f54e733bef58972adcf84e4f5d70a979681fd39d7f7847d2c0d3b5d4aead806c4fec4d8534be114" + >>> _decrypt(priv_key, _encrypt(false_pub_key, msg)) == msg + Traceback (most recent call last): + p2p.exceptions.DecryptionError: Failed to verify tag + Args: private_key (bytes): The private_key to decrypt the message with. msg (bytes): The message to be decrypted. @@ -125,6 +179,13 @@ def _decrypt(private_key: bytes, msg: bytes) -> str: def _encrypt(public_key: bytes, msg: str) -> bytes: """Use ECIES to encrypt a message with a given public key and optional MAC. + >>> from test_manifest import manifest + >>> from job import Job + >>> priv_key = "28e516f1e2f99e96a48a23cea1f94ee5f073403a1c68e818263f0eb898f1c8e5" + >>> pub_key = b"2dbc2c2c86052702e7c219339514b2e8bd4687ba1236c478ad41b43330b08488c12c8c1797aa181f3a4596a1bd8a0c18344ea44d6655f61fa73e56e743f79e0d" + >>> msg = "test" + >>> _decrypt(priv_key, _encrypt(pub_key, msg)) == msg + True Args: public_key (bytes): The public_key to encrypt the message with. @@ -193,9 +254,4 @@ def test_encrypt(self): if __name__ == "__main__": - import doctest - from test_manifest import manifest - from job import Job - - doctest.testmod(raise_on_error=True) unittest.main(exit=False)