From 21be9f648a7aa28f73badc980487056be691a93f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Wed, 4 Jun 2025 11:07:39 +0200 Subject: [PATCH 1/2] Refactor Python SDK to use same structure as Typescript one - Created a new decorator to verify signers on methods that really need it - Removed handle_transaction and use directly transact on each method - Created new handle_error to show proper error messages - Refactor all tests to match the new SDK changes --- .../sdk/python/human-protocol-sdk/example.py | 219 +--- .../human_protocol_sdk/decorators.py | 17 + .../escrow/escrow_client.py | 303 ++--- .../kvstore/kvstore_client.py | 51 +- .../staking/staking_client.py | 83 +- .../human_protocol_sdk/utils.py | 91 +- .../escrow/test_escrow_client.py | 1116 +++++++---------- .../kvstore/test_kvstore_client.py | 218 ++-- .../staking/test_staking_client.py | 346 ++--- 9 files changed, 1027 insertions(+), 1417 deletions(-) create mode 100644 packages/sdk/python/human-protocol-sdk/human_protocol_sdk/decorators.py diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index 077bc0b480..56800cbcd2 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -1,197 +1,42 @@ -import datetime +from eth_typing import URI +from web3 import Web3 +from web3.middleware import SignAndSendRawMiddlewareBuilder +from web3.providers.auto import load_provider_from_uri -from human_protocol_sdk.constants import ChainId, OrderDirection, Status -from human_protocol_sdk.escrow import EscrowUtils -from human_protocol_sdk.worker import WorkerUtils -from human_protocol_sdk.filter import ( - EscrowFilter, - PayoutFilter, - StatisticsFilter, - WorkerFilter, -) -from human_protocol_sdk.statistics import ( - StatisticsClient, - HMTHoldersParam, -) -from human_protocol_sdk.operator import OperatorUtils, OperatorFilter -from human_protocol_sdk.agreement import agreement - - -def get_escrow_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_escrow_statistics()) - print( - statistics_client.get_escrow_statistics( - StatisticsFilter( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - -def get_worker_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_worker_statistics()) - print( - statistics_client.get_worker_statistics( - StatisticsFilter( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - -def get_payment_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_payment_statistics()) - print( - statistics_client.get_payment_statistics( - StatisticsFilter( - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) +from human_protocol_sdk.escrow import EscrowClient +from human_protocol_sdk.staking import StakingClient +from human_protocol_sdk.kvstore import KVStoreClient -def get_hmt_statistics(statistics_client: StatisticsClient): - print(statistics_client.get_hmt_statistics()) - - -def get_hmt_holders(statistics_client: StatisticsClient): - print( - statistics_client.get_hmt_holders( - HMTHoldersParam( - order_direction="desc", - ) - ) - ) - print( - statistics_client.get_hmt_holders( - HMTHoldersParam( - order_direction="asc", - ) - ) +def get_w3_with_priv_key(priv_key: str): + w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) + gas_payer = w3.eth.account.from_key(priv_key) + w3.eth.default_account = gas_payer.address + w3.middleware_onion.inject( + SignAndSendRawMiddlewareBuilder.build(priv_key), + "SignAndSendRawMiddlewareBuilder", + layer=0, ) - print( - statistics_client.get_hmt_holders( - HMTHoldersParam(address="0xf183b3b34e70dd17859455389a3ab54d49d41e6f") - ) - ) - - -def get_hmt_daily_data(statistics_client: StatisticsClient): - print( - statistics_client.get_hmt_daily_data( - StatisticsFilter( - date_from=datetime.datetime(2024, 5, 8), - date_to=datetime.datetime(2024, 6, 8), - ) - ) - ) - - -def get_payouts(): - filter = PayoutFilter( - chain_id=ChainId.POLYGON, - first=5, - skip=1, - order_direction=OrderDirection.ASC, - ) - - payouts = EscrowUtils.get_payouts(filter) - for payout in payouts: - print( - f"Payout ID: {payout.id}, Amount: {payout.amount}, Recipient: {payout.recipient}" - ) + return (w3, gas_payer) -def get_escrows(): - print( - EscrowUtils.get_escrows( - EscrowFilter( - chain_id=ChainId.POLYGON_AMOY, - status=Status.Pending, - date_from=datetime.datetime(2023, 5, 8), - date_to=datetime.datetime(2023, 6, 8), - ) - ) - ) - - print( - ( - EscrowUtils.get_escrow( - ChainId.POLYGON_AMOY, "0xf9ec66feeafb850d85b88142a7305f55e0532959" - ) - ) - ) - - -def get_operators(): - operators = OperatorUtils.get_operators( - OperatorFilter(chain_id=ChainId.POLYGON_AMOY) - ) - print(operators) - print(OperatorUtils.get_operator(ChainId.POLYGON_AMOY, operators[0].address)) - print( - OperatorUtils.get_operators( - OperatorFilter(chain_id=ChainId.POLYGON_AMOY, roles="Job Launcher") - ) - ) - operators = OperatorUtils.get_operators( - OperatorFilter(chain_id=ChainId.POLYGON_AMOY, roles=["Job Launcher"]) - ) - print(len(operators)) - - operators = OperatorUtils.get_operators( - OperatorFilter( - chain_id=ChainId.POLYGON_AMOY, - min_amount_staked=1, - roles=["Job Launcher", "Reputation Oracle"], - ) - ) - print(len(operators)) - - -def get_workers(): - workers = WorkerUtils.get_workers( - WorkerFilter(chain_id=ChainId.POLYGON_AMOY, first=2) - ) - print(workers) - print(WorkerUtils.get_worker(ChainId.POLYGON_AMOY, workers[0].address)) - workers = WorkerUtils.get_workers( - WorkerFilter(chain_id=ChainId.POLYGON_AMOY, worker_address=workers[0].address) - ) - print(len(workers)) - - -def agreement_example(): - annotations = [ - ["cat", "not", "cat"], - ["cat", "cat", "cat"], - ["not", "not", "not"], - ["cat", "nan", "not"], - ] - - agreement_report = agreement(annotations, measure="fleiss_kappa") - print(agreement_report) - - -if __name__ == "__main__": - statistics_client = StatisticsClient() +(w3, gas_payer) = get_w3_with_priv_key( + "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +) +escrow_client = EscrowClient(w3) - # Run single example while testing, and remove comments before commit +staking_client = StakingClient(w3) - get_escrows() - get_operators() - get_payouts() +kvstore_client = KVStoreClient(w3) - statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) - get_hmt_holders(statistics_client) - get_escrow_statistics(statistics_client) - get_hmt_statistics(statistics_client) - get_payment_statistics(statistics_client) - get_worker_statistics(statistics_client) - get_hmt_daily_data(statistics_client) +kvstore_client.set("test", "value") - agreement_example() - get_workers() +staking_client.approve_stake(10000) +staking_client.stake(10000) +amount = Web3.to_wei(5, "ether") # convert from ETH to WEI +transaction = escrow_client.create_escrow( + "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", + ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"], + "1", +) +print(f"Transaction hash: {transaction}") diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/decorators.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/decorators.py new file mode 100644 index 0000000000..f153b93bd1 --- /dev/null +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/decorators.py @@ -0,0 +1,17 @@ +class RequiresSignerError(Exception): + """Raised when a signer or required middleware is missing in the Web3 instance.""" + + pass + + +def requires_signer(method): + def wrapper(self, *args, **kwargs): + if not self.w3.eth.default_account: + raise RequiresSignerError("You must add an account to Web3 instance") + if not self.w3.middleware_onion.get("SignAndSendRawMiddlewareBuilder"): + raise RequiresSignerError( + "You must add SignAndSendRawMiddlewareBuilder middleware to Web3 instance" + ) + return method(self, *args, **kwargs) + + return wrapper diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py index e8c9854d27..b591fb5671 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/escrow/escrow_client.py @@ -66,7 +66,7 @@ def get_w3_with_priv_key(priv_key: str): get_escrow_interface, get_factory_interface, get_erc20_interface, - handle_transaction, + handle_error, ) from web3 import Web3, contract from web3 import eth @@ -75,6 +75,7 @@ def get_w3_with_priv_key(priv_key: str): from eth_utils import abi from human_protocol_sdk.utils import validate_url +from human_protocol_sdk.decorators import requires_signer LOG = logging.getLogger("human_protocol_sdk.escrow") @@ -211,6 +212,7 @@ def __init__(self, web3: Web3): address=self.network["factory_address"], abi=factory_interface["abi"] ) + @requires_signer def create_escrow( self, token_address: str, @@ -272,24 +274,24 @@ def get_w3_with_priv_key(priv_key: str): if not Web3.is_address(handler): raise EscrowClientError(f"Invalid handler address: {handler}") - transaction_receipt = handle_transaction( - self.w3, - "Create Escrow", - self.factory_contract.functions.createEscrow( + try: + tx_hash = self.factory_contract.functions.createEscrow( token_address, trusted_handlers, job_requester_id - ), - EscrowClientError, - tx_options, - ) - return next( - ( - self.factory_contract.events.LaunchedV2().process_log(log) - for log in transaction_receipt["logs"] - if log["address"] == self.network["factory_address"] - ), - None, - ).args.escrow + ).transact(tx_options or {}) + receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) + event = next( + ( + self.factory_contract.events.LaunchedV2().process_log(log) + for log in receipt["logs"] + if log["address"] == self.network["factory_address"] + ), + None, + ) + return event.args.escrow if event else None + except Exception as e: + handle_error(e, EscrowClientError) + @requires_signer def setup( self, escrow_address: str, @@ -350,23 +352,26 @@ def get_w3_with_priv_key(priv_key: str): if not Web3.is_address(escrow_address): raise EscrowClientError(f"Invalid escrow address: {escrow_address}") - handle_transaction( - self.w3, - "Setup", - self._get_escrow_contract(escrow_address).functions.setup( - escrow_config.reputation_oracle_address, - escrow_config.recording_oracle_address, - escrow_config.exchange_oracle_address, - escrow_config.reputation_oracle_fee, - escrow_config.recording_oracle_fee, - escrow_config.exchange_oracle_fee, - escrow_config.manifest_url, - escrow_config.hash, - ), - EscrowClientError, - tx_options, - ) + try: + tx_hash = ( + self._get_escrow_contract(escrow_address) + .functions.setup( + escrow_config.reputation_oracle_address, + escrow_config.recording_oracle_address, + escrow_config.exchange_oracle_address, + escrow_config.reputation_oracle_fee, + escrow_config.recording_oracle_fee, + escrow_config.exchange_oracle_fee, + escrow_config.manifest_url, + escrow_config.hash, + ) + .transact(tx_options or {}) + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) + @requires_signer def fund( self, escrow_address: str, @@ -419,18 +424,18 @@ def get_w3_with_priv_key(priv_key: str): raise EscrowClientError("Amount must be positive") token_address = self.get_token_address(escrow_address) - erc20_interface = get_erc20_interface() token_contract = self.w3.eth.contract(token_address, abi=erc20_interface["abi"]) - handle_transaction( - self.w3, - "Fund", - token_contract.functions.transfer(escrow_address, amount), - EscrowClientError, - tx_options, - ) - + try: + tx_hash = token_contract.functions.transfer( + escrow_address, amount + ).transact(tx_options or {}) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) + + @requires_signer def store_results( self, escrow_address: str, @@ -486,17 +491,18 @@ def get_w3_with_priv_key(priv_key: str): raise EscrowClientError("Invalid empty hash") if not validate_url(url): raise EscrowClientError(f"Invalid URL: {url}") - if not self.w3.eth.default_account: - raise EscrowClientError("You must add an account to Web3 instance") - - handle_transaction( - self.w3, - "Store Results", - self._get_escrow_contract(escrow_address).functions.storeResults(url, hash), - EscrowClientError, - tx_options, - ) + try: + tx_hash = ( + self._get_escrow_contract(escrow_address) + .functions.storeResults(url, hash) + .transact(tx_options or {}) + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) + + @requires_signer def complete( self, escrow_address: str, tx_options: Optional[TxParams] = None ) -> None: @@ -539,14 +545,17 @@ def get_w3_with_priv_key(priv_key: str): if not Web3.is_address(escrow_address): raise EscrowClientError(f"Invalid escrow address: {escrow_address}") - handle_transaction( - self.w3, - "Complete", - self._get_escrow_contract(escrow_address).functions.complete(), - EscrowClientError, - tx_options, - ) + try: + tx_hash = ( + self._get_escrow_contract(escrow_address) + .functions.complete() + .transact(tx_options or {}) + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) + @requires_signer def bulk_payout( self, escrow_address: str, @@ -621,11 +630,8 @@ def get_w3_with_priv_key(priv_key: str): self.ensure_correct_bulk_payout_input( escrow_address, recipients, amounts, final_results_url, final_results_hash ) - - if force_complete: - handle_transaction( - self.w3, - "Bulk Payout", + try: + contract_func = ( self._get_escrow_contract(escrow_address).functions.bulkPayOut( recipients, amounts, @@ -633,20 +639,16 @@ def get_w3_with_priv_key(priv_key: str): final_results_hash, txId, force_complete, - ), - EscrowClientError, - tx_options, - ) - else: - handle_transaction( - self.w3, - "Bulk Payout", - self._get_escrow_contract(escrow_address).functions.bulkPayOut( + ) + if force_complete + else self._get_escrow_contract(escrow_address).functions.bulkPayOut( recipients, amounts, final_results_url, final_results_hash, txId - ), - EscrowClientError, - tx_options, + ) ) + tx_hash = contract_func.transact(tx_options or {}) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) def create_bulk_payout_transaction( self, @@ -818,6 +820,7 @@ def ensure_correct_bulk_payout_input( if not final_results_hash: raise EscrowClientError("Invalid empty final results hash") + @requires_signer def cancel( self, escrow_address: str, tx_options: Optional[TxParams] = None ) -> EscrowCancel: @@ -866,41 +869,42 @@ def get_w3_with_priv_key(priv_key: str): if not Web3.is_address(escrow_address): raise EscrowClientError(f"Invalid escrow address: {escrow_address}") - transaction_receipt = handle_transaction( - self.w3, - "Cancel", - self._get_escrow_contract(escrow_address).functions.cancel(), - EscrowClientError, - tx_options, - ) - - amount_transferred = None - token_address = self.get_token_address(escrow_address) - - erc20_interface = get_erc20_interface() - token_contract = self.w3.eth.contract(token_address, abi=erc20_interface["abi"]) - - for log in transaction_receipt["logs"]: - if log["address"] == token_address: - processed_log = token_contract.events.Transfer().process_log(log) - - if ( - processed_log["event"] == "Transfer" - and processed_log["args"]["from"] == escrow_address - ): - amount_transferred = processed_log["args"]["value"] - break - - if amount_transferred is None: - raise EscrowClientError("Transfer Event Not Found in Transaction Logs") + try: + tx_hash = ( + self._get_escrow_contract(escrow_address) + .functions.cancel() + .transact(tx_options or {}) + ) + receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) - escrow_cancel_data = EscrowCancel( - tx_hash=transaction_receipt["transactionHash"].hex(), - amount_refunded=amount_transferred, - ) + amount_transferred = None + token_address = self.get_token_address(escrow_address) + erc20_interface = get_erc20_interface() + token_contract = self.w3.eth.contract( + token_address, abi=erc20_interface["abi"] + ) - return escrow_cancel_data + for log in receipt["logs"]: + if log["address"] == token_address: + processed_log = token_contract.events.Transfer().process_log(log) + if ( + processed_log["event"] == "Transfer" + and processed_log["args"]["from"] == escrow_address + ): + amount_transferred = processed_log["args"]["value"] + break + + if amount_transferred is None: + raise EscrowClientError("Transfer Event Not Found in Transaction Logs") + + return EscrowCancel( + tx_hash=receipt["transactionHash"].hex(), + amount_refunded=amount_transferred, + ) + except Exception as e: + handle_error(e, EscrowClientError) + @requires_signer def add_trusted_handlers( self, escrow_address: str, @@ -957,16 +961,17 @@ def get_w3_with_priv_key(priv_key: str): if not Web3.is_address(handler): raise EscrowClientError(f"Invalid handler address: {handler}") - handle_transaction( - self.w3, - "Add Trusted Handlers", - self._get_escrow_contract(escrow_address).functions.addTrustedHandlers( - handlers - ), - EscrowClientError, - tx_options, - ) + try: + tx_hash = ( + self._get_escrow_contract(escrow_address) + .functions.addTrustedHandlers(handlers) + .transact(tx_options or {}) + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) + @requires_signer def withdraw( self, escrow_address: str, @@ -1024,40 +1029,40 @@ def get_w3_with_priv_key(priv_key: str): if not Web3.is_address(token_address): raise EscrowClientError(f"Invalid token address: {token_address}") - transaction_receipt = handle_transaction( - self.w3, - "Withdraw", - self._get_escrow_contract(escrow_address).functions.withdraw(token_address), - EscrowClientError, - tx_options, - ) - - amount_transferred = None - - erc20_interface = get_erc20_interface() - token_contract = self.w3.eth.contract(token_address, abi=erc20_interface["abi"]) - - for log in transaction_receipt["logs"]: - if log["address"] == token_address: - processed_log = token_contract.events.Transfer().process_log(log) - - if ( - processed_log["event"] == "Transfer" - and processed_log["args"]["from"] == escrow_address - ): - amount_transferred = processed_log["args"]["value"] - break - - if amount_transferred is None: - raise EscrowClientError("Transfer Event Not Found in Transaction Logs") + try: + tx_hash = ( + self._get_escrow_contract(escrow_address) + .functions.withdraw(token_address) + .transact(tx_options or {}) + ) + receipt = self.w3.eth.wait_for_transaction_receipt(tx_hash) - escrow_withdraw_data = EscrowWithdraw( - tx_hash=transaction_receipt["transactionHash"].hex(), - token_address=token_address, - amount_withdrawn=amount_transferred, - ) + amount_transferred = None + erc20_interface = get_erc20_interface() + token_contract = self.w3.eth.contract( + token_address, abi=erc20_interface["abi"] + ) - return escrow_withdraw_data + for log in receipt["logs"]: + if log["address"] == token_address: + processed_log = token_contract.events.Transfer().process_log(log) + if ( + processed_log["event"] == "Transfer" + and processed_log["args"]["from"] == escrow_address + ): + amount_transferred = processed_log["args"]["value"] + break + + if amount_transferred is None: + raise EscrowClientError("Transfer Event Not Found in Transaction Logs") + + return EscrowWithdraw( + tx_hash=receipt["transactionHash"].hex(), + token_address=token_address, + amount_withdrawn=amount_transferred, + ) + except Exception as e: + handle_error(e, EscrowClientError) def get_balance(self, escrow_address: str) -> Decimal: """ diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_client.py index 9cfac5bd49..54b366cba1 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/kvstore/kvstore_client.py @@ -57,10 +57,11 @@ def get_w3_with_priv_key(priv_key: str): import requests -from human_protocol_sdk.constants import NETWORKS, ChainId, KVStoreKeys +from human_protocol_sdk.constants import NETWORKS, ChainId +from human_protocol_sdk.decorators import requires_signer from human_protocol_sdk.utils import ( get_kvstore_interface, - handle_transaction, + handle_error, validate_url, ) from web3 import Web3 @@ -116,6 +117,7 @@ def __init__(self, web3: Web3, gas_limit: Optional[int] = None): ) self.gas_limit = gas_limit + @requires_signer def set(self, key: str, value: str, tx_options: Optional[TxParams] = None) -> None: """ Sets the value of a key-value pair in the contract. @@ -155,14 +157,15 @@ def get_w3_with_priv_key(priv_key: str): if not key: raise KVStoreClientError("Key cannot be empty") - handle_transaction( - self.w3, - "Set", - self.kvstore_contract.functions.set(key, value), - KVStoreClientError, - tx_options, - ) + try: + tx_hash = self.kvstore_contract.functions.set(key, value).transact( + tx_options or {} + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, KVStoreClientError) + @requires_signer def set_bulk( self, keys: List[str], values: List[str], tx_options: Optional[TxParams] = None ) -> None: @@ -211,14 +214,15 @@ def get_w3_with_priv_key(priv_key: str): if len(keys) != len(values): raise KVStoreClientError("Arrays must have the same length") - handle_transaction( - self.w3, - "Set Bulk", - self.kvstore_contract.functions.setBulk(keys, values), - KVStoreClientError, - tx_options, - ) + try: + tx_hash = self.kvstore_contract.functions.setBulk(keys, values).transact( + tx_options or {} + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, KVStoreClientError) + @requires_signer def set_file_url_and_hash( self, url: str, @@ -268,13 +272,10 @@ def get_w3_with_priv_key(priv_key: str): content = requests.get(url).text content_hash = self.w3.keccak(text=content).hex() - - handle_transaction( - self.w3, - "Set Bulk", - self.kvstore_contract.functions.setBulk( + try: + tx_hash = self.kvstore_contract.functions.setBulk( [key, key + "_hash"], [url, content_hash] - ), - KVStoreClientError, - tx_options, - ) + ).transact(tx_options or {}) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, KVStoreClientError) diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_client.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_client.py index 097177953a..c9a7f2e53c 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_client.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/staking/staking_client.py @@ -63,11 +63,12 @@ def get_w3_with_priv_key(priv_key: str): from web3.types import TxParams from human_protocol_sdk.constants import ChainId, NETWORKS +from human_protocol_sdk.decorators import requires_signer from human_protocol_sdk.utils import ( get_erc20_interface, get_factory_interface, get_staking_interface, - handle_transaction, + handle_error, ) LOG = logging.getLogger("human_protocol_sdk.staking") @@ -130,6 +131,7 @@ def __init__(self, w3: Web3): address=self.network["staking_address"], abi=staking_interface["abi"] ) + @requires_signer def approve_stake( self, amount: Decimal, tx_options: Optional[TxParams] = None ) -> None: @@ -173,17 +175,15 @@ def get_w3_with_priv_key(priv_key: str): if amount <= 0: raise StakingClientError("Amount to approve must be greater than 0") - - handle_transaction( - self.w3, - "Approve stake", - self.hmtoken_contract.functions.approve( + try: + tx_hash = self.hmtoken_contract.functions.approve( self.network["staking_address"], amount - ), - StakingClientError, - tx_options, - ) + ).transact(tx_options or {}) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, StakingClientError) + @requires_signer def stake(self, amount: Decimal, tx_options: Optional[TxParams] = None) -> None: """Stakes HMT token. @@ -228,15 +228,15 @@ def get_w3_with_priv_key(priv_key: str): if amount <= 0: raise StakingClientError("Amount to stake must be greater than 0") + try: + tx_hash = self.staking_contract.functions.stake(amount).transact( + tx_options or {} + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, StakingClientError) - handle_transaction( - self.w3, - "Stake HMT", - self.staking_contract.functions.stake(amount), - StakingClientError, - tx_options, - ) - + @requires_signer def unstake(self, amount: Decimal, tx_options: Optional[TxParams] = None) -> None: """Unstakes HMT token. @@ -279,15 +279,15 @@ def get_w3_with_priv_key(priv_key: str): if amount <= 0: raise StakingClientError("Amount to unstake must be greater than 0") + try: + tx_hash = self.staking_contract.functions.unstake(amount).transact( + tx_options or {} + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, StakingClientError) - handle_transaction( - self.w3, - "Unstake HMT", - self.staking_contract.functions.unstake(amount), - StakingClientError, - tx_options, - ) - + @requires_signer def withdraw(self, tx_options: Optional[TxParams] = None) -> None: """Withdraws HMT token. @@ -325,14 +325,15 @@ def get_w3_with_priv_key(priv_key: str): staking_client.withdraw() """ - handle_transaction( - self.w3, - "Withdraw HMT", - self.staking_contract.functions.withdraw(), - StakingClientError, - tx_options, - ) + try: + tx_hash = self.staking_contract.functions.withdraw().transact( + tx_options or {} + ) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, StakingClientError) + @requires_signer def slash( self, slasher: str, @@ -391,19 +392,15 @@ def get_w3_with_priv_key(priv_key: str): if amount <= 0: raise StakingClientError("Amount to slash must be greater than 0") - if not self._is_valid_escrow(escrow_address): raise StakingClientError(f"Invalid escrow address: {escrow_address}") - - handle_transaction( - self.w3, - "Slash HMT", - self.staking_contract.functions.slash( + try: + tx_hash = self.staking_contract.functions.slash( slasher, staker, escrow_address, amount - ), - StakingClientError, - tx_options, - ) + ).transact(tx_options or {}) + self.w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, StakingClientError) def get_staker_info(self, staker_address: str) -> dict: """Retrieves comprehensive staking information for a staker. diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index 61dfd5bed3..28bf1993c3 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -220,54 +220,59 @@ def get_data_from_subgraph(network: dict, query: str, params: dict = None): ) -def handle_transaction( - w3: Web3, tx_name: str, tx, exception: Exception, tx_options: Optional[TxParams] -): - """Executes the transaction and waits for the receipt. +def handle_error(e, exception_class): + """ + Handles and translates errors raised during contract transactions. - :param w3: Web3 instance - :param tx_name: Name of the transaction - :param tx: Transaction object - :param exception: Exception class to raise in case of error - :param tx_options: (Optional) Additional transaction parameters - - If provided, can include values like 'gas', 'gas_price', 'nonce', etc - - If 'gas' is not specified or is None, it will be estimated using tx.estimate_gas() + This function captures exceptions (especially ContractLogicError from web3.py), + extracts meaningful revert reasons if present, logs unexpected errors, and raises + a custom exception with a clear message for SDK users. - :return: The transaction receipt + :param e: The exception object raised during a transaction. + :param exception_class: The custom exception class to raise (e.g., EscrowClientError). - :validate: - - There must be a default account + :raises exception_class: With a detailed error message, including contract revert reasons if available. - :raise exception: If the transaction fails + :example: + try: + tx_hash = contract.functions.someMethod(...).transact() + w3.eth.wait_for_transaction_receipt(tx_hash) + except Exception as e: + handle_error(e, EscrowClientError) """ - if not w3.eth.default_account: - raise exception("You must add an account to Web3 instance") - if not w3.middleware_onion.get("SignAndSendRawMiddlewareBuilder"): - raise exception( - "You must add SignAndSendRawMiddlewareBuilder middleware to Web3 instance" - ) - try: - if tx_options and tx_options.get("gas") is None: - tx_options["gas"] = tx.estimate_gas() - elif tx_options is None: - tx_options = {"gas": tx.estimate_gas()} - tx_hash = tx.transact(tx_options) - return w3.eth.wait_for_transaction_receipt(tx_hash) - except ContractLogicError as e: - start_index = e.args[0].find("execution reverted: ") + len( - "execution reverted: " - ) - message = e.args[0][start_index:] - raise exception(f"{tx_name} transaction failed: {message}") - except Exception as e: - logger.exception(f"Handle transaction error: {e}") - if "reverted with reason string" in e.args[0]: - start_index = e.args[0].find("'") + 1 - end_index = e.args[0].rfind("'") - message = e.args[0][start_index:end_index] - raise exception(f"{tx_name} transaction failed: {message}") - else: - raise exception(f"{tx_name} transaction failed.") + import re + + def extract_reason(msg): + patterns = [ + r"reverted with reason string '([^']+)'", + r"execution reverted: ([^\"']+)", + r"Error: VM Exception while processing transaction: reverted with reason string '([^']+)'", + ] + for pattern in patterns: + match = re.search(pattern, msg) + if match: + return match.group(1) + return msg.strip() + + if isinstance(e, ContractLogicError): + msg = str(e) + msg = extract_reason(msg) + raise exception_class(f"Contract execution failed: {msg}") + else: + logger.exception(f"Transaction error: {e}") + msg = str(e) + # If error has a 'message' attribute or dict, try to extract it + if hasattr(e, "message"): + msg = getattr(e, "message") + elif ( + hasattr(e, "args") + and e.args + and isinstance(e.args[0], dict) + and "message" in e.args[0] + ): + msg = e.args[0]["message"] + msg = extract_reason(msg) + raise exception_class(f"Transaction failed: {msg}") def validate_url(url: str) -> bool: diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py index ac0f38ae57..4fce80e892 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/escrow/test_escrow_client.py @@ -1,10 +1,11 @@ -from types import SimpleNamespace import unittest from datetime import datetime from test.human_protocol_sdk.utils import DEFAULT_GAS_PAYER_PRIV -from unittest.mock import MagicMock, PropertyMock, patch, ANY +from types import SimpleNamespace +from unittest.mock import ANY, MagicMock, PropertyMock, patch from human_protocol_sdk.constants import NETWORKS, ChainId, Status +from human_protocol_sdk.decorators import RequiresSignerError from human_protocol_sdk.escrow import EscrowClient, EscrowClientError, EscrowConfig from human_protocol_sdk.filter import EscrowFilter, FilterError from web3 import Web3 @@ -335,39 +336,40 @@ def test_escrow_config_invalid_hash(self): self.assertEqual("Invalid empty manifest hash", str(cm.exception)) def test_create_escrow(self): - mock_function_create = MagicMock() - self.escrow.factory_contract.functions.createEscrow = mock_function_create escrow_address = "0x1234567890123456789012345678901234567890" token_address = "0x1234567890123456789012345678901234567890" job_requester_id = "job-requester" trusted_handlers = [self.w3.eth.default_account] - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - with patch( - "human_protocol_sdk.escrow.escrow_client.next" - ) as mock_function_next: - mock_function_next.return_value = SimpleNamespace( - args=SimpleNamespace(escrow=escrow_address) - ) - response = self.escrow.create_escrow( - token_address, trusted_handlers, job_requester_id - ) - - self.assertEqual(response, escrow_address) - mock_function_create.assert_called_once_with( - token_address, trusted_handlers, job_requester_id - ) - mock_function_next.assert_called_once() - - mock_function.assert_called_once_with( - self.w3, - "Create Escrow", - mock_function_create.return_value, - EscrowClientError, - None, - ) + mock_create = MagicMock() + mock_create.transact.return_value = "tx_hash" + self.escrow.factory_contract.functions.createEscrow = MagicMock( + return_value=mock_create + ) + mock_event = MagicMock() + mock_event.args.escrow = escrow_address + mock_events = MagicMock() + mock_events.LaunchedV2().process_log.return_value = mock_event + self.escrow.factory_contract.events = mock_events + self.escrow.network["factory_address"] = ( + "0x1234567890123456789012345678901234567890" + ) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": [{"address": self.escrow.network["factory_address"]}]} + ) + + result = self.escrow.create_escrow( + token_address, trusted_handlers, job_requester_id + ) + + self.escrow.factory_contract.functions.createEscrow.assert_called_once_with( + token_address, trusted_handlers, job_requester_id + ) + mock_create.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + self.assertEqual(result, escrow_address) def test_create_escrow_invalid_token(self): token_address = "invalid_address" @@ -400,7 +402,7 @@ def test_create_escrow_without_account(self): token_address = "0x1234567890123456789012345678901234567890" trusted_handlers = ["0x1234567890123456789012345678901234567890"] job_requester_id = "job-requester" - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.create_escrow( token_address, trusted_handlers, job_requester_id ) @@ -415,37 +417,45 @@ def test_create_escrow_with_tx_options(self): trusted_handlers = [self.w3.eth.default_account] tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - with patch( - "human_protocol_sdk.escrow.escrow_client.next" - ) as mock_function_next: - mock_function_next.return_value = SimpleNamespace( - args=SimpleNamespace(escrow=escrow_address) - ) - response = self.escrow.create_escrow( - token_address, trusted_handlers, job_requester_id, tx_options - ) - - self.assertEqual(response, escrow_address) - mock_function_create.assert_called_once_with( - token_address, trusted_handlers, job_requester_id - ) - mock_function_next.assert_called_once() - - mock_function.assert_called_once_with( - self.w3, - "Create Escrow", - mock_function_create.return_value, - EscrowClientError, - tx_options, - ) + mock_create = MagicMock() + mock_create.transact.return_value = "tx_hash" + self.escrow.factory_contract.functions.createEscrow = MagicMock( + return_value=mock_create + ) + mock_event = MagicMock() + mock_event.args.escrow = escrow_address + mock_events = MagicMock() + mock_events.LaunchedV2().process_log.return_value = mock_event + self.escrow.factory_contract.events = mock_events + self.escrow.network["factory_address"] = ( + "0x1234567890123456789012345678901234567890" + ) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": [{"address": self.escrow.network["factory_address"]}]} + ) + + result = self.escrow.create_escrow( + token_address, trusted_handlers, job_requester_id, tx_options + ) + + self.escrow.factory_contract.functions.createEscrow.assert_called_once_with( + token_address, trusted_handlers, job_requester_id + ) + mock_create.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + self.assertEqual(result, escrow_address) def test_setup(self): mock_contract = MagicMock() - mock_contract.functions.setup = MagicMock() + mock_setup = MagicMock() + mock_setup.transact.return_value = "tx_hash" + mock_contract.functions.setup = MagicMock(return_value=mock_setup) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" escrow_config = EscrowConfig( "0x1234567890123456789012345678901234567890", @@ -458,29 +468,23 @@ def test_setup(self): "test", ) - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.setup(escrow_address, escrow_config) + self.escrow.setup(escrow_address, escrow_config) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.setup.assert_called_once_with( - escrow_config.recording_oracle_address, - escrow_config.reputation_oracle_address, - escrow_config.exchange_oracle_address, - escrow_config.recording_oracle_fee, - escrow_config.reputation_oracle_fee, - escrow_config.exchange_oracle_fee, - escrow_config.manifest_url, - escrow_config.hash, - ) - mock_function.assert_called_once_with( - self.w3, - "Setup", - mock_contract.functions.setup.return_value, - EscrowClientError, - None, - ) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.setup.assert_called_once_with( + escrow_config.reputation_oracle_address, + escrow_config.recording_oracle_address, + escrow_config.exchange_oracle_address, + escrow_config.reputation_oracle_fee, + escrow_config.recording_oracle_fee, + escrow_config.exchange_oracle_fee, + escrow_config.manifest_url, + escrow_config.hash, + ) + mock_setup.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_setup_invalid_address(self): escrow_address = "test" @@ -518,7 +522,7 @@ def test_setup_without_account(self): "https://www.example.com/result", "test", ) - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.setup(escrow_address, escrow_config) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -544,7 +548,7 @@ def test_setup_invalid_status(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.setup(escrow_address, escrow_config) self.assertEqual( - "Setup transaction failed: Escrow not in Launched status state", + "Transaction failed: Escrow not in Launched status state", str(cm.exception), ) @@ -570,33 +574,18 @@ def test_setup_invalid_caller(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.setup(escrow_address, escrow_config) self.assertEqual( - "Setup transaction failed: Address calling not trusted", str(cm.exception) - ) - - def test_setup_invalid_escrow(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) - escrow_address = "0x1234567890123456789012345678901234567890" - escrow_config = EscrowConfig( - "0x1234567890123456789012345678901234567890", - "0x1234567890123456789012345678901234567890", - "0x1234567890123456789012345678901234567890", - 10, - 10, - 10, - "https://www.example.com/result", - "test", - ) - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.setup(escrow_address, escrow_config) - self.assertEqual( - "Escrow address is not provided by the factory", str(cm.exception) + "Transaction failed: Address calling not trusted", str(cm.exception) ) def test_setup_with_tx_options(self): mock_contract = MagicMock() - mock_contract.functions.setup = MagicMock() + mock_setup = MagicMock() + mock_setup.transact.return_value = "tx_hash" + mock_contract.functions.setup = MagicMock(return_value=mock_setup) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" escrow_config = EscrowConfig( "0x1234567890123456789012345678901234567890", @@ -610,33 +599,47 @@ def test_setup_with_tx_options(self): ) tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_handle_transaction: - self.escrow.setup(escrow_address, escrow_config, tx_options) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Setup", - mock_contract.functions.setup.return_value, - EscrowClientError, - tx_options, - ) + self.escrow.setup(escrow_address, escrow_config, tx_options) + + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.setup.assert_called_once_with( + escrow_config.reputation_oracle_address, + escrow_config.recording_oracle_address, + escrow_config.exchange_oracle_address, + escrow_config.reputation_oracle_fee, + escrow_config.recording_oracle_fee, + escrow_config.exchange_oracle_fee, + escrow_config.manifest_url, + escrow_config.hash, + ) + mock_setup.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_fund(self): escrow_address = token_address = "0x1234567890123456789012345678901234567890" amount = 100 self.escrow.get_token_address = MagicMock(return_value=token_address) + mock_token_contract = MagicMock() + mock_transfer = MagicMock() + mock_transfer.transact.return_value = "tx_hash" + mock_token_contract.functions.transfer = MagicMock(return_value=mock_transfer) + self.escrow.w3.eth.contract = MagicMock(return_value=mock_token_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.fund(escrow_address, amount) + self.escrow.fund(escrow_address, amount) - self.escrow.get_token_address.assert_called_once_with(escrow_address) - mock_function.assert_called_once_with( - self.w3, "Fund", ANY, EscrowClientError, None - ) + self.escrow.get_token_address.assert_called_once_with(escrow_address) + mock_token_contract.functions.transfer.assert_called_once_with( + escrow_address, amount + ) + mock_transfer.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_fund_invalid_address(self): escrow_address = "invalid_address" @@ -666,7 +669,7 @@ def test_fund_without_account(self): escrowClient.get_token_address = MagicMock(return_value=token_address) - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.fund(escrow_address, amount) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -675,45 +678,49 @@ def test_fund_with_tx_options(self): self.escrow.factory_contract.functions.createEscrow = mock_function_create escrow_address = token_address = "0x1234567890123456789012345678901234567890" amount = 100 - self.escrow.get_token_address = MagicMock(return_value=token_address) tx_options = {"gas": 50000} + self.escrow.get_token_address = MagicMock(return_value=token_address) + mock_token_contract = MagicMock() + mock_transfer = MagicMock() + mock_transfer.transact.return_value = "tx_hash" + mock_token_contract.functions.transfer = MagicMock(return_value=mock_transfer) + self.escrow.w3.eth.contract = MagicMock(return_value=mock_token_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.fund(escrow_address, amount, tx_options) - - self.escrow.get_token_address.assert_called_once_with(escrow_address) - mock_function.assert_called_once_with( - self.w3, - "Fund", - ANY, - EscrowClientError, - tx_options, - ) + self.escrow.fund(escrow_address, amount, tx_options) + + self.escrow.get_token_address.assert_called_once_with(escrow_address) + mock_token_contract.functions.transfer.assert_called_once_with( + escrow_address, amount + ) + mock_transfer.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_store_results(self): mock_contract = MagicMock() - mock_contract.functions.storeResults = MagicMock() + mock_store = MagicMock() + mock_store.transact.return_value = "tx_hash" + mock_contract.functions.storeResults = MagicMock(return_value=mock_store) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" url = "https://www.example.com/result" hash = "test" - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.store_results(escrow_address, url, hash) + self.escrow.store_results(escrow_address, url, hash) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.storeResults.assert_called_once_with(url, hash) - mock_function.assert_called_once_with( - self.w3, - "Store Results", - mock_contract.functions.storeResults.return_value, - EscrowClientError, - None, - ) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.storeResults.assert_called_once_with(url, hash) + mock_store.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_store_results_invalid_address(self): escrow_address = "invalid_address" @@ -754,7 +761,7 @@ def test_store_results_without_account(self): url = "https://www.example.com/result" hash = "test" - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.store_results(escrow_address, url, hash) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -772,7 +779,7 @@ def test_store_results_invalid_status(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.store_results(escrow_address, url, hash) self.assertEqual( - "Store Results transaction failed: Escrow not in Pending or Partial status state", + "Transaction failed: Escrow not in Pending or Partial status state", str(cm.exception), ) @@ -790,52 +797,43 @@ def test_store_results_invalid_caller(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.store_results(escrow_address, url, hash) self.assertEqual( - "Store Results transaction failed: Address calling not trusted", - str(cm.exception), - ) - - def test_store_results_invalid_escrow(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) - escrow_address = "0x1234567890123456789012345678901234567890" - url = "https://www.example.com/result" - hash = "test" - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.store_results(escrow_address, url, hash) - self.assertEqual( - "Escrow address is not provided by the factory", + "Transaction failed: Address calling not trusted", str(cm.exception), ) def test_store_results_with_tx_options(self): mock_contract = MagicMock() - mock_contract.functions.storeResults = MagicMock() + mock_store = MagicMock() + mock_store.transact.return_value = "tx_hash" + mock_contract.functions.storeResults = MagicMock(return_value=mock_store) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" url = "https://www.example.com/result" hash = "test" tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.store_results(escrow_address, url, hash, tx_options) - - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.storeResults.assert_called_once_with(url, hash) - mock_function.assert_called_once_with( - self.w3, - "Store Results", - mock_contract.functions.storeResults.return_value, - EscrowClientError, - tx_options, - ) + self.escrow.store_results(escrow_address, url, hash, tx_options) + + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.storeResults.assert_called_once_with(url, hash) + mock_store.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_bulk_payout(self): mock_contract = MagicMock() - mock_contract.functions.bulkPayOut = MagicMock() + mock_bulk = MagicMock() + mock_bulk.transact.return_value = "tx_hash" + mock_contract.functions.bulkPayOut = MagicMock(return_value=mock_bulk) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) self.escrow.get_balance = MagicMock(return_value=100) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" recipients = ["0x1234567890123456789012345678901234567890"] amounts = [100] @@ -843,35 +841,34 @@ def test_bulk_payout(self): final_results_hash = "test" txId = 1 - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - ) + self.escrow.bulk_payout( + escrow_address, + recipients, + amounts, + final_results_url, + final_results_hash, + txId, + ) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.bulkPayOut.assert_called_once_with( - recipients, amounts, final_results_url, final_results_hash, txId - ) - mock_function.assert_called_once_with( - self.w3, - "Bulk Payout", - mock_contract.functions.bulkPayOut.return_value, - EscrowClientError, - None, - ) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.bulkPayOut.assert_called_once_with( + recipients, amounts, final_results_url, final_results_hash, txId + ) + mock_bulk.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_bulk_payout_with_force_complete(self): mock_contract = MagicMock() - mock_contract.functions.bulkPayOut = MagicMock() + mock_bulk = MagicMock() + mock_bulk.transact.return_value = "tx_hash" + mock_contract.functions.bulkPayOut = MagicMock(return_value=mock_bulk) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) self.escrow.get_balance = MagicMock(return_value=100) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" recipients = ["0x1234567890123456789012345678901234567890"] amounts = [100] @@ -879,30 +876,24 @@ def test_bulk_payout_with_force_complete(self): final_results_hash = "test" txId = 1 - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - True, - ) + self.escrow.bulk_payout( + escrow_address, + recipients, + amounts, + final_results_url, + final_results_hash, + txId, + True, + ) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.bulkPayOut.assert_called_once_with( - recipients, amounts, final_results_url, final_results_hash, txId, True - ) - mock_function.assert_called_once_with( - self.w3, - "Bulk Payout", - mock_contract.functions.bulkPayOut.return_value, - EscrowClientError, - None, - ) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.bulkPayOut.assert_called_once_with( + recipients, amounts, final_results_url, final_results_hash, txId, True + ) + mock_bulk.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_bulk_payout_invalid_address(self): escrow_address = "0x1234567890123456789012345678901234567890" @@ -1110,7 +1101,7 @@ def test_bulk_payout_without_account(self): txId = 1 escrowClient.get_balance = MagicMock(return_value=100) - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.bulk_payout( escrow_address, recipients, @@ -1121,153 +1112,43 @@ def test_bulk_payout_without_account(self): ) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) - def test_bulk_payout_invalid_escrow_address(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) - self.escrow.get_balance = MagicMock(return_value=100) - escrow_address = "0x1234567890123456789012345678901234567890" - recipients = ["0x1234567890123456789012345678901234567890"] - amounts = [100] - final_results_url = "https://www.example.com/result" - final_results_hash = "test" - txId = 1 - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - ) - self.assertEqual( - "Escrow address is not provided by the factory", str(cm.exception) - ) - - def test_bulk_payout_exceed_max_value(self): + def test_bulk_payout_with_tx_options(self): mock_contract = MagicMock() - mock_contract.functions.bulkPayOut = MagicMock() - mock_contract.functions.bulkPayOut.return_value.transact.side_effect = Exception( - "Error: VM Exception while processing transaction: reverted with reason string 'Bulk value too high'." - ) + mock_bulk = MagicMock() + mock_bulk.transact.return_value = "tx_hash" + mock_contract.functions.bulkPayOut = MagicMock(return_value=mock_bulk) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) self.escrow.get_balance = MagicMock(return_value=100) - escrow_address = "0x1234567890123456789012345678901234567890" - recipients = ["0x1234567890123456789012345678901234567890"] - amounts = [100] - final_results_url = "https://www.example.com/result" - final_results_hash = "test" - txId = 1 - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - ) - self.assertEqual( - "Bulk Payout transaction failed: Bulk value too high", str(cm.exception) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} ) - - def test_bulk_payout_invalid_status(self): - mock_contract = MagicMock() - mock_contract.functions.bulkPayOut = MagicMock() - mock_contract.functions.bulkPayOut.return_value.transact.side_effect = Exception( - "Error: VM Exception while processing transaction: reverted with reason string 'Invalid status'." - ) - self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - self.escrow.get_balance = MagicMock(return_value=100) escrow_address = "0x1234567890123456789012345678901234567890" recipients = ["0x1234567890123456789012345678901234567890"] amounts = [100] final_results_url = "https://www.example.com/result" final_results_hash = "test" txId = 1 + tx_options = {"gas": 50000} - with self.assertRaises(EscrowClientError) as cm: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - ) - self.assertEqual( - "Bulk Payout transaction failed: Invalid status", str(cm.exception) + self.escrow.bulk_payout( + escrow_address, + recipients, + amounts, + final_results_url, + final_results_hash, + txId, + tx_options=tx_options, ) - def test_bulk_payout_invalid_caller(self): - mock_contract = MagicMock() - mock_contract.functions.bulkPayOut = MagicMock() - mock_contract.functions.bulkPayOut.return_value.transact.side_effect = Exception( - "Error: VM Exception while processing transaction: reverted with reason string 'Address calling not trusted'." + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.bulkPayOut.assert_called_once_with( + recipients, amounts, final_results_url, final_results_hash, txId ) - self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - self.escrow.get_balance = MagicMock(return_value=100) - escrow_address = "0x1234567890123456789012345678901234567890" - recipients = ["0x1234567890123456789012345678901234567890"] - amounts = [100] - final_results_url = "https://www.example.com/result" - final_results_hash = "test" - txId = 1 - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - ) - self.assertEqual( - "Bulk Payout transaction failed: Address calling not trusted", - str(cm.exception), + mock_bulk.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" ) - def test_bulk_payout_with_tx_options(self): - mock_contract = MagicMock() - mock_contract.functions.bulkPayOut = MagicMock() - self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - self.escrow.get_balance = MagicMock(return_value=100) - escrow_address = "0x1234567890123456789012345678901234567890" - recipients = ["0x1234567890123456789012345678901234567890"] - amounts = [100] - final_results_url = "https://www.example.com/result" - final_results_hash = "test" - txId = 1 - tx_options = {"gas": 50000} - - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.bulk_payout( - escrow_address, - recipients, - amounts, - final_results_url, - final_results_hash, - txId, - False, - tx_options, - ) - - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.bulkPayOut.assert_called_once_with( - recipients, amounts, final_results_url, final_results_hash, txId - ) - mock_function.assert_called_once_with( - self.w3, - "Bulk Payout", - mock_contract.functions.bulkPayOut.return_value, - EscrowClientError, - tx_options, - ) - def test_create_bulk_payout_transaction(self): mock_contract = MagicMock() mock_contract.functions.bulkPayOut = MagicMock() @@ -1440,24 +1321,23 @@ def test_create_bulk_payout_transaction_empty_hash(self): def test_complete(self): mock_contract = MagicMock() - mock_contract.functions.complete = MagicMock() + mock_complete = MagicMock() + mock_complete.transact.return_value = "tx_hash" + mock_contract.functions.complete = MagicMock(return_value=mock_complete) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.complete(escrow_address) + self.escrow.complete(escrow_address) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.complete.assert_called_once_with() - mock_function.assert_called_once_with( - self.w3, - "Complete", - mock_contract.functions.complete.return_value, - EscrowClientError, - None, - ) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.complete.assert_called_once_with() + mock_complete.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_complete_invalid_address(self): escrow_address = "invalid_address" @@ -1476,7 +1356,7 @@ def test_complete_without_account(self): escrow_address = "0x1234567890123456789012345678901234567890" - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.complete(escrow_address) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -1492,7 +1372,7 @@ def test_complete_invalid_status(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.complete(escrow_address) self.assertEqual( - "Complete transaction failed: Escrow not in Paid state", str(cm.exception) + "Transaction failed: Escrow not in Paid state", str(cm.exception) ) def test_complete_invalid_caller(self): @@ -1507,102 +1387,68 @@ def test_complete_invalid_caller(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.complete(escrow_address) self.assertEqual( - "Complete transaction failed: Address calling not trusted", + "Transaction failed: Address calling not trusted", str(cm.exception), ) - def test_complete_invalid_address(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) - escrow_address = "0x1234567890123456789012345678901234567890" - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.complete(escrow_address) - self.assertEqual( - "Escrow address is not provided by the factory", str(cm.exception) - ) - def test_complete_with_tx_options(self): mock_contract = MagicMock() - mock_contract.functions.complete = MagicMock() + mock_complete = MagicMock() + mock_complete.transact.return_value = "tx_hash" + mock_contract.functions.complete = MagicMock(return_value=mock_complete) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.complete(escrow_address, tx_options) - - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.complete.assert_called_once_with() - mock_function.assert_called_once_with( - self.w3, - "Complete", - mock_contract.functions.complete.return_value, - EscrowClientError, - tx_options, - ) + self.escrow.complete(escrow_address, tx_options) + + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.complete.assert_called_once_with() + mock_complete.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_cancel(self): + escrow_address = "0x1234567890123456789012345678901234567890" + token_address = "0x1234567890123456789012345678901234567891" + amount_refunded = 123 + + # Mock contract and cancel function mock_contract = MagicMock() - mock_contract.functions.cancel = MagicMock() + mock_cancel = MagicMock() + mock_cancel.transact.return_value = "tx_hash" + mock_contract.functions.cancel = MagicMock(return_value=mock_cancel) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - escrow_address = "0xa76507AbFE3B67cB25F16DbC75a883D4190B7e46" - token_address = "0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4" - self.escrow.get_token_address = MagicMock(return_value=token_address) - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - tx_hash = bytes.fromhex( - "01682095d5abb0270d11a31139b9a1f410b363c84add467004e728ec831bd529" - ) - amount_refunded = 187744067287473730 - mock_function.return_value = { - "transactionHash": tx_hash, - "logs": [ - { - "logIndex": 0, - "transactionIndex": 0, - "transactionHash": tx_hash, - "blockHash": bytes.fromhex( - "92abf9325a3959a911a2581e9ea36cba3060d8b293b50e5738ff959feb95258a" - ), - "blockNumber": 5, - "address": token_address, - "data": bytes.fromhex( - "000000000000000000000000000000000000000000000000029b003c075b5e42" - ), - "topics": [ - bytes.fromhex( - "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - ), - bytes.fromhex( - "000000000000000000000000a76507abfe3b67cb25f16dbc75a883d4190b7e46" - ), - bytes.fromhex( - "0000000000000000000000005607acf0828e238099aa1784541a5abd7f975c76" - ), - ], - } - ], - } + # Mock token contract and Transfer event + token_contract = MagicMock() + token_contract.events.Transfer().process_log.return_value = { + "event": "Transfer", + "args": {"from": escrow_address, "value": amount_refunded}, + } + self.escrow.w3.eth.contract = MagicMock(return_value=token_contract) - escrow_cancel_data = self.escrow.cancel(escrow_address) + # Mock receipt with logs + receipt = {"transactionHash": b"tx_hash", "logs": [{"address": token_address}]} + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value=receipt + ) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.cancel.assert_called_once_with() - mock_function.assert_called_once_with( - self.w3, - "Cancel", - mock_contract.functions.cancel.return_value, - EscrowClientError, - None, - ) + result = self.escrow.cancel(escrow_address) - self.assertEqual(escrow_cancel_data.txHash, tx_hash.hex()) - self.assertEqual(escrow_cancel_data.amountRefunded, amount_refunded) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.cancel.assert_called_once_with() + mock_cancel.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + self.assertEqual(result.amountRefunded, amount_refunded) + self.assertEqual(result.txHash, receipt["transactionHash"].hex()) def test_cancel_invalid_address(self): escrow_address = "invalid_address" @@ -1621,7 +1467,7 @@ def test_cancel_without_account(self): escrow_address = "0x1234567890123456789012345678901234567890" - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.cancel(escrow_address) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -1637,7 +1483,7 @@ def test_cancel_invalid_status(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.cancel(escrow_address) self.assertEqual( - "Cancel transaction failed: Escrow in Paid status state", str(cm.exception) + "Transaction failed: Escrow in Paid status state", str(cm.exception) ) def test_cancel_invalid_caller(self): @@ -1652,106 +1498,71 @@ def test_cancel_invalid_caller(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.cancel(escrow_address) self.assertEqual( - "Cancel transaction failed: Address calling not trusted", str(cm.exception) + "Transaction failed: Address calling not trusted", str(cm.exception) ) - def test_cancel_invalid_escrow(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) + def test_cancel_with_tx_options(self): escrow_address = "0x1234567890123456789012345678901234567890" + token_address = "0x1234567890123456789012345678901234567891" + amount_refunded = 123 + tx_options = {"gas": 50000} - with self.assertRaises(EscrowClientError) as cm: - self.escrow.cancel(escrow_address) - self.assertEqual( - "Escrow address is not provided by the factory", str(cm.exception) - ) - - def test_cancel_with_tx_options(self): + # Mock contract and cancel function mock_contract = MagicMock() - mock_contract.functions.cancel = MagicMock() + mock_cancel = MagicMock() + mock_cancel.transact.return_value = "tx_hash" + mock_contract.functions.cancel = MagicMock(return_value=mock_cancel) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - tx_options = {"gas": 50000} - - escrow_address = "0xa76507AbFE3B67cB25F16DbC75a883D4190B7e46" - token_address = "0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4" - self.escrow.get_token_address = MagicMock(return_value=token_address) - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - tx_hash = bytes.fromhex( - "01682095d5abb0270d11a31139b9a1f410b363c84add467004e728ec831bd529" - ) - amount_refunded = 187744067287473730 - mock_function.return_value = { - "transactionHash": tx_hash, - "logs": [ - { - "logIndex": 0, - "transactionIndex": 0, - "transactionHash": tx_hash, - "blockHash": bytes.fromhex( - "92abf9325a3959a911a2581e9ea36cba3060d8b293b50e5738ff959feb95258a" - ), - "blockNumber": 5, - "address": token_address, - "data": bytes.fromhex( - "000000000000000000000000000000000000000000000000029b003c075b5e42" - ), - "topics": [ - bytes.fromhex( - "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - ), - bytes.fromhex( - "000000000000000000000000a76507abfe3b67cb25f16dbc75a883d4190b7e46" - ), - bytes.fromhex( - "0000000000000000000000005607acf0828e238099aa1784541a5abd7f975c76" - ), - ], - } - ], - } + # Mock token contract and Transfer event + token_contract = MagicMock() + token_contract.events.Transfer().process_log.return_value = { + "event": "Transfer", + "args": {"from": escrow_address, "value": amount_refunded}, + } + self.escrow.w3.eth.contract = MagicMock(return_value=token_contract) - escrow_cancel_data = self.escrow.cancel(escrow_address, tx_options) + # Mock receipt with logs + receipt = {"transactionHash": b"tx_hash", "logs": [{"address": token_address}]} + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value=receipt + ) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.cancel.assert_called_once_with() - mock_function.assert_called_once_with( - self.w3, - "Cancel", - mock_contract.functions.cancel.return_value, - EscrowClientError, - tx_options, - ) + result = self.escrow.cancel(escrow_address, tx_options) - self.assertEqual(escrow_cancel_data.txHash, tx_hash.hex()) - self.assertEqual(escrow_cancel_data.amountRefunded, amount_refunded) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.cancel.assert_called_once_with() + mock_cancel.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + self.assertEqual(result.amountRefunded, amount_refunded) + self.assertEqual(result.txHash, receipt["transactionHash"].hex()) def test_add_trusted_handlers(self): mock_contract = MagicMock() - mock_contract.functions.addTrustedHandlers = MagicMock() + mock_add = MagicMock() + mock_add.transact.return_value = "tx_hash" + mock_contract.functions.addTrustedHandlers = MagicMock(return_value=mock_add) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" handlers = [ "0x1234567890123456789012345678901234567891", "0x1234567890123456789012345678901234567892", ] - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.add_trusted_handlers(escrow_address, handlers) + self.escrow.add_trusted_handlers(escrow_address, handlers) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.addTrustedHandlers.assert_called_once_with(handlers) - mock_function.assert_called_once_with( - self.w3, - "Add Trusted Handlers", - mock_contract.functions.addTrustedHandlers.return_value, - EscrowClientError, - None, - ) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.addTrustedHandlers.assert_called_once_with(handlers) + mock_add.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_add_trusted_handlers_invalid_address(self): escrow_address = "0x1234567890123456789012345678901234567890" @@ -1785,7 +1596,7 @@ def test_add_trusted_handlers_without_account(self): "0x1234567890123456789012345678901234567892", ] - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.add_trusted_handlers(escrow_address, handlers) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -1805,28 +1616,19 @@ def test_add_trusted_handlers_invalid_caller(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.add_trusted_handlers(escrow_address, handlers) self.assertEqual( - "Add Trusted Handlers transaction failed: Address calling not trusted", + "Transaction failed: Address calling not trusted", str(cm.exception), ) - def test_add_trusted_handlers_invalid_escrow(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) - escrow_address = "0x1234567890123456789012345678901234567890" - handlers = [ - "0x1234567890123456789012345678901234567891", - "0x1234567890123456789012345678901234567892", - ] - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.add_trusted_handlers(escrow_address, handlers) - self.assertEqual( - "Escrow address is not provided by the factory", str(cm.exception) - ) - def test_add_trusted_handlers_with_tx_options(self): mock_contract = MagicMock() - mock_contract.functions.addTrustedHandlers = MagicMock() + mock_add = MagicMock() + mock_add.transact.return_value = "tx_hash" + mock_contract.functions.addTrustedHandlers = MagicMock(return_value=mock_add) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) escrow_address = "0x1234567890123456789012345678901234567890" handlers = [ "0x1234567890123456789012345678901234567891", @@ -1834,80 +1636,52 @@ def test_add_trusted_handlers_with_tx_options(self): ] tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - self.escrow.add_trusted_handlers(escrow_address, handlers, tx_options) - - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.addTrustedHandlers.assert_called_once_with(handlers) - mock_function.assert_called_once_with( - self.w3, - "Add Trusted Handlers", - mock_contract.functions.addTrustedHandlers.return_value, - EscrowClientError, - tx_options, - ) + self.escrow.add_trusted_handlers(escrow_address, handlers, tx_options) + + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.addTrustedHandlers.assert_called_once_with(handlers) + mock_add.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_withdraw(self): + escrow_address = "0x1234567890123456789012345678901234567890" + token_address = "0x1234567890123456789012345678901234567891" + amount_withdrawn = 123 + + # Mock contract and withdraw function mock_contract = MagicMock() - mock_contract.functions.withdraw = MagicMock() + mock_withdraw = MagicMock() + mock_withdraw.transact.return_value = "tx_hash" + mock_contract.functions.withdraw = MagicMock(return_value=mock_withdraw) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - escrow_address = "0xa76507AbFE3B67cB25F16DbC75a883D4190B7e46" - token_address = "0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4" - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - tx_hash = bytes.fromhex( - "01682095d5abb0270d11a31139b9a1f410b363c84add467004e728ec831bd529" - ) - amount_withdrawn = 187744067287473730 - mock_function.return_value = { - "transactionHash": tx_hash, - "logs": [ - { - "logIndex": 0, - "transactionIndex": 0, - "transactionHash": tx_hash, - "blockHash": bytes.fromhex( - "92abf9325a3959a911a2581e9ea36cba3060d8b293b50e5738ff959feb95258a" - ), - "blockNumber": 5, - "address": token_address, - "data": bytes.fromhex( - "000000000000000000000000000000000000000000000000029b003c075b5e42" - ), - "topics": [ - bytes.fromhex( - "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - ), - bytes.fromhex( - "000000000000000000000000a76507abfe3b67cb25f16dbc75a883d4190b7e46" - ), - bytes.fromhex( - "0000000000000000000000005607acf0828e238099aa1784541a5abd7f975c76" - ), - ], - } - ], - } + # Mock token contract and Transfer event + token_contract = MagicMock() + token_contract.events.Transfer().process_log.return_value = { + "event": "Transfer", + "args": {"from": escrow_address, "value": amount_withdrawn}, + } + self.escrow.w3.eth.contract = MagicMock(return_value=token_contract) - escrow_withdraw_data = self.escrow.withdraw(escrow_address, token_address) + # Mock receipt with logs + receipt = {"transactionHash": b"tx_hash", "logs": [{"address": token_address}]} + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value=receipt + ) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.withdraw.assert_called_once_with(token_address) - mock_function.assert_called_once_with( - self.w3, - "Withdraw", - mock_contract.functions.withdraw.return_value, - EscrowClientError, - None, - ) + result = self.escrow.withdraw(escrow_address, token_address) - self.assertEqual(escrow_withdraw_data.txHash, tx_hash.hex()) - self.assertEqual(escrow_withdraw_data.tokenAddress, token_address) - self.assertEqual(escrow_withdraw_data.amountWithdrawn, amount_withdrawn) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.withdraw.assert_called_once_with(token_address) + mock_withdraw.transact.assert_called_once_with({}) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + self.assertEqual(result.amountWithdrawn, amount_withdrawn) + self.assertEqual(result.tokenAddress, token_address) + self.assertEqual(result.txHash, receipt["transactionHash"].hex()) def test_withdraw_invalid_escrow_address(self): escrow_address = "invalid_address" @@ -1936,7 +1710,7 @@ def test_withdraw_without_account(self): escrow_address = "0x1234567890123456789012345678901234567890" token_address = "0x1234567890123456789012345678901234567890" - with self.assertRaises(EscrowClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: escrowClient.withdraw(escrow_address, token_address) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) @@ -1953,7 +1727,7 @@ def test_withdraw_invalid_status(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.withdraw(escrow_address, token_address) self.assertEqual( - "Withdraw transaction failed: Escrow in Paid status state", + "Transaction failed: Escrow in Paid status state", str(cm.exception), ) @@ -1970,84 +1744,48 @@ def test_withdraw_invalid_caller(self): with self.assertRaises(EscrowClientError) as cm: self.escrow.withdraw(escrow_address, token_address) self.assertEqual( - "Withdraw transaction failed: Address calling not trusted", + "Transaction failed: Address calling not trusted", str(cm.exception), ) - def test_withdraw_invalid_escrow(self): - self.escrow.factory_contract.functions.hasEscrow = MagicMock(return_value=False) + def test_withdraw_with_tx_options(self): escrow_address = "0x1234567890123456789012345678901234567890" - token_address = "0x1234567890123456789012345678901234567890" - - with self.assertRaises(EscrowClientError) as cm: - self.escrow.withdraw(escrow_address, token_address) - self.assertEqual( - "Escrow address is not provided by the factory", str(cm.exception) - ) + token_address = "0x1234567890123456789012345678901234567891" + amount_withdrawn = 123 + tx_options = {"gas": 50000} - def test_withdraw_with_tx_options(self): + # Mock contract and withdraw function mock_contract = MagicMock() - mock_contract.functions.withdraw = MagicMock() + mock_withdraw = MagicMock() + mock_withdraw.transact.return_value = "tx_hash" + mock_contract.functions.withdraw = MagicMock(return_value=mock_withdraw) self.escrow._get_escrow_contract = MagicMock(return_value=mock_contract) - tx_options = {"gas": 50000} - escrow_address = "0xa76507AbFE3B67cB25F16DbC75a883D4190B7e46" - token_address = "0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4" + # Mock token contract and Transfer event + token_contract = MagicMock() + token_contract.events.Transfer().process_log.return_value = { + "event": "Transfer", + "args": {"from": escrow_address, "value": amount_withdrawn}, + } + self.escrow.w3.eth.contract = MagicMock(return_value=token_contract) - with patch( - "human_protocol_sdk.escrow.escrow_client.handle_transaction" - ) as mock_function: - tx_hash = bytes.fromhex( - "01682095d5abb0270d11a31139b9a1f410b363c84add467004e728ec831bd529" - ) - amount_withdrawn = 187744067287473730 - mock_function.return_value = { - "transactionHash": tx_hash, - "logs": [ - { - "logIndex": 0, - "transactionIndex": 0, - "transactionHash": tx_hash, - "blockHash": bytes.fromhex( - "92abf9325a3959a911a2581e9ea36cba3060d8b293b50e5738ff959feb95258a" - ), - "blockNumber": 5, - "address": token_address, - "data": bytes.fromhex( - "000000000000000000000000000000000000000000000000029b003c075b5e42" - ), - "topics": [ - bytes.fromhex( - "ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - ), - bytes.fromhex( - "000000000000000000000000a76507abfe3b67cb25f16dbc75a883d4190b7e46" - ), - bytes.fromhex( - "0000000000000000000000005607acf0828e238099aa1784541a5abd7f975c76" - ), - ], - } - ], - } + # Mock receipt with logs + receipt = {"transactionHash": b"tx_hash", "logs": [{"address": token_address}]} + self.escrow.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value=receipt + ) - escrow_withdraw_data = self.escrow.withdraw( - escrow_address, token_address, tx_options - ) + result = self.escrow.withdraw(escrow_address, token_address, tx_options) - self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) - mock_contract.functions.withdraw.assert_called_once_with(token_address) - mock_function.assert_called_once_with( - self.w3, - "Withdraw", - mock_contract.functions.withdraw.return_value, - EscrowClientError, - tx_options, - ) - - self.assertEqual(escrow_withdraw_data.txHash, tx_hash.hex()) - self.assertEqual(escrow_withdraw_data.tokenAddress, token_address) - self.assertEqual(escrow_withdraw_data.amountWithdrawn, amount_withdrawn) + self.escrow._get_escrow_contract.assert_called_once_with(escrow_address) + mock_contract.functions.withdraw.assert_called_once_with(token_address) + mock_withdraw.transact.assert_called_once_with(tx_options) + self.escrow.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + self.assertEqual(result.amountWithdrawn, amount_withdrawn) + self.assertEqual(result.tokenAddress, token_address) + self.assertEqual(result.txHash, receipt["transactionHash"].hex()) def test_get_balance(self): mock_contract = MagicMock() diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_client.py index f5bd6c1562..05966ffa1f 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/kvstore/test_kvstore_client.py @@ -1,11 +1,11 @@ import unittest -from human_protocol_sdk.constants import NETWORKS from test.human_protocol_sdk.utils import DEFAULT_GAS_PAYER_PRIV from unittest.mock import MagicMock, PropertyMock, patch from human_protocol_sdk.kvstore import KVStoreClient, KVStoreClientError -from human_protocol_sdk.constants import ChainId +from human_protocol_sdk.constants import ChainId, NETWORKS +from human_protocol_sdk.decorators import RequiresSignerError from web3 import Web3 from web3.providers.rpc import HTTPProvider from web3.middleware import SignAndSendRawMiddlewareBuilder @@ -66,24 +66,22 @@ def test_init_with_invalid_web3(self): self.assertEqual(f"Invalid Web3 Instance", str(cm.exception)) def test_set(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.set = mock_function + mock_set = MagicMock() + mock_set.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.set = MagicMock(return_value=mock_set) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) key = "key" value = "value" - with patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction: - self.kvstore.set(key, value) + self.kvstore.set(key, value) - mock_function.assert_called_once_with(key, value) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set", - mock_function.return_value, - KVStoreClientError, - None, - ) + self.kvstore.kvstore_contract.functions.set.assert_called_once_with(key, value) + mock_set.transact.assert_called_once_with({}) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_set_empty_key(self): key = "" @@ -102,50 +100,50 @@ def test_set_without_account(self): key = "key" value = "value" - with self.assertRaises(KVStoreClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: kvstore.set(key, value) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) def test_set_with_tx_options(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.set = mock_function + mock_set = MagicMock() + mock_set.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.set = MagicMock(return_value=mock_set) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) key = "key" value = "value" tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction: - self.kvstore.set(key, value, tx_options) - - mock_function.assert_called_once_with(key, value) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set", - mock_function.return_value, - KVStoreClientError, - tx_options, - ) + self.kvstore.set(key, value, tx_options) + + self.kvstore.kvstore_contract.functions.set.assert_called_once_with(key, value) + mock_set.transact.assert_called_once_with(tx_options) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_set_bulk(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.setBulk = mock_function + mock_set_bulk = MagicMock() + mock_set_bulk.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.setBulk = MagicMock( + return_value=mock_set_bulk + ) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) keys = ["key1", "key2", "key3"] values = ["value1", "value2", "value3"] - with patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction: - self.kvstore.set_bulk(keys, values) + self.kvstore.set_bulk(keys, values) - mock_function.assert_called_once_with(keys, values) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set Bulk", - mock_function.return_value, - KVStoreClientError, - None, - ) + self.kvstore.kvstore_contract.functions.setBulk.assert_called_once_with( + keys, values + ) + mock_set_bulk.transact.assert_called_once_with({}) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_set_bulk_empty_key(self): keys = ["key1", "", "key3"] @@ -178,92 +176,87 @@ def test_set_bulk_without_account(self): keys = ["key1", "key2", "key3"] values = ["value1", "", "value3"] - with self.assertRaises(KVStoreClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: kvstore.set_bulk(keys, values) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) def test_set_bulk_with_tx_options(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.setBulk = mock_function + mock_set_bulk = MagicMock() + mock_set_bulk.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.setBulk = MagicMock( + return_value=mock_set_bulk + ) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) keys = ["key1", "key2", "key3"] values = ["value1", "value2", "value3"] tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction: - self.kvstore.set_bulk(keys, values, tx_options) - - mock_function.assert_called_once_with(keys, values) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set Bulk", - mock_function.return_value, - KVStoreClientError, - tx_options, - ) + self.kvstore.set_bulk(keys, values, tx_options) - def test_set_file_url_and_hash(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.setBulk = mock_function + self.kvstore.kvstore_contract.functions.setBulk.assert_called_once_with( + keys, values + ) + mock_set_bulk.transact.assert_called_once_with(tx_options) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) + def test_set_file_url_and_hash(self): url = "https://example.com" content = "example" content_hash = self.w3.keccak(text=content).hex() - with ( - patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction, - patch("requests.get") as mock_get, - ): + mock_set_bulk = MagicMock() + mock_set_bulk.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.setBulk = MagicMock( + return_value=mock_set_bulk + ) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + with patch("requests.get") as mock_get: mock_response = mock_get.return_value mock_response.text = content self.kvstore.set_file_url_and_hash(url) - mock_function.assert_called_once_with( + self.kvstore.kvstore_contract.functions.setBulk.assert_called_once_with( ["url", "url_hash"], [url, content_hash] ) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set Bulk", - mock_function.return_value, - KVStoreClientError, - None, + mock_set_bulk.transact.assert_called_once_with({}) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" ) def test_set_file_url_and_hash_with_key(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.setBulk = mock_function - url = "https://example.com" content = "example" content_hash = self.w3.keccak(text=content).hex() - with ( - patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction, - patch("requests.get") as mock_get, - ): + mock_set_bulk = MagicMock() + mock_set_bulk.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.setBulk = MagicMock( + return_value=mock_set_bulk + ) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + with patch("requests.get") as mock_get: mock_response = mock_get.return_value mock_response.text = content self.kvstore.set_file_url_and_hash(url, "linkedin_url") - mock_function.assert_called_once_with( + self.kvstore.kvstore_contract.functions.setBulk.assert_called_once_with( ["linkedin_url", "linkedin_url_hash"], [url, content_hash] ) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set Bulk", - mock_function.return_value, - KVStoreClientError, - None, - ) + mock_set_bulk.transact.assert_called_once_with({}) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with def test_set_file_url_and_hash_invalid_url(self): invalid_url = "example.com" @@ -280,40 +273,37 @@ def test_set_file_url_and_hash_without_account(self): kvstore = KVStoreClient(w3) url = "https://example.com" - with self.assertRaises(KVStoreClientError) as cm: + with self.assertRaises(RequiresSignerError) as cm: kvstore.set_file_url_and_hash(url) self.assertEqual("You must add an account to Web3 instance", str(cm.exception)) def test_set_file_url_and_hash_with_tx_options(self): - mock_function = MagicMock() - self.kvstore.kvstore_contract.functions.setBulk = mock_function - url = "https://example.com" content = "example" content_hash = self.w3.keccak(text=content).hex() tx_options = {"gas": 50000} - with ( - patch( - "human_protocol_sdk.kvstore.kvstore_client.handle_transaction" - ) as mock_handle_transaction, - patch("requests.get") as mock_get, - ): + mock_set_bulk = MagicMock() + mock_set_bulk.transact.return_value = "tx_hash" + self.kvstore.kvstore_contract.functions.setBulk = MagicMock( + return_value=mock_set_bulk + ) + self.kvstore.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + with patch("requests.get") as mock_get: mock_response = mock_get.return_value mock_response.text = content self.kvstore.set_file_url_and_hash(url, tx_options=tx_options) - mock_function.assert_called_once_with( + self.kvstore.kvstore_contract.functions.setBulk.assert_called_once_with( ["url", "url_hash"], [url, content_hash] ) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Set Bulk", - mock_function.return_value, - KVStoreClientError, - tx_options, + mock_set_bulk.transact.assert_called_once_with(tx_options) + self.kvstore.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" ) diff --git a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_client.py b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_client.py index a1ad3cfa0a..5302dddd79 100644 --- a/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_client.py +++ b/packages/sdk/python/human-protocol-sdk/test/human_protocol_sdk/staking/test_staking_client.py @@ -74,24 +74,24 @@ def test_init_with_invalid_web3(self): self.assertEqual(f"Invalid Web3 Instance", str(cm.exception)) def test_approve_stake(self): - mock_function = MagicMock() - self.staking_client.hmtoken_contract.functions.approve = mock_function - - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.approve_stake(100) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Approve stake", - mock_function.return_value, - StakingClientError, - None, - ) - mock_function.assert_called_once_with( - NETWORKS[ChainId.LOCALHOST]["staking_address"], 100 - ) + mock_approve = MagicMock() + mock_approve.transact.return_value = "tx_hash" + self.staking_client.hmtoken_contract.functions.approve = MagicMock( + return_value=mock_approve + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + self.staking_client.approve_stake(100) + + self.staking_client.hmtoken_contract.functions.approve.assert_called_once_with( + NETWORKS[ChainId.LOCALHOST]["staking_address"], 100 + ) + mock_approve.transact.assert_called_once_with({}) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_approve_stake_invalid_amount(self): with self.assertRaises(StakingClientError) as cm: @@ -99,43 +99,45 @@ def test_approve_stake_invalid_amount(self): self.assertEqual("Amount to approve must be greater than 0", str(cm.exception)) def test_approve_stake_with_tx_options(self): - mock_function = MagicMock() - self.staking_client.hmtoken_contract.functions.approve = mock_function + mock_approve = MagicMock() + mock_approve.transact.return_value = "tx_hash" + self.staking_client.hmtoken_contract.functions.approve = MagicMock( + return_value=mock_approve + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.approve_stake(100, tx_options) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Approve stake", - mock_function.return_value, - StakingClientError, - tx_options, - ) - mock_function.assert_called_once_with( - NETWORKS[ChainId.LOCALHOST]["staking_address"], 100 - ) + self.staking_client.approve_stake(100, tx_options) + + self.staking_client.hmtoken_contract.functions.approve.assert_called_once_with( + NETWORKS[ChainId.LOCALHOST]["staking_address"], 100 + ) + mock_approve.transact.assert_called_once_with(tx_options) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_stake(self): - mock_function = MagicMock() - self.staking_client.staking_contract.functions.stake = mock_function - - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.stake(100) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Stake HMT", - mock_function.return_value, - StakingClientError, - None, - ) - mock_function.assert_called_once_with(100) + mock_stake = MagicMock() + mock_stake.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.stake = MagicMock( + return_value=mock_stake + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + self.staking_client.stake(100) + + self.staking_client.staking_contract.functions.stake.assert_called_once_with( + 100 + ) + mock_stake.transact.assert_called_once_with({}) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_stake_invalid_amount(self): with self.assertRaises(StakingClientError) as cm: @@ -143,41 +145,45 @@ def test_stake_invalid_amount(self): self.assertEqual("Amount to stake must be greater than 0", str(cm.exception)) def test_stake_with_tx_options(self): - mock_function = MagicMock() - self.staking_client.staking_contract.functions.stake = mock_function + mock_stake = MagicMock() + mock_stake.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.stake = MagicMock( + return_value=mock_stake + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.stake(100, tx_options) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Stake HMT", - mock_function.return_value, - StakingClientError, - tx_options, - ) - mock_function.assert_called_once_with(100) + self.staking_client.stake(100, tx_options) + + self.staking_client.staking_contract.functions.stake.assert_called_once_with( + 100 + ) + mock_stake.transact.assert_called_once_with(tx_options) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_unstake(self): - mock_function = MagicMock() - self.staking_client.staking_contract.functions.unstake = mock_function - - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.unstake(100) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Unstake HMT", - mock_function.return_value, - StakingClientError, - None, - ) - mock_function.assert_called_once_with(100) + mock_unstake = MagicMock() + mock_unstake.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.unstake = MagicMock( + return_value=mock_unstake + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + self.staking_client.unstake(100) + + self.staking_client.staking_contract.functions.unstake.assert_called_once_with( + 100 + ) + mock_unstake.transact.assert_called_once_with({}) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_unstake_invalid_amount(self): with self.assertRaises(StakingClientError) as cm: @@ -185,60 +191,62 @@ def test_unstake_invalid_amount(self): self.assertEqual("Amount to unstake must be greater than 0", str(cm.exception)) def test_unstake_with_tx_options(self): - mock_function = MagicMock() - self.staking_client.staking_contract.functions.unstake = mock_function + mock_unstake = MagicMock() + mock_unstake.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.unstake = MagicMock( + return_value=mock_unstake + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.unstake(100, tx_options) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Unstake HMT", - mock_function.return_value, - StakingClientError, - tx_options, - ) - mock_function.assert_called_once_with(100) + self.staking_client.unstake(100, tx_options) + + self.staking_client.staking_contract.functions.unstake.assert_called_once_with( + 100 + ) + mock_unstake.transact.assert_called_once_with(tx_options) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_withdraw(self): - mock_function = MagicMock() - self.staking_client.staking_contract.functions.withdraw = mock_function - - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.withdraw() - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Withdraw HMT", - mock_function.return_value, - StakingClientError, - None, - ) - mock_function.assert_called_once() + mock_withdraw = MagicMock() + mock_withdraw.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.withdraw = MagicMock( + return_value=mock_withdraw + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) + + self.staking_client.withdraw() + + self.staking_client.staking_contract.functions.withdraw.assert_called_once_with() + mock_withdraw.transact.assert_called_once_with({}) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_withdraw_with_tx_options(self): - mock_function = MagicMock() - self.staking_client.staking_contract.functions.withdraw = mock_function + mock_withdraw = MagicMock() + mock_withdraw.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.withdraw = MagicMock( + return_value=mock_withdraw + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.withdraw(tx_options) - - mock_handle_transaction.assert_called_once_with( - self.w3, - "Withdraw HMT", - mock_function.return_value, - StakingClientError, - tx_options, - ) - mock_function.assert_called_once() + self.staking_client.withdraw(tx_options) + + self.staking_client.staking_contract.functions.withdraw.assert_called_once_with() + mock_withdraw.transact.assert_called_once_with(tx_options) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_slash(self): slasher = "SLASHER" @@ -246,27 +254,29 @@ def test_slash(self): escrow_address = "escrow1" self.staking_client._is_valid_escrow = MagicMock(return_value=True) - mock_function = MagicMock() - self.staking_client.staking_contract.functions.slash = mock_function + mock_slash = MagicMock() + mock_slash.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.slash = MagicMock( + return_value=mock_slash + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.slash( - slasher=slasher, - staker=staker, - escrow_address=escrow_address, - amount=50, - ) + self.staking_client.slash( + slasher=slasher, + staker=staker, + escrow_address=escrow_address, + amount=50, + ) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Slash HMT", - mock_function.return_value, - StakingClientError, - None, - ) - mock_function.assert_called_once_with(slasher, staker, escrow_address, 50) + self.staking_client.staking_contract.functions.slash.assert_called_once_with( + slasher, staker, escrow_address, 50 + ) + mock_slash.transact.assert_called_once_with({}) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_slash_invalid_amount(self): slasher = "SLASHER" @@ -298,29 +308,31 @@ def test_slash_with_tx_options(self): escrow_address = "escrow1" self.staking_client._is_valid_escrow = MagicMock(return_value=True) - mock_function = MagicMock() - self.staking_client.staking_contract.functions.slash = mock_function + mock_slash = MagicMock() + mock_slash.transact.return_value = "tx_hash" + self.staking_client.staking_contract.functions.slash = MagicMock( + return_value=mock_slash + ) + self.staking_client.w3.eth.wait_for_transaction_receipt = MagicMock( + return_value={"logs": []} + ) tx_options = {"gas": 50000} - with patch( - "human_protocol_sdk.staking.staking_client.handle_transaction" - ) as mock_handle_transaction: - self.staking_client.slash( - slasher=slasher, - staker=staker, - escrow_address=escrow_address, - amount=50, - tx_options=tx_options, - ) + self.staking_client.slash( + slasher=slasher, + staker=staker, + escrow_address=escrow_address, + amount=50, + tx_options=tx_options, + ) - mock_handle_transaction.assert_called_once_with( - self.w3, - "Slash HMT", - mock_function.return_value, - StakingClientError, - tx_options, - ) - mock_function.assert_called_once_with(slasher, staker, escrow_address, 50) + self.staking_client.staking_contract.functions.slash.assert_called_once_with( + slasher, staker, escrow_address, 50 + ) + mock_slash.transact.assert_called_once_with(tx_options) + self.staking_client.w3.eth.wait_for_transaction_receipt.assert_called_once_with( + "tx_hash" + ) def test_get_staker_info(self): staker_address = "0x1234567890123456789012345678901234567890" From ac174ae2b9295a8ef556d999135cade5545b9a49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Mon, 16 Jun 2025 12:35:25 +0200 Subject: [PATCH 2/2] Refactor SDK documentation and examples --- ...human_protocol_sdk.escrow.escrow_client.md | 433 +----------------- docs/sdk/python/human_protocol_sdk.filter.md | 4 +- ...man_protocol_sdk.kvstore.kvstore_client.md | 118 +---- docs/sdk/python/human_protocol_sdk.md | 2 +- ...man_protocol_sdk.staking.staking_client.md | 212 +-------- docs/sdk/python/human_protocol_sdk.utils.md | 30 +- docs/sdk/python/index.md | 2 +- .../sdk/python/human-protocol-sdk/example.py | 219 +++++++-- .../human_protocol_sdk/utils.py | 1 - 9 files changed, 224 insertions(+), 797 deletions(-) diff --git a/docs/sdk/python/human_protocol_sdk.escrow.escrow_client.md b/docs/sdk/python/human_protocol_sdk.escrow.escrow_client.md index 86d1fd9559..2d067904ba 100644 --- a/docs/sdk/python/human_protocol_sdk.escrow.escrow_client.md +++ b/docs/sdk/python/human_protocol_sdk.escrow.escrow_client.md @@ -74,199 +74,13 @@ Initializes a Escrow instance. * **Parameters:** **web3** (`Web3`) – The Web3 object -#### add_trusted_handlers(escrow_address, handlers, tx_options=None) +#### add_trusted_handlers(\*args, \*\*kwargs) -Adds an array of addresses to the trusted handlers list. +#### bulk_payout(\*args, \*\*kwargs) -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow - * **handlers** (`List`[`str`]) – Array of trusted handler addresses - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Raises:** - [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) +#### cancel(\*args, \*\*kwargs) - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - trusted_handlers = [ - '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266' - ] - escrow_client.add_trusted_handlers( - "0x62dD51230A30401C455c8398d06F85e4EaB6309f", - trusted_handlers - ) - ``` - -#### bulk_payout(escrow_address, recipients, amounts, final_results_url, final_results_hash, txId, force_complete=False, tx_options=None) - -Pays out the amounts specified to the workers and sets the URL of the final results file. - -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow - * **recipients** (`List`[`str`]) – Array of recipient addresses - * **amounts** (`List`[`Decimal`]) – Array of amounts the recipients will receive - * **final_results_url** (`str`) – Final results file URL - * **final_results_hash** (`str`) – Final results file hash - * **txId** (`Decimal`) – Serial number of the bulks - * **force_complete** (`Optional`[`bool`]) – (Optional) Indicates if remaining balance should be transferred to the escrow creator - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Raises:** - [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - recipients = [ - '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92267' - ] - amounts = [ - Web3.to_wei(5, 'ether'), - Web3.to_wei(10, 'ether') - ] - results_url = 'http://localhost/results.json' - results_hash = 'b5dad76bf6772c0f07fd5e048f6e75a5f86ee079' - - escrow_client.bulk_payout( - "0x62dD51230A30401C455c8398d06F85e4EaB6309f", - recipients, - amounts, - results_url, - results_hash, - 1 - ) - ``` - -#### cancel(escrow_address, tx_options=None) - -Cancels the specified escrow and sends the balance to the canceler. - -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow to cancel - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - [`EscrowCancel`](#human_protocol_sdk.escrow.escrow_client.EscrowCancel) -* **Returns:** - EscrowCancel: - An instance of the EscrowCancel class containing details of the cancellation transaction, - including the transaction hash and the amount refunded. -* **Raises:** - * [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters - * [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If the transfer event associated with the cancellation - is not found in the transaction logs -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - escrow_cancel_data = escrow_client.cancel( - "0x62dD51230A30401C455c8398d06F85e4EaB6309f" - ) - ``` - -#### complete(escrow_address, tx_options=None) - -Sets the status of an escrow to completed. - -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow to complete - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Raises:** - [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - escrow_client.complete("0x62dD51230A30401C455c8398d06F85e4EaB6309f") - ``` +#### complete(\*args, \*\*kwargs) #### create_bulk_payout_transaction(escrow_address, recipients, amounts, final_results_url, final_results_hash, txId, force_complete=False, tx_options=None) @@ -343,55 +157,7 @@ Creates a prepared transaction for bulk payout without signing or sending it. print(f"Transaction receipt: {tx_receipt}") ``` -#### create_escrow(token_address, trusted_handlers, job_requester_id, tx_options=None) - -Creates a new escrow contract. - -* **Parameters:** - * **token_address** (`str`) – Address of the token to be used in the escrow - * **trusted_handlers** (`List`[`str`]) – List of trusted handler addresses - * **job_requester_id** (`str`) – ID of the job requester - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Transaction options -* **Return type:** - `str` -* **Returns:** - Address of the created escrow contract -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri( - URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - token_address = '0x1234567890abcdef1234567890abcdef12345678' - trusted_handlers = [ - '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - '0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef' - ] - job_requester_id = 'job-requester' - escrow_address = escrow_client.create_escrow( - token_address, - trusted_handlers, - job_requester_id - ) - ``` +#### create_escrow(\*args, \*\*kwargs) #### ensure_correct_bulk_payout_input(escrow_address, recipients, amounts, final_results_url, final_results_hash) @@ -410,48 +176,7 @@ Validates input parameters for bulk payout operations. * **Raises:** [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If validation fails -#### fund(escrow_address, amount, tx_options=None) - -Adds funds to the escrow. - -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow to fund - * **amount** (`Decimal`) – Amount to be added as funds - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Raises:** - [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - amount = Web3.to_wei(5, 'ether') # convert from ETH to WEI - escrow_client.fund( - "0x62dD51230A30401C455c8398d06F85e4EaB6309f", amount - ) - ``` +#### fund(\*args, \*\*kwargs) #### get_balance(escrow_address) @@ -789,151 +514,11 @@ Gets the address of the token used to fund the escrow. ) ``` -#### setup(escrow_address, escrow_config, tx_options=None) - -Sets up the parameters of the escrow. - -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow contract - * **escrow_config** ([`EscrowConfig`](#human_protocol_sdk.escrow.escrow_client.EscrowConfig)) – Configuration parameters for the escrow - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Transaction options -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri( - URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - escrow_address = "0x1234567890abcdef1234567890abcdef12345678" - escrow_config = EscrowConfig( - recording_oracle_address='0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - reputation_oracle_address='0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - exchange_oracle_address='0xabcdefabcdefabcdefabcdefabcdefabcdefabcdef', - recording_oracle_fee=100, - reputation_oracle_fee=100, - exchange_oracle_fee=100, - recording_oracle_url='https://example.com/recording', - reputation_oracle_url='https://example.com/reputation', - exchange_oracle_url='https://example.com/exchange', - manifest_url='https://example.com/manifest', - manifest_hash='0xabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdefabcdef' - ) - escrow_client.setup( - escrow_address, - escrow_config - ) - ``` -* **Return type:** - `None` - -#### store_results(escrow_address, url, hash, tx_options=None) - -Stores the results URL. - -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow - * **url** (`str`) – Results file URL - * **hash** (`str`) – Results file hash - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Raises:** - [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - escrow_client.store_results( - "0x62dD51230A30401C455c8398d06F85e4EaB6309f", - "http://localhost/results.json", - "b5dad76bf6772c0f07fd5e048f6e75a5f86ee079" - ) - ``` - -#### withdraw(escrow_address, token_address, tx_options=None) +#### setup(\*args, \*\*kwargs) -Withdraws additional tokens in the escrow to the canceler. +#### store_results(\*args, \*\*kwargs) -* **Parameters:** - * **escrow_address** (`str`) – Address of the escrow to withdraw - * **token_address** (`str`) – Address of the token to withdraw - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - [`EscrowWithdraw`](#human_protocol_sdk.escrow.escrow_client.EscrowWithdraw) -* **Returns:** - EscrowWithdraw: - An instance of the EscrowWithdraw class containing details of the withdrawal transaction, - including the transaction hash and the token address and amount withdrawn. -* **Raises:** - * [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If an error occurs while checking the parameters - * [**EscrowClientError**](#human_protocol_sdk.escrow.escrow_client.EscrowClientError) – If the transfer event associated with the withdrawal - is not found in the transaction logs -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.escrow import EscrowClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - escrow_client = EscrowClient(w3) - - escrow_cancel_data = escrow_client.withdraw( - "0x62dD51230A30401C455c8398d06F85e4EaB6309f", - "0x0376D26246Eb35FF4F9924cF13E6C05fd0bD7Fb4" - ) - ``` +#### withdraw(\*args, \*\*kwargs) ### *exception* human_protocol_sdk.escrow.escrow_client.EscrowClientError diff --git a/docs/sdk/python/human_protocol_sdk.filter.md b/docs/sdk/python/human_protocol_sdk.filter.md index ae0d301983..da7dec57ce 100644 --- a/docs/sdk/python/human_protocol_sdk.filter.md +++ b/docs/sdk/python/human_protocol_sdk.filter.md @@ -120,13 +120,13 @@ Initializes a TransactionsFilter instance. * **Raises:** **ValueError** – If start_date is after end_date -### *class* human_protocol_sdk.filter.WorkerFilter(chain_id, worker_address=None, order_by=None, order_direction=OrderDirection.DESC, first=10, skip=0) +### *class* human_protocol_sdk.filter.WorkerFilter(chain_id, worker_address=None, order_by='payoutCount', order_direction=OrderDirection.DESC, first=10, skip=0) Bases: `object` A class used to filter workers. -#### \_\_init_\_(chain_id, worker_address=None, order_by=None, order_direction=OrderDirection.DESC, first=10, skip=0) +#### \_\_init_\_(chain_id, worker_address=None, order_by='payoutCount', order_direction=OrderDirection.DESC, first=10, skip=0) Initializes a WorkerFilter instance. diff --git a/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md b/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md index 4c9cc4767f..5e2028944c 100644 --- a/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md +++ b/docs/sdk/python/human_protocol_sdk.kvstore.kvstore_client.md @@ -63,123 +63,11 @@ Initializes a KVStore instance. * **web3** (`Web3`) – The Web3 object * **gas_limit** (`Optional`[`int`]) – (Optional) Gas limit for transactions -#### set(key, value, tx_options=None) +#### set(\*args, \*\*kwargs) -Sets the value of a key-value pair in the contract. +#### set_bulk(\*args, \*\*kwargs) -* **Parameters:** - * **key** (`str`) – The key of the key-value pair to set - * **value** (`str`) – The value of the key-value pair to set - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.kvstore import KVStoreClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - kvstore_client = KVStoreClient(w3) - kvstore_client.set('Role', 'RecordingOracle') - ``` - -#### set_bulk(keys, values, tx_options=None) - -Sets multiple key-value pairs in the contract. - -* **Parameters:** - * **keys** (`List`[`str`]) – A list of keys to set - * **values** (`List`[`str`]) – A list of values to set - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.kvstore import KVStoreClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - kvstore_client = KVStoreClient(w3) - - keys = ['Role', 'Webhook_url'] - values = ['RecordingOracle', 'http://localhost'] - kvstore_client.set_bulk(keys, values) - ``` - -#### set_file_url_and_hash(url, key='url', tx_options=None) - -Sets a URL value for the address that submits the transaction, and its hash. - -* **Parameters:** - * **url** (`str`) – URL to set - * **key** (`Optional`[`str`]) – Configurable URL key. url by default. - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Raises:** - [**KVStoreClientError**](#human_protocol_sdk.kvstore.kvstore_client.KVStoreClientError) – If an error occurs while validating URL, or handling transaction -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.kvstore import KVStoreClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - kvstore_client = KVStoreClient(w3) - - kvstore_client.set_file_url_and_hash('http://localhost') - kvstore_client.set_file_url_and_hash('https://linkedin.com/me', 'linkedin_url') - ``` +#### set_file_url_and_hash(\*args, \*\*kwargs) ### *exception* human_protocol_sdk.kvstore.kvstore_client.KVStoreClientError diff --git a/docs/sdk/python/human_protocol_sdk.md b/docs/sdk/python/human_protocol_sdk.md index 7f19d62801..b9002340eb 100644 --- a/docs/sdk/python/human_protocol_sdk.md +++ b/docs/sdk/python/human_protocol_sdk.md @@ -194,7 +194,7 @@ * [`get_hmt_balance()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.get_hmt_balance) * [`get_kvstore_interface()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.get_kvstore_interface) * [`get_staking_interface()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.get_staking_interface) - * [`handle_transaction()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.handle_transaction) + * [`handle_error()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.handle_error) * [`parse_transfer_transaction()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.parse_transfer_transaction) * [`validate_url()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.validate_url) * [`with_retry()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.with_retry) diff --git a/docs/sdk/python/human_protocol_sdk.staking.staking_client.md b/docs/sdk/python/human_protocol_sdk.staking.staking_client.md index e0a9fbc526..c0155d7cb5 100644 --- a/docs/sdk/python/human_protocol_sdk.staking.staking_client.md +++ b/docs/sdk/python/human_protocol_sdk.staking.staking_client.md @@ -62,45 +62,7 @@ Initializes a Staking instance * **Parameters:** **w3** (`Web3`) – Web3 instance -#### approve_stake(amount, tx_options=None) - -Approves HMT token for Staking. - -* **Parameters:** - * **amount** (`Decimal`) – Amount to approve - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Validate:** - Amount must be greater than 0 -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.staking import StakingClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - staking_client = StakingClient(w3) - - amount = Web3.to_wei(5, 'ether') # convert from ETH to WEI - staking_client.approve_stake(amount) - ``` +#### approve_stake(\*args, \*\*kwargs) #### get_staker_info(staker_address) @@ -129,177 +91,13 @@ Retrieves comprehensive staking information for a staker. print(staking_info['stakedAmount']) ``` -#### slash(slasher, staker, escrow_address, amount, tx_options=None) +#### slash(\*args, \*\*kwargs) -Slashes HMT token. +#### stake(\*args, \*\*kwargs) -* **Parameters:** - * **slasher** (`str`) – Address of the slasher - * **staker** (`str`) – Address of the staker - * **escrow_address** (`str`) – Address of the escrow - * **amount** (`Decimal`) – Amount to slash - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Validate:** - - Amount must be greater than 0 - - Amount must be less than or equal to the amount allocated to the escrow (on-chain) - - Escrow address must be valid -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.staking import StakingClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - staking_client = StakingClient(w3) - - amount = Web3.to_wei(5, 'ether') # convert from ETH to WEI - staking_client.slash( - '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', - '0x62dD51230A30401C455c8398d06F85e4EaB6309f', - amount - ) - ``` - -#### stake(amount, tx_options=None) +#### unstake(\*args, \*\*kwargs) -Stakes HMT token. - -* **Parameters:** - * **amount** (`Decimal`) – Amount to stake - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Validate:** - - Amount must be greater than 0 - - Amount must be less than or equal to the approved amount (on-chain) - - Amount must be less than or equal to the balance of the staker (on-chain) -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.staking import StakingClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - staking_client = StakingClient(w3) - - amount = Web3.to_wei(5, 'ether') # convert from ETH to WEI - staking_client.approve_stake(amount) # if it was already approved before, this is not necessary - staking_client.stake(amount) - ``` - -#### unstake(amount, tx_options=None) - -Unstakes HMT token. - -* **Parameters:** - * **amount** (`Decimal`) – Amount to unstake - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Validate:** - - Amount must be greater than 0 - - Amount must be less than or equal to the staked amount which is not locked / allocated (on-chain) -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.staking import StakingClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - staking_client = StakingClient(w3) - - amount = Web3.to_wei(5, 'ether') # convert from ETH to WEI - staking_client.unstake(amount) - ``` - -#### withdraw(tx_options=None) - -Withdraws HMT token. - -* **Parameters:** - **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters -* **Return type:** - `None` -* **Returns:** - None -* **Validate:** - - There must be unstaked tokens which is unlocked (on-chain) -* **Example:** - ```python - from eth_typing import URI - from web3 import Web3 - from web3.middleware import SignAndSendRawMiddlewareBuilder - from web3.providers.auto import load_provider_from_uri - - from human_protocol_sdk.staking import StakingClient - - def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - 'SignAndSendRawMiddlewareBuilder', - layer=0, - ) - return (w3, gas_payer) - - (w3, gas_payer) = get_w3_with_priv_key('YOUR_PRIVATE_KEY') - staking_client = StakingClient(w3) - - staking_client.withdraw() - ``` +#### withdraw(\*args, \*\*kwargs) ### *exception* human_protocol_sdk.staking.staking_client.StakingClientError diff --git a/docs/sdk/python/human_protocol_sdk.utils.md b/docs/sdk/python/human_protocol_sdk.utils.md index 48aa0d3427..f0e73515b6 100644 --- a/docs/sdk/python/human_protocol_sdk.utils.md +++ b/docs/sdk/python/human_protocol_sdk.utils.md @@ -68,24 +68,26 @@ Retrieve the Staking interface. * **Returns:** The Staking interface of smart contract. -### human_protocol_sdk.utils.handle_transaction(w3, tx_name, tx, exception, tx_options) +### human_protocol_sdk.utils.handle_error(e, exception_class) -Executes the transaction and waits for the receipt. +Handles and translates errors raised during contract transactions. + +This function captures exceptions (especially ContractLogicError from web3.py), +extracts meaningful revert reasons if present, logs unexpected errors, and raises +a custom exception with a clear message for SDK users. * **Parameters:** - * **w3** (`Web3`) – Web3 instance - * **tx_name** (`str`) – Name of the transaction - * **tx** – Transaction object - * **exception** (`Exception`) – Exception class to raise in case of error - * **tx_options** (`Optional`[`TxParams`]) – (Optional) Additional transaction parameters - - If provided, can include values like ‘gas’, ‘gas_price’, ‘nonce’, etc - - If ‘gas’ is not specified or is None, it will be estimated using tx.estimate_gas() -* **Returns:** - The transaction receipt -* **Validate:** - - There must be a default account + * **e** – The exception object raised during a transaction. + * **exception_class** – The custom exception class to raise (e.g., EscrowClientError). * **Raises:** - **exception** – If the transaction fails + **exception_class** – With a detailed error message, including contract revert reasons if available. +* **Example:** + try: + : tx_hash = contract.functions.someMethod(…).transact() + w3.eth.wait_for_transaction_receipt(tx_hash) + + except Exception as e: + : handle_error(e, EscrowClientError) ### human_protocol_sdk.utils.parse_transfer_transaction(hmtoken_contract, tx_receipt) diff --git a/docs/sdk/python/index.md b/docs/sdk/python/index.md index 75b6eaeb1e..ed2394dc6a 100644 --- a/docs/sdk/python/index.md +++ b/docs/sdk/python/index.md @@ -71,7 +71,7 @@ pip install human-protocol-sdk[agreement] * [`get_hmt_balance()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.get_hmt_balance) * [`get_kvstore_interface()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.get_kvstore_interface) * [`get_staking_interface()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.get_staking_interface) - * [`handle_transaction()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.handle_transaction) + * [`handle_error()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.handle_error) * [`parse_transfer_transaction()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.parse_transfer_transaction) * [`validate_url()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.validate_url) * [`with_retry()`](human_protocol_sdk.utils.md#human_protocol_sdk.utils.with_retry) diff --git a/packages/sdk/python/human-protocol-sdk/example.py b/packages/sdk/python/human-protocol-sdk/example.py index 56800cbcd2..077bc0b480 100644 --- a/packages/sdk/python/human-protocol-sdk/example.py +++ b/packages/sdk/python/human-protocol-sdk/example.py @@ -1,42 +1,197 @@ -from eth_typing import URI -from web3 import Web3 -from web3.middleware import SignAndSendRawMiddlewareBuilder -from web3.providers.auto import load_provider_from_uri +import datetime -from human_protocol_sdk.escrow import EscrowClient -from human_protocol_sdk.staking import StakingClient -from human_protocol_sdk.kvstore import KVStoreClient +from human_protocol_sdk.constants import ChainId, OrderDirection, Status +from human_protocol_sdk.escrow import EscrowUtils +from human_protocol_sdk.worker import WorkerUtils +from human_protocol_sdk.filter import ( + EscrowFilter, + PayoutFilter, + StatisticsFilter, + WorkerFilter, +) +from human_protocol_sdk.statistics import ( + StatisticsClient, + HMTHoldersParam, +) +from human_protocol_sdk.operator import OperatorUtils, OperatorFilter +from human_protocol_sdk.agreement import agreement -def get_w3_with_priv_key(priv_key: str): - w3 = Web3(load_provider_from_uri(URI("http://localhost:8545"))) - gas_payer = w3.eth.account.from_key(priv_key) - w3.eth.default_account = gas_payer.address - w3.middleware_onion.inject( - SignAndSendRawMiddlewareBuilder.build(priv_key), - "SignAndSendRawMiddlewareBuilder", - layer=0, +def get_escrow_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_escrow_statistics()) + print( + statistics_client.get_escrow_statistics( + StatisticsFilter( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) ) - return (w3, gas_payer) -(w3, gas_payer) = get_w3_with_priv_key( - "ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" -) -escrow_client = EscrowClient(w3) +def get_worker_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_worker_statistics()) + print( + statistics_client.get_worker_statistics( + StatisticsFilter( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) -staking_client = StakingClient(w3) -kvstore_client = KVStoreClient(w3) +def get_payment_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_payment_statistics()) + print( + statistics_client.get_payment_statistics( + StatisticsFilter( + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) -kvstore_client.set("test", "value") -staking_client.approve_stake(10000) -staking_client.stake(10000) -amount = Web3.to_wei(5, "ether") # convert from ETH to WEI -transaction = escrow_client.create_escrow( - "0xa85233C63b9Ee964Add6F2cffe00Fd84eb32338f", - ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"], - "1", -) -print(f"Transaction hash: {transaction}") +def get_hmt_statistics(statistics_client: StatisticsClient): + print(statistics_client.get_hmt_statistics()) + + +def get_hmt_holders(statistics_client: StatisticsClient): + print( + statistics_client.get_hmt_holders( + HMTHoldersParam( + order_direction="desc", + ) + ) + ) + print( + statistics_client.get_hmt_holders( + HMTHoldersParam( + order_direction="asc", + ) + ) + ) + print( + statistics_client.get_hmt_holders( + HMTHoldersParam(address="0xf183b3b34e70dd17859455389a3ab54d49d41e6f") + ) + ) + + +def get_hmt_daily_data(statistics_client: StatisticsClient): + print( + statistics_client.get_hmt_daily_data( + StatisticsFilter( + date_from=datetime.datetime(2024, 5, 8), + date_to=datetime.datetime(2024, 6, 8), + ) + ) + ) + + +def get_payouts(): + filter = PayoutFilter( + chain_id=ChainId.POLYGON, + first=5, + skip=1, + order_direction=OrderDirection.ASC, + ) + + payouts = EscrowUtils.get_payouts(filter) + for payout in payouts: + print( + f"Payout ID: {payout.id}, Amount: {payout.amount}, Recipient: {payout.recipient}" + ) + + +def get_escrows(): + print( + EscrowUtils.get_escrows( + EscrowFilter( + chain_id=ChainId.POLYGON_AMOY, + status=Status.Pending, + date_from=datetime.datetime(2023, 5, 8), + date_to=datetime.datetime(2023, 6, 8), + ) + ) + ) + + print( + ( + EscrowUtils.get_escrow( + ChainId.POLYGON_AMOY, "0xf9ec66feeafb850d85b88142a7305f55e0532959" + ) + ) + ) + + +def get_operators(): + operators = OperatorUtils.get_operators( + OperatorFilter(chain_id=ChainId.POLYGON_AMOY) + ) + print(operators) + print(OperatorUtils.get_operator(ChainId.POLYGON_AMOY, operators[0].address)) + print( + OperatorUtils.get_operators( + OperatorFilter(chain_id=ChainId.POLYGON_AMOY, roles="Job Launcher") + ) + ) + operators = OperatorUtils.get_operators( + OperatorFilter(chain_id=ChainId.POLYGON_AMOY, roles=["Job Launcher"]) + ) + print(len(operators)) + + operators = OperatorUtils.get_operators( + OperatorFilter( + chain_id=ChainId.POLYGON_AMOY, + min_amount_staked=1, + roles=["Job Launcher", "Reputation Oracle"], + ) + ) + print(len(operators)) + + +def get_workers(): + workers = WorkerUtils.get_workers( + WorkerFilter(chain_id=ChainId.POLYGON_AMOY, first=2) + ) + print(workers) + print(WorkerUtils.get_worker(ChainId.POLYGON_AMOY, workers[0].address)) + workers = WorkerUtils.get_workers( + WorkerFilter(chain_id=ChainId.POLYGON_AMOY, worker_address=workers[0].address) + ) + print(len(workers)) + + +def agreement_example(): + annotations = [ + ["cat", "not", "cat"], + ["cat", "cat", "cat"], + ["not", "not", "not"], + ["cat", "nan", "not"], + ] + + agreement_report = agreement(annotations, measure="fleiss_kappa") + print(agreement_report) + + +if __name__ == "__main__": + statistics_client = StatisticsClient() + + # Run single example while testing, and remove comments before commit + + get_escrows() + get_operators() + get_payouts() + + statistics_client = StatisticsClient(ChainId.POLYGON_AMOY) + get_hmt_holders(statistics_client) + get_escrow_statistics(statistics_client) + get_hmt_statistics(statistics_client) + get_payment_statistics(statistics_client) + get_worker_statistics(statistics_client) + get_hmt_daily_data(statistics_client) + + agreement_example() + get_workers() diff --git a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py index 28bf1993c3..a099270afd 100644 --- a/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py +++ b/packages/sdk/python/human-protocol-sdk/human_protocol_sdk/utils.py @@ -240,7 +240,6 @@ def handle_error(e, exception_class): except Exception as e: handle_error(e, EscrowClientError) """ - import re def extract_reason(msg): patterns = [