From cb155918268bf5f440d618599f824bfd59c527a1 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 15:48:34 +0300 Subject: [PATCH 001/100] Pass RPCEndpoint properly --- skale/utils/contracts_provision/main.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index 38542390..18370cb6 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -18,6 +18,7 @@ # along with SKALE.py. If not, see . from web3 import Web3 +from web3.types import RPCEndpoint from skale.contracts.manager.nodes import NodeStatus from skale.transactions.result import TxRes @@ -48,18 +49,19 @@ def _skip_evm_time(web3: Web3, seconds: int, mine: bool = True) -> int: """For test purposes only, works only with hardhat node""" - res = web3.provider.make_request('evm_increaseTime', [seconds]) - web3.provider.make_request("evm_mine", []) + res = web3.provider.make_request(RPCEndpoint('evm_increaseTime'), [seconds]) + if mine: + web3.provider.make_request(RPCEndpoint('evm_mine'), []) return int(res['result']) def set_automining(web3: Web3, value: bool) -> int: - res = web3.provider.make_request('evm_setAutomine', [value]) + res = web3.provider.make_request(RPCEndpoint('evm_setAutomine'), [value]) return int(res['result']) def set_mining_interval(web3: Web3, ms: int) -> int: - res = web3.provider.make_request('evm_setIntervalMining', [ms]) + res = web3.provider.make_request(RPCEndpoint('evm_setIntervalMining'), [ms]) return int(res['result']) From 9b923a77057b6e8a6bcf99311875645d63b069a2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 16:49:37 +0300 Subject: [PATCH 002/100] Add types to rotation history --- skale/contracts/manager/key_storage.py | 10 ++++++- skale/schain_config/rotation_history.py | 37 +++++++++++++++++++++---- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/skale/contracts/manager/key_storage.py b/skale/contracts/manager/key_storage.py index 353a35b8..ee8a2e13 100644 --- a/skale/contracts/manager/key_storage.py +++ b/skale/contracts/manager/key_storage.py @@ -17,11 +17,19 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from collections import namedtuple +from typing import NamedTuple from skale.contracts.base_contract import BaseContract +Fp2Point = namedtuple('Fp2Point', ['a', 'b']) +class G2Point(NamedTuple): + x: Fp2Point + y: Fp2Point + + class KeyStorage(BaseContract): - def get_common_public_key(self, group_index): + def get_common_public_key(self, group_index) -> G2Point: return self.contract.functions.getCommonPublicKey(group_index).call() def get_previous_public_key(self, group_index): diff --git a/skale/schain_config/rotation_history.py b/skale/schain_config/rotation_history.py index 7ef488fe..81226708 100644 --- a/skale/schain_config/rotation_history.py +++ b/skale/schain_config/rotation_history.py @@ -17,17 +17,41 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from __future__ import annotations import logging from collections import namedtuple +from typing import TYPE_CHECKING, Optional, TypedDict from skale import Skale from skale.contracts.manager.node_rotation import Rotation +if TYPE_CHECKING: + from skale.contracts.manager.key_storage import G2Point + logger = logging.getLogger(__name__) RotationNodeData = namedtuple('RotationNodeData', ['index', 'node_id', 'public_key']) +class NodesSwap(TypedDict): + leaving_node_id: int + new_node_id: int + + +class BlsPublicKey(TypedDict): + blsPublicKey0: str + blsPublicKey1: str + blsPublicKey2: str + blsPublicKey3: str + + +class NodesGroup(TypedDict): + rotation: NodesSwap | None + nodes: dict[int, RotationNodeData] + finish_ts: int | None + bls_public_key: BlsPublicKey | None + + def get_previous_schain_groups( skale: Skale, schain_name: str, @@ -38,7 +62,7 @@ def get_previous_schain_groups( In case of no rotations returns the current state. """ logger.info(f'Collecting rotation history for {schain_name}...') - node_groups = {} + node_groups: dict[int, NodesGroup] = {} group_id = skale.schains.name_to_group_id(schain_name) @@ -67,11 +91,11 @@ def get_previous_schain_groups( def _add_current_schain_state( skale: Skale, - node_groups: dict, + node_groups: dict[int, NodesGroup], rotation: Rotation, schain_name: str, - current_public_key: list -) -> dict: + current_public_key: G2Point +): """ Internal function, composes the initial info about the current sChain state and adds it to the node_groups dictionary @@ -97,7 +121,7 @@ def _add_previous_schain_rotations_state( schain_name: str, previous_public_keys: list, leaving_node_id=None -) -> dict: +): """ Internal function, handles rotations from (rotation_counter - 2) to 0 and adds them to the node_groups dictionary @@ -169,7 +193,7 @@ def _pop_previous_bls_public_key(previous_public_keys): return bls_keys -def _compose_bls_public_key_info(bls_public_key: str) -> dict: +def _compose_bls_public_key_info(bls_public_key: G2Point) -> Optional[BlsPublicKey]: if bls_public_key: return { 'blsPublicKey0': str(bls_public_key[0][0]), @@ -177,6 +201,7 @@ def _compose_bls_public_key_info(bls_public_key: str) -> dict: 'blsPublicKey2': str(bls_public_key[1][0]), 'blsPublicKey3': str(bls_public_key[1][1]) } + return None def get_new_nodes_list(skale: Skale, name: str, node_groups) -> list: From 8e6951ea93a34a6ce681fd2b6a6df83b2066290c Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 16:53:21 +0300 Subject: [PATCH 003/100] Add black lines --- skale/contracts/manager/key_storage.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skale/contracts/manager/key_storage.py b/skale/contracts/manager/key_storage.py index ee8a2e13..e1fbb275 100644 --- a/skale/contracts/manager/key_storage.py +++ b/skale/contracts/manager/key_storage.py @@ -23,6 +23,8 @@ Fp2Point = namedtuple('Fp2Point', ['a', 'b']) + + class G2Point(NamedTuple): x: Fp2Point y: Fp2Point From fd7a2c1873f6cd10f538994acb916871845d4a37 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:16:51 +0300 Subject: [PATCH 004/100] Don't pass multiple values for argument --- skale/utils/account_tools.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skale/utils/account_tools.py b/skale/utils/account_tools.py index cb438fe1..493abe74 100644 --- a/skale/utils/account_tools.py +++ b/skale/utils/account_tools.py @@ -86,12 +86,12 @@ def send_eth( wei_amount = web3.to_wei(amount, 'ether') gas_price = gas_price or default_gas_price(web3) tx = compose_eth_transfer_tx( - web3=web3, - *args, + web3, + wallet.address, + receiver_address, + wei_amount, gas_price=gas_price, - from_address=wallet.address, - to_address=receiver_address, - value=wei_amount, + *args, **kwargs ) tx_hash = wallet.sign_and_send( From 932d3fdcc049d2e2474d0b5501d160dac6ae211a Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:20:34 +0300 Subject: [PATCH 005/100] Fix type hint for contract class --- skale/utils/contract_info.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skale/utils/contract_info.py b/skale/utils/contract_info.py index 33cfe1d6..d3e2999a 100644 --- a/skale/utils/contract_info.py +++ b/skale/utils/contract_info.py @@ -18,7 +18,7 @@ # along with SKALE.py. If not, see . """ Contract info utilities """ -from typing import NamedTuple +from typing import NamedTuple, Type from skale.contracts.base_contract import BaseContract from skale.utils.contract_types import ContractTypes @@ -27,6 +27,6 @@ class ContractInfo(NamedTuple): name: str contract_name: str - contract_class: BaseContract + contract_class: Type[BaseContract] type: ContractTypes upgradeable: bool From bf737c039266f91e42b5f6d65724dcfbbf76bff0 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:23:00 +0300 Subject: [PATCH 006/100] Fix get all plans return type --- skale/contracts/allocator/allocator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index 37f64b1d..fa504b26 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -151,7 +151,7 @@ def __get_plan_raw(self, plan_id: int): def get_plan(self, plan_id: int) -> dict: return self.__get_plan_raw(plan_id) - def get_all_plans(self) -> dict: + def get_all_plans(self) -> list: plans = [] for i in range(1, MAX_NUM_OF_PLANS): try: From eda258366ce27c99159d25e2a65316a5593cc760 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:28:37 +0300 Subject: [PATCH 007/100] Fix return types --- skale/contracts/manager/delegation/delegation_controller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skale/contracts/manager/delegation/delegation_controller.py b/skale/contracts/manager/delegation/delegation_controller.py index adcb2e1b..677841a3 100644 --- a/skale/contracts/manager/delegation/delegation_controller.py +++ b/skale/contracts/manager/delegation/delegation_controller.py @@ -33,7 +33,7 @@ class DelegationController(BaseContract): """Wrapper for DelegationController.sol functions""" @format_fields(FIELDS) - def get_delegation(self, delegation_id: int) -> dict: + def get_delegation(self, delegation_id: int) -> list: """Returns delegation structure. :returns: Info about delegation request @@ -76,10 +76,10 @@ def _get_delegation_ids_by_holder(self, address: str) -> list: for _id in range(delegation_ids_len) ] - def _get_delegation_ids_len_by_validator(self, validator_id: int) -> list: + def _get_delegation_ids_len_by_validator(self, validator_id: int) -> int: return self.contract.functions.getDelegationsByValidatorLength(validator_id).call() - def _get_delegation_ids_len_by_holder(self, address: str) -> list: + def _get_delegation_ids_len_by_holder(self, address: str) -> int: return self.contract.functions.getDelegationsByHolderLength(address).call() def _get_delegation_state_index(self, delegation_id: int) -> str: From eb3b58cc4c53438d71fdfd05134ff7aa1fe7be8b Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:30:08 +0300 Subject: [PATCH 008/100] Add optional --- skale/contracts/manager/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skale/contracts/manager/manager.py b/skale/contracts/manager/manager.py index e3a844f8..13a6bfc0 100644 --- a/skale/contracts/manager/manager.py +++ b/skale/contracts/manager/manager.py @@ -71,8 +71,8 @@ def create_schain( type_of_nodes: int, deposit: str, name: str, - schain_originator: str = None, - options: SchainOptions = None + schain_originator: str | None = None, + options: SchainOptions | None = None ): logger.info( f'create_schain: type_of_nodes: {type_of_nodes}, name: {name}') From 8c4f64fd3d836a0789753129e2b171e33eb4b4ee Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:30:59 +0300 Subject: [PATCH 009/100] Fix return type --- skale/contracts/manager/delegation/validator_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale/contracts/manager/delegation/validator_service.py b/skale/contracts/manager/delegation/validator_service.py index ffba3305..74e12872 100644 --- a/skale/contracts/manager/delegation/validator_service.py +++ b/skale/contracts/manager/delegation/validator_service.py @@ -44,7 +44,7 @@ def __get_raw(self, _id) -> list: return self.contract.functions.validators(_id).call() @format_fields(FIELDS) - def get(self, _id) -> dict: + def get(self, _id) -> list: """Returns validator info. :returns: Validator info From 990728e392f6fdbb99dc835168b3654685fef7d6 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:35:33 +0300 Subject: [PATCH 010/100] Fix return types --- skale/contracts/manager/delegation/delegation_controller.py | 2 +- skale/contracts/manager/node_rotation.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/skale/contracts/manager/delegation/delegation_controller.py b/skale/contracts/manager/delegation/delegation_controller.py index 677841a3..77e470cb 100644 --- a/skale/contracts/manager/delegation/delegation_controller.py +++ b/skale/contracts/manager/delegation/delegation_controller.py @@ -37,7 +37,7 @@ def get_delegation(self, delegation_id: int) -> list: """Returns delegation structure. :returns: Info about delegation request - :rtype: dict + :rtype: list """ return self.__raw_get_delegation(delegation_id) diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index 8e3a9a9f..ad9b2a5d 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -77,7 +77,7 @@ def get_leaving_history(self, node_id): ] return history - def get_schain_finish_ts(self, node_id: int, schain_name: str) -> int: + def get_schain_finish_ts(self, node_id: int, schain_name: str) -> int | None: raw_history = self.contract.functions.getLeavingHistory(node_id).call() schain_id = self.skale.schains.name_to_id(schain_name) finish_ts = next( @@ -129,7 +129,7 @@ def has_role(self, role: bytes, address: str) -> bool: def debugger_role(self): return self.contract.functions.DEBUGGER_ROLE().call() - def get_previous_node(self, schain_name: str, node_id: int) -> int: + def get_previous_node(self, schain_name: str, node_id: int) -> int | None: schain_id = self.schains.name_to_id(schain_name) try: return self.contract.functions.getPreviousNode(schain_id, node_id).call() From 0b686e22c9f4eb609ee8d98c6783827484de1be5 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:42:30 +0300 Subject: [PATCH 011/100] Do not access an uninitialized variable --- skale/contracts/base_contract.py | 4 +++- skale/contracts/manager/schains.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index e984c1aa..596866fe 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -70,13 +70,15 @@ def wrapper( call_result, tx_hash, receipt = None, None, None should_dry_run = not skip_dry_run and not config.DISABLE_DRY_RUN + dry_run_success = False if should_dry_run: call_result = make_dry_run_call(self.skale, method, gas_limit, value) if call_result.status == TxStatus.SUCCESS: gas_limit = gas_limit or call_result.data['gas'] + dry_run_success = True should_send = not dry_run_only and \ - (not should_dry_run or call_result.status == TxStatus.SUCCESS) + (not should_dry_run or dry_run_success) if should_send: gas_limit = gas_limit or config.DEFAULT_GAS_LIMIT diff --git a/skale/contracts/manager/schains.py b/skale/contracts/manager/schains.py index e12d18cd..72afff1e 100644 --- a/skale/contracts/manager/schains.py +++ b/skale/contracts/manager/schains.py @@ -141,7 +141,7 @@ def add_schain_by_foundation( type_of_nodes: int, nonce: int, name: str, - options: SchainOptions = None, + options: SchainOptions | None = None, schain_owner=None, schain_originator=None ) -> TxRes: From a2823cb7378348b75df05ff094437dcd47bf0979 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 17:46:41 +0300 Subject: [PATCH 012/100] Update redis --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7892f26e..63888014 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ "asyncio==3.4.3", "pyyaml==6.0", "sgx.py==0.9dev2", - "redis==4.4.4", + "redis==5.0.3", "typing-extensions==4.9.0", "web3==6.13.0" ], From 41b5d0b04360497a67b15287364074f79802141c Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 5 Apr 2024 19:09:13 +0300 Subject: [PATCH 013/100] Fix types errors --- skale/wallets/redis_wallet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skale/wallets/redis_wallet.py b/skale/wallets/redis_wallet.py index 2d68b71f..eb0c420c 100644 --- a/skale/wallets/redis_wallet.py +++ b/skale/wallets/redis_wallet.py @@ -112,7 +112,7 @@ def _make_record( cls, tx: Dict, score: int, - multiplier: int = config.DEFAULT_GAS_MULTIPLIER, + multiplier: float = config.DEFAULT_GAS_MULTIPLIER, method: Optional[str] = None, meta: Optional[Dict] = None ) -> Tuple[bytes, bytes]: @@ -135,7 +135,7 @@ def _make_record( def _to_raw_id(cls, tx_id: str) -> bytes: return tx_id.encode('utf-8') - def _to_id(cls, raw_id: str) -> str: + def _to_id(cls, raw_id: bytes) -> str: return raw_id.decode('utf-8') def sign_and_send( @@ -153,7 +153,7 @@ def sign_and_send( raw_id, tx_record = self._make_record( tx, score, - multiplier=multiplier, + multiplier=multiplier or config.DEFAULT_GAS_MULTIPLIER, method=method, meta=meta ) From fc1d170b4d5c7bab1055cfb549a79be7ee6d79e1 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 12:33:11 +0300 Subject: [PATCH 014/100] Fix exception arguments types --- skale/transactions/tools.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skale/transactions/tools.py b/skale/transactions/tools.py index f0451933..c87e5f09 100644 --- a/skale/transactions/tools.py +++ b/skale/transactions/tools.py @@ -60,8 +60,11 @@ def make_dry_run_call(skale, method, gas_limit=None, value=0) -> TxCallResult: estimated_gas = estimate_gas(skale.web3, method, opts) logger.info(f'Estimated gas for {method.fn_name}: {estimated_gas}') except ContractLogicError as e: + message = e.message or 'Contract logic error' + error_data = e.data or {} + data = {'data': error_data} if isinstance(error_data, str) else error_data return TxCallResult(status=TxStatus.FAILED, - error='revert', message=e.message, data=e.data) + error='revert', message=message, data=data) except (Web3Exception, ValueError) as e: logger.exception('Dry run for %s failed', method) return TxCallResult(status=TxStatus.FAILED, error='exception', message=str(e), data={}) From 9eee65992dc528928edd5820f610072d6c5ad29c Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 12:35:21 +0300 Subject: [PATCH 015/100] Fix wait method --- skale/wallets/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale/wallets/common.py b/skale/wallets/common.py index bb97ee8c..19d25ea9 100644 --- a/skale/wallets/common.py +++ b/skale/wallets/common.py @@ -67,5 +67,5 @@ def public_key(self) -> str: pass @abstractmethod - def wait(self, tx: str, confirmation_blocks: int = None): + def wait(self, tx: str, confirmation_blocks: int | None = None): pass From a4823fa61ecc3a0a1ebde03f5665b74219fcebc9 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 12:42:22 +0300 Subject: [PATCH 016/100] Fix types in web3_utils --- skale/utils/web3_utils.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index eae748ea..91a25525 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -99,7 +99,7 @@ def outdated_client_file_msg(method, latest_block_number, saved_number, state_pa def make_client_checking_middleware(allowed_ts_diff: int, - state_path: str = None): + state_path: str | None = None): def eth_client_checking_middleware(make_request, web3): def middleware(method, params): if method in ('eth_block_number', 'eth_getBlockByNumber'): @@ -139,23 +139,23 @@ def middleware(method, params): def init_web3(endpoint: str, provider_timeout: int = DEFAULT_HTTP_TIMEOUT, - middlewares: Iterable = None, - state_path: str = None, ts_diff: int = None): + middlewares: Iterable | None = None, + state_path: str | None = None, ts_diff: int | None = None): if not middlewares: ts_diff = ts_diff or config.ALLOWED_TS_DIFF state_path = state_path or config.LAST_BLOCK_FILE if not ts_diff == config.NO_SYNC_TS_DIFF: sync_middleware = make_client_checking_middleware(ts_diff, state_path) - middewares = ( + middewares = [ http_retry_request_middleware, sync_middleware, attrdict_middleware - ) + ] else: - middewares = ( + middewares = [ http_retry_request_middleware, attrdict_middleware - ) + ] provider = get_provider(endpoint, timeout=provider_timeout) web3 = Web3(provider) From 71057d13271790658b91fb110e2c10bbb05df871 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 12:45:41 +0300 Subject: [PATCH 017/100] Fix types in web3_wallet --- skale/wallets/common.py | 2 +- skale/wallets/web3_wallet.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/skale/wallets/common.py b/skale/wallets/common.py index 19d25ea9..2264715f 100644 --- a/skale/wallets/common.py +++ b/skale/wallets/common.py @@ -46,7 +46,7 @@ def sign(self, tx): def sign_and_send( self, tx_dict: Dict, - multiplier: Optional[int] = None, + multiplier: Optional[float] = None, priority: Optional[int] = None, method: Optional[str] = None ) -> str: diff --git a/skale/wallets/web3_wallet.py b/skale/wallets/web3_wallet.py index 64b9faf5..95f72587 100644 --- a/skale/wallets/web3_wallet.py +++ b/skale/wallets/web3_wallet.py @@ -91,10 +91,10 @@ def sign_hash(self, unsigned_hash: str): def sign_and_send( self, tx_dict: Dict, - multiplier: int = config.DEFAULT_GAS_MULTIPLIER, - priority: int = config.DEFAULT_PRIORITY, - method: Optional[str] = None, - meta: Optional[Dict] = None + multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, + priority: int | None = config.DEFAULT_PRIORITY, + method: str | None = None, + meta: Dict | None = None ) -> str: signed_tx = self.sign(tx_dict) try: From a6bd0e84b938a8b37cba1f96e5e2a509f3719c51 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 12:54:45 +0300 Subject: [PATCH 018/100] Fix types in sgx_wallet --- skale/wallets/sgx_wallet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skale/wallets/sgx_wallet.py b/skale/wallets/sgx_wallet.py index c2053bce..69d822d2 100644 --- a/skale/wallets/sgx_wallet.py +++ b/skale/wallets/sgx_wallet.py @@ -55,10 +55,10 @@ def sign(self, tx_dict): def sign_and_send( self, tx_dict: Dict, - multiplier: int = config.DEFAULT_GAS_MULTIPLIER, - priority: int = config.DEFAULT_PRIORITY, - method: Optional[str] = None, - meta: Optional[Dict] = None + multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, + priority: int | None = config.DEFAULT_PRIORITY, + method: str | None = None, + meta: Dict | None = None ) -> str: signed_tx = self.sign(tx_dict) try: From effdd35cf7c9c8c73eacb99a978ef6a4f62180e7 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 16:05:11 +0300 Subject: [PATCH 019/100] Fix types in ledger_wallet --- skale/wallets/ledger_wallet.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index 6d2638e0..4c74c116 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -173,10 +173,10 @@ def sign(self, tx_dict): def sign_and_send( self, tx: Dict, - multiplier: int = config.DEFAULT_GAS_MULTIPLIER, - priority: int = config.DEFAULT_PRIORITY, - method: Optional[str] = None, - meta: Optional[Dict] = None + multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, + priority: int | None = config.DEFAULT_PRIORITY, + method: str | None = None, + meta: Dict | None = None ) -> str: signed_tx = self.sign(tx) try: From d3bc27c67a2d8081da72f6c96068e7459ce343f3 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 16:28:45 +0300 Subject: [PATCH 020/100] Fix types in redis_wallet --- skale/wallets/redis_wallet.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/skale/wallets/redis_wallet.py b/skale/wallets/redis_wallet.py index eb0c420c..f1efe8c6 100644 --- a/skale/wallets/redis_wallet.py +++ b/skale/wallets/redis_wallet.py @@ -23,7 +23,7 @@ import os import time from enum import Enum -from typing import Dict, Optional, Tuple +from typing import Awaitable, Dict, Optional, Tuple from redis import Redis @@ -36,6 +36,7 @@ ) from skale.utils.web3_utils import get_receipt, MAX_WAITING_TIME from skale.wallets import BaseWallet +from skale.wallets.web3_wallet import Web3Wallet logger = logging.getLogger(__name__) @@ -76,11 +77,11 @@ def __init__( self, rs: Redis, pool: str, - base_wallet: BaseWallet, + web3_wallet: Web3Wallet, ) -> None: self.rs = rs self.pool = pool - self.wallet = base_wallet + self.wallet = web3_wallet def sign(self, tx: Dict) -> Dict: return self.wallet.sign(tx) @@ -173,7 +174,10 @@ def get_status(self, tx_id: str) -> str: def get_record(self, tx_id: str) -> Dict: rid = self._to_raw_id(tx_id) - return json.loads(self.rs.get(rid).decode('utf-8')) + response = self.rs.get(rid) + if isinstance(response, bytes): + return json.loads(response.decode('utf-8')) + raise ValueError('Unknown value was returned from get() call', response) def wait( self, From a50a23037890d007d00eef710c36b7c6de8d2c33 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 16 Apr 2024 17:20:10 +0300 Subject: [PATCH 021/100] Add type hints to allocator.py --- skale/utils/account_tools.py | 5 ++-- skale/utils/contracts_provision/allocator.py | 25 ++++++++++++++++---- skale/wallets/ledger_wallet.py | 2 +- skale/wallets/redis_wallet.py | 2 +- skale/wallets/sgx_wallet.py | 2 +- skale/wallets/web3_wallet.py | 2 +- 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/skale/utils/account_tools.py b/skale/utils/account_tools.py index 493abe74..0a15346c 100644 --- a/skale/utils/account_tools.py +++ b/skale/utils/account_tools.py @@ -23,6 +23,7 @@ from web3 import Web3 +from skale.skale_manager import SkaleManager from skale.transactions.tools import compose_eth_transfer_tx from skale.utils.constants import LONG_LINE from skale.wallets import LedgerWallet, Web3Wallet @@ -46,12 +47,12 @@ def create_wallet(wallet_type='web3', *args, **kwargs): def send_tokens( - skale, + skale: SkaleManager, receiver_address, amount, *args, **kwargs -): +) -> None: logger.info( f'Sending {amount} SKALE tokens from {skale.wallet.address} => ' f'{receiver_address}' diff --git a/skale/utils/contracts_provision/allocator.py b/skale/utils/contracts_provision/allocator.py index b5bbd017..ef30d181 100644 --- a/skale/utils/contracts_provision/allocator.py +++ b/skale/utils/contracts_provision/allocator.py @@ -19,16 +19,23 @@ from time import sleep +from web3.contract.contract import ContractEvent +from web3.types import LogReceipt +from web3._utils.filters import LogFilter + +from skale.skale_allocator import SkaleAllocator +from skale.skale_manager import SkaleManager from skale.utils.account_tools import send_tokens from skale.utils.contracts_provision import ( TEST_SKALE_AMOUNT, TEST_VESTING_CLIFF, TEST_TOTAL_VESTING_DURATION, TEST_VESTING_INTERVAL_TIME_UNIT, TEST_VESTING_INTERVAL, TEST_CAN_DELEGATE, TEST_IS_TERMINATABLE, POLL_INTERVAL, TEST_START_MONTH, TEST_FULL_AMOUNT, TEST_LOCKUP_AMOUNT ) +from skale.wallets.common import BaseWallet -def _catch_event(event_obj): - event_filter = event_obj.createFilter( +def _catch_event(event_obj: ContractEvent) -> LogReceipt: + event_filter: LogFilter = event_obj.create_filter( fromBlock=0, toBlock='latest' ) @@ -38,7 +45,11 @@ def _catch_event(event_obj): sleep(POLL_INTERVAL) -def transfer_tokens_to_allocator(skale_manager, skale_allocator, amount=TEST_SKALE_AMOUNT): +def transfer_tokens_to_allocator( + skale_manager: SkaleManager, + skale_allocator: SkaleAllocator, + amount: int = TEST_SKALE_AMOUNT +) -> None: send_tokens(skale_manager, skale_allocator.allocator.address, amount) @@ -46,7 +57,7 @@ def transfer_tokens_to_allocator(skale_manager, skale_allocator, amount=TEST_SKA # send_tokens(skale, skale.wallet, skale.token_launch_manager.address, amount) -def add_test_plan(skale_allocator): +def add_test_plan(skale_allocator: SkaleAllocator) -> int: skale_allocator.allocator.add_plan( vesting_cliff=TEST_VESTING_CLIFF, total_vesting_duration=TEST_TOTAL_VESTING_DURATION, @@ -58,7 +69,11 @@ def add_test_plan(skale_allocator): return len(skale_allocator.allocator.get_all_plans()) -def connect_test_beneficiary(skale_allocator, plan_id, wallet): +def connect_test_beneficiary( + skale_allocator: SkaleAllocator, + plan_id: int, + wallet: BaseWallet +) -> None: skale_allocator.allocator.connect_beneficiary_to_plan( beneficiary_address=wallet.address, plan_id=plan_id, diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index 4c74c116..13e522ae 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -19,7 +19,7 @@ import logging import struct -from typing import Dict, Optional +from typing import Dict from hexbytes import HexBytes from eth_account.datastructures import SignedTransaction diff --git a/skale/wallets/redis_wallet.py b/skale/wallets/redis_wallet.py index f1efe8c6..04c864aa 100644 --- a/skale/wallets/redis_wallet.py +++ b/skale/wallets/redis_wallet.py @@ -23,7 +23,7 @@ import os import time from enum import Enum -from typing import Awaitable, Dict, Optional, Tuple +from typing import Dict, Optional, Tuple from redis import Redis diff --git a/skale/wallets/sgx_wallet.py b/skale/wallets/sgx_wallet.py index 69d822d2..188498ed 100644 --- a/skale/wallets/sgx_wallet.py +++ b/skale/wallets/sgx_wallet.py @@ -18,7 +18,7 @@ # along with SKALE.py. If not, see . import logging -from typing import Dict, Optional +from typing import Dict from sgx import SgxClient from web3 import Web3 diff --git a/skale/wallets/web3_wallet.py b/skale/wallets/web3_wallet.py index 95f72587..05cab8c9 100644 --- a/skale/wallets/web3_wallet.py +++ b/skale/wallets/web3_wallet.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from typing import Dict, Optional +from typing import Dict from eth_keys import keys from web3 import Web3 from eth_account import messages From 9a3f3e2f0e192fb8987f745a38bfff859788b077 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 13:15:42 +0300 Subject: [PATCH 022/100] Add type hints to contracts privision --- skale/utils/contracts_provision/main.py | 62 ++++++++++++------------ skale/utils/contracts_provision/utils.py | 4 +- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index 18370cb6..5dab6798 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -21,6 +21,8 @@ from web3.types import RPCEndpoint from skale.contracts.manager.nodes import NodeStatus +from skale.dataclasses.schain_options import SchainOptions +from skale.skale_manager import SkaleManager from skale.transactions.result import TxRes from skale.utils.contracts_provision import ( D_VALIDATOR_ID, @@ -65,15 +67,15 @@ def set_mining_interval(web3: Web3, ms: int) -> int: return int(res['result']) -def set_default_mining_interval(web3) -> int: +def set_default_mining_interval(web3: Web3) -> int: return set_mining_interval(web3, DEFAULT_MINING_INTERVAL) -def add_test_permissions(skale): +def add_test_permissions(skale: SkaleManager) -> None: add_all_permissions(skale, skale.wallet.address) -def add_all_permissions(skale, address): +def add_all_permissions(skale: SkaleManager, address: str) -> None: default_admin_role = skale.manager.default_admin_role() if not skale.manager.has_role(default_admin_role, address): skale.manager.grant_role(default_admin_role, address) @@ -139,7 +141,7 @@ def add_all_permissions(skale, address): skale.slashing_table.grant_role(penalty_setter_role, address) -def add_test2_schain_type(skale) -> TxRes: +def add_test2_schain_type(skale: SkaleManager) -> TxRes: part_of_node = 1 number_of_nodes = 2 return skale.schains_internal.add_schain_type( @@ -147,7 +149,7 @@ def add_test2_schain_type(skale) -> TxRes: ) -def add_test4_schain_type(skale) -> TxRes: +def add_test4_schain_type(skale: SkaleManager) -> TxRes: part_of_node = 1 number_of_nodes = 4 return skale.schains_internal.add_schain_type( @@ -155,7 +157,7 @@ def add_test4_schain_type(skale) -> TxRes: ) -def cleanup_nodes(skale, ids=()): +def cleanup_nodes(skale: SkaleManager, ids: list[int] = []) -> None: active_ids = filter( lambda i: skale.nodes.get_node_status(i) == NodeStatus.ACTIVE, ids or skale.nodes.get_active_node_ids() @@ -166,7 +168,7 @@ def cleanup_nodes(skale, ids=()): skale.manager.node_exit(node_id) -def cleanup_schains(skale): +def cleanup_schains(skale: SkaleManager) -> None: for schain_id in skale.schains_internal.get_all_schains_ids(): schain_data = skale.schains.get(schain_id) schain_name = schain_data.get('name', None) @@ -174,19 +176,19 @@ def cleanup_schains(skale): skale.manager.delete_schain_by_root(schain_name, wait_for=True) -def cleanup_nodes_schains(skale): +def cleanup_nodes_schains(skale: SkaleManager) -> None: print('Cleanup nodes and schains') cleanup_schains(skale) cleanup_nodes(skale) -def create_clean_schain(skale): +def create_clean_schain(skale: SkaleManager) -> str: cleanup_nodes_schains(skale) create_nodes(skale) return create_schain(skale, random_name=True) -def create_node(skale) -> str: +def create_node(skale: SkaleManager) -> str: cleanup_nodes_schains(skale) ip, public_ip, port, name = generate_random_node_data() skale.manager.create_node( @@ -200,13 +202,13 @@ def create_node(skale) -> str: return name -def validator_exist(skale): +def validator_exist(skale: SkaleManager) -> bool: return skale.validator_service.validator_address_exists( skale.wallet.address ) -def add_delegation_period(skale): +def add_delegation_period(skale: SkaleManager) -> None: is_added = skale.delegation_period_manager.is_delegation_period_allowed(D_DELEGATION_PERIOD) if not is_added: skale.delegation_period_manager.set_delegation_period( @@ -216,7 +218,7 @@ def add_delegation_period(skale): ) -def setup_validator(skale): +def setup_validator(skale: SkaleManager) -> int: """Create and activate a validator""" set_test_msr(skale) print('Address', skale.wallet.address) @@ -236,7 +238,7 @@ def setup_validator(skale): return validator_id -def link_address_to_validator(skale): +def link_address_to_validator(skale: SkaleManager) -> None: print('Linking address to validator') signature = skale.validator_service.get_link_node_signature(D_VALIDATOR_ID) tx_res = skale.validator_service.link_node_address( @@ -247,7 +249,7 @@ def link_address_to_validator(skale): tx_res.raise_for_status() -def link_nodes_to_validator(skale, validator_id, node_skale_objs=()): +def link_nodes_to_validator(skale: SkaleManager, validator_id: int, node_skale_objs=()) -> None: print('Linking address to validator') node_skale_objs = node_skale_objs or (skale,) validator_id = validator_id or D_VALIDATOR_ID @@ -261,7 +263,7 @@ def link_nodes_to_validator(skale, validator_id, node_skale_objs=()): ) -def skip_delegation_delay(skale, delegation_id): +def skip_delegation_delay(skale: SkaleManager, delegation_id: int) -> None: print(f'Activating delegation with ID {delegation_id}') skale.token_state._skip_transition_delay( delegation_id, @@ -269,7 +271,7 @@ def skip_delegation_delay(skale, delegation_id): ) -def accept_pending_delegation(skale, delegation_id): +def accept_pending_delegation(skale: SkaleManager, delegation_id: int) -> None: print(f'Accepting delegation with ID: {delegation_id}') skale.delegation_controller.accept_pending_delegation( delegation_id=delegation_id, @@ -277,23 +279,23 @@ def accept_pending_delegation(skale, delegation_id): ) -def get_test_delegation_amount(skale): +def get_test_delegation_amount(skale: SkaleManager) -> int: msr = skale.constants_holder.msr() return msr * 30 -def set_test_msr(skale, msr=D_VALIDATOR_MIN_DEL): +def set_test_msr(skale: SkaleManager, msr: int = D_VALIDATOR_MIN_DEL) -> None: skale.constants_holder._set_msr( new_msr=msr, wait_for=True ) -def set_test_mda(skale): +def set_test_mda(skale: SkaleManager) -> None: skale.validator_service.set_validator_mda(0, wait_for=True) -def delegate_to_validator(skale, validator_id=D_VALIDATOR_ID): +def delegate_to_validator(skale: SkaleManager, validator_id: int = D_VALIDATOR_ID) -> None: print(f'Delegating tokens to validator ID: {validator_id}') skale.delegation_controller.delegate( validator_id=validator_id, @@ -304,12 +306,12 @@ def delegate_to_validator(skale, validator_id=D_VALIDATOR_ID): ) -def enable_validator(skale, validator_id=D_VALIDATOR_ID): +def enable_validator(skale: SkaleManager, validator_id: int = D_VALIDATOR_ID) -> None: print(f'Enabling validator ID: {D_VALIDATOR_ID}') skale.validator_service._enable_validator(validator_id, wait_for=True) -def create_validator(skale): +def create_validator(skale: SkaleManager) -> None: print('Creating default validator') skale.validator_service.register_validator( name=D_VALIDATOR_NAME, @@ -320,7 +322,7 @@ def create_validator(skale): ) -def create_nodes(skales, names=()): +def create_nodes(skales, names: tuple[str, str] | None = None) -> list[int]: # create couple of nodes print('Creating two nodes') node_names = names or (DEFAULT_NODE_NAME, SECOND_NODE_NAME) @@ -342,12 +344,12 @@ def create_nodes(skales, names=()): def create_schain( - skale, - schain_name=DEFAULT_SCHAIN_NAME, - schain_type=1, - random_name=False, - schain_options=None -): + skale: SkaleManager, + schain_name: str = DEFAULT_SCHAIN_NAME, + schain_type: int = 1, + random_name: bool = False, + schain_options: SchainOptions | None = None +) -> str: print('Creating schain') # create 1 s-chain type_of_nodes, lifetime_seconds, name = generate_random_schain_data(skale) diff --git a/skale/utils/contracts_provision/utils.py b/skale/utils/contracts_provision/utils.py index 17e87648..45e041d3 100644 --- a/skale/utils/contracts_provision/utils.py +++ b/skale/utils/contracts_provision/utils.py @@ -35,12 +35,12 @@ def generate_random_port(): return random.randint(0, 60000) -def generate_random_node_data(): +def generate_random_node_data() -> tuple[str, str, int, str]: return generate_random_ip(), generate_random_ip(), \ generate_random_port(), generate_random_name() -def generate_random_schain_data(skale): +def generate_random_schain_data(skale) -> tuple[int, int, str]: schain_type = skale.schains_internal.number_of_schain_types() lifetime_seconds = 3600 # 1 hour return schain_type, lifetime_seconds, generate_random_name() From 55004250c0efec61c7357566ebde1b9ce62ec272 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 15:53:28 +0300 Subject: [PATCH 023/100] Add type hints to exceptions --- skale/utils/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skale/utils/exceptions.py b/skale/utils/exceptions.py index a59912ae..26ecf1e3 100644 --- a/skale/utils/exceptions.py +++ b/skale/utils/exceptions.py @@ -36,13 +36,13 @@ class SChainNotFoundException(Exception): class IncompatibleAbiError(Exception): """Raised when required contract is not found in the ABI file""" - def __init__(self, key): + def __init__(self, key: str): message = f'Required field was not found in the ABI file: {key}' super().__init__(message) class InvalidNodeIdError(Exception): """Raised when wrong node id passed""" - def __init__(self, node_id): + def __init__(self, node_id: int): message = f'Node with ID = {node_id} doesn\'t exist!' super().__init__(message) From 9baa8d27e5a1039bd164fad87d310a33e731cf39 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 15:56:31 +0300 Subject: [PATCH 024/100] Add type hints to generator --- skale/schain_config/generator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/skale/schain_config/generator.py b/skale/schain_config/generator.py index 4f6f07d8..3e9f66e5 100644 --- a/skale/schain_config/generator.py +++ b/skale/schain_config/generator.py @@ -18,7 +18,10 @@ # along with SKALE.py. If not, see . -def get_nodes_for_schain(skale, name): +from skale.skale_manager import SkaleManager + + +def get_nodes_for_schain(skale: SkaleManager, name: str) -> list[int]: nodes = [] ids = skale.schains_internal.get_node_ids_for_schain(name) for id_ in ids: @@ -28,7 +31,7 @@ def get_nodes_for_schain(skale, name): return nodes -def get_schain_nodes_with_schains(skale, schain_name) -> list: +def get_schain_nodes_with_schains(skale: SkaleManager, schain_name: str) -> list: """Returns list of nodes for schain with schains for all nodes""" nodes = get_nodes_for_schain(skale, schain_name) for node in nodes: From aedc9090174c93a1f2cc53b7e030aaae9c3ecacb Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 16:09:05 +0300 Subject: [PATCH 025/100] Add type hints to schain_options --- skale/contracts/manager/schains.py | 3 +++ skale/dataclasses/schain_options.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/skale/contracts/manager/schains.py b/skale/contracts/manager/schains.py index 72afff1e..f27c625e 100644 --- a/skale/contracts/manager/schains.py +++ b/skale/contracts/manager/schains.py @@ -38,6 +38,9 @@ ] +SchainOption = tuple[str, bytes] + + @dataclass class SchainStructure: name: str diff --git a/skale/dataclasses/schain_options.py b/skale/dataclasses/schain_options.py index 32d74357..2eae2d63 100644 --- a/skale/dataclasses/schain_options.py +++ b/skale/dataclasses/schain_options.py @@ -19,20 +19,22 @@ from dataclasses import dataclass +from skale.contracts.manager.schains import SchainOption + @dataclass class SchainOptions: multitransaction_mode: bool threshold_encryption: bool - def to_tuples(self) -> list: + def to_tuples(self) -> list[SchainOption]: return [ ('multitr', bool_to_bytes(self.multitransaction_mode)), ('encrypt', bool_to_bytes(self.threshold_encryption)) ] -def parse_schain_options(raw_options: list) -> SchainOptions: +def parse_schain_options(raw_options: list[SchainOption]) -> SchainOptions: """ Parses raw sChain options from smart contracts (list of tuples). Returns default values if nothing is set on contracts. From 88d7b314d48d35b9a115e85f80f1b21025569a93 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 16:25:11 +0300 Subject: [PATCH 026/100] Add type hints to ports_allocation --- skale/contracts/manager/schains.py | 2 +- skale/contracts/manager/schains_internal.py | 18 +++++++++++++++++- skale/schain_config/ports_allocation.py | 13 ++++++++++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/skale/contracts/manager/schains.py b/skale/contracts/manager/schains.py index f27c625e..d2300899 100644 --- a/skale/contracts/manager/schains.py +++ b/skale/contracts/manager/schains.py @@ -102,7 +102,7 @@ def get_schains_for_owner(self, account): schains.append(schain) return schains - def get_schains_for_node(self, node_id): + def get_schains_for_node(self, node_id) -> list[SchainStructure]: schains = [] schain_ids = self.schains_internal.get_schain_ids_for_node(node_id) for schain_id in schain_ids: diff --git a/skale/contracts/manager/schains_internal.py b/skale/contracts/manager/schains_internal.py index e91d47ff..ac7f4ac0 100644 --- a/skale/contracts/manager/schains_internal.py +++ b/skale/contracts/manager/schains_internal.py @@ -18,11 +18,27 @@ # along with SKALE.py. If not, see . """ SchainsInternal.sol functions """ +from dataclasses import dataclass import functools from skale.contracts.base_contract import BaseContract, transaction_method from skale.transactions.result import TxRes +@dataclass +class Schain: + name: str + owner: str + indexInOwnerList: int + partOfNode: int + lifetime: int + startDate: int + startBlock: int + deposit: int + index: int + generation: int + originator: str + + class SChainsInternal(BaseContract): """Wrapper for some of the SchainsInternal.sol functions""" @@ -31,7 +47,7 @@ class SChainsInternal(BaseContract): def schains(self): return self.skale.schains - def get_raw(self, name): + def get_raw(self, name) -> Schain: return self.contract.functions.schains(name).call() def get_all_schains_ids(self): diff --git a/skale/schain_config/ports_allocation.py b/skale/schain_config/ports_allocation.py index 90fa71e2..577cd7fc 100644 --- a/skale/schain_config/ports_allocation.py +++ b/skale/schain_config/ports_allocation.py @@ -17,21 +17,28 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from skale.contracts.manager.schains import SchainStructure +from skale.types.node import Port +from skale.types.schain import SchainName from skale.utils.exceptions import SChainNotFoundException from skale.schain_config import PORTS_PER_SCHAIN -def calc_schain_base_port(node_base_port, schain_index): +def calc_schain_base_port(node_base_port: Port, schain_index: int) -> Port: return node_base_port + schain_index * PORTS_PER_SCHAIN -def get_schain_index_in_node(schain_name, node_schains): +def get_schain_index_in_node(schain_name: SchainName, node_schains: list[SchainStructure]) -> int: for index, schain in enumerate(node_schains): if schain_name == schain['name']: return index raise SChainNotFoundException(f'sChain {schain_name} is not found in the list: {node_schains}') -def get_schain_base_port_on_node(schains_on_node, schain_name, node_base_port): +def get_schain_base_port_on_node( + schains_on_node: list[SchainStructure], + schain_name: SchainName, + node_base_port: Port +) -> Port: schain_index = get_schain_index_in_node(schain_name, schains_on_node) return calc_schain_base_port(node_base_port, schain_index) From df2bcb3838ae8a0fc5d10c59f766f72223df465d Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 16:29:13 +0300 Subject: [PATCH 027/100] Add type hints to node_info --- skale/dataclasses/node_info.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skale/dataclasses/node_info.py b/skale/dataclasses/node_info.py index 36d8ddbc..87fca103 100644 --- a/skale/dataclasses/node_info.py +++ b/skale/dataclasses/node_info.py @@ -19,6 +19,7 @@ from dataclasses import dataclass from skale.dataclasses.skaled_ports import SkaledPorts +from skale.types.node import NodeId, Port @dataclass @@ -28,7 +29,7 @@ class NodeInfo(): name: str base_port: int - def calc_ports(self): + def calc_ports(self) -> dict[str, Port]: return { 'httpRpcPort': self.base_port + SkaledPorts.HTTP_JSON.value, 'httpsRpcPort': self.base_port + SkaledPorts.HTTPS_JSON.value, @@ -37,7 +38,7 @@ def calc_ports(self): 'infoHttpRpcPort': self.base_port + SkaledPorts.INFO_HTTP_JSON.value } - def to_dict(self): + def to_dict(self) -> dict[str, NodeId | str | Port]: return { 'nodeID': self.node_id, 'nodeName': self.name, From 5e9765f83eedfd53cdecf8eea17dc1d49b7c5ccd Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 17:14:30 +0300 Subject: [PATCH 028/100] Add type hints to helper --- skale/utils/helper.py | 76 +++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 25 deletions(-) diff --git a/skale/utils/helper.py b/skale/utils/helper.py index e3a69e46..21bacf4f 100644 --- a/skale/utils/helper.py +++ b/skale/utils/helper.py @@ -27,18 +27,33 @@ import sys from logging import Formatter, StreamHandler from random import randint +from typing import Any, Callable, Generator, cast from skale.config import ENV +from skale.types.node import Port +from skale.utils.contract_info import ContractInfo logger = logging.getLogger(__name__) -def decapitalize(s): +def decapitalize(s: str) -> str: return s[:1].lower() + s[1:] if s else '' -def format_fields(fields, flist=False): +def format_fields( + fields: list[str], + flist: bool = False +) -> Callable[ + [Callable[ + ..., + Callable[ + ..., + dict[str, Any] | list[dict[str, Any]] | Any | None + ] + ]], + Callable[..., dict[str, Any] | list[dict[str, Any]] | Any | None] +]: """ Transform array to object with passed fields Usage: @@ -49,8 +64,19 @@ def my_method() => {'field_name1': 0, 'field_name2': 'Test'} """ - def real_decorator(function): - def wrapper(*args, **kwargs): + def real_decorator( + function: Callable[ + ..., + Callable[ + ..., + dict[str, Any] | list[dict[str, Any]] | Any | None + ] + ] + ) -> Callable[..., dict[str, Any] | list[dict[str, Any]] | Any | None]: + def wrapper( + *args: Any, + **kwargs: Any + ) -> dict[str, Any] | list[dict[str, Any]] | Any | None: result = function(*args, **kwargs) if result is None: @@ -86,7 +112,7 @@ def ip_to_bytes(ip: str) -> bytes: # pragma: no cover return socket.inet_aton(ip) -def is_valid_ipv4_address(address): +def is_valid_ipv4_address(address: str) -> bool: try: ipaddress.IPv4Address(address) except ValueError: @@ -94,43 +120,43 @@ def is_valid_ipv4_address(address): return True -def get_abi(abi_filepath: string = None): +def get_abi(abi_filepath: str | None = None) -> dict[str, Any]: if abi_filepath: with open(abi_filepath, encoding='utf-8') as data_file: - return json.load(data_file) + return cast(dict[str, Any], json.load(data_file)) return {} -def get_skale_manager_address(abi_filepath: string = None) -> str: - return get_abi(abi_filepath)['skale_manager_address'] +def get_skale_manager_address(abi_filepath: str | None = None) -> str: + return cast(str, get_abi(abi_filepath)['skale_manager_address']) -def get_allocator_address(abi_filepath: string = None) -> str: - return get_abi(abi_filepath)['allocator_address'] +def get_allocator_address(abi_filepath: str | None = None) -> str: + return cast(str, get_abi(abi_filepath)['allocator_address']) -def generate_nonce(): # pragma: no cover +def generate_nonce() -> int: # pragma: no cover return randint(0, 65534) -def random_string(size=6, chars=string.ascii_lowercase): # pragma: no cover +def random_string(size: int = 6, chars: str = string.ascii_lowercase) -> str: # pragma: no cover return ''.join(random.choice(chars) for x in range(size)) -def generate_random_ip(): # pragma: no cover +def generate_random_ip() -> str: # pragma: no cover return '.'.join('%s' % random.randint(0, 255) for i in range(4)) -def generate_random_name(len=8): # pragma: no cover +def generate_random_name(len: int = 8) -> str: # pragma: no cover return ''.join( random.choices(string.ascii_uppercase + string.digits, k=len)) -def generate_random_port(): # pragma: no cover +def generate_random_port() -> Port: # pragma: no cover return random.randint(0, 60000) -def generate_custom_config(ip, ws_port): +def generate_custom_config(ip: str, ws_port: Port) -> dict[str, str | Port]: if not ip or not ws_port: raise ValueError( f'For custom init you should provide ip and ws_port: {ip}, {ws_port}' @@ -141,17 +167,17 @@ def generate_custom_config(ip, ws_port): } -def add_0x_prefix(bytes_string): # pragma: no cover +def add_0x_prefix(bytes_string: str) -> str: # pragma: no cover return '0x' + bytes_string -def rm_0x_prefix(bytes_string): +def rm_0x_prefix(bytes_string: str) -> str: if bytes_string.startswith('0x'): return bytes_string[2:] return bytes_string -def init_default_logger(): # pragma: no cover +def init_default_logger() -> None: # pragma: no cover handlers = [] formatter = Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s') @@ -164,7 +190,7 @@ def init_default_logger(): # pragma: no cover logging.basicConfig(level=logging.DEBUG, handlers=handlers) -def chunk(in_string, num_chunks): # pragma: no cover +def chunk(in_string: str, num_chunks: int) -> Generator[str, None, None]: # pragma: no cover chunk_size = len(in_string) // num_chunks if len(in_string) % num_chunks: chunk_size += 1 @@ -179,23 +205,23 @@ def chunk(in_string, num_chunks): # pragma: no cover yield ''.join(accumulator) -def split_public_key(public_key: str) -> list: +def split_public_key(public_key: str) -> list[bytes]: public_key = rm_0x_prefix(public_key) pk_parts = list(chunk(public_key, 2)) return list(map(bytes.fromhex, pk_parts)) -def get_contracts_info(contracts_data): +def get_contracts_info(contracts_data: list[ContractInfo]) -> dict[str, ContractInfo]: contracts_info = {} for contract_info in contracts_data: contracts_info[contract_info.name] = contract_info return contracts_info -def to_camel_case(snake_str): +def to_camel_case(snake_str: str) -> str: components = snake_str.split('_') return components[0] + ''.join(x.title() for x in components[1:]) -def is_test_env(): +def is_test_env() -> bool: return "pytest" in sys.modules or ENV == 'test' From 981ad93b74446d666b9535b5449c9ba6f0528fe6 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 17:29:24 +0300 Subject: [PATCH 029/100] Add type hints to generator --- skale/utils/random_names/generator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skale/utils/random_names/generator.py b/skale/utils/random_names/generator.py index 57263114..d90f6912 100644 --- a/skale/utils/random_names/generator.py +++ b/skale/utils/random_names/generator.py @@ -22,21 +22,21 @@ SOUND_AND_APPEARANCE_ADJECTIVES, POSITIVE_AND_TIME_ADJECTIVES -def generate_random_node_name(): +def generate_random_node_name() -> str: return generate_name(CONSTELLATIONS, POSITIVE_AND_TIME_ADJECTIVES) -def generate_random_schain_name(): +def generate_random_schain_name() -> str: return generate_name(STARS, SOUND_AND_APPEARANCE_ADJECTIVES) -def generate_name(noun_dict, adjective_dict): +def generate_name(noun_dict: list[str], adjective_dict: list[str]) -> str: noun = get_random_word_from_dict(noun_dict) adjective = get_random_word_from_dict(adjective_dict) return f'{adjective}-{noun}' -def get_random_word_from_dict(vocabulary_dict): +def get_random_word_from_dict(vocabulary_dict: list[str]) -> str: return random.choice(vocabulary_dict).lower().replace(" ", "-") From 90ec4ec3b35d1e9a0324d371591d3ae57a284872 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 17:32:01 +0300 Subject: [PATCH 030/100] Add type hints to utils --- skale/types/node.py | 21 +++++++++++++++++++++ skale/types/schain.py | 20 ++++++++++++++++++++ skale/utils/contracts_provision/utils.py | 13 ++++++++----- 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 skale/types/node.py create mode 100644 skale/types/schain.py diff --git a/skale/types/node.py b/skale/types/node.py new file mode 100644 index 00000000..a3746b51 --- /dev/null +++ b/skale/types/node.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +NodeId = int +Port = int diff --git a/skale/types/schain.py b/skale/types/schain.py new file mode 100644 index 00000000..0d538c8d --- /dev/null +++ b/skale/types/schain.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +SchainName = str diff --git a/skale/utils/contracts_provision/utils.py b/skale/utils/contracts_provision/utils.py index 45e041d3..aae78c0d 100644 --- a/skale/utils/contracts_provision/utils.py +++ b/skale/utils/contracts_provision/utils.py @@ -20,18 +20,21 @@ import random import string +from skale.skale_manager import SkaleManager +from skale.types.node import Port -def generate_random_ip(): + +def generate_random_ip() -> str: return '.'.join('%s' % random.randint(0, 255) for i in range(4)) -def generate_random_name(len=8): +def generate_random_name(length: int = 8) -> str: return ''.join( - random.choices(string.ascii_uppercase + string.digits, k=len) + random.choices(string.ascii_uppercase + string.digits, k=length) ) -def generate_random_port(): +def generate_random_port() -> Port: return random.randint(0, 60000) @@ -40,7 +43,7 @@ def generate_random_node_data() -> tuple[str, str, int, str]: generate_random_port(), generate_random_name() -def generate_random_schain_data(skale) -> tuple[int, int, str]: +def generate_random_schain_data(skale: SkaleManager) -> tuple[int, int, str]: schain_type = skale.schains_internal.number_of_schain_types() lifetime_seconds = 3600 # 1 hour return schain_type, lifetime_seconds, generate_random_name() From 625521abbe22e031a138b7528a916781bb50d034 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 18:08:09 +0300 Subject: [PATCH 031/100] Remove circular imports --- skale/dataclasses/schain_options.py | 6 ++++-- skale/utils/helper.py | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/skale/dataclasses/schain_options.py b/skale/dataclasses/schain_options.py index 2eae2d63..d655b576 100644 --- a/skale/dataclasses/schain_options.py +++ b/skale/dataclasses/schain_options.py @@ -16,10 +16,12 @@ # # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . - +from __future__ import annotations from dataclasses import dataclass +from typing import TYPE_CHECKING -from skale.contracts.manager.schains import SchainOption +if TYPE_CHECKING: + from skale.contracts.manager.schains import SchainOption @dataclass diff --git a/skale/utils/helper.py b/skale/utils/helper.py index 21bacf4f..ad991c3a 100644 --- a/skale/utils/helper.py +++ b/skale/utils/helper.py @@ -18,6 +18,8 @@ # along with SKALE.py. If not, see . """ SKALE helper utilities """ +from __future__ import annotations + import ipaddress import json import logging @@ -27,11 +29,13 @@ import sys from logging import Formatter, StreamHandler from random import randint -from typing import Any, Callable, Generator, cast +from typing import TYPE_CHECKING, Any, Callable, Generator, cast from skale.config import ENV from skale.types.node import Port -from skale.utils.contract_info import ContractInfo + +if TYPE_CHECKING: + from skale.utils.contract_info import ContractInfo logger = logging.getLogger(__name__) From ed0da4836e937e5ce32a32474d62a6f6209d0c7b Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 18:39:20 +0300 Subject: [PATCH 032/100] Add type hints to common --- skale/wallets/common.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/skale/wallets/common.py b/skale/wallets/common.py index 2264715f..4a308515 100644 --- a/skale/wallets/common.py +++ b/skale/wallets/common.py @@ -18,12 +18,15 @@ # along with SKALE.py. If not, see . from abc import ABC, abstractmethod -from typing import Dict, Optional +from typing import Optional + +from web3 import Web3 +from web3.types import SignedTx, TxParams, TxReceipt from skale.transactions.exceptions import ChainIdError -def ensure_chain_id(tx_dict, web3): +def ensure_chain_id(tx_dict: TxParams, web3: Web3) -> None: if not tx_dict.get('chainId'): tx_dict['chainId'] = web3.eth.chain_id if not tx_dict.get('chainId'): @@ -39,13 +42,13 @@ class MessageNotSignedError(Exception): class BaseWallet(ABC): @abstractmethod - def sign(self, tx): + def sign(self, tx: TxParams) -> SignedTx: pass @abstractmethod def sign_and_send( self, - tx_dict: Dict, + tx_dict: TxParams, multiplier: Optional[float] = None, priority: Optional[int] = None, method: Optional[str] = None @@ -67,5 +70,5 @@ def public_key(self) -> str: pass @abstractmethod - def wait(self, tx: str, confirmation_blocks: int | None = None): + def wait(self, tx: str, confirmation_blocks: int | None = None) -> TxReceipt: pass From 2dbf9c1cb0376031668f79d954de1b461114c16e Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 18:57:46 +0300 Subject: [PATCH 033/100] Add type hints to results --- skale/transactions/result.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/skale/transactions/result.py b/skale/transactions/result.py index f5c0251f..7b86c7cd 100644 --- a/skale/transactions/result.py +++ b/skale/transactions/result.py @@ -20,6 +20,9 @@ import enum from typing import NamedTuple +from web3.types import TxReceipt +from eth_typing import HexStr + from skale.transactions.exceptions import ( DryRunFailedError, DryRunRevertError, @@ -36,11 +39,16 @@ class TxCallResult(NamedTuple): status: TxStatus error: str message: str - data: dict + data: dict[str, str] class TxRes: - def __init__(self, tx_call_result=None, tx_hash=None, receipt=None, revert=None): + def __init__( + self, + tx_call_result: TxCallResult | None = None, + tx_hash: HexStr | None = None, + receipt: TxReceipt | None = None + ): self.tx_call_result = tx_call_result self.tx_hash = tx_hash self.receipt = receipt @@ -61,7 +69,10 @@ def __repr__(self) -> str: def raise_for_status(self) -> None: if self.receipt is not None: if self.receipt['status'] == TxStatus.FAILED: - raise TransactionFailedError(self.receipt) + raise TransactionFailedError( + "Tx status is failed", + {key: str(value) for key, value in self.receipt.items()} + ) elif self.tx_call_result is not None and self.tx_call_result.status == TxStatus.FAILED: if self.tx_call_result.error == 'revert': raise DryRunRevertError(self.tx_call_result.message) From deca0b8b85d7e059c7b7f4dd22bc800912f42f60 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 17 Apr 2024 19:39:44 +0300 Subject: [PATCH 034/100] Add type hints to web3_utils --- skale/dataclasses/node_info.py | 14 ++++++------ skale/types/node.py | 7 ++++-- skale/types/schain.py | 5 ++++- skale/utils/helper.py | 2 +- skale/utils/web3_utils.py | 41 +++++++++++++++++++++++++++------- 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/skale/dataclasses/node_info.py b/skale/dataclasses/node_info.py index 87fca103..69ee2bc2 100644 --- a/skale/dataclasses/node_info.py +++ b/skale/dataclasses/node_info.py @@ -25,17 +25,17 @@ @dataclass class NodeInfo(): """Dataclass that represents base info about the node""" - node_id: int + node_id: NodeId name: str - base_port: int + base_port: Port def calc_ports(self) -> dict[str, Port]: return { - 'httpRpcPort': self.base_port + SkaledPorts.HTTP_JSON.value, - 'httpsRpcPort': self.base_port + SkaledPorts.HTTPS_JSON.value, - 'wsRpcPort': self.base_port + SkaledPorts.WS_JSON.value, - 'wssRpcPort': self.base_port + SkaledPorts.WSS_JSON.value, - 'infoHttpRpcPort': self.base_port + SkaledPorts.INFO_HTTP_JSON.value + 'httpRpcPort': Port(self.base_port + SkaledPorts.HTTP_JSON.value), + 'httpsRpcPort': Port(self.base_port + SkaledPorts.HTTPS_JSON.value), + 'wsRpcPort': Port(self.base_port + SkaledPorts.WS_JSON.value), + 'wssRpcPort': Port(self.base_port + SkaledPorts.WSS_JSON.value), + 'infoHttpRpcPort': Port(self.base_port + SkaledPorts.INFO_HTTP_JSON.value) } def to_dict(self) -> dict[str, NodeId | str | Port]: diff --git a/skale/types/node.py b/skale/types/node.py index a3746b51..3e51984b 100644 --- a/skale/types/node.py +++ b/skale/types/node.py @@ -17,5 +17,8 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -NodeId = int -Port = int +from typing import NewType + + +NodeId = NewType("NodeId", int) +Port = NewType("Port", int) diff --git a/skale/types/schain.py b/skale/types/schain.py index 0d538c8d..0e2e527b 100644 --- a/skale/types/schain.py +++ b/skale/types/schain.py @@ -17,4 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -SchainName = str +from typing import NewType + + +SchainName = NewType("SchainName", str) diff --git a/skale/utils/helper.py b/skale/utils/helper.py index ad991c3a..7dbe056d 100644 --- a/skale/utils/helper.py +++ b/skale/utils/helper.py @@ -157,7 +157,7 @@ def generate_random_name(len: int = 8) -> str: # pragma: no cover def generate_random_port() -> Port: # pragma: no cover - return random.randint(0, 60000) + return Port(random.randint(0, 60000)) def generate_custom_config(ip: str, ws_port: Port) -> dict[str, str | Port]: diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index 91a25525..e424d895 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -21,10 +21,11 @@ import logging import os import time -from typing import Iterable +from typing import Any, Callable, Dict, Iterable from urllib.parse import urlparse from eth_keys import keys +from eth_typing import BlockNumber from web3 import Web3, WebsocketProvider, HTTPProvider from web3.exceptions import TransactionNotFound from web3.middleware import ( @@ -32,6 +33,8 @@ geth_poa_middleware, http_retry_request_middleware ) +from web3.providers.base import JSONBaseProvider +from web3.types import RPCEndpoint, RPCResponse, Timestamp import skale.config as config from skale.transactions.exceptions import TransactionFailedError @@ -50,7 +53,11 @@ DEFAULT_BLOCKS_TO_WAIT = 50 -def get_provider(endpoint, timeout=DEFAULT_HTTP_TIMEOUT, request_kwargs={}): +def get_provider( + endpoint: str, + timeout: int = DEFAULT_HTTP_TIMEOUT, + request_kwargs: Dict[str, Any] = {} +) -> JSONBaseProvider: scheme = urlparse(endpoint).scheme if scheme == 'ws' or scheme == 'wss': kwargs = request_kwargs or {'max_size': WS_MAX_MESSAGE_DATA_BYTES} @@ -87,21 +94,39 @@ def save_last_known_block_number(state_path: str, block_number: int) -> None: last_block_file.write(str(block_number)) -def outdated_client_time_msg(method, current_time, latest_block_timestamp, allowed_ts_diff): +def outdated_client_time_msg( + method: RPCEndpoint, + current_time: float, + latest_block_timestamp: Timestamp, + allowed_ts_diff: int +) -> str: return f'{method} failed; \ current_time: {current_time}, latest_block_timestamp: {latest_block_timestamp}, \ allowed_ts_diff: {allowed_ts_diff}' -def outdated_client_file_msg(method, latest_block_number, saved_number, state_path): +def outdated_client_file_msg( + method: RPCEndpoint, + latest_block_number: BlockNumber, + saved_number: int, + state_path: str +) -> str: return f'{method} failed: latest_block_number: {latest_block_number}, \ saved_number: {saved_number}, state_path: {state_path}' -def make_client_checking_middleware(allowed_ts_diff: int, - state_path: str | None = None): - def eth_client_checking_middleware(make_request, web3): - def middleware(method, params): +def make_client_checking_middleware( + allowed_ts_diff: int, + state_path: str | None = None +) -> Callable[ + [Callable[[RPCEndpoint, Any], RPCResponse], Web3], + Callable[[RPCEndpoint, Any], RPCResponse] +]: + def eth_client_checking_middleware( + make_request: Callable[[RPCEndpoint, Any], RPCResponse], + web3: Web3 + ) -> Callable[[RPCEndpoint, Any], RPCResponse]: + def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: if method in ('eth_block_number', 'eth_getBlockByNumber'): response = make_request(method, params) else: From 8607380ecb7fde6cf990d11b3ed890525bd3b0b2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 18 Apr 2024 12:41:31 +0300 Subject: [PATCH 035/100] Add type hints to web3_utils --- skale/utils/web3_utils.py | 65 ++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index e424d895..a21aeea8 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -25,7 +25,8 @@ from urllib.parse import urlparse from eth_keys import keys -from eth_typing import BlockNumber +from eth_keys.datatypes import PublicKey +from eth_typing import Address, AnyAddress, BlockNumber, ChecksumAddress, HexStr from web3 import Web3, WebsocketProvider, HTTPProvider from web3.exceptions import TransactionNotFound from web3.middleware import ( @@ -34,7 +35,16 @@ http_retry_request_middleware ) from web3.providers.base import JSONBaseProvider -from web3.types import RPCEndpoint, RPCResponse, Timestamp +from web3.types import ( + _Hash32, + ENS, + Middleware, + Nonce, + RPCEndpoint, + RPCResponse, + Timestamp, + TxReceipt +) import skale.config as config from skale.transactions.exceptions import TransactionFailedError @@ -164,8 +174,8 @@ def middleware(method: RPCEndpoint, params: Any) -> RPCResponse: def init_web3(endpoint: str, provider_timeout: int = DEFAULT_HTTP_TIMEOUT, - middlewares: Iterable | None = None, - state_path: str | None = None, ts_diff: int | None = None): + middlewares: Iterable[Middleware] | None = None, + state_path: str | None = None, ts_diff: int | None = None) -> Web3: if not middlewares: ts_diff = ts_diff or config.ALLOWED_TS_DIFF state_path = state_path or config.LAST_BLOCK_FILE @@ -191,20 +201,20 @@ def init_web3(endpoint: str, return web3 -def get_receipt(web3, tx): +def get_receipt(web3: Web3, tx: _Hash32) -> TxReceipt: return web3.eth.get_transaction_receipt(tx) -def get_eth_nonce(web3, address): +def get_eth_nonce(web3: Web3, address: Address | ChecksumAddress | ENS) -> Nonce: return web3.eth.get_transaction_count(address) def wait_for_receipt_by_blocks( - web3, - tx, - blocks_to_wait=DEFAULT_BLOCKS_TO_WAIT, - timeout=MAX_WAITING_TIME -): + web3: Web3, + tx: _Hash32, + blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, + timeout: int = MAX_WAITING_TIME +) -> TxReceipt: blocks_to_wait = blocks_to_wait or DEFAULT_BLOCKS_TO_WAIT timeout = timeout or MAX_WAITING_TIME previous_block = web3.eth.block_number @@ -221,11 +231,11 @@ def wait_for_receipt_by_blocks( current_block = web3.eth.block_number time.sleep(3) raise TransactionNotMinedError( - f'Transaction with hash: {tx} not found in {blocks_to_wait} blocks.' + f'Transaction with hash: {str(tx)} not found in {blocks_to_wait} blocks.' ) -def wait_receipt(web3, tx, retries=30, timeout=5): +def wait_receipt(web3: Web3, tx: _Hash32, retries: int = 30, timeout: int = 5) -> TxReceipt: for _ in range(0, retries): try: receipt = get_receipt(web3, tx) @@ -235,11 +245,11 @@ def wait_receipt(web3, tx, retries=30, timeout=5): return receipt time.sleep(timeout) # pragma: no cover raise TransactionNotMinedError( - f'Transaction with hash: {tx} not mined after {retries} retries.' + f'Transaction with hash: {str(tx)} not mined after {retries} retries.' ) -def check_receipt(receipt, raise_error=True): +def check_receipt(receipt: TxReceipt, raise_error: bool = True) -> bool: if receipt['status'] != 1: # pragma: no cover if raise_error: raise TransactionFailedError( @@ -251,11 +261,11 @@ def check_receipt(receipt, raise_error=True): def wait_for_confirmation_blocks( - web3, - blocks_to_wait, - timeout=MAX_WAITING_TIME, - request_timeout=5 -): + web3: Web3, + blocks_to_wait: int, + timeout: int = MAX_WAITING_TIME, + request_timeout: int = 5 +) -> None: current_block = start_block = web3.eth.block_number logger.info( f'Current block number is {current_block}, ' @@ -268,32 +278,25 @@ def wait_for_confirmation_blocks( time.sleep(request_timeout) -def private_key_to_public(pr): +def private_key_to_public(pr: HexStr) -> PublicKey: pr_bytes = Web3.to_bytes(hexstr=pr) pk = keys.PrivateKey(pr_bytes) return pk.public_key -def public_key_to_address(pk): +def public_key_to_address(pk: PublicKey) -> HexStr: hash = Web3.keccak(hexstr=str(pk)) return Web3.to_hex(hash[-20:]) -def private_key_to_address(pr): +def private_key_to_address(pr: HexStr) -> HexStr: pk = private_key_to_public(pr) return public_key_to_address(pk) -def to_checksum_address(address): +def to_checksum_address(address: AnyAddress | str | bytes) -> ChecksumAddress: return Web3.to_checksum_address(address) -def wallet_to_public_key(wallet): - if isinstance(wallet, dict): - return private_key_to_public(wallet['private_key']) - else: - return wallet['public_key'] - - def default_gas_price(web3: Web3) -> int: return web3.eth.gas_price * GAS_PRICE_COEFFICIENT From 869bf4b523a11157e0f4a310ef51baed9ca68b59 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 15:52:19 +0300 Subject: [PATCH 036/100] Add type hints to web3_wallet --- skale/wallets/common.py | 10 +++-- skale/wallets/web3_wallet.py | 73 +++++++++++++++++++++++------------- 2 files changed, 52 insertions(+), 31 deletions(-) diff --git a/skale/wallets/common.py b/skale/wallets/common.py index 4a308515..dd226f78 100644 --- a/skale/wallets/common.py +++ b/skale/wallets/common.py @@ -20,10 +20,12 @@ from abc import ABC, abstractmethod from typing import Optional +from eth_account.datastructures import SignedMessage, SignedTransaction from web3 import Web3 -from web3.types import SignedTx, TxParams, TxReceipt +from web3.types import _Hash32, TxParams, TxReceipt from skale.transactions.exceptions import ChainIdError +from skale.utils.web3_utils import DEFAULT_BLOCKS_TO_WAIT def ensure_chain_id(tx_dict: TxParams, web3: Web3) -> None: @@ -42,7 +44,7 @@ class MessageNotSignedError(Exception): class BaseWallet(ABC): @abstractmethod - def sign(self, tx: TxParams) -> SignedTx: + def sign(self, tx_dict: TxParams) -> SignedTransaction: pass @abstractmethod @@ -56,7 +58,7 @@ def sign_and_send( pass @abstractmethod - def sign_hash(self, unsigned_hash: str) -> str: + def sign_hash(self, unsigned_hash: str) -> SignedMessage: pass @property @@ -70,5 +72,5 @@ def public_key(self) -> str: pass @abstractmethod - def wait(self, tx: str, confirmation_blocks: int | None = None) -> TxReceipt: + def wait(self, tx: _Hash32, confirmation_blocks: int = DEFAULT_BLOCKS_TO_WAIT) -> TxReceipt: pass diff --git a/skale/wallets/web3_wallet.py b/skale/wallets/web3_wallet.py index 05cab8c9..e2c708ff 100644 --- a/skale/wallets/web3_wallet.py +++ b/skale/wallets/web3_wallet.py @@ -17,10 +17,14 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from typing import Dict +from typing import cast from eth_keys import keys +from eth_keys.datatypes import PublicKey from web3 import Web3 +from web3.types import _Hash32, TxParams, TxReceipt from eth_account import messages +from eth_account.datastructures import SignedMessage, SignedTransaction +from eth_typing import AnyAddress, ChecksumAddress, HexStr from web3.exceptions import Web3Exception import skale.config as config @@ -28,73 +32,77 @@ TransactionNotSignedError, TransactionNotSentError ) -from skale.utils.web3_utils import get_eth_nonce, wait_for_receipt_by_blocks +from skale.utils.web3_utils import ( + DEFAULT_BLOCKS_TO_WAIT, + MAX_WAITING_TIME, + get_eth_nonce, + wait_for_receipt_by_blocks +) from skale.wallets.common import BaseWallet, ensure_chain_id, MessageNotSignedError -def private_key_to_public(pr): +def private_key_to_public(pr: HexStr) -> PublicKey: pr_bytes = Web3.to_bytes(hexstr=pr) pk = keys.PrivateKey(pr_bytes) return pk.public_key -def public_key_to_address(pk): +def public_key_to_address(pk: PublicKey) -> ChecksumAddress: hash = Web3.keccak(hexstr=str(pk)) return to_checksum_address(Web3.to_hex(hash[-20:])) -def private_key_to_address(pr): +def private_key_to_address(pr: HexStr) -> ChecksumAddress: pk = private_key_to_public(pr) return public_key_to_address(pk) -def to_checksum_address(address): +def to_checksum_address(address: AnyAddress | str | bytes) -> ChecksumAddress: return Web3.to_checksum_address(address) -def generate_wallet(web3): - account = web3.eth.account.create() - private_key = account.key.hex() - return Web3Wallet(private_key, web3) - - class Web3Wallet(BaseWallet): - def __init__(self, private_key, web3): + def __init__(self, private_key: HexStr, web3: Web3): self._private_key = private_key self._public_key = private_key_to_public(self._private_key) self._address = public_key_to_address(self._public_key) self._web3 = web3 - def sign(self, tx_dict): + def sign(self, tx_dict: TxParams) -> SignedTransaction: if not tx_dict.get('nonce'): tx_dict['nonce'] = get_eth_nonce(self._web3, self._address) ensure_chain_id(tx_dict, self._web3) try: - return self._web3.eth.account.sign_transaction( - tx_dict, - private_key=self._private_key + return cast( + SignedTransaction, + self._web3.eth.account.sign_transaction( + tx_dict, + private_key=self._private_key + ) ) except (TypeError, ValueError, Web3Exception) as e: raise TransactionNotSignedError(e) - def sign_hash(self, unsigned_hash: str): + def sign_hash(self, unsigned_hash: str) -> SignedMessage: try: unsigned_message = messages.encode_defunct(hexstr=unsigned_hash) - return self._web3.eth.account.sign_message( - unsigned_message, - private_key=self._private_key + return cast( + SignedMessage, + self._web3.eth.account.sign_message( + unsigned_message, + private_key=self._private_key + ) ) except (TypeError, ValueError, Web3Exception) as e: raise MessageNotSignedError(e) def sign_and_send( self, - tx_dict: Dict, + tx_dict: TxParams, multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, priority: int | None = config.DEFAULT_PRIORITY, - method: str | None = None, - meta: Dict | None = None + method: str | None = None ) -> str: signed_tx = self.sign(tx_dict) try: @@ -105,17 +113,28 @@ def sign_and_send( raise TransactionNotSentError(e) @property - def address(self): + def address(self) -> ChecksumAddress: return self._address @property - def public_key(self): + def public_key(self) -> str: return str(self._public_key) - def wait(self, tx_hash: str, blocks_to_wait=None, timeout=None): + def wait( + self, + tx_hash: _Hash32, + blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, + timeout: int = MAX_WAITING_TIME + ) -> TxReceipt: return wait_for_receipt_by_blocks( self._web3, tx_hash, blocks_to_wait=blocks_to_wait, timeout=timeout ) + + +def generate_wallet(web3: Web3) -> Web3Wallet: + account = web3.eth.account.create() + private_key = account.key.hex() + return Web3Wallet(private_key, web3) From 3e835005068e91970d69fe9fe95314bbca061dc2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 16:01:17 +0300 Subject: [PATCH 037/100] Add type hints to tools --- skale/contracts/base_contract.py | 4 ++-- skale/transactions/tools.py | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 1c11ed71..1f7d6217 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -20,7 +20,7 @@ import logging from functools import wraps -from typing import Dict, Optional +from typing import Callable, Dict, Optional from web3 import Web3 @@ -40,7 +40,7 @@ logger = logging.getLogger(__name__) -def transaction_method(transaction): +def transaction_method(transaction: Callable[...]): @wraps(transaction) def wrapper( self, diff --git a/skale/transactions/tools.py b/skale/transactions/tools.py index c87e5f09..3cdda4e7 100644 --- a/skale/transactions/tools.py +++ b/skale/transactions/tools.py @@ -23,10 +23,12 @@ from typing import Dict, Optional from web3 import Web3 +from web3.contract.contract import ContractFunction from web3.exceptions import ContractLogicError, Web3Exception from web3._utils.transactions import get_block_gas_limit import skale.config as config +from skale.skale_base import SkaleBase from skale.transactions.exceptions import TransactionError from skale.transactions.result import TxCallResult, TxRes, TxStatus from skale.utils.web3_utils import get_eth_nonce @@ -38,7 +40,12 @@ DEFAULT_ETH_SEND_GAS_LIMIT = 22000 -def make_dry_run_call(skale, method, gas_limit=None, value=0) -> TxCallResult: +def make_dry_run_call( + skale: SkaleBase, + method: ContractFunction, + gas_limit: int | None = None, + value: int = 0 +) -> TxCallResult: opts = { 'from': skale.wallet.address, 'value': value From e1e0e51095f3ba5e0dd49f794dd446da5e3de66d Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 16:16:52 +0300 Subject: [PATCH 038/100] Add type hints to sgx_wallet --- skale/wallets/sgx_wallet.py | 65 ++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/skale/wallets/sgx_wallet.py b/skale/wallets/sgx_wallet.py index 188498ed..85525fe9 100644 --- a/skale/wallets/sgx_wallet.py +++ b/skale/wallets/sgx_wallet.py @@ -18,15 +18,23 @@ # along with SKALE.py. If not, see . import logging -from typing import Dict +from typing import Tuple, cast +from eth_account.datastructures import SignedMessage, SignedTransaction +from eth_typing import ChecksumAddress from sgx import SgxClient from web3 import Web3 from web3.exceptions import Web3Exception +from web3.types import _Hash32, TxParams, TxReceipt import skale.config as config from skale.transactions.exceptions import TransactionNotSentError, TransactionNotSignedError -from skale.utils.web3_utils import get_eth_nonce, wait_for_receipt_by_blocks +from skale.utils.web3_utils import ( + DEFAULT_BLOCKS_TO_WAIT, + MAX_WAITING_TIME, + get_eth_nonce, + wait_for_receipt_by_blocks +) from skale.wallets.common import BaseWallet, ensure_chain_id, MessageNotSignedError @@ -34,7 +42,13 @@ class SgxWallet(BaseWallet): - def __init__(self, sgx_endpoint, web3, key_name=None, path_to_cert=None): + def __init__( + self, + sgx_endpoint: str, + web3: Web3, + key_name: str | None = None, + path_to_cert: str | None = None + ): self.sgx_client = SgxClient(sgx_endpoint, path_to_cert=path_to_cert) self._web3 = web3 if key_name is None: @@ -43,22 +57,21 @@ def __init__(self, sgx_endpoint, web3, key_name=None, path_to_cert=None): self._key_name = key_name self._address, self._public_key = self._get_account(key_name) - def sign(self, tx_dict): + def sign(self, tx_dict: TxParams) -> SignedTransaction: if tx_dict.get('nonce') is None: tx_dict['nonce'] = get_eth_nonce(self._web3, self._address) ensure_chain_id(tx_dict, self._web3) try: - return self.sgx_client.sign(tx_dict, self.key_name) + return cast(SignedTransaction, self.sgx_client.sign(tx_dict, self.key_name)) except Exception as e: raise TransactionNotSignedError(e) def sign_and_send( self, - tx_dict: Dict, + tx_dict: TxParams, multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, priority: int | None = config.DEFAULT_PRIORITY, - method: str | None = None, - meta: Dict | None = None + method: str | None = None ) -> str: signed_tx = self.sign(tx_dict) try: @@ -68,7 +81,7 @@ def sign_and_send( except (ValueError, Web3Exception) as e: raise TransactionNotSentError(e) - def sign_hash(self, unsigned_hash: str): + def sign_hash(self, unsigned_hash: str) -> SignedMessage: if unsigned_hash.startswith('0x'): unsigned_hash = unsigned_hash[2:] @@ -78,35 +91,43 @@ def sign_hash(self, unsigned_hash: str): hash_to_sign = Web3.keccak(hexstr='0x' + normalized_hash.hex()) chain_id = None try: - return self.sgx_client.sign_hash( - hash_to_sign, - self._key_name, - chain_id + return cast( + SignedMessage, + self.sgx_client.sign_hash( + hash_to_sign, + self._key_name, + chain_id + ) ) except Exception as e: raise MessageNotSignedError(e) @property - def address(self): + def address(self) -> ChecksumAddress: return self._address @property - def public_key(self): + def public_key(self) -> str: return self._public_key @property - def key_name(self): + def key_name(self) -> str: return self._key_name - def _generate(self): + def _generate(self) -> Tuple[str, ChecksumAddress, str]: key = self.sgx_client.generate_key() - return key.name, key.address, key.public_key + return key.name, Web3.to_checksum_address(key.address), key.public_key - def _get_account(self, key_name): + def _get_account(self, key_name: str) -> Tuple[ChecksumAddress, str]: account = self.sgx_client.get_account(key_name) - return account.address, account.public_key - - def wait(self, tx_hash: str, blocks_to_wait=None, timeout=None): + return Web3.to_checksum_address(account.address), account.public_key + + def wait( + self, + tx_hash: _Hash32, + blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, + timeout: int = MAX_WAITING_TIME + ) -> TxReceipt: return wait_for_receipt_by_blocks( self._web3, tx_hash, From 07aa67dd2d7b7f0cd5f4c58dfcbccf014277c333 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 16:17:42 +0300 Subject: [PATCH 039/100] Add type hints to sgx_wallet --- skale/wallets/ledger_wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index 13e522ae..05643e14 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -48,7 +48,7 @@ class LedgerCommunicationError(Exception): pass -def encode_bip32_path(path): +def encode_bip32_path(path: str) -> bytes: if len(path) == 0: return b'' encoded_chunks = [] From 2d17b1fd4eb8523b0698c43dfc33dfab2647cfa3 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 17:00:58 +0300 Subject: [PATCH 040/100] Add type hints to ledger_wallet --- skale/contracts/base_contract.py | 4 +-- skale/transactions/tools.py | 14 ++++++---- skale/wallets/ledger_wallet.py | 46 +++++++++++++++++++------------- 3 files changed, 39 insertions(+), 25 deletions(-) diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 1f7d6217..1c11ed71 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -20,7 +20,7 @@ import logging from functools import wraps -from typing import Callable, Dict, Optional +from typing import Dict, Optional from web3 import Web3 @@ -40,7 +40,7 @@ logger = logging.getLogger(__name__) -def transaction_method(transaction: Callable[...]): +def transaction_method(transaction): @wraps(transaction) def wrapper( self, diff --git a/skale/transactions/tools.py b/skale/transactions/tools.py index 3cdda4e7..9662b177 100644 --- a/skale/transactions/tools.py +++ b/skale/transactions/tools.py @@ -17,22 +17,26 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from __future__ import annotations import logging import time from functools import partial, wraps -from typing import Dict, Optional +from typing import Dict, Optional, TYPE_CHECKING from web3 import Web3 from web3.contract.contract import ContractFunction from web3.exceptions import ContractLogicError, Web3Exception from web3._utils.transactions import get_block_gas_limit +from web3.types import TxParams, Wei import skale.config as config -from skale.skale_base import SkaleBase from skale.transactions.exceptions import TransactionError from skale.transactions.result import TxCallResult, TxRes, TxStatus from skale.utils.web3_utils import get_eth_nonce +if TYPE_CHECKING: + from skale.skale_base import SkaleBase + logger = logging.getLogger(__name__) @@ -44,12 +48,12 @@ def make_dry_run_call( skale: SkaleBase, method: ContractFunction, gas_limit: int | None = None, - value: int = 0 + value: Wei = Wei(0) ) -> TxCallResult: - opts = { + opts = TxParams({ 'from': skale.wallet.address, 'value': value - } + }) logger.info( f'Dry run tx: {method.fn_name}, ' f'sender: {skale.wallet.address}, ' diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index 05643e14..f04f76d2 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -19,16 +19,22 @@ import logging import struct -from typing import Dict +from typing import Dict, Generator, cast +from eth_typing import ChecksumAddress from hexbytes import HexBytes from eth_account.datastructures import SignedTransaction -from eth_account._utils.legacy_transactions import encode_transaction -from eth_account._utils.legacy_transactions import \ - serializable_unsigned_transaction_from_dict as tx_from_dict +from eth_account._utils.legacy_transactions import ( + encode_transaction, + serializable_unsigned_transaction_from_dict as tx_from_dict, + Transaction, + UnsignedTransaction +) +from eth_account._utils.typed_transactions import TypedTransaction from eth_utils.crypto import keccak from rlp import encode +from web3 import Web3 from web3.exceptions import Web3Exception import skale.config as config @@ -63,27 +69,27 @@ def encode_bip32_path(path: str) -> bytes: return b''.join(encoded_chunks) -def derivation_path_prefix(bin32_path): +def derivation_path_prefix(bin32_path: str) -> bytes: encoded_path = encode_bip32_path(bin32_path) encoded_path_len_bytes = (len(encoded_path) // 4).to_bytes(1, 'big') return encoded_path_len_bytes + encoded_path -def chunks(sequence, size): +def chunks(sequence: bytes, size: int) -> Generator[bytes, None, None]: return (sequence[pos:pos + size] for pos in range(0, len(sequence), size)) -def get_derivation_path(address_index, legacy) -> str: +def get_derivation_path(address_index: int, legacy: bool) -> str: if legacy: return get_legacy_derivation_path(address_index) return get_live_derivation_path(address_index) -def get_live_derivation_path(address_index) -> str: +def get_live_derivation_path(address_index: int) -> str: return f'44\'/60\'/{address_index}\'/0/0' -def get_legacy_derivation_path(address_index) -> str: +def get_legacy_derivation_path(address_index: int) -> str: return f'44\'/60\'/0\'/{address_index}' @@ -91,7 +97,7 @@ class LedgerWallet(BaseWallet): CHUNK_SIZE = 255 CLA = b'\xe0' - def __init__(self, web3, address_index, legacy=False, debug=False): + def __init__(self, web3: Web3, address_index: int, legacy: bool = False, debug: bool = False): from ledgerblue.comm import getDongle from ledgerblue.commException import CommException @@ -108,25 +114,29 @@ def __init__(self, web3, address_index, legacy=False, debug=False): ) @property - def address(self): + def address(self) -> ChecksumAddress: return self._address @property - def public_key(self): + def public_key(self) -> str: return self._public_key # todo: remove this method after making software wallet as class - def __getitem__(self, key): + def __getitem__(self, key: str) -> str: items = {'address': self.address, 'public_key': self.public_key} return items[key] - def make_payload(self, data=''): - encoded_data = encode(data) + def make_payload(self, data: str = '') -> bytes: + encoded_data = cast(bytes, encode(data)) path_prefix = derivation_path_prefix(self._bip32_path) return path_prefix + encoded_data @classmethod - def parse_sign_result(cls, tx, exchange_result): + def parse_sign_result( + cls, + tx: TypedTransaction | Transaction | UnsignedTransaction, + exchange_result: bytearray | bytes + ) -> SignedTransaction: sign_v = exchange_result[0] sign_r = int((exchange_result[1:1 + 32]).hex(), 16) sign_s = int((exchange_result[1 + 32: 1 + 32 + 32]).hex(), 16) @@ -141,7 +151,7 @@ def parse_sign_result(cls, tx, exchange_result): s=sign_s ) - def exchange_sign_payload_by_chunks(self, payload): + def exchange_sign_payload_by_chunks(self, payload: bytes) -> bytearray: INS = b'\x04' P1_FIRST = b'\x00' P1_SUBSEQUENT = b'\x80' @@ -210,7 +220,7 @@ def exchange_derive_payload(self, payload): ]) return self.dongle.exchange(apdu) - def get_address_with_public_key(self): + def get_address_with_public_key(self) -> tuple[ChecksumAddress, str]: payload = self.make_payload() exchange_result = self.exchange_derive_payload(payload) return LedgerWallet.parse_derive_result(exchange_result) From 2d0343abfb87c3b24212db2b9fe44c164036704a Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 17:46:24 +0300 Subject: [PATCH 041/100] Add type hints to tools --- skale/contracts/base_contract.py | 5 +- skale/transactions/result.py | 4 +- skale/transactions/tools.py | 84 +++++++++++++++++++------------- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 1c11ed71..4adad536 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -20,7 +20,6 @@ import logging from functools import wraps -from typing import Dict, Optional from web3 import Web3 @@ -60,7 +59,6 @@ def wrapper( multiplier=None, priority=None, confirmation_blocks=0, - meta: Optional[Dict] = None, **kwargs ): method = transaction(self, *args, **kwargs) @@ -97,8 +95,7 @@ def wrapper( tx, multiplier=multiplier, priority=priority, - method=method_name, - meta=meta + method=method_name ) should_wait = tx_hash is not None and wait_for diff --git a/skale/transactions/result.py b/skale/transactions/result.py index 7b86c7cd..e29372b1 100644 --- a/skale/transactions/result.py +++ b/skale/transactions/result.py @@ -18,7 +18,7 @@ # along with SKALE.py. If not, see . import enum -from typing import NamedTuple +from typing import Mapping, NamedTuple from web3.types import TxReceipt from eth_typing import HexStr @@ -39,7 +39,7 @@ class TxCallResult(NamedTuple): status: TxStatus error: str message: str - data: dict[str, str] + data: Mapping[str, str | int] class TxRes: diff --git a/skale/transactions/tools.py b/skale/transactions/tools.py index 9662b177..f6153d8b 100644 --- a/skale/transactions/tools.py +++ b/skale/transactions/tools.py @@ -21,13 +21,14 @@ import logging import time from functools import partial, wraps -from typing import Dict, Optional, TYPE_CHECKING +from typing import Any, Callable, Optional, TYPE_CHECKING +from eth_typing import ChecksumAddress from web3 import Web3 from web3.contract.contract import ContractFunction from web3.exceptions import ContractLogicError, Web3Exception from web3._utils.transactions import get_block_gas_limit -from web3.types import TxParams, Wei +from web3.types import Nonce, TxParams, Wei import skale.config as config from skale.transactions.exceptions import TransactionError @@ -74,17 +75,25 @@ def make_dry_run_call( message = e.message or 'Contract logic error' error_data = e.data or {} data = {'data': error_data} if isinstance(error_data, str) else error_data - return TxCallResult(status=TxStatus.FAILED, - error='revert', message=message, data=data) + return TxCallResult( + status=TxStatus.FAILED, + error='revert', + message=message, + data=data + ) except (Web3Exception, ValueError) as e: logger.exception('Dry run for %s failed', method) return TxCallResult(status=TxStatus.FAILED, error='exception', message=str(e), data={}) - return TxCallResult(status=TxStatus.SUCCESS, error='', - message='success', data={'gas': estimated_gas}) + return TxCallResult( + status=TxStatus.SUCCESS, + error='', + message='success', + data={'gas': estimated_gas} + ) -def estimate_gas(web3, method, opts): +def estimate_gas(web3: Web3, method: ContractFunction, opts: TxParams) -> int: try: block_gas_limit = get_block_gas_limit(web3) except AttributeError: @@ -104,25 +113,25 @@ def estimate_gas(web3, method, opts): return normalized_estimated_gas -def build_tx_dict(method, *args, **kwargs): +def build_tx_dict(method: ContractFunction, *args: Any, **kwargs: Any) -> TxParams: base_fields = compose_base_fields(*args, **kwargs) return method.build_transaction(base_fields) def compose_base_fields( - nonce: int, + nonce: Nonce, gas_limit: int, - gas_price: Optional[int] = None, - max_fee_per_gas: Optional[int] = None, - max_priority_fee_per_gas: Optional[int] = None, - value: Optional[int] = 0, -) -> Dict: - fee_fields = { + gas_price: Wei | None = None, + max_fee_per_gas: Wei | None = None, + max_priority_fee_per_gas: Wei | None = None, + value: Wei = Wei(0), +) -> TxParams: + fee_fields = TxParams({ 'gas': gas_limit, 'nonce': nonce, 'value': value - } - if max_priority_fee_per_gas is not None: + }) + if max_priority_fee_per_gas is not None and max_fee_per_gas is not None: fee_fields.update({ 'maxPriorityFeePerGas': max_priority_fee_per_gas, 'maxFeePerGas': max_fee_per_gas @@ -135,12 +144,12 @@ def compose_base_fields( def transaction_from_method( - method, + method: ContractFunction, *, multiplier: Optional[float] = None, priority: Optional[int] = None, - **kwargs -) -> str: + **kwargs: Any +) -> TxParams: tx = build_tx_dict(method, **kwargs) logger.info( f'Tx: {method.fn_name}, ' @@ -151,11 +160,11 @@ def transaction_from_method( def compose_eth_transfer_tx( web3: Web3, - from_address: str, - to_address: str, - value: int, - **kwargs -) -> Dict: + from_address: ChecksumAddress, + to_address: ChecksumAddress, + value: Wei, + **kwargs: Any +) -> TxParams: nonce = get_eth_nonce(web3, from_address) base_fields = compose_base_fields( nonce=nonce, @@ -163,20 +172,25 @@ def compose_eth_transfer_tx( value=value, **kwargs ) - tx = { + tx = TxParams({ 'from': from_address, 'to': to_address, **base_fields - } + }) return tx -def retry_tx(tx=None, *, max_retries=3, timeout=-1): +def retry_tx( + tx: Callable[..., TxRes] | None = None, + *, + max_retries: int = 3, + timeout: int = -1 +) -> Callable[..., TxRes | None] | partial[Any]: if tx is None: return partial(retry_tx, max_retries=3, timeout=timeout) @wraps(tx) - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: Any) -> TxRes | None: return run_tx_with_retry( tx, *args, max_retries=max_retries, @@ -185,10 +199,14 @@ def wrapper(*args, **kwargs): return wrapper -def run_tx_with_retry(transaction, *args, max_retries=3, - retry_timeout=-1, - raise_for_status=True, - **kwargs) -> TxRes: +def run_tx_with_retry( + transaction: Callable[..., TxRes], + *args: Any, + max_retries: int = 3, + retry_timeout: int = -1, + raise_for_status: bool = True, + **kwargs: Any +) -> TxRes | None: attempt = 0 tx_res = None exp_timeout = 1 From 1ba8109a41e9a3ee4c8d1e231730755bff518b95 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 17:51:31 +0300 Subject: [PATCH 042/100] Add type hints to tools --- skale/wallets/ledger_wallet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index f04f76d2..be1e0ee0 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -140,7 +140,7 @@ def parse_sign_result( sign_v = exchange_result[0] sign_r = int((exchange_result[1:1 + 32]).hex(), 16) sign_s = int((exchange_result[1 + 32: 1 + 32 + 32]).hex(), 16) - enctx = encode_transaction(tx, (sign_v, sign_r, sign_s)) + enctx = cast(bytes, encode_transaction(tx, (sign_v, sign_r, sign_s))) transaction_hash = keccak(enctx) return SignedTransaction( From 4db9f1e1a2f6f312624cfb01caaa4da6d006d0e6 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 19 Apr 2024 18:33:52 +0300 Subject: [PATCH 043/100] Add type hints to ledger_wallet --- skale/utils/web3_utils.py | 10 +++---- skale/wallets/ledger_wallet.py | 49 +++++++++++++++++++++------------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index a21aeea8..65214a55 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -25,7 +25,6 @@ from urllib.parse import urlparse from eth_keys import keys -from eth_keys.datatypes import PublicKey from eth_typing import Address, AnyAddress, BlockNumber, ChecksumAddress, HexStr from web3 import Web3, WebsocketProvider, HTTPProvider from web3.exceptions import TransactionNotFound @@ -278,13 +277,14 @@ def wait_for_confirmation_blocks( time.sleep(request_timeout) -def private_key_to_public(pr: HexStr) -> PublicKey: +def private_key_to_public(pr: HexStr) -> HexStr: pr_bytes = Web3.to_bytes(hexstr=pr) - pk = keys.PrivateKey(pr_bytes) - return pk.public_key + prk = keys.PrivateKey(pr_bytes) + pk = prk.public_key + return HexStr(pk.to_hex()) -def public_key_to_address(pk: PublicKey) -> HexStr: +def public_key_to_address(pk: HexStr) -> HexStr: hash = Web3.keccak(hexstr=str(pk)) return Web3.to_hex(hash[-20:]) diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index be1e0ee0..bb4fd21b 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -19,11 +19,11 @@ import logging import struct -from typing import Dict, Generator, cast +from typing import Generator, Tuple, cast -from eth_typing import ChecksumAddress +from eth_typing import ChecksumAddress, HexStr from hexbytes import HexBytes -from eth_account.datastructures import SignedTransaction +from eth_account.datastructures import SignedMessage, SignedTransaction from eth_account._utils.legacy_transactions import ( encode_transaction, serializable_unsigned_transaction_from_dict as tx_from_dict, @@ -35,11 +35,15 @@ from eth_utils.crypto import keccak from rlp import encode from web3 import Web3 +from web3.contract.contract import ContractFunction from web3.exceptions import Web3Exception +from web3.types import _Hash32, TxParams, TxReceipt import skale.config as config from skale.transactions.exceptions import TransactionNotSentError, TransactionNotSignedError from skale.utils.web3_utils import ( + DEFAULT_BLOCKS_TO_WAIT, + MAX_WAITING_TIME, get_eth_nonce, public_key_to_address, to_checksum_address, @@ -140,7 +144,7 @@ def parse_sign_result( sign_v = exchange_result[0] sign_r = int((exchange_result[1:1 + 32]).hex(), 16) sign_s = int((exchange_result[1 + 32: 1 + 32 + 32]).hex(), 16) - enctx = cast(bytes, encode_transaction(tx, (sign_v, sign_r, sign_s))) + enctx = encode_transaction(tx, (sign_v, sign_r, sign_s)) transaction_hash = keccak(enctx) return SignedTransaction( @@ -165,9 +169,9 @@ def exchange_sign_payload_by_chunks(self, payload: bytes) -> bytearray: ]) exchange_result = self.dongle.exchange(apdu) p1 = P1_SUBSEQUENT - return exchange_result + return cast(bytearray, exchange_result) - def sign(self, tx_dict): + def sign(self, tx_dict: TxParams) -> SignedTransaction: ensure_chain_id(tx_dict, self._web3) if tx_dict.get('nonce') is None: tx_dict['nonce'] = self._web3.eth.get_transaction_count(self.address) @@ -182,11 +186,10 @@ def sign(self, tx_dict): def sign_and_send( self, - tx: Dict, + tx: TxParams, multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, priority: int | None = config.DEFAULT_PRIORITY, - method: str | None = None, - meta: Dict | None = None + method: str | None = None ) -> str: signed_tx = self.sign(tx) try: @@ -196,20 +199,20 @@ def sign_and_send( except (ValueError, Web3Exception) as e: raise TransactionNotSentError(e) - def sign_hash(self, unsigned_hash: str): + def sign_hash(self, unsigned_hash: str) -> SignedMessage: raise NotImplementedError( 'sign_hash is not implemented for hardware wallet' ) @classmethod - def parse_derive_result(cls, exchange_result): + def parse_derive_result(cls, exchange_result: bytearray) -> Tuple[ChecksumAddress, str]: pk_len = exchange_result[0] - pk = exchange_result[1: pk_len + 1].hex()[2:] + pk = HexStr(exchange_result[1: pk_len + 1].hex()[2:]) address = public_key_to_address(pk) checksum_address = to_checksum_address(address) return checksum_address, pk - def exchange_derive_payload(self, payload): + def exchange_derive_payload(self, payload: bytes) -> bytearray: INS = b'\x02' P1 = b'\x00' P2 = b'\x00' @@ -218,14 +221,19 @@ def exchange_derive_payload(self, payload): LedgerWallet.CLA, INS, P1, P2, payload_size_in_bytes, payload ]) - return self.dongle.exchange(apdu) + return cast(bytearray, self.dongle.exchange(apdu)) def get_address_with_public_key(self) -> tuple[ChecksumAddress, str]: payload = self.make_payload() exchange_result = self.exchange_derive_payload(payload) return LedgerWallet.parse_derive_result(exchange_result) - def wait(self, tx_hash: str, blocks_to_wait=None, timeout=None): + def wait( + self, + tx_hash: _Hash32, + blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, + timeout: int = MAX_WAITING_TIME + ) -> TxReceipt: return wait_for_receipt_by_blocks( self._web3, tx_hash, @@ -234,8 +242,13 @@ def wait(self, tx_hash: str, blocks_to_wait=None, timeout=None): ) -def hardware_sign_and_send(web3, method, gas_amount, wallet) -> str: - address_from = wallet['address'] +def hardware_sign_and_send( + web3: Web3, + method: ContractFunction, + gas_amount: int, + wallet: LedgerWallet +) -> str: + address_from = wallet.address eth_nonce = get_eth_nonce(web3, address_from) tx_dict = method.build_transaction({ 'gas': gas_amount, @@ -244,6 +257,6 @@ def hardware_sign_and_send(web3, method, gas_amount, wallet) -> str: signed_txn = wallet.sign(tx_dict) tx = web3.eth.send_raw_transaction(signed_txn.rawTransaction).hex() logger.info( - f'{method.__class__.__name__} - transaction_hash: {web3.to_hex(tx)}' + f'{method.__class__.__name__} - transaction_hash: {tx}' ) return tx From b00b11677cea676280d6b61f0acc27609bfe8874 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 12:26:01 +0300 Subject: [PATCH 044/100] Add type hints to base_contract --- skale/contracts/base_contract.py | 111 +++++++++++++++++-------------- 1 file changed, 62 insertions(+), 49 deletions(-) diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 4adad536..04bc5d7c 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -17,11 +17,15 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . """ SKALE base contract class """ - +from __future__ import annotations import logging from functools import wraps +from typing import Any, Callable, TYPE_CHECKING +from eth_typing import ChecksumAddress from web3 import Web3 +from web3.contract.contract import ContractFunction +from web3.types import ABI, Nonce, Wei import skale.config as config from skale.transactions.result import TxRes @@ -35,32 +39,68 @@ from skale.utils.helper import to_camel_case +if TYPE_CHECKING: + from skale.skale_base import SkaleBase + logger = logging.getLogger(__name__) -def transaction_method(transaction): +class BaseContract: + def __init__( + self, + skale: SkaleBase, + name: str, + address: ChecksumAddress | str | bytes, + abi: ABI + ): + self.skale = skale + self.name = name + self.address = Web3.to_checksum_address(address) + self.init_contract(skale, self.address, abi) + + def init_contract(self, skale: SkaleBase, address: ChecksumAddress, abi: ABI) -> None: + self.contract = skale.web3.eth.contract(address=address, abi=abi) + + def __getattr__(self, attr: str) -> Callable[..., Any]: + """Fallback for contract calls""" + logger.debug("Calling contract function: %s", attr) + + def wrapper(*args: Any, **kw: Any) -> Any: + logger.debug('called with %r and %r' % (args, kw)) + camel_case_fn_name = to_camel_case(attr) + if hasattr(self.contract.functions, camel_case_fn_name): + return getattr(self.contract.functions, + camel_case_fn_name)(*args, **kw).call() + if hasattr(self.contract.functions, attr): + return getattr(self.contract.functions, + attr)(*args, **kw).call() + raise AttributeError(attr) + return wrapper + + +def transaction_method(transaction: Callable[..., ContractFunction]) -> Callable[..., TxRes]: @wraps(transaction) def wrapper( - self, - *args, - wait_for=True, - blocks_to_wait=DEFAULT_BLOCKS_TO_WAIT, - timeout=MAX_WAITING_TIME, - gas_limit=None, - gas_price=None, - nonce=None, - max_fee_per_gas=None, - max_priority_fee_per_gas=None, - value=0, - dry_run_only=False, - skip_dry_run=False, - raise_for_status=True, - multiplier=None, - priority=None, - confirmation_blocks=0, - **kwargs - ): + self: BaseContract, + *args: Any, + wait_for: bool = True, + blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, + timeout: int = MAX_WAITING_TIME, + gas_limit: int | None = None, + gas_price: int | None = None, + nonce: Nonce | None = None, + max_fee_per_gas: int | None = None, + max_priority_fee_per_gas: int | None = None, + value: Wei = Wei(0), + dry_run_only: bool = False, + skip_dry_run: bool = False, + raise_for_status: bool = True, + multiplier: float | None = None, + priority: int | None = None, + confirmation_blocks: int = 0, + **kwargs: Any + ) -> TxRes: method = transaction(self, *args, **kwargs) nonce = get_eth_nonce(self.skale.web3, self.skale.wallet.address) @@ -72,7 +112,7 @@ def wrapper( if should_dry_run: call_result = make_dry_run_call(self.skale, method, gas_limit, value) if call_result.status == TxStatus.SUCCESS: - gas_limit = gas_limit or call_result.data['gas'] + gas_limit = gas_limit or int(call_result.data['gas']) dry_run_success = True should_send = not dry_run_only and \ @@ -113,30 +153,3 @@ def wrapper( return tx_res return wrapper - - -class BaseContract: - def __init__(self, skale, name, address, abi): - self.skale = skale - self.name = name - self.address = Web3.to_checksum_address(address) - self.init_contract(skale, address, abi) - - def init_contract(self, skale, address, abi) -> None: - self.contract = skale.web3.eth.contract(address=address, abi=abi) - - def __getattr__(self, attr): - """Fallback for contract calls""" - logger.debug("Calling contract function: %s", attr) - - def wrapper(*args, **kw): - logger.debug('called with %r and %r' % (args, kw)) - camel_case_fn_name = to_camel_case(attr) - if hasattr(self.contract.functions, camel_case_fn_name): - return getattr(self.contract.functions, - camel_case_fn_name)(*args, **kw).call() - if hasattr(self.contract.functions, attr): - return getattr(self.contract.functions, - attr)(*args, **kw).call() - raise AttributeError(attr) - return wrapper From e6333a6b03c6359b9ec60c9aad4541b30aaaa47e Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 15:45:06 +0300 Subject: [PATCH 045/100] Add type hints to redis_wallet --- skale/wallets/common.py | 3 +- skale/wallets/redis_wallet.py | 66 +++++++++++++++++++---------- tests/wallets/redis_adapter_test.py | 2 +- 3 files changed, 46 insertions(+), 25 deletions(-) diff --git a/skale/wallets/common.py b/skale/wallets/common.py index dd226f78..e3bd24ea 100644 --- a/skale/wallets/common.py +++ b/skale/wallets/common.py @@ -21,6 +21,7 @@ from typing import Optional from eth_account.datastructures import SignedMessage, SignedTransaction +from eth_typing import ChecksumAddress from web3 import Web3 from web3.types import _Hash32, TxParams, TxReceipt @@ -63,7 +64,7 @@ def sign_hash(self, unsigned_hash: str) -> SignedMessage: @property @abstractmethod - def address(self) -> str: + def address(self) -> ChecksumAddress: pass @property diff --git a/skale/wallets/redis_wallet.py b/skale/wallets/redis_wallet.py index 04c864aa..68e862d4 100644 --- a/skale/wallets/redis_wallet.py +++ b/skale/wallets/redis_wallet.py @@ -23,9 +23,13 @@ import os import time from enum import Enum -from typing import Dict, Optional, Tuple +from typing import Optional, Tuple, TypedDict +from eth_account.datastructures import SignedMessage, SignedTransaction +from eth_typing import ChecksumAddress, Hash32, HexStr +from hexbytes import HexBytes from redis import Redis +from web3.types import _Hash32, TxParams, TxReceipt import skale.config as config from skale.transactions.exceptions import ( @@ -34,7 +38,7 @@ TransactionNotSentError, TransactionWaitError ) -from skale.utils.web3_utils import get_receipt, MAX_WAITING_TIME +from skale.utils.web3_utils import DEFAULT_BLOCKS_TO_WAIT, get_receipt, MAX_WAITING_TIME from skale.wallets import BaseWallet from skale.wallets.web3_wallet import Web3Wallet @@ -70,6 +74,15 @@ def __str__(self) -> str: return str.__str__(self) +TxRecord = TypedDict( + 'TxRecord', + { + 'status': TxRecordStatus, + 'tx_hash': HexStr + }, +) + + class RedisWalletAdapter(BaseWallet): ID_SIZE = 16 @@ -83,14 +96,14 @@ def __init__( self.pool = pool self.wallet = web3_wallet - def sign(self, tx: Dict) -> Dict: + def sign(self, tx: TxParams) -> SignedTransaction: return self.wallet.sign(tx) - def sign_hash(self, unsigned_hash: str) -> str: + def sign_hash(self, unsigned_hash: str) -> SignedMessage: return self.wallet.sign_hash(unsigned_hash) @property - def address(self) -> str: + def address(self) -> ChecksumAddress: return self.wallet.address @property @@ -106,16 +119,15 @@ def _make_raw_id(cls) -> bytes: @classmethod def _make_score(cls, priority: int) -> int: ts = int(time.time()) - return priority * 10 ** len(str(ts)) + ts + return priority * int(10 ** len(str(ts))) + ts @classmethod def _make_record( cls, - tx: Dict, + tx: TxParams, score: int, multiplier: float = config.DEFAULT_GAS_MULTIPLIER, - method: Optional[str] = None, - meta: Optional[Dict] = None + method: Optional[str] = None ) -> Tuple[bytes, bytes]: tx_id = cls._make_raw_id() params = { @@ -124,7 +136,6 @@ def _make_record( 'multiplier': multiplier, 'tx_hash': None, 'method': method, - 'meta': meta, **tx } # Ensure gas will be restimated in TM @@ -133,19 +144,25 @@ def _make_record( return tx_id, record @classmethod - def _to_raw_id(cls, tx_id: str) -> bytes: - return tx_id.encode('utf-8') + def _to_raw_id(cls, tx_id: _Hash32) -> bytes: + if type(tx_id) == HexStr: + return tx_id.encode('utf-8') + if type(tx_id) == Hash32: + return bytes(tx_id) + if type(tx_id) == HexBytes: + return bytes(tx_id) + raise ValueError('tx_id has unknown type', type(tx_id)) + @classmethod def _to_id(cls, raw_id: bytes) -> str: return raw_id.decode('utf-8') def sign_and_send( self, - tx: Dict, + tx: TxParams, multiplier: Optional[float] = None, priority: Optional[int] = None, - method: Optional[str] = None, - meta: Optional[Dict] = None + method: Optional[str] = None ) -> str: priority = priority or config.DEFAULT_PRIORITY try: @@ -155,8 +172,7 @@ def sign_and_send( tx, score, multiplier=multiplier or config.DEFAULT_GAS_MULTIPLIER, - method=method, - meta=meta + method=method ) pipe = self.rs.pipeline() logger.info('Adding tx %s to the pool', raw_id) @@ -169,22 +185,26 @@ def sign_and_send( logger.exception(f'Sending {tx} with redis wallet errored') raise RedisWalletNotSentError(err) - def get_status(self, tx_id: str) -> str: + def get_status(self, tx_id: _Hash32) -> str: return self.get_record(tx_id)['status'] - def get_record(self, tx_id: str) -> Dict: + def get_record(self, tx_id: _Hash32) -> TxRecord: rid = self._to_raw_id(tx_id) response = self.rs.get(rid) if isinstance(response, bytes): - return json.loads(response.decode('utf-8')) + parsed_json = json.loads(response.decode('utf-8')) + return TxRecord({ + 'status': parsed_json['status'], + 'tx_hash': parsed_json['tx_hash'] + }) raise ValueError('Unknown value was returned from get() call', response) def wait( self, - tx_id: str, - blocks_to_wait: Optional[int] = None, + tx_id: _Hash32, + blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, timeout: int = MAX_WAITING_TIME - ) -> Dict: + ) -> TxReceipt: start_ts = time.time() status, result = None, None while status not in [ diff --git a/tests/wallets/redis_adapter_test.py b/tests/wallets/redis_adapter_test.py index 4d032a13..7bc8d879 100644 --- a/tests/wallets/redis_adapter_test.py +++ b/tests/wallets/redis_adapter_test.py @@ -51,7 +51,7 @@ def test_make_record(): score = '51623233060' tx_id, r = RedisWalletAdapter._make_record(tx, score, 2, method='createNode') assert tx_id.startswith(b'tx-') and len(tx_id) == 19 - assert r == b'{"status": "PROPOSED", "score": "51623233060", "multiplier": 2, "tx_hash": null, "method": "createNode", "meta": null, "from": "0x1", "to": "0x2", "value": 1, "gasPrice": 1, "gas": null, "nonce": 1, "chainId": 1}' # noqa + assert r == b'{"status": "PROPOSED", "score": "51623233060", "multiplier": 2, "tx_hash": null, "method": "createNode", "from": "0x1", "to": "0x2", "value": 1, "gasPrice": 1, "gas": null, "nonce": 1, "chainId": 1}' # noqa def test_sign_and_send(rdp): From 3905d781245c6043fa0070dad695d42547934e7a Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 15:47:55 +0300 Subject: [PATCH 046/100] Add type hints to contract_manager --- skale/contracts/contract_manager.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/skale/contracts/contract_manager.py b/skale/contracts/contract_manager.py index 5b33de65..73271d49 100644 --- a/skale/contracts/contract_manager.py +++ b/skale/contracts/contract_manager.py @@ -19,16 +19,18 @@ """ SKALE Contract manager class """ from Crypto.Hash import keccak +from eth_typing import ChecksumAddress +from web3 import Web3 from skale.contracts.base_contract import BaseContract from skale.utils.helper import add_0x_prefix class ContractManager(BaseContract): - def get_contract_address(self, name): + def get_contract_address(self, name: str) -> ChecksumAddress: contract_hash = add_0x_prefix(self.get_contract_hash_by_name(name)) - return self.contract.functions.contracts(contract_hash).call() + return Web3.to_checksum_address(self.contract.functions.contracts(contract_hash).call()) - def get_contract_hash_by_name(self, name): + def get_contract_hash_by_name(self, name: str) -> str: keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) return keccak_hash.hexdigest() From caa047e164793e08b4eb897da323c442be436821 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 15:54:22 +0300 Subject: [PATCH 047/100] Add type hints to wallets --- skale/contracts/manager/wallets.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skale/contracts/manager/wallets.py b/skale/contracts/manager/wallets.py index 01a579e0..41e2628a 100644 --- a/skale/contracts/manager/wallets.py +++ b/skale/contracts/manager/wallets.py @@ -17,8 +17,9 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class Wallets(BaseContract): @@ -28,13 +29,13 @@ def get_validator_balance(self, validator_id: int) -> int: :returns: SRW balance (wei) :rtype: int """ - return self.contract.functions.getValidatorBalance(validator_id).call() + return int(self.contract.functions.getValidatorBalance(validator_id).call()) @transaction_method - def recharge_validator_wallet(self, validator_id: int) -> TxRes: + def recharge_validator_wallet(self, validator_id: int) -> ContractFunction: """Pass value kwarg (in wei) to the function when calling it""" return self.contract.functions.rechargeValidatorWallet(validator_id) @transaction_method - def withdraw_funds_from_validator_wallet(self, amount: int) -> TxRes: + def withdraw_funds_from_validator_wallet(self, amount: int) -> ContractFunction: return self.contract.functions.withdrawFundsFromValidatorWallet(amount) From 6889822a559e3e22ede2cd1e5877eaaa25b35b3b Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 16:00:32 +0300 Subject: [PATCH 048/100] Add type hints to token --- skale/contracts/manager/token.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/skale/contracts/manager/token.py b/skale/contracts/manager/token.py index a58a891d..1b5d067b 100644 --- a/skale/contracts/manager/token.py +++ b/skale/contracts/manager/token.py @@ -18,24 +18,34 @@ # along with SKALE.py. If not, see . """ SKALE token operations """ +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import Wei + from skale.contracts.base_contract import BaseContract, transaction_method class Token(BaseContract): @transaction_method - def transfer(self, address, value): + def transfer(self, address: ChecksumAddress, value: Wei) -> ContractFunction: return self.contract.functions.send(address, value, b'') - def get_balance(self, address): - return self.contract.functions.balanceOf(address).call() + def get_balance(self, address: ChecksumAddress) -> Wei: + return Wei(self.contract.functions.balanceOf(address).call()) @transaction_method - def add_authorized(self, address, wallet): # pragma: no cover + def add_authorized(self, address: ChecksumAddress) -> ContractFunction: # pragma: no cover return self.contract.functions.addAuthorized(address) - def get_and_update_slashed_amount(self, address: str) -> int: - return self.contract.functions.getAndUpdateSlashedAmount(address).call() + def get_and_update_slashed_amount(self, address: ChecksumAddress) -> Wei: + return Wei(self.contract.functions.getAndUpdateSlashedAmount(address).call()) @transaction_method - def mint(self, address, amount, user_data=b'', operator_data=b''): + def mint( + self, + address: ChecksumAddress, + amount: Wei, + user_data: bytes = b'', + operator_data: bytes = b'' + ) -> ContractFunction: return self.contract.functions.mint(address, amount, user_data, operator_data) From b5fca6e6f6e903318c7940e068841c9c0c20fd66 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 16:09:16 +0300 Subject: [PATCH 049/100] Add type hints to sync_manager --- skale/contracts/manager/sync_manager.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/skale/contracts/manager/sync_manager.py b/skale/contracts/manager/sync_manager.py index 1b2c6f1f..aa07fa4a 100644 --- a/skale/contracts/manager/sync_manager.py +++ b/skale/contracts/manager/sync_manager.py @@ -21,6 +21,7 @@ from collections import namedtuple from typing import List +from web3.contract.contract import ContractFunction from skale.contracts.base_contract import BaseContract, transaction_method from skale.transactions.result import TxRes @@ -40,7 +41,7 @@ class SyncManager(BaseContract): """Wrapper for SyncManager.sol functions""" @transaction_method - def add_ip_range(self, name, start_ip: str, end_ip: str) -> TxRes: + def add_ip_range(self, name: str, start_ip: str, end_ip: str) -> ContractFunction: return self.contract.functions.addIPRange( name, ip_to_bytes(start_ip), @@ -48,11 +49,11 @@ def add_ip_range(self, name, start_ip: str, end_ip: str) -> TxRes: ) @transaction_method - def remove_ip_range(self, name: str) -> TxRes: + def remove_ip_range(self, name: str) -> ContractFunction: return self.contract.functions.removeIPRange(name) def get_ip_ranges_number(self) -> int: - return self.contract.functions.getIPRangesNumber().call() + return int(self.contract.functions.getIPRangesNumber().call()) def get_ip_range_by_index(self, index: int) -> IpRange: packed = self.contract.functions.getIPRangeByIndex(index).call() @@ -66,8 +67,8 @@ def grant_sync_manager_role(self, address: str) -> TxRes: return self.grant_role(self.sync_manager_role(), address) def sync_manager_role(self) -> bytes: - return self.contract.functions.SYNC_MANAGER_ROLE().call() + return bytes(self.contract.functions.SYNC_MANAGER_ROLE().call()) @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: str) -> ContractFunction: return self.contract.functions.grantRole(role, owner) From 3fe15a0f6e7a14fca870a94668debafc74f5aac1 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 17:23:23 +0300 Subject: [PATCH 050/100] Add type hints to schains_internal --- skale/contracts/manager/schains.py | 9 +- skale/contracts/manager/schains_internal.py | 102 ++++++++++++-------- skale/types/schain.py | 3 +- 3 files changed, 72 insertions(+), 42 deletions(-) diff --git a/skale/contracts/manager/schains.py b/skale/contracts/manager/schains.py index d2300899..6b722de4 100644 --- a/skale/contracts/manager/schains.py +++ b/skale/contracts/manager/schains.py @@ -22,9 +22,12 @@ from dataclasses import dataclass, asdict from Crypto.Hash import keccak +from hexbytes import HexBytes from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.manager.schains_internal import SChainsInternal from skale.transactions.result import TxRes +from skale.types.schain import SchainHash, SchainName from skale.utils.helper import format_fields from skale.dataclasses.schain_options import ( SchainOptions, get_default_schain_options, parse_schain_options @@ -61,12 +64,12 @@ class SchainStructure: class SChains(BaseContract): """Wrapper for some of the Schains.sol functions""" - def name_to_group_id(self, name): + def name_to_group_id(self, name: str) -> HexBytes: return self.skale.web3.keccak(text=name) @property @functools.lru_cache() - def schains_internal(self): + def schains_internal(self) -> SChainsInternal: return self.skale.schains_internal @property @@ -120,7 +123,7 @@ def get_active_schains_for_node(self, node_id): schains.append(schain) return schains - def name_to_id(self, name): + def name_to_id(self, name: SchainName) -> SchainHash: keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) return '0x' + keccak_hash.hexdigest() diff --git a/skale/contracts/manager/schains_internal.py b/skale/contracts/manager/schains_internal.py index ac7f4ac0..7e023a34 100644 --- a/skale/contracts/manager/schains_internal.py +++ b/skale/contracts/manager/schains_internal.py @@ -18,10 +18,19 @@ # along with SKALE.py. If not, see . """ SchainsInternal.sol functions """ +from __future__ import annotations from dataclasses import dataclass import functools +from typing import TYPE_CHECKING, List, cast + +from eth_typing import ChecksumAddress from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes +from skale.types.node import NodeId +from skale.types.schain import SchainHash, SchainName + +if TYPE_CHECKING: + from web3.contract.contract import ContractFunction + from skale.contracts.manager.schains import SChains @dataclass @@ -44,72 +53,89 @@ class SChainsInternal(BaseContract): @property @functools.lru_cache() - def schains(self): - return self.skale.schains + def schains(self) -> SChains: + from skale.contracts.manager.schains import SChains + return cast(SChains, self.skale.schains) - def get_raw(self, name) -> Schain: - return self.contract.functions.schains(name).call() + def get_raw(self, name: str) -> Schain: + return cast(Schain, self.contract.functions.schains(name).call()) - def get_all_schains_ids(self): - return self.contract.functions.getSchains().call() + def get_all_schains_ids(self) -> List[SchainHash]: + return [ + SchainHash(schain_hash) + for schain_hash + in self.contract.functions.getSchains().call() + ] - def get_schains_number(self): - return self.contract.functions.numberOfSchains().call() + def get_schains_number(self) -> int: + return int(self.contract.functions.numberOfSchains().call()) - def get_schain_list_size(self, account): - return self.contract.functions.getSchainListSize(account).call( - {'from': account}) + def get_schain_list_size(self, account: ChecksumAddress) -> int: + return int(self.contract.functions.getSchainListSize(account).call( + {'from': account})) - def get_schain_id_by_index_for_owner(self, account, index): - return self.contract.functions.schainIndexes(account, index).call() + def get_schain_id_by_index_for_owner(self, account: ChecksumAddress, index: int) -> SchainHash: + return SchainHash(self.contract.functions.schainIndexes(account, index).call()) - def get_node_ids_for_schain(self, name): + def get_node_ids_for_schain(self, name: SchainName) -> List[NodeId]: id_ = self.schains.name_to_id(name) - return self.contract.functions.getNodesInGroup(id_).call() - - def get_schain_ids_for_node(self, node_id): - return self.contract.functions.getSchainHashesForNode(node_id).call() - - def is_schain_exist(self, name): + return [ + NodeId(node) + for node + in self.contract.functions.getNodesInGroup(id_).call() + ] + + def get_schain_ids_for_node(self, node_id: NodeId) -> List[SchainHash]: + return [ + SchainHash(schain) + for schain + in self.contract.functions.getSchainHashesForNode(node_id).call() + ] + + def is_schain_exist(self, name: SchainName) -> bool: id_ = self.schains.name_to_id(name) - return self.contract.functions.isSchainExist(id_).call() + return bool(self.contract.functions.isSchainExist(id_).call()) - def get_active_schain_ids_for_node(self, node_id): - return self.contract.functions.getActiveSchains(node_id).call() + def get_active_schain_ids_for_node(self, node_id: NodeId) -> List[SchainHash]: + return [ + SchainHash(schain) + for schain + in self.contract.functions.getActiveSchains(node_id).call() + ] def number_of_schain_types(self) -> int: - return self.contract.functions.numberOfSchainTypes().call() + return int(self.contract.functions.numberOfSchainTypes().call()) @transaction_method def add_schain_type( self, part_of_node: int, number_of_nodes: int - ) -> TxRes: + ) -> ContractFunction: return self.contract.functions.addSchainType( part_of_node, number_of_nodes) def current_generation(self) -> int: - return self.contract.functions.currentGeneration().call() + return int(self.contract.functions.currentGeneration().call()) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) def schain_type_manager_role(self) -> bytes: - return self.contract.functions.SCHAIN_TYPE_MANAGER_ROLE().call() + return bytes(self.contract.functions.SCHAIN_TYPE_MANAGER_ROLE().call()) - def debugger_role(self): - return self.contract.functions.DEBUGGER_ROLE().call() + def debugger_role(self) -> bytes: + return bytes(self.contract.functions.DEBUGGER_ROLE().call()) - def generation_manager_role(self): - return self.contract.functions.GENERATION_MANAGER_ROLE().call() + def generation_manager_role(self) -> bytes: + return bytes(self.contract.functions.GENERATION_MANAGER_ROLE().call()) @transaction_method - def new_generation(self) -> TxRes: + def new_generation(self) -> ContractFunction: return self.contract.functions.newGeneration() - def check_exception(self, schain_name: str, node_id: int) -> bool: + def check_exception(self, schain_name: SchainName, node_id: NodeId) -> bool: id_ = self.schains.name_to_id(schain_name) - return self.contract.functions.checkException(id_, node_id).call() + return bool(self.contract.functions.checkException(id_, node_id).call()) diff --git a/skale/types/schain.py b/skale/types/schain.py index 0e2e527b..44adb26a 100644 --- a/skale/types/schain.py +++ b/skale/types/schain.py @@ -20,4 +20,5 @@ from typing import NewType -SchainName = NewType("SchainName", str) +SchainName = NewType('SchainName', str) +SchainHash = NewType('SchainHash', bytes) From 84d8059fae25c50c9be2520b205ecadf51b529cb Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 23 Apr 2024 17:36:58 +0300 Subject: [PATCH 051/100] Add type hints to punisher --- skale/contracts/manager/punisher.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/skale/contracts/manager/punisher.py b/skale/contracts/manager/punisher.py index bfa14a3d..a0d99579 100644 --- a/skale/contracts/manager/punisher.py +++ b/skale/contracts/manager/punisher.py @@ -17,17 +17,19 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class Punisher(BaseContract): @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) - def forgiver_role(self): - return self.contract.functions.FORGIVER_ROLE().call() + def forgiver_role(self) -> bytes: + return bytes(self.contract.functions.FORGIVER_ROLE().call()) From 48eabb16c79fd03ce7975ce304c99517f97388f8 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 12:51:44 +0300 Subject: [PATCH 052/100] Add type hints to nodes --- skale/contracts/manager/nodes.py | 143 +++++++++++++++++++++---------- skale/utils/helper.py | 23 +++-- 2 files changed, 109 insertions(+), 57 deletions(-) diff --git a/skale/contracts/manager/nodes.py b/skale/contracts/manager/nodes.py index 86af9360..262e3a07 100644 --- a/skale/contracts/manager/nodes.py +++ b/skale/contracts/manager/nodes.py @@ -20,13 +20,17 @@ import socket from enum import IntEnum +from typing import Any, Dict, List, Tuple, TypedDict, cast from Crypto.Hash import keccak +from eth_typing import BlockNumber, ChecksumAddress +from web3.contract.contract import ContractFunction from web3.exceptions import BadFunctionCallOutput, ContractLogicError from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes +from skale.types.node import NodeId, Port +from skale.types.validator import ValidatorId from skale.utils.exceptions import InvalidNodeIdError from skale.utils.helper import format_fields @@ -43,134 +47,183 @@ class NodeStatus(IntEnum): IN_MAINTENANCE = 3 +class Node(TypedDict): + name: str + ip: bytes + publicIP: bytes + port: Port + start_block: BlockNumber + last_reward_date: int + finish_time: int + status: NodeStatus + validator_id: ValidatorId + publicKey: str + domain_name: str + + class Nodes(BaseContract): - def __get_raw(self, node_id): + def __get_raw(self, node_id: NodeId) -> List[Any]: try: - return self.contract.functions.nodes(node_id).call() + return list(self.contract.functions.nodes(node_id).call()) except (ContractLogicError, ValueError, BadFunctionCallOutput): raise InvalidNodeIdError(node_id) - def __get_raw_w_pk(self, node_id): + def __get_raw_w_pk(self, node_id: NodeId) -> List[Any]: raw_node_struct = self.__get_raw(node_id) raw_node_struct.append(self.get_node_public_key(node_id)) return raw_node_struct - def __get_raw_w_pk_w_domain(self, node_id): + def __get_raw_w_pk_w_domain(self, node_id: NodeId) -> List[Any]: raw_node_struct_w_pk = self.__get_raw_w_pk(node_id) raw_node_struct_w_pk.append(self.get_domain_name(node_id)) return raw_node_struct_w_pk @format_fields(FIELDS) - def get(self, node_id): + def untyped_get(self, node_id: NodeId) -> List[Any]: return self.__get_raw_w_pk_w_domain(node_id) + def get(self, node_id: NodeId) -> Node: + node = self.untyped_get(node_id) + if node is None: + raise ValueError('Node with id ', node_id, ' is not found') + if isinstance(node, dict): + return self._to_node(node) + if isinstance(node, list): + return self._to_node(node[0]) + raise ValueError("Can't process returned node type") + @format_fields(FIELDS) - def get_by_name(self, name): + def get_by_name(self, name: str) -> List[Any]: name_hash = self.name_to_id(name) _id = self.contract.functions.nodesNameToIndex(name_hash).call() return self.__get_raw_w_pk_w_domain(_id) - def get_nodes_number(self): - return self.contract.functions.getNumberOfNodes().call() + def get_nodes_number(self) -> int: + return int(self.contract.functions.getNumberOfNodes().call()) - def get_active_node_ids(self): + def get_active_node_ids(self) -> List[NodeId]: nodes_number = self.get_nodes_number() return [ - node_id + NodeId(node_id) for node_id in range(0, nodes_number) - if self.get_node_status(node_id) == NodeStatus.ACTIVE + if self.get_node_status(NodeId(node_id)) == NodeStatus.ACTIVE ] - def get_active_node_ips(self): + def get_active_node_ips(self) -> List[str]: nodes_number = self.get_nodes_number() return [ - self.get(node_id)['ip'] + self.get(NodeId(node_id))['ip'] for node_id in range(0, nodes_number) - if self.get_node_status(node_id) == NodeStatus.ACTIVE + if self.get_node_status(NodeId(node_id)) == NodeStatus.ACTIVE ] - def name_to_id(self, name): + def name_to_id(self, name: str) -> bytes: keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) return keccak_hash.digest() - def is_node_name_available(self, name): + def is_node_name_available(self, name: str) -> bool: node_id = self.name_to_id(name) return not self.contract.functions.nodesNameCheck(node_id).call() - def is_node_ip_available(self, ip): + def is_node_ip_available(self, ip: str) -> bool: ip_bytes = socket.inet_aton(ip) return not self.contract.functions.nodesIPCheck(ip_bytes).call() - def node_name_to_index(self, name): + def node_name_to_index(self, name: str) -> int: name_hash = self.name_to_id(name) - return self.contract.functions.nodesNameToIndex(name_hash).call() + return int(self.contract.functions.nodesNameToIndex(name_hash).call()) - def get_node_status(self, node_id): + def get_node_status(self, node_id: NodeId) -> NodeStatus: try: - return self.contract.functions.getNodeStatus(node_id).call() + return NodeStatus(self.contract.functions.getNodeStatus(node_id).call()) except (ContractLogicError, ValueError, BadFunctionCallOutput): raise InvalidNodeIdError(node_id) - def get_node_finish_time(self, node_id): + def get_node_finish_time(self, node_id: NodeId) -> int: try: - return self.contract.functions.getNodeFinishTime(node_id).call() + return int(self.contract.functions.getNodeFinishTime(node_id).call()) except (ContractLogicError, ValueError, BadFunctionCallOutput): raise InvalidNodeIdError(node_id) - def __get_node_public_key_raw(self, node_id): + def __get_node_public_key_raw(self, node_id: NodeId) -> Tuple[bytes, bytes]: try: - return self.contract.functions.getNodePublicKey(node_id).call() + return cast( + Tuple[bytes, bytes], + self.contract.functions.getNodePublicKey(node_id).call() + ) except (ContractLogicError, ValueError, BadFunctionCallOutput): raise InvalidNodeIdError(node_id) - def get_node_public_key(self, node_id): + def get_node_public_key(self, node_id: NodeId) -> str: raw_key = self.__get_node_public_key_raw(node_id) key_bytes = raw_key[0] + raw_key[1] return self.skale.web3.to_hex(key_bytes) - def get_validator_node_indices(self, validator_id: int) -> list: + def get_validator_node_indices(self, validator_id: int) -> list[NodeId]: """Returns list of node indices to the validator :returns: List of trusted node indices :rtype: list """ - return self.contract.functions.getValidatorNodeIndexes(validator_id).call() + return [ + NodeId(id) + for id + in self.contract.functions.getValidatorNodeIndexes(validator_id).call() + ] - def get_last_change_ip_time(self, node_id: int) -> list: - return self.contract.functions.getLastChangeIpTime(node_id).call() + def get_last_change_ip_time(self, node_id: NodeId) -> int: + return int(self.contract.functions.getLastChangeIpTime(node_id).call()) @transaction_method - def set_node_in_maintenance(self, node_id): + def set_node_in_maintenance(self, node_id: NodeId) -> ContractFunction: return self.contract.functions.setNodeInMaintenance(node_id) @transaction_method - def remove_node_from_in_maintenance(self, node_id): + def remove_node_from_in_maintenance(self, node_id: NodeId) -> ContractFunction: return self.contract.functions.removeNodeFromInMaintenance(node_id) @transaction_method - def set_domain_name(self, node_id: int, domain_name: str): + def set_domain_name(self, node_id: NodeId, domain_name: str) -> ContractFunction: return self.contract.functions.setDomainName(node_id, domain_name) - def get_domain_name(self, node_id: int): - return self.contract.functions.getNodeDomainName(node_id).call() + def get_domain_name(self, node_id: NodeId) -> str: + return str(self.contract.functions.getNodeDomainName(node_id).call()) @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) - def node_manager_role(self): - return self.contract.functions.NODE_MANAGER_ROLE().call() + def node_manager_role(self) -> bytes: + return bytes(self.contract.functions.NODE_MANAGER_ROLE().call()) - def compliance_role(self): - return self.contract.functions.COMPLIANCE_ROLE().call() + def compliance_role(self) -> bytes: + return bytes(self.contract.functions.COMPLIANCE_ROLE().call()) @transaction_method - def init_exit(self, node_id: int) -> TxRes: + def init_exit(self, node_id: NodeId) -> ContractFunction: return self.contract.functions.initExit(node_id) @transaction_method - def change_ip(self, node_id: int, ip: bytes, public_ip: bytes) -> TxRes: + def change_ip(self, node_id: NodeId, ip: bytes, public_ip: bytes) -> ContractFunction: return self.contract.functions.changeIP(node_id, ip, public_ip) + + def _to_node(self, untyped_node: Dict[str, Any]) -> Node: + for key in Node.__annotations__: + if key not in untyped_node: + raise ValueError(f"Key: {key} is not available in node.") + return Node({ + 'name': str(untyped_node['name']), + 'ip': bytes(untyped_node['ip']), + 'publicIP': bytes(untyped_node['publicIP']), + 'port': Port(untyped_node['port']), + 'start_block': BlockNumber(untyped_node['start_block']), + 'last_reward_date': int(untyped_node['last_reward_date']), + 'finish_time': int(untyped_node['finish_time']), + 'status': NodeStatus(untyped_node['status']), + 'validator_id': ValidatorId(untyped_node['validator_id']), + 'publicKey': str(untyped_node['publicKey']), + 'domain_name': str(untyped_node['domain_name']), + }) diff --git a/skale/utils/helper.py b/skale/utils/helper.py index 7dbe056d..603e6bea 100644 --- a/skale/utils/helper.py +++ b/skale/utils/helper.py @@ -29,7 +29,7 @@ import sys from logging import Formatter, StreamHandler from random import randint -from typing import TYPE_CHECKING, Any, Callable, Generator, cast +from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, cast from skale.config import ENV from skale.types.node import Port @@ -45,18 +45,20 @@ def decapitalize(s: str) -> str: return s[:1].lower() + s[1:] if s else '' +WrapperReturnType = Dict[str, Any] | List[Dict[str, Any]] | None + + def format_fields( fields: list[str], flist: bool = False ) -> Callable[ - [Callable[ - ..., + [ Callable[ ..., - dict[str, Any] | list[dict[str, Any]] | Any | None + List[Any] ] - ]], - Callable[..., dict[str, Any] | list[dict[str, Any]] | Any | None] + ], + Callable[..., WrapperReturnType] ]: """ Transform array to object with passed fields @@ -71,16 +73,13 @@ def my_method() def real_decorator( function: Callable[ ..., - Callable[ - ..., - dict[str, Any] | list[dict[str, Any]] | Any | None - ] + List[Any] ] - ) -> Callable[..., dict[str, Any] | list[dict[str, Any]] | Any | None]: + ) -> Callable[..., WrapperReturnType]: def wrapper( *args: Any, **kwargs: Any - ) -> dict[str, Any] | list[dict[str, Any]] | Any | None: + ) -> WrapperReturnType: result = function(*args, **kwargs) if result is None: From 15a528cc7a3d860b96244361f5d260d7e31ca80f Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 15:40:55 +0300 Subject: [PATCH 053/100] Add type hints to node_rotation --- skale/contracts/manager/node_rotation.py | 80 +++++++++++++----------- skale/contracts/manager/nodes.py | 2 +- tests/manager/node_rotation_test.py | 11 +--- 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index ad9b2a5d..c11f4f6a 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -18,15 +18,24 @@ # along with SKALE.py. If not, see . """ NodeRotation.sol functions """ +from __future__ import annotations import logging import functools -import warnings +from typing import TYPE_CHECKING, List, TypedDict, cast from dataclasses import dataclass +from eth_typing import ChecksumAddress + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes +from web3.contract.contract import ContractFunction from web3.exceptions import ContractLogicError +from skale.types.node import NodeId +from skale.types.schain import SchainHash, SchainName + +if TYPE_CHECKING: + from skale.contracts.manager.schains import SChains + logger = logging.getLogger(__name__) @@ -42,59 +51,54 @@ class Rotation: rotation_counter: int +class RotationSwap(TypedDict): + schain_id: SchainHash + finished_rotation: int + + class NodeRotation(BaseContract): """Wrapper for NodeRotation.sol functions""" @property @functools.lru_cache() - def schains(self): - return self.skale.schains + def schains(self) -> SChains: + from skale.contracts.manager.schains import SChains + return cast(SChains, self.skale.schains) - def get_rotation_obj(self, schain_name) -> Rotation: + def get_rotation(self, schain_name: SchainName) -> Rotation: schain_id = self.schains.name_to_id(schain_name) rotation_data = self.contract.functions.getRotation(schain_id).call() return Rotation(*rotation_data) - def get_rotation(self, schain_name): - warnings.warn('Deprecated, will be removed in v6', DeprecationWarning) - schain_id = self.schains.name_to_id(schain_name) - rotation_data = self.contract.functions.getRotation(schain_id).call() - return { - 'leaving_node': rotation_data[0], - 'new_node': rotation_data[1], - 'freeze_until': rotation_data[2], - 'rotation_id': rotation_data[3] - } - - def get_leaving_history(self, node_id): + def get_leaving_history(self, node_id: NodeId) -> List[RotationSwap]: raw_history = self.contract.functions.getLeavingHistory(node_id).call() history = [ - { - 'schain_id': schain[0], - 'finished_rotation': schain[1] - } + RotationSwap({ + 'schain_id': SchainHash(schain[0]), + 'finished_rotation': int(schain[1]) + }) for schain in raw_history ] return history - def get_schain_finish_ts(self, node_id: int, schain_name: str) -> int | None: + def get_schain_finish_ts(self, node_id: NodeId, schain_name: SchainName) -> int | None: raw_history = self.contract.functions.getLeavingHistory(node_id).call() schain_id = self.skale.schains.name_to_id(schain_name) finish_ts = next( (schain[1] for schain in raw_history if '0x' + schain[0].hex() == schain_id), None) if not finish_ts: return None - return finish_ts + return int(finish_ts) - def is_rotation_in_progress(self, schain_name) -> bool: + def is_rotation_in_progress(self, schain_name: SchainName) -> bool: schain_id = self.schains.name_to_id(schain_name) - return self.contract.functions.isRotationInProgress(schain_id).call() + return bool(self.contract.functions.isRotationInProgress(schain_id).call()) - def is_new_node_found(self, schain_name) -> bool: + def is_new_node_found(self, schain_name: SchainName) -> bool: schain_id = self.schains.name_to_id(schain_name) - return self.contract.functions.isNewNodeFound(schain_id).call() + return bool(self.contract.functions.isNewNodeFound(schain_id).call()) - def is_rotation_active(self, schain_name) -> bool: + def is_rotation_active(self, schain_name: SchainName) -> bool: """ The public function that tells whether rotation is in the active phase - the new group is already generated @@ -102,7 +106,7 @@ def is_rotation_active(self, schain_name) -> bool: finish_ts_reached = self.is_finish_ts_reached(schain_name) return self.is_rotation_in_progress(schain_name) and not finish_ts_reached - def is_finish_ts_reached(self, schain_name) -> bool: + def is_finish_ts_reached(self, schain_name: SchainName) -> bool: rotation = self.skale.node_rotation.get_rotation_obj(schain_name) schain_finish_ts = self.get_schain_finish_ts(rotation.leaving_node_id, schain_name) @@ -115,24 +119,24 @@ def is_finish_ts_reached(self, schain_name) -> bool: logger.info(f'current_ts: {current_ts}, schain_finish_ts: {schain_finish_ts}') return current_ts > schain_finish_ts - def wait_for_new_node(self, schain_name): + def wait_for_new_node(self, schain_name: SchainName) -> bool: schain_id = self.schains.name_to_id(schain_name) - return self.contract.functions.waitForNewNode(schain_id).call() + return bool(self.contract.functions.waitForNewNode(schain_id).call()) @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: str) -> ContractFunction: return self.contract.functions.grantRole(role, owner) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) - def debugger_role(self): - return self.contract.functions.DEBUGGER_ROLE().call() + def debugger_role(self) -> bytes: + return bytes(self.contract.functions.DEBUGGER_ROLE().call()) - def get_previous_node(self, schain_name: str, node_id: int) -> int | None: + def get_previous_node(self, schain_name: SchainName, node_id: NodeId) -> NodeId | None: schain_id = self.schains.name_to_id(schain_name) try: - return self.contract.functions.getPreviousNode(schain_id, node_id).call() + return NodeId(self.contract.functions.getPreviousNode(schain_id, node_id).call()) except (ContractLogicError, ValueError) as e: if NO_PREVIOUS_NODE_EXCEPTION_TEXT in str(e): return None diff --git a/skale/contracts/manager/nodes.py b/skale/contracts/manager/nodes.py index 262e3a07..bba17041 100644 --- a/skale/contracts/manager/nodes.py +++ b/skale/contracts/manager/nodes.py @@ -109,7 +109,7 @@ def get_active_node_ids(self) -> List[NodeId]: if self.get_node_status(NodeId(node_id)) == NodeStatus.ACTIVE ] - def get_active_node_ips(self) -> List[str]: + def get_active_node_ips(self) -> List[bytes]: nodes_number = self.get_nodes_number() return [ self.get(NodeId(node_id))['ip'] diff --git a/tests/manager/node_rotation_test.py b/tests/manager/node_rotation_test.py index 9aaa6c61..a2aa609f 100644 --- a/tests/manager/node_rotation_test.py +++ b/tests/manager/node_rotation_test.py @@ -12,16 +12,7 @@ def test_get_rotation(skale): - assert skale.node_rotation.get_rotation(DEFAULT_SCHAIN_NAME) == { - 'leaving_node': 0, - 'new_node': 0, - 'freeze_until': 0, - 'rotation_id': 0 - } - - -def test_get_rotation_obj(skale): - assert skale.node_rotation.get_rotation_obj(DEFAULT_SCHAIN_NAME) == Rotation( + assert skale.node_rotation.get_rotation(DEFAULT_SCHAIN_NAME) == Rotation( leaving_node_id=0, new_node_id=0, freeze_until=0, From f166c134ae05c5b084c87f3f42c286918b1581a2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 16:00:34 +0300 Subject: [PATCH 054/100] Add type hints to skale manager --- skale/contracts/manager/manager.py | 41 +++++++++++++++--------- skale/contracts/manager/node_rotation.py | 5 +-- skale/skale_base.py | 5 +-- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/skale/contracts/manager/manager.py b/skale/contracts/manager/manager.py index 13a6bfc0..1b30a6bd 100644 --- a/skale/contracts/manager/manager.py +++ b/skale/contracts/manager/manager.py @@ -23,8 +23,12 @@ import socket from eth_abi import encode +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction from skale.contracts.base_contract import BaseContract, transaction_method +from skale.types.node import NodeId, Port +from skale.types.schain import SchainName from skale.utils import helper from skale.transactions.result import TxRes from skale.dataclasses.schain_options import ( @@ -37,7 +41,14 @@ class Manager(BaseContract): @transaction_method - def create_node(self, ip, port, name, domain_name, public_ip=None): + def create_node( + self, + ip: str, + port: Port, + name: str, + domain_name: str, + public_ip: str | None = None + ) -> ContractFunction: logger.info( f'create_node: {ip}:{port}, name: {name}, domain_name: {domain_name}') skale_nonce = helper.generate_nonce() @@ -56,7 +67,7 @@ def create_node(self, ip, port, name, domain_name, public_ip=None): domain_name ) - def create_default_schain(self, name): + def create_default_schain(self, name: SchainName) -> TxRes: lifetime = 3600 nodes_type = self.skale.schains_internal.number_of_schain_types() price_in_wei = self.skale.schains.get_schain_price( @@ -70,10 +81,10 @@ def create_schain( lifetime: int, type_of_nodes: int, deposit: str, - name: str, - schain_originator: str | None = None, + name: SchainName, + schain_originator: ChecksumAddress | None = None, options: SchainOptions | None = None - ): + ) -> ContractFunction: logger.info( f'create_schain: type_of_nodes: {type_of_nodes}, name: {name}') skale_nonce = helper.generate_nonce() @@ -95,33 +106,33 @@ def create_schain( ) @transaction_method - def get_bounty(self, node_id): + def get_bounty(self, node_id: NodeId) -> ContractFunction: return self.contract.functions.getBounty(node_id) @transaction_method - def delete_schain(self, schain_name): + def delete_schain(self, schain_name: SchainName) -> ContractFunction: return self.contract.functions.deleteSchain(schain_name) @transaction_method - def delete_schain_by_root(self, schain_name): + def delete_schain_by_root(self, schain_name: SchainName) -> ContractFunction: return self.contract.functions.deleteSchainByRoot(schain_name) @transaction_method - def node_exit(self, node_id): + def node_exit(self, node_id: NodeId) -> ContractFunction: return self.contract.functions.nodeExit(node_id) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) def default_admin_role(self) -> bytes: - return self.contract.functions.DEFAULT_ADMIN_ROLE().call() + return bytes(self.contract.functions.DEFAULT_ADMIN_ROLE().call()) def admin_role(self) -> bytes: - return self.contract.functions.ADMIN_ROLE().call() + return bytes(self.contract.functions.ADMIN_ROLE().call()) def schain_removal_role(self) -> bytes: - return self.contract.functions.SCHAIN_REMOVAL_ROLE().call() + return bytes(self.contract.functions.SCHAIN_REMOVAL_ROLE().call()) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index c11f4f6a..02dea742 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -82,8 +82,9 @@ def get_leaving_history(self, node_id: NodeId) -> List[RotationSwap]: return history def get_schain_finish_ts(self, node_id: NodeId, schain_name: SchainName) -> int | None: + from skale.contracts.manager.schains import SChains raw_history = self.contract.functions.getLeavingHistory(node_id).call() - schain_id = self.skale.schains.name_to_id(schain_name) + schain_id = cast(SChains, self.skale.schains).name_to_id(schain_name) finish_ts = next( (schain[1] for schain in raw_history if '0x' + schain[0].hex() == schain_id), None) if not finish_ts: @@ -107,7 +108,7 @@ def is_rotation_active(self, schain_name: SchainName) -> bool: return self.is_rotation_in_progress(schain_name) and not finish_ts_reached def is_finish_ts_reached(self, schain_name: SchainName) -> bool: - rotation = self.skale.node_rotation.get_rotation_obj(schain_name) + rotation = cast(NodeRotation, self.skale.node_rotation).get_rotation_obj(schain_name) schain_finish_ts = self.get_schain_finish_ts(rotation.leaving_node_id, schain_name) if not schain_finish_ts: diff --git a/skale/skale_base.py b/skale/skale_base.py index 92adbfff..9bddd737 100644 --- a/skale/skale_base.py +++ b/skale/skale_base.py @@ -25,6 +25,7 @@ from skale_contracts import skale_contracts +from skale.contracts.base_contract import BaseContract from skale.wallets import BaseWallet from skale.utils.exceptions import InvalidWalletError, EmptyWalletError from skale.utils.web3_utils import default_gas_price, init_web3 @@ -135,11 +136,11 @@ def get_contract_address(self, name) -> ChecksumAddress: def __get_contract_by_name(self, name): return self.__contracts[name] - def __getattr__(self, name): + def __getattr__(self, name) -> BaseContract: if name not in self.__contracts: if not self.__contracts_info.get(name): logger.warning("%s method/contract wasn't found", name) - return None + raise ValueError(name, ' is an unknown contract') logger.debug("Contract %s wasn't inited, creating now", name) contract_info = self.__contracts_info[name] self.__init_contract_from_info(contract_info) From 82b1f81f0cfa724d7b061fef4c649dfd6d44c9fb Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 16:06:14 +0300 Subject: [PATCH 055/100] Add type hints to key_storage --- skale/contracts/manager/key_storage.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/skale/contracts/manager/key_storage.py b/skale/contracts/manager/key_storage.py index e1fbb275..375df21d 100644 --- a/skale/contracts/manager/key_storage.py +++ b/skale/contracts/manager/key_storage.py @@ -18,8 +18,9 @@ # along with SKALE.py. If not, see . from collections import namedtuple -from typing import NamedTuple +from typing import List, NamedTuple from skale.contracts.base_contract import BaseContract +from skale.types.schain import SchainHash Fp2Point = namedtuple('Fp2Point', ['a', 'b']) @@ -31,11 +32,15 @@ class G2Point(NamedTuple): class KeyStorage(BaseContract): - def get_common_public_key(self, group_index) -> G2Point: - return self.contract.functions.getCommonPublicKey(group_index).call() - - def get_previous_public_key(self, group_index): - return self.contract.functions.getPreviousPublicKey(group_index).call() - - def get_all_previous_public_keys(self, group_index): - return self.contract.functions.getAllPreviousPublicKeys(group_index).call() + def get_common_public_key(self, schain_hash: SchainHash) -> G2Point: + return G2Point(*self.contract.functions.getCommonPublicKey(schain_hash).call()) + + def get_previous_public_key(self, schain_hash: SchainHash) -> G2Point: + return G2Point(*self.contract.functions.getPreviousPublicKey(schain_hash).call()) + + def get_all_previous_public_keys(self, schain_hash: SchainHash) -> List[G2Point]: + return [ + G2Point(*key) + for key + in self.contract.functions.getAllPreviousPublicKeys(schain_hash).call() + ] From e89a96a74542f13a1103accb421fdbb2748df04e Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 16:47:37 +0300 Subject: [PATCH 056/100] Add type hints to dkg --- skale/contracts/manager/dkg.py | 183 +++++++++++++++---------- skale/contracts/manager/key_storage.py | 12 +- skale/types/dkg.py | 37 +++++ skale/types/validator.py | 23 ++++ tests/manager/dkg_test.py | 9 +- 5 files changed, 179 insertions(+), 85 deletions(-) create mode 100644 skale/types/dkg.py create mode 100644 skale/types/validator.py diff --git a/skale/contracts/manager/dkg.py b/skale/contracts/manager/dkg.py index 47d22c79..9e2597b7 100644 --- a/skale/contracts/manager/dkg.py +++ b/skale/contracts/manager/dkg.py @@ -17,45 +17,46 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from typing import List, Tuple +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method from skale.transactions.tools import retry_tx -from skale.utils.helper import split_public_key - - -class KeyShare: - def __init__(self, public_key: str, share: bytes): - self.public_key = split_public_key(public_key) - self.share = share - self.tuple = (self.public_key, self.share) - - -class G2Point: - def __init__(self, xa, xb, ya, yb): - self.x = (xa, xb) - self.y = (ya, yb) - self.tuple = (self.x, self.y) +from skale.types.dkg import G2Point, KeyShare, VerificationVector +from skale.types.node import NodeId +from skale.types.schain import SchainHash class DKG(BaseContract): @retry_tx @transaction_method - def broadcast(self, group_index, node_index, - verification_vector, secret_key_contribution, rotation_id): - return self.contract.functions.broadcast(group_index, node_index, - verification_vector, - secret_key_contribution, - rotation_id) + def broadcast( + self, + group_index: SchainHash, + node_index: NodeId, + verification_vector: VerificationVector, + secret_key_contribution: List[KeyShare], + rotation_id: int + ) -> ContractFunction: + return self.contract.functions.broadcast( + group_index, + node_index, + verification_vector, + secret_key_contribution, + rotation_id + ) @retry_tx @transaction_method def pre_response( self, - group_index: str, - from_node_index: int, - verification_vector: list, - verification_vector_mult: list, - secret_key_contribution: list - ): + group_index: SchainHash, + from_node_index: NodeId, + verification_vector: VerificationVector, + verification_vector_mult: VerificationVector, + secret_key_contribution: List[KeyShare] + ) -> ContractFunction: return self.contract.functions.preResponse( group_index, fromNodeIndex=from_node_index, @@ -68,11 +69,11 @@ def pre_response( @transaction_method def response( self, - group_index: bytes, - from_node_index: int, + group_index: SchainHash, + from_node_index: NodeId, secret_number: int, multiplied_share: G2Point - ): + ) -> ContractFunction: return self.contract.functions.response( group_index, fromNodeIndex=from_node_index, @@ -82,78 +83,118 @@ def response( @retry_tx @transaction_method - def alright(self, group_index, from_node_index): + def alright(self, group_index: SchainHash, from_node_index: NodeId) -> ContractFunction: return self.contract.functions.alright(group_index, from_node_index) @retry_tx @transaction_method - def complaint(self, group_index, from_node_index, to_node_index): + def complaint( + self, + group_index: SchainHash, + from_node_index: NodeId, + to_node_index: NodeId + ) -> ContractFunction: return self.contract.functions.complaint(group_index, from_node_index, to_node_index) @retry_tx @transaction_method - def complaint_bad_data(self, group_index, from_node_index, to_node_index): + def complaint_bad_data( + self, + group_index: SchainHash, + from_node_index: NodeId, + to_node_index: NodeId + ) -> ContractFunction: return self.contract.functions.complaintBadData(group_index, from_node_index, to_node_index) - def is_last_dkg_successful(self, group_index): - return self.contract.functions.isLastDKGSuccessful(group_index).call() + def is_last_dkg_successful(self, group_index: SchainHash) -> bool: + return bool(self.contract.functions.isLastDKGSuccessful(group_index).call()) - def is_channel_opened(self, group_index): - return self.contract.functions.isChannelOpened(group_index).call() + def is_channel_opened(self, group_index: SchainHash) -> bool: + return bool(self.contract.functions.isChannelOpened(group_index).call()) - def is_broadcast_possible(self, group_index, node_id, address): - return self.contract.functions.isBroadcastPossible(group_index, node_id).call( + def is_broadcast_possible( + self, + group_index: SchainHash, + node_id: NodeId, + address: ChecksumAddress + ) -> bool: + return bool(self.contract.functions.isBroadcastPossible(group_index, node_id).call( {'from': address} - ) + )) - def is_alright_possible(self, group_index, node_id, address): - return self.contract.functions.isAlrightPossible(group_index, node_id).call( + def is_alright_possible( + self, + group_index: SchainHash, + node_id: NodeId, + address: ChecksumAddress + ) -> bool: + return bool(self.contract.functions.isAlrightPossible(group_index, node_id).call( {'from': address} - ) + )) - def is_complaint_possible(self, group_index, node_from, node_to, address): - return self.contract.functions.isComplaintPossible(group_index, node_from, node_to).call( - {'from': address} + def is_complaint_possible( + self, + group_index: SchainHash, + node_from: NodeId, + node_to: NodeId, + address: ChecksumAddress + ) -> bool: + return bool( + self.contract.functions.isComplaintPossible( + group_index, + node_from, + node_to + ).call({'from': address}) ) - def is_pre_response_possible(self, group_index, node_id, address): - return self.contract.functions.isPreResponsePossible(group_index, node_id).call( + def is_pre_response_possible( + self, + group_index: SchainHash, + node_id: NodeId, + address: ChecksumAddress + ) -> bool: + return bool(self.contract.functions.isPreResponsePossible(group_index, node_id).call( {'from': address} - ) + )) - def is_response_possible(self, group_index, node_id, address): - return self.contract.functions.isResponsePossible(group_index, node_id).call( + def is_response_possible( + self, + group_index: SchainHash, + node_id: NodeId, + address: ChecksumAddress + ) -> bool: + return bool(self.contract.functions.isResponsePossible(group_index, node_id).call( {'from': address} - ) + )) - def is_all_data_received(self, group_index, node_from): - return self.contract.functions.isAllDataReceived(group_index, node_from).call() + def is_all_data_received(self, group_index: SchainHash, node_from: NodeId) -> bool: + return bool(self.contract.functions.isAllDataReceived(group_index, node_from).call()) - def is_everyone_broadcasted(self, group_index, address): - return self.contract.functions.isEveryoneBroadcasted(group_index).call( + def is_everyone_broadcasted(self, group_index: SchainHash, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.isEveryoneBroadcasted(group_index).call( {'from': address} - ) + )) - def get_number_of_completed(self, group_index): - return self.contract.functions.getNumberOfCompleted(group_index).call() + def get_number_of_completed(self, group_index: SchainHash) -> int: + return int(self.contract.functions.getNumberOfCompleted(group_index).call()) - def get_channel_started_time(self, group_index): - return self.contract.functions.getChannelStartedTime(group_index).call() + def get_channel_started_time(self, group_index: SchainHash) -> int: + return int(self.contract.functions.getChannelStartedTime(group_index).call()) - def get_complaint_started_time(self, group_index): - return self.contract.functions.getComplaintStartedTime(group_index).call() + def get_complaint_started_time(self, group_index: SchainHash) -> int: + return int(self.contract.functions.getComplaintStartedTime(group_index).call()) - def get_alright_started_time(self, group_index): - return self.contract.functions.getAlrightStartedTime(group_index).call() + def get_alright_started_time(self, group_index: SchainHash) -> int: + return int(self.contract.functions.getAlrightStartedTime(group_index).call()) - def get_complaint_data(self, group_index): - return self.contract.functions.getComplaintData(group_index).call() + def get_complaint_data(self, group_index: SchainHash) -> Tuple[NodeId, NodeId]: + return tuple(self.contract.functions.getComplaintData(group_index).call()) - def get_time_of_last_successful_dkg(self, group_index): - return self.contract.functions.getTimeOfLastSuccessfulDKG(group_index).call() + def get_time_of_last_successful_dkg(self, group_index: SchainHash) -> int: + return int(self.contract.functions.getTimeOfLastSuccessfulDKG(group_index).call()) - def is_node_broadcasted(self, group_index: int, node_id: int) -> bool: - return self.contract.functions.isNodeBroadcasted(group_index, node_id).call() + def is_node_broadcasted(self, group_index: SchainHash, node_id: NodeId) -> bool: + return bool(self.contract.functions.isNodeBroadcasted(group_index, node_id).call()) diff --git a/skale/contracts/manager/key_storage.py b/skale/contracts/manager/key_storage.py index 375df21d..d449e72e 100644 --- a/skale/contracts/manager/key_storage.py +++ b/skale/contracts/manager/key_storage.py @@ -17,20 +17,12 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from collections import namedtuple -from typing import List, NamedTuple +from typing import List from skale.contracts.base_contract import BaseContract +from skale.contracts.manager.dkg import G2Point from skale.types.schain import SchainHash -Fp2Point = namedtuple('Fp2Point', ['a', 'b']) - - -class G2Point(NamedTuple): - x: Fp2Point - y: Fp2Point - - class KeyStorage(BaseContract): def get_common_public_key(self, schain_hash: SchainHash) -> G2Point: return G2Point(*self.contract.functions.getCommonPublicKey(schain_hash).call()) diff --git a/skale/types/dkg.py b/skale/types/dkg.py new file mode 100644 index 00000000..a6b8f50e --- /dev/null +++ b/skale/types/dkg.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +from collections import namedtuple +from typing import List, NamedTuple, NewType, Tuple + + +Fp2Point = namedtuple('Fp2Point', ['a', 'b']) + + +class G2Point(NamedTuple): + x: Fp2Point + y: Fp2Point + + +VerificationVector = NewType('VerificationVector', List[G2Point]) + + +class KeyShare(NamedTuple): + publicKey: Tuple[bytes, bytes] + share: bytes diff --git a/skale/types/validator.py b/skale/types/validator.py new file mode 100644 index 00000000..e0a2c0d5 --- /dev/null +++ b/skale/types/validator.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +from typing import NewType + + +ValidatorId = NewType('ValidatorId', int) diff --git a/tests/manager/dkg_test.py b/tests/manager/dkg_test.py index 5bdcc18e..2fe51df1 100644 --- a/tests/manager/dkg_test.py +++ b/tests/manager/dkg_test.py @@ -4,6 +4,7 @@ from hexbytes import HexBytes from skale.contracts.manager.dkg import G2Point, KeyShare +from skale.utils.helper import split_public_key SCHAIN_NAME = 'pointed-asellus-australis' PUBLIC_KEY = '0xfcb3765bdb954ab0672fce731583ad8a94cf05fe63c147f881f8feea18e072d4cad3ec142a65de66a1d50e4fc34a7841c5488ccb55d02cf86013208c17517d64' # noqa @@ -68,10 +69,10 @@ def test_response(skale): group_index = skale.schains.name_to_id(SCHAIN_NAME) share = group_index # not an invariant, only a mock secret_number = 1 - multiplied_share = G2Point(1, 2, 3, 4).tuple - verification_vector = [G2Point(1, 2, 3, 4).tuple for i in range(0, 3)] - verification_vector_mult = [G2Point(1, 2, 3, 4).tuple for i in range(0, 3)] - secret_key_contribution = [KeyShare(PUBLIC_KEY, share).tuple] + multiplied_share = G2Point((1, 2), (3, 4)) + verification_vector = [G2Point((1, 2), (3, 4)) for i in range(0, 3)] + verification_vector_mult = [G2Point((1, 2), (3, 4)) for i in range(0, 3)] + secret_key_contribution = [KeyShare(split_public_key(PUBLIC_KEY), share)] exp = skale.web3.eth.account.sign_transaction( expected_txn, skale.wallet._private_key).rawTransaction From de0a75117f78de73c78ee7f9222e6b646f7c69bc Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 18:20:50 +0300 Subject: [PATCH 057/100] Add type hints to constants_holder --- skale/contracts/manager/constants_holder.py | 54 +++++++++++---------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/skale/contracts/manager/constants_holder.py b/skale/contracts/manager/constants_holder.py index dda23842..b7e6cd3f 100644 --- a/skale/contracts/manager/constants_holder.py +++ b/skale/contracts/manager/constants_holder.py @@ -17,40 +17,42 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class ConstantsHolder(BaseContract): @transaction_method - def set_periods(self, new_reward_period, new_delta_period): + def set_periods(self, new_reward_period: int, new_delta_period: int) -> ContractFunction: return self.contract.functions.setPeriods( new_reward_period, new_delta_period ) - def get_reward_period(self): - return self.contract.functions.rewardPeriod().call() + def get_reward_period(self) -> int: + return int(self.contract.functions.rewardPeriod().call()) - def get_delta_period(self): - return self.contract.functions.deltaPeriod().call() + def get_delta_period(self) -> int: + return int(self.contract.functions.deltaPeriod().call()) @transaction_method - def set_check_time(self, new_check_time): + def set_check_time(self, new_check_time: int) -> ContractFunction: return self.contract.functions.setCheckTime(new_check_time) - def get_check_time(self): - return self.contract.functions.checkTime().call() + def get_check_time(self) -> int: + return int(self.contract.functions.checkTime().call()) @transaction_method - def set_latency(self, new_allowable_latency): + def set_latency(self, new_allowable_latency: int) -> ContractFunction: return self.contract.functions.setLatency(new_allowable_latency) - def get_latency(self): - return self.contract.functions.allowableLatency().call() + def get_latency(self) -> int: + return int(self.contract.functions.allowableLatency().call()) - def get_first_delegation_month(self): - return self.contract.functions.firstDelegationsMonth().call() + def get_first_delegation_month(self) -> int: + return int(self.contract.functions.firstDelegationsMonth().call()) def msr(self) -> int: """Minimum staking requirement to create a node. @@ -58,41 +60,41 @@ def msr(self) -> int: :returns: MSR (in wei) :rtype: int """ - return self.contract.functions.msr().call() + return int(self.contract.functions.msr().call()) @transaction_method - def _set_msr(self, new_msr: int) -> None: + def _set_msr(self, new_msr: int) -> ContractFunction: """For internal usage only""" return self.contract.functions.setMSR(new_msr) def get_launch_timestamp(self) -> int: - return self.contract.functions.launchTimestamp().call() + return int(self.contract.functions.launchTimestamp().call()) @transaction_method - def set_launch_timestamp(self, launch_timestamp: int): + def set_launch_timestamp(self, launch_timestamp: int) -> ContractFunction: return self.contract.functions.setLaunchTimestamp(launch_timestamp) @transaction_method - def set_rotation_delay(self, rotation_delay: int) -> None: + def set_rotation_delay(self, rotation_delay: int) -> ContractFunction: """For internal usage only""" return self.contract.functions.setRotationDelay(rotation_delay) def get_rotation_delay(self) -> int: - return self.contract.functions.rotationDelay().call() + return int(self.contract.functions.rotationDelay().call()) def get_dkg_timeout(self) -> int: - return self.contract.functions.complaintTimeLimit().call() + return int(self.contract.functions.complaintTimeLimit().call()) @transaction_method - def set_complaint_timelimit(self, complaint_timelimit: int): + def set_complaint_timelimit(self, complaint_timelimit: int) -> ContractFunction: return self.contract.functions.setComplaintTimeLimit(complaint_timelimit) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) def constants_holder_role(self) -> bytes: - return self.contract.functions.CONSTANTS_HOLDER_MANAGER_ROLE().call() + return bytes(self.contract.functions.CONSTANTS_HOLDER_MANAGER_ROLE().call()) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) From e5f6a6d90a3c1a255f76a4494c0ac5287c936f1c Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 18:23:06 +0300 Subject: [PATCH 058/100] Add type hints to bountry_v2 --- skale/contracts/manager/bounty_v2.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/skale/contracts/manager/bounty_v2.py b/skale/contracts/manager/bounty_v2.py index 3fba4724..a5a179d5 100644 --- a/skale/contracts/manager/bounty_v2.py +++ b/skale/contracts/manager/bounty_v2.py @@ -17,17 +17,19 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class BountyV2(BaseContract): @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) - def bounty_reduction_manager_role(self): - return self.contract.functions.BOUNTY_REDUCTION_MANAGER_ROLE().call() + def bounty_reduction_manager_role(self) -> bytes: + return bytes(self.contract.functions.BOUNTY_REDUCTION_MANAGER_ROLE().call()) From 0e4f8c4346ec5b946bdf6d37da68bb9402edd296 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 18:25:17 +0300 Subject: [PATCH 059/100] Add type hints to time_helpers_with_debug --- skale/contracts/manager/test/time_helpers_with_debug.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/skale/contracts/manager/test/time_helpers_with_debug.py b/skale/contracts/manager/test/time_helpers_with_debug.py index 6ffb701d..a8f34cb5 100644 --- a/skale/contracts/manager/test/time_helpers_with_debug.py +++ b/skale/contracts/manager/test/time_helpers_with_debug.py @@ -17,15 +17,16 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class TimeHelpersWithDebug(BaseContract): """Wrapper for TimeHelpersWithDebug.sol functions (internal usage only)""" @transaction_method - def skip_time(self, sec: int) -> TxRes: + def skip_time(self, sec: int) -> ContractFunction: """Skip time on contracts :param sec: Time to skip in seconds @@ -41,4 +42,4 @@ def get_current_month(self) -> int: :returns: Month index :rtype: int """ - return self.contract.functions.getCurrentMonth().call() + return int(self.contract.functions.getCurrentMonth().call()) From e296c9cfa173862933b3397c1c41ecd2c87c1293 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Wed, 24 Apr 2024 19:47:17 +0300 Subject: [PATCH 060/100] Add type hints to validator_service --- skale/contracts/base_contract.py | 3 +- .../manager/delegation/validator_service.py | 145 ++++++++++++------ skale/skale_base.py | 4 +- skale/types/validator.py | 21 ++- skale/wallets/common.py | 4 +- skale/wallets/ledger_wallet.py | 6 +- skale/wallets/redis_wallet.py | 7 +- skale/wallets/sgx_wallet.py | 8 +- skale/wallets/web3_wallet.py | 6 +- 9 files changed, 133 insertions(+), 71 deletions(-) diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 04bc5d7c..d7de65cc 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -138,8 +138,7 @@ def wrapper( method=method_name ) - should_wait = tx_hash is not None and wait_for - if should_wait: + if tx_hash is not None and wait_for: receipt = self.skale.wallet.wait(tx_hash) should_confirm = receipt is not None and confirmation_blocks > 0 diff --git a/skale/contracts/manager/delegation/validator_service.py b/skale/contracts/manager/delegation/validator_service.py index 74e12872..7ab6b78f 100644 --- a/skale/contracts/manager/delegation/validator_service.py +++ b/skale/contracts/manager/delegation/validator_service.py @@ -17,13 +17,16 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from typing import Any, Dict, List +from eth_typing import ChecksumAddress from web3 import Web3 +from web3.contract.contract import ContractFunction +from web3.types import Wei from skale.contracts.base_contract import BaseContract, transaction_method +from skale.types.validator import Validator, ValidatorId, ValidatorWithId from skale.utils.helper import format_fields -from skale.transactions.result import TxRes - FIELDS = [ 'name', 'validator_address', 'requested_address', 'description', 'fee_rate', @@ -35,16 +38,16 @@ class ValidatorService(BaseContract): """Wrapper for ValidatorService.sol functions""" - def __get_raw(self, _id) -> list: + def __get_raw(self, _id: ValidatorId) -> List[Any]: """Returns raw validator info. :returns: Raw validator info :rtype: list """ - return self.contract.functions.validators(_id).call() + return list(self.contract.functions.validators(_id).call()) @format_fields(FIELDS) - def get(self, _id) -> list: + def untyped_get(self, _id: ValidatorId) -> List[Any]: """Returns validator info. :returns: Validator info @@ -55,25 +58,36 @@ def get(self, _id) -> list: validator.append(trusted) return validator - def get_with_id(self, _id) -> dict: + def get(self, _id: ValidatorId) -> Validator: + untyped_validator = self.untyped_get(_id) + if untyped_validator is None: + raise ValueError('Validator with id ', _id, ' is missing') + if isinstance(untyped_validator, dict): + return self._to_validator(untyped_validator) + if isinstance(untyped_validator, list): + return self._to_validator(untyped_validator[0]) + raise TypeError(_id) + + def get_with_id(self, _id: ValidatorId) -> ValidatorWithId: """Returns validator info with ID. :returns: Validator info with ID :rtype: dict """ validator = self.get(_id) - validator['id'] = _id - return validator + return ValidatorWithId({'id': _id, **validator}) + # validator['id'] = _id + # return validator - def number_of_validators(self): + def number_of_validators(self) -> int: """Returns number of registered validators. :returns: List of validators :rtype: int """ - return self.contract.functions.numberOfValidators().call() + return int(self.contract.functions.numberOfValidators().call()) - def ls(self, trusted_only=False): + def ls(self, trusted_only: bool = False) -> List[ValidatorWithId]: """Returns list of registered validators. :returns: List of validators @@ -84,30 +98,42 @@ def ls(self, trusted_only=False): self.get_with_id(val_id) for val_id in self.get_trusted_validator_ids() ] if trusted_only else [ - self.get_with_id(val_id) + self.get_with_id(ValidatorId(val_id)) for val_id in range(1, number_of_validators + 1) ] return validators - def get_linked_addresses_by_validator_address(self, address: str) -> list: + def get_linked_addresses_by_validator_address( + self, + address: ChecksumAddress + ) -> List[ChecksumAddress]: """Returns list of node addresses linked to the validator address. :returns: List of node addresses :rtype: list """ - return self.contract.functions.getMyNodesAddresses().call({ - 'from': address - }) + return [ + Web3.to_checksum_address(address) + for address + in self.contract.functions.getMyNodesAddresses().call({'from': address}) + ] - def get_linked_addresses_by_validator_id(self, validator_id: int) -> list: + def get_linked_addresses_by_validator_id( + self, + validator_id: ValidatorId + ) -> List[ChecksumAddress]: """Returns list of node addresses linked to the validator ID. :returns: List of node addresses :rtype: list """ - return self.contract.functions.getNodeAddresses(validator_id).call() + return [ + Web3.to_checksum_address(address) + for address + in self.contract.functions.getNodeAddresses(validator_id).call() + ] - def is_main_address(self, validator_address: str) -> bool: + def is_main_address(self, validator_address: ChecksumAddress) -> bool: """Checks if provided address is the main validator address :returns: True if provided address is the main validator address, otherwise False @@ -125,59 +151,63 @@ def is_main_address(self, validator_address: str) -> bool: return validator_address == validator['validator_address'] - def validator_address_exists(self, validator_address: str) -> bool: + def validator_address_exists(self, validator_address: ChecksumAddress) -> bool: """Checks if there is a validator with provided address :returns: True if validator exists, otherwise False :rtype: bool """ - return self.contract.functions.validatorAddressExists(validator_address).call() + return bool(self.contract.functions.validatorAddressExists(validator_address).call()) - def validator_exists(self, validator_id: str) -> bool: + def validator_exists(self, validator_id: ValidatorId) -> bool: """Checks if there is a validator with provided ID :returns: True if validator exists, otherwise False :rtype: bool """ - return self.contract.functions.validatorExists(validator_id).call() + return bool(self.contract.functions.validatorExists(validator_id).call()) - def validator_id_by_address(self, validator_address: str) -> int: + def validator_id_by_address(self, validator_address: ChecksumAddress) -> ValidatorId: """Returns validator ID by validator address :returns: Validator ID :rtype: int """ - return self.contract.functions.getValidatorId(validator_address).call() + return ValidatorId(self.contract.functions.getValidatorId(validator_address).call()) - def get_trusted_validator_ids(self) -> list: + def get_trusted_validator_ids(self) -> List[ValidatorId]: """Returns list of trusted validators id. :returns: List of trusted validators id :rtype: list """ - return self.contract.functions.getTrustedValidators().call() + return [ + ValidatorId(id) + for id + in self.contract.functions.getTrustedValidators().call() + ] @transaction_method - def _enable_validator(self, validator_id: int) -> TxRes: + def _enable_validator(self, validator_id: ValidatorId) -> ContractFunction: """For internal usage only""" return self.contract.functions.enableValidator(validator_id) @transaction_method - def _disable_validator(self, validator_id: int) -> TxRes: + def _disable_validator(self, validator_id: ValidatorId) -> ContractFunction: """For internal usage only""" return self.contract.functions.disableValidator(validator_id) - def _is_authorized_validator(self, validator_id: int) -> bool: + def _is_authorized_validator(self, validator_id: ValidatorId) -> bool: """For internal usage only""" - return self.contract.functions.isAuthorizedValidator(validator_id).call() + return bool(self.contract.functions.isAuthorizedValidator(validator_id).call()) - def is_accepting_new_requests(self, validator_id: int) -> bool: + def is_accepting_new_requests(self, validator_id: ValidatorId) -> bool: """For internal usage only""" - return self.contract.functions.isAcceptingNewRequests(validator_id).call() + return bool(self.contract.functions.isAcceptingNewRequests(validator_id).call()) @transaction_method def register_validator(self, name: str, description: str, fee_rate: int, - min_delegation_amount: int) -> TxRes: + min_delegation_amount: int) -> ContractFunction: """Registers a new validator in the SKALE Manager contracts. :param name: Validator name @@ -194,13 +224,13 @@ def register_validator(self, name: str, description: str, fee_rate: int, return self.contract.functions.registerValidator( name, description, fee_rate, min_delegation_amount) - def get_link_node_signature(self, validator_id: int) -> str: + def get_link_node_signature(self, validator_id: ValidatorId) -> str: unsigned_hash = Web3.solidity_keccak(['uint256'], [validator_id]) signed_hash = self.skale.wallet.sign_hash(unsigned_hash.hex()) return signed_hash.signature.hex() @transaction_method - def link_node_address(self, node_address: str, signature: str) -> TxRes: + def link_node_address(self, node_address: ChecksumAddress, signature: str) -> ContractFunction: """Link node address to your validator account. :param node_address: Address of the node to link @@ -213,7 +243,7 @@ def link_node_address(self, node_address: str, signature: str) -> TxRes: return self.contract.functions.linkNodeAddress(node_address, signature) @transaction_method - def unlink_node_address(self, node_address: str) -> TxRes: + def unlink_node_address(self, node_address: ChecksumAddress) -> ContractFunction: """Unlink node address from your validator account. :param node_address: Address of the node to unlink @@ -224,7 +254,7 @@ def unlink_node_address(self, node_address: str) -> TxRes: return self.contract.functions.unlinkNodeAddress(node_address) @transaction_method - def disable_whitelist(self) -> TxRes: + def disable_whitelist(self) -> ContractFunction: """ Disable validator whitelist. Master key only transaction. :returns: Transaction results :rtype: TxRes @@ -236,18 +266,18 @@ def get_use_whitelist(self) -> bool: :returns: useWhitelist value :rtype: bool """ - return self.contract.functions.useWhitelist().call() + return bool(self.contract.functions.useWhitelist().call()) - def get_and_update_bond_amount(self, validator_id: int) -> int: + def get_and_update_bond_amount(self, validator_id: ValidatorId) -> int: """Return amount of token that validator delegated to himself :param validator_id: id of the validator :returns: :rtype: int """ - return self.contract.functions.getAndUpdateBondAmount(validator_id).call() + return int(self.contract.functions.getAndUpdateBondAmount(validator_id).call()) @transaction_method - def set_validator_mda(self, minimum_delegation_amount: int) -> TxRes: + def set_validator_mda(self, minimum_delegation_amount: Wei) -> ContractFunction: """ Allows a validator to set the minimum delegation amount. :param new_minimum_delegation_amount: Minimum delegation amount @@ -258,7 +288,7 @@ def set_validator_mda(self, minimum_delegation_amount: int) -> TxRes: return self.contract.functions.setValidatorMDA(minimum_delegation_amount) @transaction_method - def request_for_new_address(self, new_validator_address: str) -> TxRes: + def request_for_new_address(self, new_validator_address: ChecksumAddress) -> ContractFunction: """ Allows a validator to request a new address. :param new_validator_address: New validator address @@ -269,7 +299,7 @@ def request_for_new_address(self, new_validator_address: str) -> TxRes: return self.contract.functions.requestForNewAddress(new_validator_address) @transaction_method - def confirm_new_address(self, validator_id: int) -> TxRes: + def confirm_new_address(self, validator_id: ValidatorId) -> ContractFunction: """ Confirm change of the address. :param validator_id: ID of the validator @@ -280,7 +310,7 @@ def confirm_new_address(self, validator_id: int) -> TxRes: return self.contract.functions.confirmNewAddress(validator_id) @transaction_method - def set_validator_name(self, new_name: str) -> TxRes: + def set_validator_name(self, new_name: str) -> ContractFunction: """ Allows a validator to change the name. :param new_name: New validator name @@ -291,7 +321,7 @@ def set_validator_name(self, new_name: str) -> TxRes: return self.contract.functions.setValidatorName(new_name) @transaction_method - def set_validator_description(self, new_description: str) -> TxRes: + def set_validator_description(self, new_description: str) -> ContractFunction: """ Allows a validator to change the name. :param new_description: New validator description @@ -302,11 +332,24 @@ def set_validator_description(self, new_description: str) -> TxRes: return self.contract.functions.setValidatorDescription(new_description) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) def validator_manager_role(self) -> bytes: - return self.contract.functions.VALIDATOR_MANAGER_ROLE().call() - - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + return bytes(self.contract.functions.VALIDATOR_MANAGER_ROLE().call()) + + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) + + def _to_validator(self, untyped_validator: Dict[str, Any]) -> Validator: + return Validator({ + 'name': str(untyped_validator['name']), + 'validator_address': ChecksumAddress(untyped_validator['validator_address']), + 'requested_address': ChecksumAddress(untyped_validator['requested_address']), + 'description': str(untyped_validator['description']), + 'fee_rate': int(untyped_validator['fee_rate']), + 'registration_time': int(untyped_validator['registration_time']), + 'minimum_delegation_amount': Wei(untyped_validator['minimum_delegation_amount']), + 'accept_new_requests': bool(untyped_validator['accept_new_requests']), + 'trusted': bool(untyped_validator['trusted']) + }) diff --git a/skale/skale_base.py b/skale/skale_base.py index 9bddd737..932960cd 100644 --- a/skale/skale_base.py +++ b/skale/skale_base.py @@ -26,9 +26,9 @@ from skale_contracts import skale_contracts from skale.contracts.base_contract import BaseContract -from skale.wallets import BaseWallet from skale.utils.exceptions import InvalidWalletError, EmptyWalletError from skale.utils.web3_utils import default_gas_price, init_web3 +from skale.wallets import BaseWallet from skale.contracts.contract_manager import ContractManager @@ -75,7 +75,7 @@ def gas_price(self): return default_gas_price(self.web3) @property - def wallet(self): + def wallet(self) -> BaseWallet: if not self._wallet: raise EmptyWalletError('No wallet provided') return self._wallet diff --git a/skale/types/validator.py b/skale/types/validator.py index e0a2c0d5..9eeaebc1 100644 --- a/skale/types/validator.py +++ b/skale/types/validator.py @@ -17,7 +17,26 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from typing import NewType +from typing import NewType, TypedDict + +from eth_typing import ChecksumAddress +from web3.types import Wei ValidatorId = NewType('ValidatorId', int) + + +class Validator(TypedDict): + name: str + validator_address: ChecksumAddress + requested_address: ChecksumAddress + description: str + fee_rate: int + registration_time: int + minimum_delegation_amount: Wei + accept_new_requests: bool + trusted: bool + + +class ValidatorWithId(Validator): + id: ValidatorId diff --git a/skale/wallets/common.py b/skale/wallets/common.py index e3bd24ea..9bc8ff38 100644 --- a/skale/wallets/common.py +++ b/skale/wallets/common.py @@ -21,7 +21,7 @@ from typing import Optional from eth_account.datastructures import SignedMessage, SignedTransaction -from eth_typing import ChecksumAddress +from eth_typing import ChecksumAddress, HexStr from web3 import Web3 from web3.types import _Hash32, TxParams, TxReceipt @@ -55,7 +55,7 @@ def sign_and_send( multiplier: Optional[float] = None, priority: Optional[int] = None, method: Optional[str] = None - ) -> str: + ) -> HexStr: pass @abstractmethod diff --git a/skale/wallets/ledger_wallet.py b/skale/wallets/ledger_wallet.py index bb4fd21b..096a364b 100644 --- a/skale/wallets/ledger_wallet.py +++ b/skale/wallets/ledger_wallet.py @@ -190,12 +190,12 @@ def sign_and_send( multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, priority: int | None = config.DEFAULT_PRIORITY, method: str | None = None - ) -> str: + ) -> HexStr: signed_tx = self.sign(tx) try: - return self._web3.eth.send_raw_transaction( + return Web3.to_hex(self._web3.eth.send_raw_transaction( signed_tx.rawTransaction - ).hex() + )) except (ValueError, Web3Exception) as e: raise TransactionNotSentError(e) diff --git a/skale/wallets/redis_wallet.py b/skale/wallets/redis_wallet.py index 68e862d4..9cbf2902 100644 --- a/skale/wallets/redis_wallet.py +++ b/skale/wallets/redis_wallet.py @@ -29,6 +29,7 @@ from eth_typing import ChecksumAddress, Hash32, HexStr from hexbytes import HexBytes from redis import Redis +from web3 import Web3 from web3.types import _Hash32, TxParams, TxReceipt import skale.config as config @@ -154,8 +155,8 @@ def _to_raw_id(cls, tx_id: _Hash32) -> bytes: raise ValueError('tx_id has unknown type', type(tx_id)) @classmethod - def _to_id(cls, raw_id: bytes) -> str: - return raw_id.decode('utf-8') + def _to_id(cls, raw_id: bytes) -> HexStr: + return Web3.to_hex(raw_id) def sign_and_send( self, @@ -163,7 +164,7 @@ def sign_and_send( multiplier: Optional[float] = None, priority: Optional[int] = None, method: Optional[str] = None - ) -> str: + ) -> HexStr: priority = priority or config.DEFAULT_PRIORITY try: logger.info('Sending %s to redis pool, method: %s', tx, method) diff --git a/skale/wallets/sgx_wallet.py b/skale/wallets/sgx_wallet.py index 85525fe9..478a1d76 100644 --- a/skale/wallets/sgx_wallet.py +++ b/skale/wallets/sgx_wallet.py @@ -21,7 +21,7 @@ from typing import Tuple, cast from eth_account.datastructures import SignedMessage, SignedTransaction -from eth_typing import ChecksumAddress +from eth_typing import ChecksumAddress, HexStr from sgx import SgxClient from web3 import Web3 from web3.exceptions import Web3Exception @@ -72,12 +72,12 @@ def sign_and_send( multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, priority: int | None = config.DEFAULT_PRIORITY, method: str | None = None - ) -> str: + ) -> HexStr: signed_tx = self.sign(tx_dict) try: - return self._web3.eth.send_raw_transaction( + return Web3.to_hex(self._web3.eth.send_raw_transaction( signed_tx.rawTransaction - ).hex() + )) except (ValueError, Web3Exception) as e: raise TransactionNotSentError(e) diff --git a/skale/wallets/web3_wallet.py b/skale/wallets/web3_wallet.py index e2c708ff..fc81716e 100644 --- a/skale/wallets/web3_wallet.py +++ b/skale/wallets/web3_wallet.py @@ -103,12 +103,12 @@ def sign_and_send( multiplier: float | None = config.DEFAULT_GAS_MULTIPLIER, priority: int | None = config.DEFAULT_PRIORITY, method: str | None = None - ) -> str: + ) -> HexStr: signed_tx = self.sign(tx_dict) try: - return self._web3.eth.send_raw_transaction( + return Web3.to_hex(self._web3.eth.send_raw_transaction( signed_tx.rawTransaction - ).hex() + )) except (ValueError, Web3Exception) as e: raise TransactionNotSentError(e) From 46d45cb110894b8444112945583ae7a0c74e4eff Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 25 Apr 2024 13:01:26 +0300 Subject: [PATCH 061/100] Add type hints to token_state --- .../manager/delegation/token_state.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/skale/contracts/manager/delegation/token_state.py b/skale/contracts/manager/delegation/token_state.py index 1871e0e9..4410da3b 100644 --- a/skale/contracts/manager/delegation/token_state.py +++ b/skale/contracts/manager/delegation/token_state.py @@ -17,28 +17,31 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import Wei + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class TokenState(BaseContract): """Wrapper for TokenState.sol functions""" - def get_and_update_locked_amount(self, holder_address: str) -> int: + def get_and_update_locked_amount(self, holder_address: ChecksumAddress) -> Wei: """This method is for check quantity of `freezed` tokens :param holder_address: Address of the holder :type holder_address: str :returns: :rtype: int """ - return self.contract.functions.getAndUpdateLockedAmount(holder_address).call() + return Wei(self.contract.functions.getAndUpdateLockedAmount(holder_address).call()) @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) - def locker_manager_role(self): - return self.contract.functions.LOCKER_MANAGER_ROLE().call() + def locker_manager_role(self) -> bytes: + return bytes(self.contract.functions.LOCKER_MANAGER_ROLE().call()) From 4b67c0b2a2a82a23ac53edb6c3dd89a02b0c02f2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 25 Apr 2024 13:04:50 +0300 Subject: [PATCH 062/100] Add type hints to slashing_table --- .../manager/delegation/slashing_table.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/skale/contracts/manager/delegation/slashing_table.py b/skale/contracts/manager/delegation/slashing_table.py index c2c31f00..60fc5643 100644 --- a/skale/contracts/manager/delegation/slashing_table.py +++ b/skale/contracts/manager/delegation/slashing_table.py @@ -1,12 +1,15 @@ +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import Wei + from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class SlashingTable(BaseContract): """ Wrapper for SlashingTable.sol functions """ @transaction_method - def set_penalty(self, offense, penalty) -> TxRes: + def set_penalty(self, offense: str, penalty: Wei) -> ContractFunction: """ Set slashing penalty :param offense: reason of slashing :type offense: str @@ -16,20 +19,20 @@ def set_penalty(self, offense, penalty) -> TxRes: """ return self.contract.functions.setPenalty(offense, penalty) - def get_penalty(self, offense) -> int: + def get_penalty(self, offense: str) -> Wei: """ Get slashing penalty value :param offense: reason of slashing :type offense: str :rtype: int """ - return self.contract.functions.getPenalty(offense).call() + return Wei(self.contract.functions.getPenalty(offense).call()) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) def penalty_setter_role(self) -> bytes: - return self.contract.functions.PENALTY_SETTER_ROLE().call() + return bytes(self.contract.functions.PENALTY_SETTER_ROLE().call()) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) From 82972f45e5b6cf8072d47660227ac03dd10c2a39 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 25 Apr 2024 16:42:04 +0300 Subject: [PATCH 063/100] Add type hints to distributor --- .../manager/delegation/distributor.py | 40 +++++++++++++------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/skale/contracts/manager/delegation/distributor.py b/skale/contracts/manager/delegation/distributor.py index d3312a5d..95a977e4 100644 --- a/skale/contracts/manager/delegation/distributor.py +++ b/skale/contracts/manager/delegation/distributor.py @@ -18,19 +18,29 @@ # along with SKALE.py. If not, see . from functools import wraps +from typing import Any, Callable, Tuple, TypedDict + +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import Wei from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes +from skale.types.validator import ValidatorId + + +class EarnedData(TypedDict): + earned: Wei + end_month: int -def formatter(method): +def formatter(method: Callable[..., Tuple[Wei, int]]) -> Callable[..., EarnedData]: @wraps(method) - def wrapper(self, *args, **kwargs): + def wrapper(self: BaseContract, *args: Any, **kwargs: Any) -> EarnedData: res = method(self, *args, **kwargs) - return { + return EarnedData({ 'earned': res[0], 'end_month': res[1], - } + }) return wrapper @@ -38,7 +48,11 @@ class Distributor(BaseContract): """Wrapper for Distributor.sol functions""" @formatter - def get_earned_bounty_amount(self, validator_id: int, address: str) -> dict: + def get_earned_bounty_amount( + self, + validator_id: ValidatorId, + address: ChecksumAddress + ) -> Tuple[Wei, int]: """Get earned bounty amount for the validator :param validator_id: ID of the validator @@ -46,12 +60,12 @@ def get_earned_bounty_amount(self, validator_id: int, address: str) -> dict: :returns: Earned bounty amount and end month :rtype: dict """ - return self.contract.functions.getAndUpdateEarnedBountyAmount(validator_id).call({ + return tuple(self.contract.functions.getAndUpdateEarnedBountyAmount(validator_id).call({ 'from': address - }) + })) @formatter - def get_earned_fee_amount(self, address: str) -> dict: + def get_earned_fee_amount(self, address: str) -> Tuple[Wei, int]: """Get earned fee amount for the address :param address: Address of the validator @@ -59,12 +73,12 @@ def get_earned_fee_amount(self, address: str) -> dict: :returns: Earned bounty amount and end month :rtype: dict """ - return self.contract.functions.getEarnedFeeAmount().call({ + return tuple(self.contract.functions.getEarnedFeeAmount().call({ 'from': address - }) + })) @transaction_method - def withdraw_bounty(self, validator_id: int, to: str) -> TxRes: + def withdraw_bounty(self, validator_id: ValidatorId, to: ChecksumAddress) -> ContractFunction: """Withdraw earned bounty to specified address :param validator_id: ID of the validator @@ -77,7 +91,7 @@ def withdraw_bounty(self, validator_id: int, to: str) -> TxRes: return self.contract.functions.withdrawBounty(validator_id, to) @transaction_method - def withdraw_fee(self, to: str) -> TxRes: + def withdraw_fee(self, to: ChecksumAddress) -> ContractFunction: """Withdraw earned fee to specified address :param to: Address to transfer bounty From 0f5c6053d21ffc0d4c4436e073b15a342f27a871 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 25 Apr 2024 16:42:56 +0300 Subject: [PATCH 064/100] Add type hints to distributor --- .../contracts/manager/delegation/delegation_period_manager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/skale/contracts/manager/delegation/delegation_period_manager.py b/skale/contracts/manager/delegation/delegation_period_manager.py index 3b753bfc..49474852 100644 --- a/skale/contracts/manager/delegation/delegation_period_manager.py +++ b/skale/contracts/manager/delegation/delegation_period_manager.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method from skale.transactions.result import TxRes @@ -26,7 +28,7 @@ class DelegationPeriodManager(BaseContract): @transaction_method def set_delegation_period(self, months_count: int, - stake_multiplier: int) -> None: + stake_multiplier: int) -> ContractFunction: return self.contract.functions.setDelegationPeriod( monthsCount=months_count, stakeMultiplier=stake_multiplier From 581d6725d27a71c8554447cd8dd3deaba33197b3 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 25 Apr 2024 16:44:30 +0300 Subject: [PATCH 065/100] Add type hints to delegation_period_manager --- .../delegation/delegation_period_manager.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/skale/contracts/manager/delegation/delegation_period_manager.py b/skale/contracts/manager/delegation/delegation_period_manager.py index 49474852..8bda2f4f 100644 --- a/skale/contracts/manager/delegation/delegation_period_manager.py +++ b/skale/contracts/manager/delegation/delegation_period_manager.py @@ -17,10 +17,10 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction from skale.contracts.base_contract import BaseContract, transaction_method -from skale.transactions.result import TxRes class DelegationPeriodManager(BaseContract): @@ -35,16 +35,16 @@ def set_delegation_period(self, months_count: int, ) def is_delegation_period_allowed(self, months_count: int) -> bool: - return self.contract.functions.isDelegationPeriodAllowed( + return bool(self.contract.functions.isDelegationPeriodAllowed( monthsCount=months_count - ).call() + ).call()) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) def delegation_period_setter_role(self) -> bytes: - return self.contract.functions.DELEGATION_PERIOD_SETTER_ROLE().call() + return bytes(self.contract.functions.DELEGATION_PERIOD_SETTER_ROLE().call()) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) From 2601e4bb986e3aebe686daa7c5b4435c0b52f4f0 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 25 Apr 2024 18:00:30 +0300 Subject: [PATCH 066/100] Add type hints to linker --- skale/contracts/ima/linker.py | 11 +- .../delegation/delegation_controller.py | 112 ++++++++++++------ skale/dataclasses/delegation_status.py | 30 ----- .../delegation/delegation_controller_test.py | 11 +- 4 files changed, 89 insertions(+), 75 deletions(-) delete mode 100644 skale/dataclasses/delegation_status.py diff --git a/skale/contracts/ima/linker.py b/skale/contracts/ima/linker.py index 8fb40c48..0a8b0bf7 100644 --- a/skale/contracts/ima/linker.py +++ b/skale/contracts/ima/linker.py @@ -17,16 +17,21 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from typing import List +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction + from skale.contracts.base_contract import BaseContract, transaction_method +from skale.types.schain import SchainName class Linker(BaseContract): @transaction_method def connect_schain( self, - schain_name: str, - mainnet_contracts: list - ): + schain_name: SchainName, + mainnet_contracts: List[ChecksumAddress] + ) -> ContractFunction: return self.contract.functions.connectSchain( schain_name, mainnet_contracts diff --git a/skale/contracts/manager/delegation/delegation_controller.py b/skale/contracts/manager/delegation/delegation_controller.py index 77e470cb..a756d71a 100644 --- a/skale/contracts/manager/delegation/delegation_controller.py +++ b/skale/contracts/manager/delegation/delegation_controller.py @@ -17,10 +17,16 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from typing import Any, Dict, List, cast + +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import Wei + from skale.contracts.base_contract import BaseContract, transaction_method +from skale.types.delegation import Delegation, DelegationId, DelegationStatus, FullDelegation +from skale.types.validator import ValidatorId from skale.utils.helper import format_fields -from skale.transactions.result import TxRes -from skale.dataclasses.delegation_status import DelegationStatus FIELDS = [ @@ -33,7 +39,7 @@ class DelegationController(BaseContract): """Wrapper for DelegationController.sol functions""" @format_fields(FIELDS) - def get_delegation(self, delegation_id: int) -> list: + def get_untyped_delegation(self, delegation_id: DelegationId) -> List[Any]: """Returns delegation structure. :returns: Info about delegation request @@ -41,55 +47,70 @@ def get_delegation(self, delegation_id: int) -> list: """ return self.__raw_get_delegation(delegation_id) - def get_delegation_full(self, delegation_id: int) -> dict: + def get_delegation(self, delegation_id: DelegationId) -> Delegation: + delegation = self.get_untyped_delegation(delegation_id) + if delegation is None: + raise ValueError("Can't get delegation with id ", delegation_id) + if isinstance(delegation, dict): + return self._to_delegation(delegation) + if isinstance(delegation, list): + return self._to_delegation(delegation[0]) + raise TypeError(delegation_id) + + def get_delegation_full(self, delegation_id: DelegationId) -> FullDelegation: """Returns delegation structure with ID and status fields. :returns: Info about delegation request :rtype: dict """ delegation = self.get_delegation(delegation_id) - delegation['id'] = delegation_id - delegation['status'] = self._get_delegation_status(delegation_id) - return delegation + return FullDelegation({ + 'id': delegation_id, + 'status': self._get_delegation_status(delegation_id), + **delegation + }) - def __raw_get_delegation(self, delegation_id: int) -> list: + def __raw_get_delegation(self, delegation_id: DelegationId) -> List[Any]: """Returns raw delegation fields. :returns: Info about delegation request :rtype: list """ - return self.contract.functions.getDelegation(delegation_id).call() + return list(self.contract.functions.getDelegation(delegation_id).call()) - def _get_delegation_ids_by_validator(self, validator_id: int) -> list: + def _get_delegation_ids_by_validator(self, validator_id: ValidatorId) -> List[DelegationId]: delegation_ids_len = self._get_delegation_ids_len_by_validator( validator_id) return [ - self.contract.functions.delegationsByValidator( - validator_id, _id).call() + DelegationId( + self.contract.functions.delegationsByValidator(validator_id, _id).call() + ) for _id in range(delegation_ids_len) ] - def _get_delegation_ids_by_holder(self, address: str) -> list: + def _get_delegation_ids_by_holder(self, address: ChecksumAddress) -> List[DelegationId]: delegation_ids_len = self._get_delegation_ids_len_by_holder(address) return [ - self.contract.functions.delegationsByHolder(address, _id).call() + DelegationId( + self.contract.functions.delegationsByHolder(address, _id).call() + ) for _id in range(delegation_ids_len) ] - def _get_delegation_ids_len_by_validator(self, validator_id: int) -> int: - return self.contract.functions.getDelegationsByValidatorLength(validator_id).call() + def _get_delegation_ids_len_by_validator(self, validator_id: ValidatorId) -> int: + return int(self.contract.functions.getDelegationsByValidatorLength(validator_id).call()) - def _get_delegation_ids_len_by_holder(self, address: str) -> int: - return self.contract.functions.getDelegationsByHolderLength(address).call() + def _get_delegation_ids_len_by_holder(self, address: ChecksumAddress) -> int: + return int(self.contract.functions.getDelegationsByHolderLength(address).call()) - def _get_delegation_state_index(self, delegation_id: int) -> str: - return self.contract.functions.getState(delegation_id).call() + def _get_delegation_state_index(self, delegation_id: DelegationId) -> int: + return int(self.contract.functions.getState(delegation_id).call()) - def _get_delegation_status(self, delegation_id: int) -> str: + def _get_delegation_status(self, delegation_id: DelegationId) -> DelegationStatus: index = self._get_delegation_state_index(delegation_id) - return DelegationStatus(index).name + return DelegationStatus(index) - def get_all_delegations(self, delegation_ids: list) -> list: + def get_all_delegations(self, delegation_ids: List[DelegationId]) -> List[FullDelegation]: """Returns list of formatted delegations with particular status. :param delegation_ids: List of delegation IDs @@ -98,11 +119,11 @@ def get_all_delegations(self, delegation_ids: list) -> list: :rtype: list """ return [ - self.skale.delegation_controller.get_delegation_full(_id) + cast(DelegationController, self.skale.delegation_controller).get_delegation_full(_id) for _id in delegation_ids ] - def get_all_delegations_by_holder(self, address: str) -> list: + def get_all_delegations_by_holder(self, address: ChecksumAddress) -> List[FullDelegation]: """Returns list of formatted delegations for token holder. :param address: Ethereum address @@ -113,7 +134,7 @@ def get_all_delegations_by_holder(self, address: str) -> list: delegation_ids = self._get_delegation_ids_by_holder(address) return self.get_all_delegations(delegation_ids) - def get_all_delegations_by_validator(self, validator_id: int) -> list: + def get_all_delegations_by_validator(self, validator_id: ValidatorId) -> List[FullDelegation]: """Returns list of formatted delegations for validator. :param validator_id: ID of the validator @@ -121,12 +142,17 @@ def get_all_delegations_by_validator(self, validator_id: int) -> list: :returns: List of formatted delegations :rtype: list """ - validator_id = int(validator_id) delegation_ids = self._get_delegation_ids_by_validator(validator_id) return self.get_all_delegations(delegation_ids) @transaction_method - def delegate(self, validator_id: int, amount: int, delegation_period: int, info: str) -> TxRes: + def delegate( + self, + validator_id: ValidatorId, + amount: Wei, + delegation_period: int, + info: str + ) -> ContractFunction: """Creates request to delegate amount of tokens to validator_id. :param validator_id: ID of the validator to delegate tokens @@ -143,7 +169,7 @@ def delegate(self, validator_id: int, amount: int, delegation_period: int, info: return self.contract.functions.delegate(validator_id, amount, delegation_period, info) @transaction_method - def accept_pending_delegation(self, delegation_id: int) -> TxRes: + def accept_pending_delegation(self, delegation_id: DelegationId) -> ContractFunction: """Accepts a pending delegation by delegation ID. :param delegation_id: Delegation ID to accept @@ -154,7 +180,7 @@ def accept_pending_delegation(self, delegation_id: int) -> TxRes: return self.contract.functions.acceptPendingDelegation(delegation_id) @transaction_method - def cancel_pending_delegation(self, delegation_id: int) -> TxRes: + def cancel_pending_delegation(self, delegation_id: DelegationId) -> ContractFunction: """Cancel pending delegation request. :param delegation_id: ID of the delegation to cancel @@ -165,7 +191,7 @@ def cancel_pending_delegation(self, delegation_id: int) -> TxRes: return self.contract.functions.cancelPendingDelegation(delegation_id) @transaction_method - def request_undelegation(self, delegation_id: int) -> TxRes: + def request_undelegation(self, delegation_id: DelegationId) -> ContractFunction: """ This method is for undelegating request in the end of delegation period (3/6/12 months) @@ -176,7 +202,7 @@ def request_undelegation(self, delegation_id: int) -> TxRes: """ return self.contract.functions.requestUndelegation(delegation_id) - def get_delegated_to_validator_now(self, validator_id: int) -> int: + def get_delegated_to_validator_now(self, validator_id: ValidatorId) -> Wei: """Amount of delegated tokens to the validator :param validator_id: ID of the validator @@ -184,9 +210,9 @@ def get_delegated_to_validator_now(self, validator_id: int) -> int: :returns: Amount of delegated tokens :rtype: int """ - return self.contract.functions.getAndUpdateDelegatedToValidatorNow(validator_id).call() + return Wei(self.contract.functions.getAndUpdateDelegatedToValidatorNow(validator_id).call()) - def get_delegated_to_validator(self, validator_id: int, month: int) -> int: + def get_delegated_to_validator(self, validator_id: ValidatorId, month: int) -> Wei: """Amount of delegated tokens to the validator unil month :param validator_id: ID of the validator @@ -197,9 +223,9 @@ def get_delegated_to_validator(self, validator_id: int, month: int) -> int: :rtype: int """ - return self.contract.functions.getDelegatedToValidator(validator_id, month).call() + return Wei(self.contract.functions.getDelegatedToValidator(validator_id, month).call()) - def get_delegated_amount(self, address: str) -> int: + def get_delegated_amount(self, address: ChecksumAddress) -> Wei: """Amount of delegated tokens by token holder :param address: Token holder address @@ -207,4 +233,16 @@ def get_delegated_amount(self, address: str) -> int: :returns: Amount of delegated tokens :rtype: int """ - return self.contract.functions.getAndUpdateDelegatedAmount(address).call() + return Wei(self.contract.functions.getAndUpdateDelegatedAmount(address).call()) + + def _to_delegation(self, delegation: Dict[str, Any]) -> Delegation: + return Delegation({ + 'address': ChecksumAddress(delegation['address']), + 'validator_id': ValidatorId(delegation['validator_id']), + 'amount': Wei(delegation['amount']), + 'delegation_period': int(delegation['delegation_period']), + 'created': int(delegation['created']), + 'started': int(delegation['started']), + 'finished': int(delegation['finished']), + 'info': str(delegation['info']) + }) diff --git a/skale/dataclasses/delegation_status.py b/skale/dataclasses/delegation_status.py deleted file mode 100644 index f25bd675..00000000 --- a/skale/dataclasses/delegation_status.py +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 -*- -# -# This file is part of SKALE.py -# -# Copyright (C) 2019-Present SKALE Labs -# -# SKALE.py is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SKALE.py is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with SKALE.py. If not, see . - -from enum import Enum - - -class DelegationStatus(Enum): - PROPOSED = 0 - ACCEPTED = 1 - CANCELED = 2 - REJECTED = 3 - DELEGATED = 4 - UNDELEGATION_REQUESTED = 5 - COMPLETED = 6 diff --git a/tests/manager/delegation/delegation_controller_test.py b/tests/manager/delegation/delegation_controller_test.py index e8ec4249..2e1e425e 100644 --- a/tests/manager/delegation/delegation_controller_test.py +++ b/tests/manager/delegation/delegation_controller_test.py @@ -5,6 +5,7 @@ from skale.contracts.manager.delegation.delegation_controller import FIELDS from skale.transactions.exceptions import ContractLogicError from skale.transactions.result import DryRunRevertError +from skale.types.delegation import DelegationStatus from skale.utils.contracts_provision.main import _skip_evm_time from skale.utils.contracts_provision.utils import generate_random_name @@ -149,14 +150,14 @@ def test_accept_pending_delegation(skale, validator): validator_id=validator_id ) delegation_id = delegations[-1]['id'] - assert delegations[-1]['status'] == 'PROPOSED' + assert delegations[-1]['status'] == DelegationStatus.PROPOSED assert delegations[-1]['info'] == info skale.delegation_controller.accept_pending_delegation(delegation_id) delegations = skale.delegation_controller.get_all_delegations_by_validator( validator_id=validator_id ) assert delegations[-1]['id'] == delegation_id - assert delegations[-1]['status'] == 'ACCEPTED' + assert delegations[-1]['status'] == DelegationStatus.ACCEPTED assert delegations[-1]['info'] == info @@ -173,7 +174,7 @@ def test_cancel_pending_delegation(skale, validator): validator_id=validator_id ) delegation_id = delegations[-1]['id'] - assert delegations[-1]['status'] == 'PROPOSED' + assert delegations[-1]['status'] == DelegationStatus.PROPOSED skale.delegation_controller.cancel_pending_delegation( delegation_id, wait_for=True @@ -182,7 +183,7 @@ def test_cancel_pending_delegation(skale, validator): validator_id=validator_id ) assert delegations[-1]['id'] == delegation_id - assert delegations[-1]['status'] == 'CANCELED' + assert delegations[-1]['status'] == DelegationStatus.CANCELED def test_request_undelegate(skale, validator): @@ -225,4 +226,4 @@ def test_request_undelegate(skale, validator): validator_id=validator_id ) assert delegations[-1]['id'] == delegation_id - assert delegations[-1]['status'] == 'UNDELEGATION_REQUESTED' + assert delegations[-1]['status'] == DelegationStatus.UNDELEGATION_REQUESTED From b3fb7ac82c5f547b9358246cb64db291dfd7eede Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 26 Apr 2024 15:58:02 +0300 Subject: [PATCH 067/100] Add type hints to escrow --- skale/contracts/allocator/allocator.py | 5 ++- skale/contracts/allocator/escrow.py | 49 +++++++++++++++++------ skale/contracts/allocator_contract.py | 6 +++ skale/contracts/base_contract.py | 12 ++++-- skale/skale_allocator.py | 40 +++++++++++-------- skale/skale_base.py | 16 ++++---- skale/types/delegation.py | 55 ++++++++++++++++++++++++++ 7 files changed, 140 insertions(+), 43 deletions(-) create mode 100644 skale/contracts/allocator_contract.py create mode 100644 skale/types/delegation.py diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index fa504b26..4882fc16 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -20,7 +20,8 @@ from enum import IntEnum -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.allocator_contract import AllocatorContract +from skale.contracts.base_contract import transaction_method from skale.transactions.exceptions import ContractLogicError from skale.transactions.result import TxRes from skale.utils.helper import format_fields @@ -60,7 +61,7 @@ class BeneficiaryStatus(IntEnum): TERMINATED = 3 -class Allocator(BaseContract): +class Allocator(AllocatorContract): def is_beneficiary_registered(self, beneficiary_address: str) -> bool: """Confirms whether the beneficiary is registered in a Plan. diff --git a/skale/contracts/allocator/escrow.py b/skale/contracts/allocator/escrow.py index c65feda6..c221578a 100644 --- a/skale/contracts/allocator/escrow.py +++ b/skale/contracts/allocator/escrow.py @@ -18,32 +18,49 @@ # along with SKALE.py. If not, see . """ SKALE Allocator Core Escrow methods """ +from __future__ import annotations import functools +from typing import Any, Callable, TYPE_CHECKING -from skale.contracts.base_contract import BaseContract, transaction_method +from eth_typing import ChecksumAddress +from web3.contract.contract import ContractFunction +from web3.types import Wei + +from skale.contracts.allocator_contract import AllocatorContract +from skale.contracts.base_contract import transaction_method from skale.transactions.result import TxRes +from skale.types.delegation import DelegationId +from skale.types.validator import ValidatorId + +if TYPE_CHECKING: + from skale.contracts.allocator.allocator import Allocator -def beneficiary_escrow(transaction): +def beneficiary_escrow(transaction: Callable[..., TxRes]) -> Callable[..., TxRes]: @functools.wraps(transaction) - def wrapper(self, *args, beneficiary_address, **kwargs): + def wrapper( + self: AllocatorContract, + *args: Any, + beneficiary_address: ChecksumAddress, + **kwargs: Any + ) -> TxRes: self.contract = self.skale.instance.get_contract('Escrow', beneficiary_address) return transaction(self, *args, **kwargs) return wrapper -class Escrow(BaseContract): +class Escrow(AllocatorContract): @property @functools.lru_cache() - def allocator(self): + def allocator(self) -> Allocator: return self.skale.allocator - def init_contract(self, skale, address, abi) -> None: - self.contract = None + def init_contract(self, *args: Any) -> None: + self.contract = self.allocator.contract @beneficiary_escrow @transaction_method - def retrieve(self) -> TxRes: + def retrieve(self) -> ContractFunction: """Allows Holder to retrieve vested tokens from the Escrow contract :returns: Transaction results @@ -53,7 +70,7 @@ def retrieve(self) -> TxRes: @beneficiary_escrow @transaction_method - def retrieve_after_termination(self, address: str) -> TxRes: + def retrieve_after_termination(self, address: ChecksumAddress) -> ContractFunction: """Allows Core Owner to retrieve remaining transferrable escrow balance after Core holder termination. Slashed tokens are non-transferable @@ -64,7 +81,13 @@ def retrieve_after_termination(self, address: str) -> TxRes: @beneficiary_escrow @transaction_method - def delegate(self, validator_id: int, amount: int, delegation_period: int, info: str) -> TxRes: + def delegate( + self, + validator_id: ValidatorId, + amount: Wei, + delegation_period: int, + info: str + ) -> ContractFunction: """Allows Core holder to propose a delegation to a validator :param validator_id: ID of the validator to delegate tokens @@ -82,7 +105,7 @@ def delegate(self, validator_id: int, amount: int, delegation_period: int, info: @beneficiary_escrow @transaction_method - def request_undelegation(self, delegation_id: int) -> TxRes: + def request_undelegation(self, delegation_id: DelegationId) -> ContractFunction: """Allows Holder and Owner to request undelegation. Only Owner can request undelegation after Core holder is deactivated (upon holder termination) @@ -95,7 +118,7 @@ def request_undelegation(self, delegation_id: int) -> TxRes: @beneficiary_escrow @transaction_method - def withdraw_bounty(self, validator_id: int, to: str) -> TxRes: + def withdraw_bounty(self, validator_id: ValidatorId, to: ChecksumAddress) -> ContractFunction: """Allows Beneficiary and Vesting Owner to withdraw earned bounty. :param validator_id: ID of the validator @@ -109,7 +132,7 @@ def withdraw_bounty(self, validator_id: int, to: str) -> TxRes: @beneficiary_escrow @transaction_method - def cancel_pending_delegation(self, delegation_id: int) -> TxRes: + def cancel_pending_delegation(self, delegation_id: DelegationId) -> ContractFunction: """Cancel pending delegation request. :param delegation_id: ID of the delegation to cancel diff --git a/skale/contracts/allocator_contract.py b/skale/contracts/allocator_contract.py new file mode 100644 index 00000000..f77283a3 --- /dev/null +++ b/skale/contracts/allocator_contract.py @@ -0,0 +1,6 @@ +from skale.contracts.base_contract import BaseContract +from skale.skale_allocator import SkaleAllocator + + +class AllocatorContract(BaseContract[SkaleAllocator]): + pass diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index d7de65cc..2acb52f2 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -20,7 +20,7 @@ from __future__ import annotations import logging from functools import wraps -from typing import Any, Callable, TYPE_CHECKING +from typing import Any, Callable, TYPE_CHECKING, Generic, TypeVar from eth_typing import ChecksumAddress from web3 import Web3 @@ -37,19 +37,23 @@ wait_for_confirmation_blocks ) +from skale.skale_base import SkaleBase from skale.utils.helper import to_camel_case if TYPE_CHECKING: - from skale.skale_base import SkaleBase + pass logger = logging.getLogger(__name__) -class BaseContract: +SkaleType = TypeVar('SkaleType', bound=SkaleBase) + + +class BaseContract(Generic[SkaleType]): def __init__( self, - skale: SkaleBase, + skale: SkaleType, name: str, address: ChecksumAddress | str | bytes, abi: ABI diff --git a/skale/skale_allocator.py b/skale/skale_allocator.py index db0f00ce..465b0745 100644 --- a/skale/skale_allocator.py +++ b/skale/skale_allocator.py @@ -19,11 +19,10 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING -from web3.constants import ADDRESS_ZERO +from typing import TYPE_CHECKING, List, cast +from web3.constants import CHECKSUM_ADDRESSS_ZERO from skale.skale_base import SkaleBase -import skale.contracts.allocator as contracts from skale.contracts.contract_manager import ContractManager from skale.utils.contract_info import ContractInfo from skale.utils.contract_types import ContractTypes @@ -31,23 +30,26 @@ if TYPE_CHECKING: from eth_typing import ChecksumAddress + import skale.contracts.allocator as contracts logger = logging.getLogger(__name__) -CONTRACTS_INFO = [ - ContractInfo('contract_manager', 'ContractManager', - ContractManager, ContractTypes.API, False), - ContractInfo('escrow', 'Escrow', contracts.Escrow, - ContractTypes.API, True), - ContractInfo('allocator', 'Allocator', contracts.Allocator, - ContractTypes.API, True) -] +def contracts_info() -> List[ContractInfo]: + import skale.contracts.allocator as contracts + return [ + ContractInfo('contract_manager', 'ContractManager', ContractManager, + ContractTypes.API, False), + ContractInfo('escrow', 'Escrow', contracts.Escrow, + ContractTypes.API, True), + ContractInfo('allocator', 'Allocator', contracts.Allocator, + ContractTypes.API, True) + ] -def spawn_skale_allocator_lib(skale): - return SkaleAllocator(skale._endpoint, skale._abi_filepath, skale.wallet) +def spawn_skale_allocator_lib(skale: SkaleAllocator) -> SkaleAllocator: + return SkaleAllocator(skale._endpoint, skale.instance.address, skale.wallet) class SkaleAllocator(SkaleBase): @@ -56,10 +58,14 @@ class SkaleAllocator(SkaleBase): def project_name(self) -> str: return 'skale-allocator' - def get_contract_address(self, name) -> ChecksumAddress: + @property + def allocator(self) -> contracts.Allocator: + return cast('contracts.Allocator', super()._get_contract('allocator')) + + def get_contract_address(self, name: str) -> ChecksumAddress: if name == 'Escrow': - return ADDRESS_ZERO + return CHECKSUM_ADDRESSS_ZERO return super().get_contract_address(name) - def set_contracts_info(self): - self._SkaleBase__contracts_info = get_contracts_info(CONTRACTS_INFO) + def set_contracts_info(self) -> None: + self._SkaleBase__contracts_info = get_contracts_info(contracts_info()) diff --git a/skale/skale_base.py b/skale/skale_base.py index 932960cd..863f55dc 100644 --- a/skale/skale_base.py +++ b/skale/skale_base.py @@ -25,15 +25,13 @@ from skale_contracts import skale_contracts -from skale.contracts.base_contract import BaseContract from skale.utils.exceptions import InvalidWalletError, EmptyWalletError from skale.utils.web3_utils import default_gas_price, init_web3 from skale.wallets import BaseWallet -from skale.contracts.contract_manager import ContractManager - if TYPE_CHECKING: from eth_typing import Address, ChecksumAddress + from skale.contracts.base_contract import BaseContract logger = logging.getLogger(__name__) @@ -96,6 +94,7 @@ def set_contracts_info(self): return def init_contract_manager(self): + from skale.contracts.contract_manager import ContractManager self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') def __init_contract_from_info(self, contract_info): @@ -133,10 +132,7 @@ def get_contract_address(self, name) -> ChecksumAddress: self.instance.get_contract_address(name) ) - def __get_contract_by_name(self, name): - return self.__contracts[name] - - def __getattr__(self, name) -> BaseContract: + def _get_contract(self, name) -> BaseContract: if name not in self.__contracts: if not self.__contracts_info.get(name): logger.warning("%s method/contract wasn't found", name) @@ -145,3 +141,9 @@ def __getattr__(self, name) -> BaseContract: contract_info = self.__contracts_info[name] self.__init_contract_from_info(contract_info) return self.__get_contract_by_name(name) + + def __get_contract_by_name(self, name): + return self.__contracts[name] + + def __getattr__(self, name) -> BaseContract: + return self._get_contract(name) diff --git a/skale/types/delegation.py b/skale/types/delegation.py new file mode 100644 index 00000000..896cbf78 --- /dev/null +++ b/skale/types/delegation.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +from enum import Enum +from typing import NewType, TypedDict + +from eth_typing import ChecksumAddress +from web3.types import Wei + +from skale.types.validator import ValidatorId + + +DelegationId = NewType('DelegationId', int) + + +class DelegationStatus(Enum): + PROPOSED = 0 + ACCEPTED = 1 + CANCELED = 2 + REJECTED = 3 + DELEGATED = 4 + UNDELEGATION_REQUESTED = 5 + COMPLETED = 6 + + +class Delegation(TypedDict): + address: ChecksumAddress + validator_id: ValidatorId + amount: Wei + delegation_period: int + created: int + started: int + finished: int + info: str + + +class FullDelegation(Delegation): + id: DelegationId + status: DelegationStatus From d3551ef52de15d2a2109c635f4a3f17ee6c358ec Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 26 Apr 2024 17:17:36 +0300 Subject: [PATCH 068/100] Add type hints to skale_base --- skale/skale_base.py | 56 ++++++++++++++++++++---------------- skale/utils/contract_info.py | 14 +++++---- skale/utils/helper.py | 5 +++- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/skale/skale_base.py b/skale/skale_base.py index 863f55dc..e34d2424 100644 --- a/skale/skale_base.py +++ b/skale/skale_base.py @@ -21,7 +21,7 @@ import abc import logging -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Self, Type from skale_contracts import skale_contracts @@ -30,8 +30,9 @@ from skale.wallets import BaseWallet if TYPE_CHECKING: - from eth_typing import Address, ChecksumAddress + from eth_typing import ChecksumAddress from skale.contracts.base_contract import BaseContract + from skale.utils.contract_info import ContractInfo logger = logging.getLogger(__name__) @@ -44,9 +45,14 @@ class EmptyPrivateKey(Exception): class SkaleBase: __metaclass__ = abc.ABCMeta - def __init__(self, endpoint, alias_or_address: str, - wallet=None, state_path=None, - ts_diff=None, provider_timeout=30): + def __init__( + self, + endpoint: str, + alias_or_address: str, + wallet: BaseWallet | None = None, + state_path: str | None = None, + ts_diff: int | None = None, + provider_timeout: int = 30): logger.info('Initializing skale.py, endpoint: %s, wallet: %s', endpoint, type(wallet).__name__) self._endpoint = endpoint @@ -57,8 +63,8 @@ def __init__(self, endpoint, alias_or_address: str, self.network = skale_contracts.get_network_by_provider(self.web3.provider) self.project = self.network.get_project(self.project_name) self.instance = self.project.get_instance(alias_or_address) - self.__contracts = {} - self.__contracts_info = {} + self.__contracts: Dict[str, BaseContract[Self]] = {} + self.__contracts_info: Dict[str, ContractInfo[Self]] = {} self.set_contracts_info() if wallet: self.wallet = wallet @@ -69,7 +75,7 @@ def project_name(self) -> str: """Name of smart contracts project""" @property - def gas_price(self): + def gas_price(self) -> int: return default_gas_price(self.web3) @property @@ -79,25 +85,22 @@ def wallet(self) -> BaseWallet: return self._wallet @wallet.setter - def wallet(self, wallet): + def wallet(self, wallet: BaseWallet) -> None: if issubclass(type(wallet), BaseWallet): self._wallet = wallet else: raise InvalidWalletError(f'Wrong wallet class: {type(wallet).__name__}. \ Must be one of the BaseWallet subclasses') - def __is_debug_contracts(self, abi): - return abi.get('time_helpers_with_debug_address', None) - @abc.abstractmethod - def set_contracts_info(self): - return + def set_contracts_info(self) -> None: + pass - def init_contract_manager(self): + def init_contract_manager(self) -> None: from skale.contracts.contract_manager import ContractManager self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') - def __init_contract_from_info(self, contract_info): + def __init_contract_from_info(self, contract_info: ContractInfo[Self]) -> None: if contract_info.upgradeable: self.init_upgradeable_contract(contract_info) else: @@ -107,7 +110,7 @@ def __init_contract_from_info(self, contract_info): contract_info.contract_name ) - def init_upgradeable_contract(self, contract_info): + def init_upgradeable_contract(self, contract_info: ContractInfo[Self]) -> None: address = self.get_contract_address(contract_info.contract_name) self.add_lib_contract( contract_info.name, @@ -116,23 +119,28 @@ def init_upgradeable_contract(self, contract_info): address ) - def add_lib_contract(self, name: str, contract_class, - contract_name: str, contract_address: Address = None): + def add_lib_contract( + self, + name: str, + contract_class: Type[BaseContract[Self]], + contract_name: str, + contract_address: ChecksumAddress | None = None + ) -> None: address = contract_address or self.instance.get_contract_address(contract_name) logger.debug('Fetching abi for %s, address %s', name, address) contract_abi = self.instance.abi[contract_name] self.add_contract(name, contract_class( self, name, address, contract_abi)) - def add_contract(self, name, contract): + def add_contract(self, name: str, contract: BaseContract[Self]) -> None: self.__contracts[name] = contract - def get_contract_address(self, name) -> ChecksumAddress: + def get_contract_address(self, name: str) -> ChecksumAddress: return self.web3.to_checksum_address( self.instance.get_contract_address(name) ) - def _get_contract(self, name) -> BaseContract: + def _get_contract(self, name: str) -> BaseContract[Self]: if name not in self.__contracts: if not self.__contracts_info.get(name): logger.warning("%s method/contract wasn't found", name) @@ -142,8 +150,8 @@ def _get_contract(self, name) -> BaseContract: self.__init_contract_from_info(contract_info) return self.__get_contract_by_name(name) - def __get_contract_by_name(self, name): + def __get_contract_by_name(self, name: str) -> BaseContract[Self]: return self.__contracts[name] - def __getattr__(self, name) -> BaseContract: + def __getattr__(self, name: str) -> BaseContract[Self]: return self._get_contract(name) diff --git a/skale/utils/contract_info.py b/skale/utils/contract_info.py index d3e2999a..aaf1c77f 100644 --- a/skale/utils/contract_info.py +++ b/skale/utils/contract_info.py @@ -18,15 +18,19 @@ # along with SKALE.py. If not, see . """ Contract info utilities """ -from typing import NamedTuple, Type +from __future__ import annotations +from typing import Generic, NamedTuple, Type, TYPE_CHECKING -from skale.contracts.base_contract import BaseContract -from skale.utils.contract_types import ContractTypes +from skale.contracts.base_contract import SkaleType +if TYPE_CHECKING: + from skale.contracts.base_contract import BaseContract + from skale.utils.contract_types import ContractTypes -class ContractInfo(NamedTuple): + +class ContractInfo(NamedTuple, Generic[SkaleType]): name: str contract_name: str - contract_class: Type[BaseContract] + contract_class: Type[BaseContract[SkaleType]] type: ContractTypes upgradeable: bool diff --git a/skale/utils/helper.py b/skale/utils/helper.py index 603e6bea..4d8e6797 100644 --- a/skale/utils/helper.py +++ b/skale/utils/helper.py @@ -35,6 +35,7 @@ from skale.types.node import Port if TYPE_CHECKING: + from skale.contracts.base_contract import SkaleType from skale.utils.contract_info import ContractInfo @@ -214,7 +215,9 @@ def split_public_key(public_key: str) -> list[bytes]: return list(map(bytes.fromhex, pk_parts)) -def get_contracts_info(contracts_data: list[ContractInfo]) -> dict[str, ContractInfo]: +def get_contracts_info( + contracts_data: list[ContractInfo[SkaleType]] +) -> dict[str, ContractInfo[SkaleType]]: contracts_info = {} for contract_info in contracts_data: contracts_info[contract_info.name] = contract_info From 0271575a10de4742e852f47b76fb3db0b3f8beae Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 26 Apr 2024 18:09:52 +0300 Subject: [PATCH 069/100] Add SkaleManagerContract --- skale/contracts/allocator_contract.py | 18 ++++ skale/contracts/base_contract.py | 2 +- skale/contracts/ima/__init__.py | 2 +- skale/contracts/manager/__init__.py | 2 +- skale/contracts/manager/bounty_v2.py | 5 +- skale/contracts/manager/constants_holder.py | 5 +- .../{ => manager}/contract_manager.py | 4 +- .../delegation/delegation_controller.py | 5 +- .../delegation/delegation_period_manager.py | 5 +- .../manager/delegation/distributor.py | 3 +- .../manager/delegation/slashing_table.py | 5 +- .../manager/delegation/token_state.py | 5 +- .../manager/delegation/validator_service.py | 5 +- skale/contracts/manager/dkg.py | 5 +- skale/contracts/manager/groups.py | 4 +- skale/contracts/manager/key_storage.py | 4 +- skale/contracts/manager/manager.py | 5 +- skale/contracts/manager/node_rotation.py | 5 +- skale/contracts/manager/nodes.py | 5 +- skale/contracts/manager/punisher.py | 5 +- skale/contracts/manager/schains_internal.py | 5 +- skale/contracts/manager/sync_manager.py | 5 +- .../manager/test/time_helpers_with_debug.py | 5 +- skale/contracts/manager/token.py | 5 +- skale/contracts/manager/wallets.py | 5 +- skale/contracts/skale_manager_contract.py | 24 +++++ skale/skale_allocator.py | 2 +- skale/skale_base.py | 4 - skale/skale_manager.py | 97 ++++++++++--------- 29 files changed, 156 insertions(+), 95 deletions(-) rename skale/contracts/{ => manager}/contract_manager.py (92%) create mode 100644 skale/contracts/skale_manager_contract.py diff --git a/skale/contracts/allocator_contract.py b/skale/contracts/allocator_contract.py index f77283a3..00eea17f 100644 --- a/skale/contracts/allocator_contract.py +++ b/skale/contracts/allocator_contract.py @@ -1,3 +1,21 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . from skale.contracts.base_contract import BaseContract from skale.skale_allocator import SkaleAllocator diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 2acb52f2..27b4dd0d 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -86,7 +86,7 @@ def wrapper(*args: Any, **kw: Any) -> Any: def transaction_method(transaction: Callable[..., ContractFunction]) -> Callable[..., TxRes]: @wraps(transaction) def wrapper( - self: BaseContract, + self: BaseContract[SkaleType], *args: Any, wait_for: bool = True, blocks_to_wait: int = DEFAULT_BLOCKS_TO_WAIT, diff --git a/skale/contracts/ima/__init__.py b/skale/contracts/ima/__init__.py index dd484307..8395fab4 100644 --- a/skale/contracts/ima/__init__.py +++ b/skale/contracts/ima/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa -from skale.contracts.contract_manager import ContractManager +from skale.contracts.manager.contract_manager import ContractManager from skale.contracts.base_contract import BaseContract, transaction_method from skale.contracts.ima.linker import Linker diff --git a/skale/contracts/manager/__init__.py b/skale/contracts/manager/__init__.py index 475967bf..eb0d530f 100644 --- a/skale/contracts/manager/__init__.py +++ b/skale/contracts/manager/__init__.py @@ -1,6 +1,6 @@ # flake8: noqa -from skale.contracts.contract_manager import ContractManager +from skale.contracts.manager.contract_manager import ContractManager from skale.contracts.base_contract import BaseContract, transaction_method from skale.contracts.manager.manager import Manager diff --git a/skale/contracts/manager/bounty_v2.py b/skale/contracts/manager/bounty_v2.py index a5a179d5..9df79de2 100644 --- a/skale/contracts/manager/bounty_v2.py +++ b/skale/contracts/manager/bounty_v2.py @@ -20,10 +20,11 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class BountyV2(BaseContract): +class BountyV2(SkaleManagerContract): @transaction_method def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) diff --git a/skale/contracts/manager/constants_holder.py b/skale/contracts/manager/constants_holder.py index b7e6cd3f..d912bf6b 100644 --- a/skale/contracts/manager/constants_holder.py +++ b/skale/contracts/manager/constants_holder.py @@ -20,10 +20,11 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class ConstantsHolder(BaseContract): +class ConstantsHolder(SkaleManagerContract): @transaction_method def set_periods(self, new_reward_period: int, new_delta_period: int) -> ContractFunction: return self.contract.functions.setPeriods( diff --git a/skale/contracts/contract_manager.py b/skale/contracts/manager/contract_manager.py similarity index 92% rename from skale/contracts/contract_manager.py rename to skale/contracts/manager/contract_manager.py index 73271d49..d2fe91ce 100644 --- a/skale/contracts/contract_manager.py +++ b/skale/contracts/manager/contract_manager.py @@ -22,11 +22,11 @@ from eth_typing import ChecksumAddress from web3 import Web3 -from skale.contracts.base_contract import BaseContract +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.utils.helper import add_0x_prefix -class ContractManager(BaseContract): +class ContractManager(SkaleManagerContract): def get_contract_address(self, name: str) -> ChecksumAddress: contract_hash = add_0x_prefix(self.get_contract_hash_by_name(name)) return Web3.to_checksum_address(self.contract.functions.contracts(contract_hash).call()) diff --git a/skale/contracts/manager/delegation/delegation_controller.py b/skale/contracts/manager/delegation/delegation_controller.py index a756d71a..f95028c6 100644 --- a/skale/contracts/manager/delegation/delegation_controller.py +++ b/skale/contracts/manager/delegation/delegation_controller.py @@ -23,7 +23,8 @@ from web3.contract.contract import ContractFunction from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.delegation import Delegation, DelegationId, DelegationStatus, FullDelegation from skale.types.validator import ValidatorId from skale.utils.helper import format_fields @@ -35,7 +36,7 @@ ] -class DelegationController(BaseContract): +class DelegationController(SkaleManagerContract): """Wrapper for DelegationController.sol functions""" @format_fields(FIELDS) diff --git a/skale/contracts/manager/delegation/delegation_period_manager.py b/skale/contracts/manager/delegation/delegation_period_manager.py index 8bda2f4f..316b07e8 100644 --- a/skale/contracts/manager/delegation/delegation_period_manager.py +++ b/skale/contracts/manager/delegation/delegation_period_manager.py @@ -20,10 +20,11 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class DelegationPeriodManager(BaseContract): +class DelegationPeriodManager(SkaleManagerContract): """Wrapper for DelegationPeriodManager.sol functions""" @transaction_method diff --git a/skale/contracts/manager/delegation/distributor.py b/skale/contracts/manager/delegation/distributor.py index 95a977e4..0d4a6ab5 100644 --- a/skale/contracts/manager/delegation/distributor.py +++ b/skale/contracts/manager/delegation/distributor.py @@ -25,6 +25,7 @@ from web3.types import Wei from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.validator import ValidatorId @@ -44,7 +45,7 @@ def wrapper(self: BaseContract, *args: Any, **kwargs: Any) -> EarnedData: return wrapper -class Distributor(BaseContract): +class Distributor(SkaleManagerContract): """Wrapper for Distributor.sol functions""" @formatter diff --git a/skale/contracts/manager/delegation/slashing_table.py b/skale/contracts/manager/delegation/slashing_table.py index 60fc5643..240a4dd5 100644 --- a/skale/contracts/manager/delegation/slashing_table.py +++ b/skale/contracts/manager/delegation/slashing_table.py @@ -2,10 +2,11 @@ from web3.contract.contract import ContractFunction from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class SlashingTable(BaseContract): +class SlashingTable(SkaleManagerContract): """ Wrapper for SlashingTable.sol functions """ @transaction_method diff --git a/skale/contracts/manager/delegation/token_state.py b/skale/contracts/manager/delegation/token_state.py index 4410da3b..6a14b9de 100644 --- a/skale/contracts/manager/delegation/token_state.py +++ b/skale/contracts/manager/delegation/token_state.py @@ -21,10 +21,11 @@ from web3.contract.contract import ContractFunction from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class TokenState(BaseContract): +class TokenState(SkaleManagerContract): """Wrapper for TokenState.sol functions""" def get_and_update_locked_amount(self, holder_address: ChecksumAddress) -> Wei: diff --git a/skale/contracts/manager/delegation/validator_service.py b/skale/contracts/manager/delegation/validator_service.py index 7ab6b78f..fd157b81 100644 --- a/skale/contracts/manager/delegation/validator_service.py +++ b/skale/contracts/manager/delegation/validator_service.py @@ -23,7 +23,8 @@ from web3.contract.contract import ContractFunction from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.validator import Validator, ValidatorId, ValidatorWithId from skale.utils.helper import format_fields @@ -35,7 +36,7 @@ ] -class ValidatorService(BaseContract): +class ValidatorService(SkaleManagerContract): """Wrapper for ValidatorService.sol functions""" def __get_raw(self, _id: ValidatorId) -> List[Any]: diff --git a/skale/contracts/manager/dkg.py b/skale/contracts/manager/dkg.py index 9e2597b7..8e601310 100644 --- a/skale/contracts/manager/dkg.py +++ b/skale/contracts/manager/dkg.py @@ -21,14 +21,15 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.transactions.tools import retry_tx from skale.types.dkg import G2Point, KeyShare, VerificationVector from skale.types.node import NodeId from skale.types.schain import SchainHash -class DKG(BaseContract): +class DKG(SkaleManagerContract): @retry_tx @transaction_method def broadcast( diff --git a/skale/contracts/manager/groups.py b/skale/contracts/manager/groups.py index b67d8735..2adb43cf 100644 --- a/skale/contracts/manager/groups.py +++ b/skale/contracts/manager/groups.py @@ -18,8 +18,8 @@ # along with SKALE.py. If not, see . """ SKALE group class """ -from skale.contracts.base_contract import BaseContract +from skale.contracts.skale_manager_contract import SkaleManagerContract -class Groups(BaseContract): +class Groups(SkaleManagerContract): pass diff --git a/skale/contracts/manager/key_storage.py b/skale/contracts/manager/key_storage.py index d449e72e..ff4a3a2f 100644 --- a/skale/contracts/manager/key_storage.py +++ b/skale/contracts/manager/key_storage.py @@ -18,12 +18,12 @@ # along with SKALE.py. If not, see . from typing import List -from skale.contracts.base_contract import BaseContract from skale.contracts.manager.dkg import G2Point +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.schain import SchainHash -class KeyStorage(BaseContract): +class KeyStorage(SkaleManagerContract): def get_common_public_key(self, schain_hash: SchainHash) -> G2Point: return G2Point(*self.contract.functions.getCommonPublicKey(schain_hash).call()) diff --git a/skale/contracts/manager/manager.py b/skale/contracts/manager/manager.py index 1b30a6bd..3e9156db 100644 --- a/skale/contracts/manager/manager.py +++ b/skale/contracts/manager/manager.py @@ -26,7 +26,8 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.node import NodeId, Port from skale.types.schain import SchainName from skale.utils import helper @@ -38,7 +39,7 @@ logger = logging.getLogger(__name__) -class Manager(BaseContract): +class Manager(SkaleManagerContract): @transaction_method def create_node( diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index 02dea742..7e5ea16f 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -26,10 +26,11 @@ from eth_typing import ChecksumAddress -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method from web3.contract.contract import ContractFunction from web3.exceptions import ContractLogicError +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.node import NodeId from skale.types.schain import SchainHash, SchainName @@ -56,7 +57,7 @@ class RotationSwap(TypedDict): finished_rotation: int -class NodeRotation(BaseContract): +class NodeRotation(SkaleManagerContract): """Wrapper for NodeRotation.sol functions""" @property diff --git a/skale/contracts/manager/nodes.py b/skale/contracts/manager/nodes.py index bba17041..5afac6af 100644 --- a/skale/contracts/manager/nodes.py +++ b/skale/contracts/manager/nodes.py @@ -27,8 +27,9 @@ from web3.contract.contract import ContractFunction from web3.exceptions import BadFunctionCallOutput, ContractLogicError -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.node import NodeId, Port from skale.types.validator import ValidatorId from skale.utils.exceptions import InvalidNodeIdError @@ -61,7 +62,7 @@ class Node(TypedDict): domain_name: str -class Nodes(BaseContract): +class Nodes(SkaleManagerContract): def __get_raw(self, node_id: NodeId) -> List[Any]: try: return list(self.contract.functions.nodes(node_id).call()) diff --git a/skale/contracts/manager/punisher.py b/skale/contracts/manager/punisher.py index a0d99579..f65df93b 100644 --- a/skale/contracts/manager/punisher.py +++ b/skale/contracts/manager/punisher.py @@ -20,10 +20,11 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class Punisher(BaseContract): +class Punisher(SkaleManagerContract): @transaction_method def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) diff --git a/skale/contracts/manager/schains_internal.py b/skale/contracts/manager/schains_internal.py index 7e023a34..fb01bfb4 100644 --- a/skale/contracts/manager/schains_internal.py +++ b/skale/contracts/manager/schains_internal.py @@ -24,7 +24,8 @@ from typing import TYPE_CHECKING, List, cast from eth_typing import ChecksumAddress -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.node import NodeId from skale.types.schain import SchainHash, SchainName @@ -48,7 +49,7 @@ class Schain: originator: str -class SChainsInternal(BaseContract): +class SChainsInternal(SkaleManagerContract): """Wrapper for some of the SchainsInternal.sol functions""" @property diff --git a/skale/contracts/manager/sync_manager.py b/skale/contracts/manager/sync_manager.py index aa07fa4a..16996c60 100644 --- a/skale/contracts/manager/sync_manager.py +++ b/skale/contracts/manager/sync_manager.py @@ -23,7 +23,8 @@ from typing import List from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.transactions.result import TxRes from skale.utils.helper import ip_from_bytes, ip_to_bytes @@ -37,7 +38,7 @@ def from_packed(cls, packed_ips: List[bytes]) -> IpRange: ) -class SyncManager(BaseContract): +class SyncManager(SkaleManagerContract): """Wrapper for SyncManager.sol functions""" @transaction_method diff --git a/skale/contracts/manager/test/time_helpers_with_debug.py b/skale/contracts/manager/test/time_helpers_with_debug.py index a8f34cb5..cc0cb795 100644 --- a/skale/contracts/manager/test/time_helpers_with_debug.py +++ b/skale/contracts/manager/test/time_helpers_with_debug.py @@ -19,10 +19,11 @@ from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class TimeHelpersWithDebug(BaseContract): +class TimeHelpersWithDebug(SkaleManagerContract): """Wrapper for TimeHelpersWithDebug.sol functions (internal usage only)""" @transaction_method diff --git a/skale/contracts/manager/token.py b/skale/contracts/manager/token.py index 1b5d067b..648d8850 100644 --- a/skale/contracts/manager/token.py +++ b/skale/contracts/manager/token.py @@ -22,10 +22,11 @@ from web3.contract.contract import ContractFunction from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class Token(BaseContract): +class Token(SkaleManagerContract): @transaction_method def transfer(self, address: ChecksumAddress, value: Wei) -> ContractFunction: return self.contract.functions.send(address, value, b'') diff --git a/skale/contracts/manager/wallets.py b/skale/contracts/manager/wallets.py index 41e2628a..34c3d853 100644 --- a/skale/contracts/manager/wallets.py +++ b/skale/contracts/manager/wallets.py @@ -19,10 +19,11 @@ from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.skale_manager_contract import SkaleManagerContract -class Wallets(BaseContract): +class Wallets(SkaleManagerContract): def get_validator_balance(self, validator_id: int) -> int: """Returns SRW balance by validator id (in wei). diff --git a/skale/contracts/skale_manager_contract.py b/skale/contracts/skale_manager_contract.py new file mode 100644 index 00000000..3c32734f --- /dev/null +++ b/skale/contracts/skale_manager_contract.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . +from skale.contracts.base_contract import BaseContract +from skale.skale_manager import SkaleManager + + +class SkaleManagerContract(BaseContract[SkaleManager]): + pass diff --git a/skale/skale_allocator.py b/skale/skale_allocator.py index 465b0745..97dfc814 100644 --- a/skale/skale_allocator.py +++ b/skale/skale_allocator.py @@ -23,7 +23,7 @@ from web3.constants import CHECKSUM_ADDRESSS_ZERO from skale.skale_base import SkaleBase -from skale.contracts.contract_manager import ContractManager +from skale.contracts.manager.contract_manager import ContractManager from skale.utils.contract_info import ContractInfo from skale.utils.contract_types import ContractTypes from skale.utils.helper import get_contracts_info diff --git a/skale/skale_base.py b/skale/skale_base.py index e34d2424..8c3e19e1 100644 --- a/skale/skale_base.py +++ b/skale/skale_base.py @@ -96,10 +96,6 @@ def wallet(self, wallet: BaseWallet) -> None: def set_contracts_info(self) -> None: pass - def init_contract_manager(self) -> None: - from skale.contracts.contract_manager import ContractManager - self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') - def __init_contract_from_info(self, contract_info: ContractInfo[Self]) -> None: if contract_info.upgradeable: self.init_upgradeable_contract(contract_info) diff --git a/skale/skale_manager.py b/skale/skale_manager.py index 592b6848..1ef9a8b1 100644 --- a/skale/skale_manager.py +++ b/skale/skale_manager.py @@ -18,10 +18,9 @@ # along with SKALE.py. If not, see . import logging +from typing import List from skale.skale_base import SkaleBase -import skale.contracts.manager as contracts -from skale.contracts.contract_manager import ContractManager from skale.utils.contract_info import ContractInfo from skale.utils.contract_types import ContractTypes from skale.utils.helper import get_contracts_info @@ -30,49 +29,51 @@ logger = logging.getLogger(__name__) -CONTRACTS_INFO = [ - ContractInfo('contract_manager', 'ContractManager', - ContractManager, ContractTypes.API, False), - ContractInfo('token', 'SkaleToken', contracts.Token, ContractTypes.API, - False), - ContractInfo('manager', 'SkaleManager', contracts.Manager, - ContractTypes.API, True), - ContractInfo('constants_holder', 'ConstantsHolder', contracts.ConstantsHolder, - ContractTypes.INTERNAL, True), - ContractInfo('nodes', 'Nodes', contracts.Nodes, - ContractTypes.API, True), - ContractInfo('node_rotation', 'NodeRotation', contracts.NodeRotation, - ContractTypes.API, True), - ContractInfo('schains', 'Schains', contracts.SChains, - ContractTypes.API, True), - ContractInfo('schains_internal', 'SchainsInternal', contracts.SChainsInternal, - ContractTypes.API, True), - ContractInfo('dkg', 'SkaleDKG', contracts.DKG, ContractTypes.API, True), - ContractInfo('key_storage', 'KeyStorage', - contracts.KeyStorage, ContractTypes.API, True), - ContractInfo('delegation_controller', 'DelegationController', contracts.DelegationController, - ContractTypes.API, False), - ContractInfo('delegation_period_manager', 'DelegationPeriodManager', - contracts.DelegationPeriodManager, ContractTypes.API, False), - ContractInfo('validator_service', 'ValidatorService', contracts.ValidatorService, - ContractTypes.API, False), - ContractInfo('token_state', 'TokenState', contracts.TokenState, - ContractTypes.API, False), - ContractInfo('distributor', 'Distributor', contracts.Distributor, - ContractTypes.API, False), - ContractInfo('slashing_table', 'SlashingTable', contracts.SlashingTable, - ContractTypes.API, False), - ContractInfo('wallets', 'Wallets', contracts.Wallets, - ContractTypes.API, True), - ContractInfo('bounty_v2', 'BountyV2', contracts.BountyV2, - ContractTypes.API, True), - ContractInfo('punisher', 'Punisher', contracts.Punisher, - ContractTypes.API, True), - ContractInfo('sync_manager', 'SyncManager', contracts.SyncManager, - ContractTypes.API, False), - ContractInfo('time_helpers_with_debug', 'TimeHelpersWithDebug', contracts.TimeHelpersWithDebug, - ContractTypes.API, False) -] +def contracts_info() -> List[ContractInfo]: + import skale.contracts.manager as contracts + return [ + ContractInfo('contract_manager', 'ContractManager', + contracts.ContractManager, ContractTypes.API, False), + ContractInfo('token', 'SkaleToken', contracts.Token, ContractTypes.API, + False), + ContractInfo('manager', 'SkaleManager', contracts.Manager, + ContractTypes.API, True), + ContractInfo('constants_holder', 'ConstantsHolder', contracts.ConstantsHolder, + ContractTypes.INTERNAL, True), + ContractInfo('nodes', 'Nodes', contracts.Nodes, + ContractTypes.API, True), + ContractInfo('node_rotation', 'NodeRotation', contracts.NodeRotation, + ContractTypes.API, True), + ContractInfo('schains', 'Schains', contracts.SChains, + ContractTypes.API, True), + ContractInfo('schains_internal', 'SchainsInternal', contracts.SChainsInternal, + ContractTypes.API, True), + ContractInfo('dkg', 'SkaleDKG', contracts.DKG, ContractTypes.API, True), + ContractInfo('key_storage', 'KeyStorage', + contracts.KeyStorage, ContractTypes.API, True), + ContractInfo('delegation_controller', 'DelegationController', + contracts.DelegationController, ContractTypes.API, False), + ContractInfo('delegation_period_manager', 'DelegationPeriodManager', + contracts.DelegationPeriodManager, ContractTypes.API, False), + ContractInfo('validator_service', 'ValidatorService', + contracts.ValidatorService, ContractTypes.API, False), + ContractInfo('token_state', 'TokenState', contracts.TokenState, + ContractTypes.API, False), + ContractInfo('distributor', 'Distributor', contracts.Distributor, + ContractTypes.API, False), + ContractInfo('slashing_table', 'SlashingTable', contracts.SlashingTable, + ContractTypes.API, False), + ContractInfo('wallets', 'Wallets', contracts.Wallets, + ContractTypes.API, True), + ContractInfo('bounty_v2', 'BountyV2', contracts.BountyV2, + ContractTypes.API, True), + ContractInfo('punisher', 'Punisher', contracts.Punisher, + ContractTypes.API, True), + ContractInfo('sync_manager', 'SyncManager', contracts.SyncManager, + ContractTypes.API, False), + ContractInfo('time_helpers_with_debug', 'TimeHelpersWithDebug', + contracts.TimeHelpersWithDebug, ContractTypes.API, False) + ] def spawn_skale_manager_lib(skale): @@ -86,6 +87,10 @@ class SkaleManager(SkaleBase): def project_name(self) -> str: return 'skale-manager' + def init_contract_manager(self) -> None: + from skale.contracts.manager.contract_manager import ContractManager + self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') + def set_contracts_info(self): self.init_contract_manager() - self._SkaleBase__contracts_info = get_contracts_info(CONTRACTS_INFO) + self._SkaleBase__contracts_info = get_contracts_info(contracts_info()) From 877c08ff16d2897ad196a26c639ad943b88f882d Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 26 Apr 2024 18:15:38 +0300 Subject: [PATCH 070/100] Add ImaContract --- skale/contracts/ima/linker.py | 5 +++-- skale/skale_ima.py | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/skale/contracts/ima/linker.py b/skale/contracts/ima/linker.py index 0a8b0bf7..9895a9c2 100644 --- a/skale/contracts/ima/linker.py +++ b/skale/contracts/ima/linker.py @@ -21,11 +21,12 @@ from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.ima_contract import ImaContract from skale.types.schain import SchainName -class Linker(BaseContract): +class Linker(ImaContract): @transaction_method def connect_schain( self, diff --git a/skale/skale_ima.py b/skale/skale_ima.py index a1a07140..97f2cd24 100644 --- a/skale/skale_ima.py +++ b/skale/skale_ima.py @@ -18,9 +18,9 @@ # along with SKALE.py. If not, see . import logging +from typing import List from skale.skale_base import SkaleBase -import skale.contracts.ima as contracts from skale.utils.contract_info import ContractInfo from skale.utils.contract_types import ContractTypes from skale.utils.helper import get_contracts_info @@ -29,10 +29,12 @@ logger = logging.getLogger(__name__) -CONTRACTS_INFO = [ - ContractInfo('linker', 'Linker', - contracts.Linker, ContractTypes.API, False) -] +def contracts_info() -> List[ContractInfo]: + import skale.contracts.ima as contracts + return [ + ContractInfo('linker', 'Linker', + contracts.Linker, ContractTypes.API, False) + ] def spawn_skale_ima_lib(skale_ima): @@ -42,4 +44,4 @@ def spawn_skale_ima_lib(skale_ima): class SkaleIma(SkaleBase): def set_contracts_info(self): - self._SkaleBase__contracts_info = get_contracts_info(CONTRACTS_INFO) + self._SkaleBase__contracts_info = get_contracts_info(contracts_info()) From d1e77d27860b93501073e8aa394f1bf2fd5bbb43 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 26 Apr 2024 18:54:51 +0300 Subject: [PATCH 071/100] Add type hints to skale_manager --- skale/skale_manager.py | 108 ++++++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/skale/skale_manager.py b/skale/skale_manager.py index 1ef9a8b1..f19bdf6d 100644 --- a/skale/skale_manager.py +++ b/skale/skale_manager.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from __future__ import annotations import logging from typing import List @@ -29,68 +30,67 @@ logger = logging.getLogger(__name__) -def contracts_info() -> List[ContractInfo]: - import skale.contracts.manager as contracts - return [ - ContractInfo('contract_manager', 'ContractManager', - contracts.ContractManager, ContractTypes.API, False), - ContractInfo('token', 'SkaleToken', contracts.Token, ContractTypes.API, - False), - ContractInfo('manager', 'SkaleManager', contracts.Manager, - ContractTypes.API, True), - ContractInfo('constants_holder', 'ConstantsHolder', contracts.ConstantsHolder, - ContractTypes.INTERNAL, True), - ContractInfo('nodes', 'Nodes', contracts.Nodes, - ContractTypes.API, True), - ContractInfo('node_rotation', 'NodeRotation', contracts.NodeRotation, - ContractTypes.API, True), - ContractInfo('schains', 'Schains', contracts.SChains, - ContractTypes.API, True), - ContractInfo('schains_internal', 'SchainsInternal', contracts.SChainsInternal, - ContractTypes.API, True), - ContractInfo('dkg', 'SkaleDKG', contracts.DKG, ContractTypes.API, True), - ContractInfo('key_storage', 'KeyStorage', - contracts.KeyStorage, ContractTypes.API, True), - ContractInfo('delegation_controller', 'DelegationController', - contracts.DelegationController, ContractTypes.API, False), - ContractInfo('delegation_period_manager', 'DelegationPeriodManager', - contracts.DelegationPeriodManager, ContractTypes.API, False), - ContractInfo('validator_service', 'ValidatorService', - contracts.ValidatorService, ContractTypes.API, False), - ContractInfo('token_state', 'TokenState', contracts.TokenState, - ContractTypes.API, False), - ContractInfo('distributor', 'Distributor', contracts.Distributor, - ContractTypes.API, False), - ContractInfo('slashing_table', 'SlashingTable', contracts.SlashingTable, - ContractTypes.API, False), - ContractInfo('wallets', 'Wallets', contracts.Wallets, - ContractTypes.API, True), - ContractInfo('bounty_v2', 'BountyV2', contracts.BountyV2, - ContractTypes.API, True), - ContractInfo('punisher', 'Punisher', contracts.Punisher, - ContractTypes.API, True), - ContractInfo('sync_manager', 'SyncManager', contracts.SyncManager, - ContractTypes.API, False), - ContractInfo('time_helpers_with_debug', 'TimeHelpersWithDebug', - contracts.TimeHelpersWithDebug, ContractTypes.API, False) - ] - - -def spawn_skale_manager_lib(skale): - """ Clone skale manager object with the same wallet """ - return SkaleManager(skale._endpoint, skale._abi_filepath, skale.wallet) - - class SkaleManager(SkaleBase): """Represents skale-manager smart contracts""" @property def project_name(self) -> str: return 'skale-manager' + def contracts_info(self) -> List[ContractInfo[SkaleManager]]: + import skale.contracts.manager as contracts + return [ + ContractInfo('contract_manager', 'ContractManager', + contracts.ContractManager, ContractTypes.API, False), + ContractInfo('token', 'SkaleToken', contracts.Token, ContractTypes.API, + False), + ContractInfo('manager', 'SkaleManager', contracts.Manager, + ContractTypes.API, True), + ContractInfo('constants_holder', 'ConstantsHolder', contracts.ConstantsHolder, + ContractTypes.INTERNAL, True), + ContractInfo('nodes', 'Nodes', contracts.Nodes, + ContractTypes.API, True), + ContractInfo('node_rotation', 'NodeRotation', contracts.NodeRotation, + ContractTypes.API, True), + ContractInfo('schains', 'Schains', contracts.SChains, + ContractTypes.API, True), + ContractInfo('schains_internal', 'SchainsInternal', contracts.SChainsInternal, + ContractTypes.API, True), + ContractInfo('dkg', 'SkaleDKG', contracts.DKG, ContractTypes.API, True), + ContractInfo('key_storage', 'KeyStorage', + contracts.KeyStorage, ContractTypes.API, True), + ContractInfo('delegation_controller', 'DelegationController', + contracts.DelegationController, ContractTypes.API, False), + ContractInfo('delegation_period_manager', 'DelegationPeriodManager', + contracts.DelegationPeriodManager, ContractTypes.API, False), + ContractInfo('validator_service', 'ValidatorService', + contracts.ValidatorService, ContractTypes.API, False), + ContractInfo('token_state', 'TokenState', contracts.TokenState, + ContractTypes.API, False), + ContractInfo('distributor', 'Distributor', contracts.Distributor, + ContractTypes.API, False), + ContractInfo('slashing_table', 'SlashingTable', contracts.SlashingTable, + ContractTypes.API, False), + ContractInfo('wallets', 'Wallets', contracts.Wallets, + ContractTypes.API, True), + ContractInfo('bounty_v2', 'BountyV2', contracts.BountyV2, + ContractTypes.API, True), + ContractInfo('punisher', 'Punisher', contracts.Punisher, + ContractTypes.API, True), + ContractInfo('sync_manager', 'SyncManager', contracts.SyncManager, + ContractTypes.API, False), + ContractInfo('time_helpers_with_debug', 'TimeHelpersWithDebug', + contracts.TimeHelpersWithDebug, ContractTypes.API, False) + ] + def init_contract_manager(self) -> None: from skale.contracts.manager.contract_manager import ContractManager self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') - def set_contracts_info(self): + def set_contracts_info(self) -> None: self.init_contract_manager() - self._SkaleBase__contracts_info = get_contracts_info(contracts_info()) + self._SkaleBase__contracts_info = get_contracts_info(self.contracts_info()) + + +def spawn_skale_manager_lib(skale: SkaleManager) -> SkaleManager: + """ Clone skale manager object with the same wallet """ + return SkaleManager(skale._endpoint, skale.instance.address, skale.wallet) From b06ef21e9443f55233c6b2f68b44079458c864f6 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 26 Apr 2024 19:02:38 +0300 Subject: [PATCH 072/100] Add type hints to skale_ima --- skale/contracts/ima_contract.py | 24 ++++++++++++++++ .../manager/delegation/distributor.py | 4 +-- skale/skale_ima.py | 28 +++++++++++-------- 3 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 skale/contracts/ima_contract.py diff --git a/skale/contracts/ima_contract.py b/skale/contracts/ima_contract.py new file mode 100644 index 00000000..ff45ab18 --- /dev/null +++ b/skale/contracts/ima_contract.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2019-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . +from skale.contracts.base_contract import BaseContract +from skale.skale_ima import SkaleIma + + +class ImaContract(BaseContract[SkaleIma]): + pass diff --git a/skale/contracts/manager/delegation/distributor.py b/skale/contracts/manager/delegation/distributor.py index 0d4a6ab5..4d565b79 100644 --- a/skale/contracts/manager/delegation/distributor.py +++ b/skale/contracts/manager/delegation/distributor.py @@ -24,7 +24,7 @@ from web3.contract.contract import ContractFunction from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.validator import ValidatorId @@ -36,7 +36,7 @@ class EarnedData(TypedDict): def formatter(method: Callable[..., Tuple[Wei, int]]) -> Callable[..., EarnedData]: @wraps(method) - def wrapper(self: BaseContract, *args: Any, **kwargs: Any) -> EarnedData: + def wrapper(self: SkaleManagerContract, *args: Any, **kwargs: Any) -> EarnedData: res = method(self, *args, **kwargs) return EarnedData({ 'earned': res[0], diff --git a/skale/skale_ima.py b/skale/skale_ima.py index 97f2cd24..ae9f75e4 100644 --- a/skale/skale_ima.py +++ b/skale/skale_ima.py @@ -17,6 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from __future__ import annotations import logging from typing import List @@ -29,19 +30,22 @@ logger = logging.getLogger(__name__) -def contracts_info() -> List[ContractInfo]: - import skale.contracts.ima as contracts - return [ - ContractInfo('linker', 'Linker', - contracts.Linker, ContractTypes.API, False) - ] +class SkaleIma(SkaleBase): + @property + def project_name(self) -> str: + return 'mainnet-ima' + def contracts_info(self) -> List[ContractInfo[SkaleIma]]: + import skale.contracts.ima as contracts + return [ + ContractInfo('linker', 'Linker', + contracts.Linker, ContractTypes.API, False) + ] -def spawn_skale_ima_lib(skale_ima): - """ Clone skale ima object with the same wallet """ - return SkaleIma(skale_ima._endpoint, skale_ima._abi_filepath, skale_ima.wallet) + def set_contracts_info(self) -> None: + self._SkaleBase__contracts_info = get_contracts_info(self.contracts_info()) -class SkaleIma(SkaleBase): - def set_contracts_info(self): - self._SkaleBase__contracts_info = get_contracts_info(contracts_info()) +def spawn_skale_ima_lib(skale_ima: SkaleIma) -> SkaleIma: + """ Clone skale ima object with the same wallet """ + return SkaleIma(skale_ima._endpoint, skale_ima.instance.address, skale_ima.wallet) From 0ec835d2982792345ab9af387ba452b6300424ca Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 29 Apr 2024 11:06:15 +0300 Subject: [PATCH 073/100] Add type hints to skale_allocator --- skale/skale_allocator.py | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/skale/skale_allocator.py b/skale/skale_allocator.py index 97dfc814..7fad86e4 100644 --- a/skale/skale_allocator.py +++ b/skale/skale_allocator.py @@ -23,31 +23,18 @@ from web3.constants import CHECKSUM_ADDRESSS_ZERO from skale.skale_base import SkaleBase -from skale.contracts.manager.contract_manager import ContractManager from skale.utils.contract_info import ContractInfo from skale.utils.contract_types import ContractTypes from skale.utils.helper import get_contracts_info if TYPE_CHECKING: from eth_typing import ChecksumAddress - import skale.contracts.allocator as contracts + from skale.contracts.allocator.allocator import Allocator logger = logging.getLogger(__name__) -def contracts_info() -> List[ContractInfo]: - import skale.contracts.allocator as contracts - return [ - ContractInfo('contract_manager', 'ContractManager', ContractManager, - ContractTypes.API, False), - ContractInfo('escrow', 'Escrow', contracts.Escrow, - ContractTypes.API, True), - ContractInfo('allocator', 'Allocator', contracts.Allocator, - ContractTypes.API, True) - ] - - def spawn_skale_allocator_lib(skale: SkaleAllocator) -> SkaleAllocator: return SkaleAllocator(skale._endpoint, skale.instance.address, skale.wallet) @@ -59,8 +46,17 @@ def project_name(self) -> str: return 'skale-allocator' @property - def allocator(self) -> contracts.Allocator: - return cast('contracts.Allocator', super()._get_contract('allocator')) + def allocator(self) -> Allocator: + return cast('Allocator', super()._get_contract('allocator')) + + def contracts_info(self) -> List[ContractInfo[SkaleAllocator]]: + import skale.contracts.allocator as contracts + return [ + ContractInfo('escrow', 'Escrow', contracts.Escrow, + ContractTypes.API, True), + ContractInfo('allocator', 'Allocator', contracts.Allocator, + ContractTypes.API, True) + ] def get_contract_address(self, name: str) -> ChecksumAddress: if name == 'Escrow': @@ -68,4 +64,4 @@ def get_contract_address(self, name: str) -> ChecksumAddress: return super().get_contract_address(name) def set_contracts_info(self) -> None: - self._SkaleBase__contracts_info = get_contracts_info(contracts_info()) + self._SkaleBase__contracts_info = get_contracts_info(self.contracts_info()) From 4da622dc9cf1dda56347d80e06bd012ca2eabd2b Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 29 Apr 2024 11:37:35 +0300 Subject: [PATCH 074/100] Add type hints to allocator --- skale/contracts/allocator/allocator.py | 131 +++++++++++++++---------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index 4882fc16..882aa30d 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -18,12 +18,17 @@ # along with SKALE.py. If not, see . """ SKALE Allocator Core Escrow methods """ -from enum import IntEnum +from typing import Any, Dict, List + +from eth_typing import ChecksumAddress +from web3 import Web3 +from web3.contract.contract import ContractFunction +from web3.types import Wei from skale.contracts.allocator_contract import AllocatorContract from skale.contracts.base_contract import transaction_method from skale.transactions.exceptions import ContractLogicError -from skale.transactions.result import TxRes +from skale.types.allocation import BeneficiaryStatus, Plan, PlanId, PlanWithId, TimeUnit from skale.utils.helper import format_fields @@ -48,36 +53,25 @@ MAX_NUM_OF_BENEFICIARIES = 9999 -class TimeUnit(IntEnum): - DAY = 0 - MONTH = 1 - YEAR = 2 - - -class BeneficiaryStatus(IntEnum): - UNKNOWN = 0 - CONFIRMED = 1 - ACTIVE = 2 - TERMINATED = 3 - - class Allocator(AllocatorContract): - def is_beneficiary_registered(self, beneficiary_address: str) -> bool: + def is_beneficiary_registered(self, beneficiary_address: ChecksumAddress) -> bool: """Confirms whether the beneficiary is registered in a Plan. :returns: Boolean value :rtype: bool """ - return self.contract.functions.isBeneficiaryRegistered(beneficiary_address).call() + return bool(self.contract.functions.isBeneficiaryRegistered(beneficiary_address).call()) - def is_delegation_allowed(self, beneficiary_address: str) -> bool: - return self.contract.functions.isDelegationAllowed(beneficiary_address).call() + def is_delegation_allowed(self, beneficiary_address: ChecksumAddress) -> bool: + return bool(self.contract.functions.isDelegationAllowed(beneficiary_address).call()) - def is_vesting_active(self, beneficiary_address: str) -> bool: - return self.contract.functions.isVestingActive(beneficiary_address).call() + def is_vesting_active(self, beneficiary_address: ChecksumAddress) -> bool: + return bool(self.contract.functions.isVestingActive(beneficiary_address).call()) - def get_escrow_address(self, beneficiary_address: str) -> str: - return self.contract.functions.getEscrowAddress(beneficiary_address).call() + def get_escrow_address(self, beneficiary_address: ChecksumAddress) -> ChecksumAddress: + return Web3.to_checksum_address( + self.contract.functions.getEscrowAddress(beneficiary_address).call() + ) @transaction_method def add_plan( @@ -88,7 +82,7 @@ def add_plan( vesting_interval: int, can_delegate: bool, is_terminatable: bool - ) -> TxRes: + ) -> ContractFunction: return self.contract.functions.addPlan( vestingCliff=vesting_cliff, totalVestingDuration=total_vesting_duration, @@ -101,12 +95,12 @@ def add_plan( @transaction_method def connect_beneficiary_to_plan( self, - beneficiary_address: str, + beneficiary_address: ChecksumAddress, plan_id: int, start_month: int, full_amount: int, lockup_amount: int, - ) -> TxRes: + ) -> ContractFunction: return self.contract.functions.connectBeneficiaryToPlan( beneficiary=beneficiary_address, planId=plan_id, @@ -116,61 +110,92 @@ def connect_beneficiary_to_plan( ) @transaction_method - def start_vesting(self, beneficiary_address: str) -> TxRes: + def start_vesting(self, beneficiary_address: ChecksumAddress) -> ContractFunction: return self.contract.functions.startVesting(beneficiary_address) @transaction_method - def stop_vesting(self, beneficiary_address: str) -> TxRes: + def stop_vesting(self, beneficiary_address: ChecksumAddress) -> ContractFunction: return self.contract.functions.stopVesting(beneficiary_address) @transaction_method - def grant_role(self, role: bytes, address: str) -> TxRes: + def grant_role(self, role: bytes, address: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, address) def vesting_manager_role(self) -> bytes: - return self.contract.functions.VESTING_MANAGER_ROLE().call() + return bytes(self.contract.functions.VESTING_MANAGER_ROLE().call()) - def has_role(self, role: bytes, address: str) -> bool: - return self.contract.functions.hasRole(role, address).call() + def has_role(self, role: bytes, address: ChecksumAddress) -> bool: + return bool(self.contract.functions.hasRole(role, address).call()) - def __get_beneficiary_plan_params_raw(self, beneficiary_address: str): - return self.contract.functions.getBeneficiaryPlanParams(beneficiary_address).call() + def __get_beneficiary_plan_params_raw(self, beneficiary_address: ChecksumAddress) -> List[Any]: + return list(self.contract.functions.getBeneficiaryPlanParams(beneficiary_address).call()) @format_fields(BENEFICIARY_FIELDS) - def get_beneficiary_plan_params_dict(self, beneficiary_address: str) -> dict: + def get_beneficiary_plan_params_dict(self, beneficiary_address: ChecksumAddress) -> List[Any]: return self.__get_beneficiary_plan_params_raw(beneficiary_address) - def get_beneficiary_plan_params(self, beneficiary_address: str) -> dict: + def get_beneficiary_plan_params(self, beneficiary_address: ChecksumAddress) -> Plan: plan_params = self.get_beneficiary_plan_params_dict(beneficiary_address) - plan_params['statusName'] = BeneficiaryStatus(plan_params['status']).name - return plan_params - - def __get_plan_raw(self, plan_id: int): - return self.contract.functions.getPlan(plan_id).call() + if plan_params is None: + raise ValueError('Plan for ', beneficiary_address, ' is missing') + if isinstance(plan_params, list): + return self._to_plan({ + **plan_params[0], + 'statusName': BeneficiaryStatus(plan_params[0]['status']).name + }) + if isinstance(plan_params, dict): + return self._to_plan({ + **plan_params, + 'statusName': BeneficiaryStatus(plan_params['status']).name + }) + raise TypeError(beneficiary_address) + + def __get_plan_raw(self, plan_id: PlanId) -> List[Any]: + return list(self.contract.functions.getPlan(plan_id).call()) @format_fields(PLAN_FIELDS) - def get_plan(self, plan_id: int) -> dict: + def get_untyped_plan(self, plan_id: PlanId) -> List[Any]: return self.__get_plan_raw(plan_id) - def get_all_plans(self) -> list: + def get_plan(self, plan_id: PlanId) -> Plan: + untyped_plan = self.get_untyped_plan(plan_id) + if untyped_plan is None: + raise ValueError('Plan ', plan_id, ' is missing') + if isinstance(untyped_plan, list): + return self._to_plan(untyped_plan[0]) + if isinstance(untyped_plan, dict): + return self._to_plan(untyped_plan) + raise TypeError(plan_id) + + def get_all_plans(self) -> List[PlanWithId]: plans = [] for i in range(1, MAX_NUM_OF_PLANS): try: - plan = self.get_plan(i) - plan['planId'] = i + plan_id = PlanId(i) + plan = PlanWithId({**self.get_plan(plan_id), 'planId': plan_id}) plans.append(plan) except (ContractLogicError, ValueError): break return plans - def calculate_vested_amount(self, address: str) -> int: - return self.contract.functions.calculateVestedAmount(address).call() + def calculate_vested_amount(self, address: ChecksumAddress) -> Wei: + return Wei(self.contract.functions.calculateVestedAmount(address).call()) + + def get_finish_vesting_time(self, address: ChecksumAddress) -> int: + return int(self.contract.functions.getFinishVestingTime(address).call()) - def get_finish_vesting_time(self, address: str) -> int: - return self.contract.functions.getFinishVestingTime(address).call() + def get_lockup_period_end_timestamp(self, address: ChecksumAddress) -> int: + return int(self.contract.functions.getLockupPeriodEndTimestamp(address).call()) - def get_lockup_period_end_timestamp(self, address: str) -> int: - return self.contract.functions.getLockupPeriodEndTimestamp(address).call() + def get_time_of_next_vest(self, address: ChecksumAddress) -> int: + return int(self.contract.functions.getTimeOfNextVest(address).call()) - def get_time_of_next_vest(self, address: str) -> int: - return self.contract.functions.getTimeOfNextVest(address).call() + def _to_plan(self, untyped_plan: Dict[str, Any]) -> Plan: + return Plan({ + 'totalVestingDuration': int(untyped_plan['totalVestingDuration']), + 'vestingCliff': int(untyped_plan['vestingCliff']), + 'vestingIntervalTimeUnit': TimeUnit(untyped_plan['vestingIntervalTimeUnit']), + 'vestingInterval': int(untyped_plan['vestingInterval']), + 'isDelegationAllowed': bool(untyped_plan['isDelegationAllowed']), + 'isTerminatable': bool(untyped_plan['isTerminatable']) + }) From 3f173e0727bceba6d4ae496bef02313f7062950a Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 30 Apr 2024 16:30:45 +0300 Subject: [PATCH 075/100] Add type hints to schains --- skale/contracts/manager/node_rotation.py | 10 +- skale/contracts/manager/schains.py | 128 +++++++++----------- skale/contracts/manager/schains_internal.py | 28 +---- skale/dataclasses/schain_options.py | 2 +- skale/skale_manager.py | 24 +++- skale/types/allocation.py | 50 ++++++++ skale/types/schain.py | 33 +++++ skale/utils/account_tools.py | 34 +++--- skale/utils/contracts_provision/main.py | 2 +- tests/manager/schains_internal_test.py | 9 +- tests/manager/schains_test.py | 42 +++---- 11 files changed, 217 insertions(+), 145 deletions(-) create mode 100644 skale/types/allocation.py diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index 7e5ea16f..7662b602 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -21,7 +21,7 @@ from __future__ import annotations import logging import functools -from typing import TYPE_CHECKING, List, TypedDict, cast +from typing import TYPE_CHECKING, List, TypedDict from dataclasses import dataclass from eth_typing import ChecksumAddress @@ -63,8 +63,7 @@ class NodeRotation(SkaleManagerContract): @property @functools.lru_cache() def schains(self) -> SChains: - from skale.contracts.manager.schains import SChains - return cast(SChains, self.skale.schains) + return self.skale.schains def get_rotation(self, schain_name: SchainName) -> Rotation: schain_id = self.schains.name_to_id(schain_name) @@ -83,9 +82,8 @@ def get_leaving_history(self, node_id: NodeId) -> List[RotationSwap]: return history def get_schain_finish_ts(self, node_id: NodeId, schain_name: SchainName) -> int | None: - from skale.contracts.manager.schains import SChains raw_history = self.contract.functions.getLeavingHistory(node_id).call() - schain_id = cast(SChains, self.skale.schains).name_to_id(schain_name) + schain_id = self.skale.schains.name_to_id(schain_name) finish_ts = next( (schain[1] for schain in raw_history if '0x' + schain[0].hex() == schain_id), None) if not finish_ts: @@ -109,7 +107,7 @@ def is_rotation_active(self, schain_name: SchainName) -> bool: return self.is_rotation_in_progress(schain_name) and not finish_ts_reached def is_finish_ts_reached(self, schain_name: SchainName) -> bool: - rotation = cast(NodeRotation, self.skale.node_rotation).get_rotation_obj(schain_name) + rotation = self.skale.node_rotation.get_rotation_obj(schain_name) schain_finish_ts = self.get_schain_finish_ts(rotation.leaving_node_id, schain_name) if not schain_finish_ts: diff --git a/skale/contracts/manager/schains.py b/skale/contracts/manager/schains.py index 6b722de4..fca48ba9 100644 --- a/skale/contracts/manager/schains.py +++ b/skale/contracts/manager/schains.py @@ -19,16 +19,22 @@ """ Schains.sol functions """ import functools -from dataclasses import dataclass, asdict +from dataclasses import asdict +from typing import Any, List from Crypto.Hash import keccak +from eth_typing import ChecksumAddress, HexStr from hexbytes import HexBytes +from web3 import Web3 +from web3.contract.contract import ContractFunction +from web3.types import Wei -from skale.contracts.base_contract import BaseContract, transaction_method +from skale.contracts.base_contract import transaction_method +from skale.contracts.manager.node_rotation import NodeRotation from skale.contracts.manager.schains_internal import SChainsInternal -from skale.transactions.result import TxRes -from skale.types.schain import SchainHash, SchainName -from skale.utils.helper import format_fields +from skale.contracts.skale_manager_contract import SkaleManagerContract +from skale.types.node import NodeId +from skale.types.schain import SchainHash, SchainName, SchainStructure, SchainStructureWithStatus from skale.dataclasses.schain_options import ( SchainOptions, get_default_schain_options, parse_schain_options ) @@ -36,35 +42,14 @@ FIELDS = [ 'name', 'mainnetOwner', 'indexInOwnerList', 'partOfNode', 'lifetime', 'startDate', 'startBlock', - 'deposit', 'index', 'generation', 'originator', 'chainId', 'multitransactionMode', - 'thresholdEncryption' + 'deposit', 'index', 'generation', 'originator', 'chainId', 'options' ] -SchainOption = tuple[str, bytes] - - -@dataclass -class SchainStructure: - name: str - mainnet_owner: str - index_in_owner_list: int - part_of_node: int - lifetime: int - start_date: int - start_block: int - deposit: int - index: int - generation: int - originator: str - chain_id: int - options: SchainOptions - - -class SChains(BaseContract): +class SChains(SkaleManagerContract): """Wrapper for some of the Schains.sol functions""" - def name_to_group_id(self, name: str) -> HexBytes: + def name_to_group_id(self, name: SchainName) -> HexBytes: return self.skale.web3.keccak(text=name) @property @@ -74,28 +59,19 @@ def schains_internal(self) -> SChainsInternal: @property @functools.lru_cache() - def node_rotation(self): + def node_rotation(self) -> NodeRotation: return self.skale.node_rotation - @format_fields(FIELDS) - def get(self, id_, obj=False): + def get(self, id_: SchainHash) -> SchainStructure: res = self.schains_internal.get_raw(id_) - hash_obj = keccak.new(data=res[0].encode("utf8"), digest_bits=256) - hash_str = "0x" + hash_obj.hexdigest()[:13] - res.append(hash_str) options = self.get_options(id_) - if obj: # TODO: temporary solution for backwards compatibility - return SchainStructure(*res, options=options) - else: - res += asdict(options).values() - return res - - @format_fields(FIELDS) - def get_by_name(self, name, obj=False): + return SchainStructure(**asdict(res), chainId=self.name_to_id(res.name), options=options) + + def get_by_name(self, name: SchainName) -> SchainStructure: id_ = self.name_to_id(name) - return self.get(id_, obj=obj) + return self.get(id_) - def get_schains_for_owner(self, account): + def get_schains_for_owner(self, account: ChecksumAddress) -> List[SchainStructure]: schains = [] list_size = self.schains_internal.get_schain_list_size(account) @@ -105,40 +81,48 @@ def get_schains_for_owner(self, account): schains.append(schain) return schains - def get_schains_for_node(self, node_id) -> list[SchainStructure]: + def get_schains_for_node(self, node_id: NodeId) -> list[SchainStructureWithStatus]: schains = [] schain_ids = self.schains_internal.get_schain_ids_for_node(node_id) for schain_id in schain_ids: - schain = self.get(schain_id) - schain['active'] = True if self.schain_active(schain) else False + simple_schain = self.get(schain_id) + schain = SchainStructureWithStatus( + **asdict(simple_schain), + active=self.schain_active(simple_schain) + ) schains.append(schain) return schains - def get_active_schains_for_node(self, node_id): + def get_active_schains_for_node(self, node_id: NodeId) -> List[SchainStructureWithStatus]: schains = [] schain_ids = self.schains_internal.get_active_schain_ids_for_node(node_id) for schain_id in schain_ids: - schain = self.get(schain_id) - schain['active'] = True + simple_schain = self.get(schain_id) + schain = SchainStructureWithStatus( + **asdict(simple_schain), + active=True + ) schains.append(schain) return schains def name_to_id(self, name: SchainName) -> SchainHash: keccak_hash = keccak.new(data=name.encode("utf8"), digest_bits=256) - return '0x' + keccak_hash.hexdigest() + return SchainHash(Web3.to_bytes(hexstr=Web3.to_hex(hexstr=HexStr(keccak_hash.hexdigest())))) - def get_last_rotation_id(self, schain_name): + def get_last_rotation_id(self, schain_name: SchainName) -> int: rotation_data = self.node_rotation.get_rotation(schain_name) - return rotation_data['rotation_id'] + return rotation_data.rotation_counter - def schain_active(self, schain): - if schain['name'] != '' and \ - schain['mainnetOwner'] != '0x0000000000000000000000000000000000000000': + def schain_active(self, schain: SchainStructure) -> bool: + if schain.name != '' and \ + schain.mainnetOwner != '0x0000000000000000000000000000000000000000': return True + return False - def get_schain_price(self, index_of_type, lifetime): - return self.contract.functions.getSchainPrice(index_of_type, - lifetime).call() + def get_schain_price(self, index_of_type: int, lifetime: int) -> Wei: + return Wei( + self.contract.functions.getSchainPrice(index_of_type, lifetime).call() + ) @transaction_method def add_schain_by_foundation( @@ -146,11 +130,11 @@ def add_schain_by_foundation( lifetime: int, type_of_nodes: int, nonce: int, - name: str, + name: SchainName, options: SchainOptions | None = None, - schain_owner=None, - schain_originator=None - ) -> TxRes: + schain_owner: ChecksumAddress | None = None, + schain_originator: ChecksumAddress | None = None + ) -> ContractFunction: if schain_owner is None: schain_owner = self.skale.wallet.address if schain_originator is None: @@ -169,23 +153,23 @@ def add_schain_by_foundation( ) @transaction_method - def grant_role(self, role: bytes, owner: str) -> TxRes: + def grant_role(self, role: bytes, owner: ChecksumAddress) -> ContractFunction: return self.contract.functions.grantRole(role, owner) - def schain_creator_role(self): - return self.contract.functions.SCHAIN_CREATOR_ROLE().call() + def schain_creator_role(self) -> bytes: + return bytes(self.contract.functions.SCHAIN_CREATOR_ROLE().call()) - def __raw_get_options(self, schain_id: str) -> list: - return self.contract.functions.getOptions(schain_id).call() + def __raw_get_options(self, schain_id: SchainHash) -> List[Any]: + return list(self.contract.functions.getOptions(schain_id).call()) - def get_options(self, schain_id: str) -> SchainOptions: + def get_options(self, schain_id: SchainHash) -> SchainOptions: return parse_schain_options( raw_options=self.__raw_get_options(schain_id) ) - def get_options_by_name(self, name: str) -> SchainOptions: + def get_options_by_name(self, name: SchainName) -> SchainOptions: id_ = self.name_to_id(name) return self.get_options(id_) - def restart_schain_creation(self, name: str) -> TxRes: + def restart_schain_creation(self, name: SchainName) -> ContractFunction: return self.contract.functions.restartSchainCreation(name) diff --git a/skale/contracts/manager/schains_internal.py b/skale/contracts/manager/schains_internal.py index fb01bfb4..d3e4d1e2 100644 --- a/skale/contracts/manager/schains_internal.py +++ b/skale/contracts/manager/schains_internal.py @@ -19,47 +19,31 @@ """ SchainsInternal.sol functions """ from __future__ import annotations -from dataclasses import dataclass import functools -from typing import TYPE_CHECKING, List, cast +from typing import TYPE_CHECKING, List from eth_typing import ChecksumAddress + from skale.contracts.base_contract import transaction_method from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.node import NodeId -from skale.types.schain import SchainHash, SchainName +from skale.types.schain import Schain, SchainHash, SchainName if TYPE_CHECKING: from web3.contract.contract import ContractFunction from skale.contracts.manager.schains import SChains -@dataclass -class Schain: - name: str - owner: str - indexInOwnerList: int - partOfNode: int - lifetime: int - startDate: int - startBlock: int - deposit: int - index: int - generation: int - originator: str - - class SChainsInternal(SkaleManagerContract): """Wrapper for some of the SchainsInternal.sol functions""" @property @functools.lru_cache() def schains(self) -> SChains: - from skale.contracts.manager.schains import SChains - return cast(SChains, self.skale.schains) + return self.skale.schains - def get_raw(self, name: str) -> Schain: - return cast(Schain, self.contract.functions.schains(name).call()) + def get_raw(self, name: SchainHash) -> Schain: + return Schain(*self.contract.functions.schains(name).call()) def get_all_schains_ids(self) -> List[SchainHash]: return [ diff --git a/skale/dataclasses/schain_options.py b/skale/dataclasses/schain_options.py index d655b576..d85057f8 100644 --- a/skale/dataclasses/schain_options.py +++ b/skale/dataclasses/schain_options.py @@ -21,7 +21,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from skale.contracts.manager.schains import SchainOption + from skale.types.schain import SchainOption @dataclass diff --git a/skale/skale_manager.py b/skale/skale_manager.py index f19bdf6d..dfa51e55 100644 --- a/skale/skale_manager.py +++ b/skale/skale_manager.py @@ -19,13 +19,19 @@ from __future__ import annotations import logging -from typing import List +from typing import List, TYPE_CHECKING, cast from skale.skale_base import SkaleBase from skale.utils.contract_info import ContractInfo from skale.utils.contract_types import ContractTypes from skale.utils.helper import get_contracts_info +if TYPE_CHECKING: + from skale.contracts.manager.node_rotation import NodeRotation + from skale.contracts.manager.schains import SChains + from skale.contracts.manager.schains_internal import SChainsInternal + from skale.contracts.manager.token import Token + logger = logging.getLogger(__name__) @@ -82,6 +88,22 @@ def contracts_info(self) -> List[ContractInfo[SkaleManager]]: contracts.TimeHelpersWithDebug, ContractTypes.API, False) ] + @property + def node_rotation(self) -> NodeRotation: + return cast('NodeRotation', self._get_contract('node_rotation')) + + @property + def schains(self) -> SChains: + return cast('SChains', self._get_contract('schains')) + + @property + def schains_internal(self) -> SChainsInternal: + return cast('SChainsInternal', self._get_contract('schains_internal')) + + @property + def token(self) -> Token: + return cast('Token', self._get_contract('token')) + def init_contract_manager(self) -> None: from skale.contracts.manager.contract_manager import ContractManager self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') diff --git a/skale/types/allocation.py b/skale/types/allocation.py new file mode 100644 index 00000000..5b8ef712 --- /dev/null +++ b/skale/types/allocation.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +from enum import IntEnum +from typing import NewType, TypedDict + + +class TimeUnit(IntEnum): + DAY = 0 + MONTH = 1 + YEAR = 2 + + +class BeneficiaryStatus(IntEnum): + UNKNOWN = 0 + CONFIRMED = 1 + ACTIVE = 2 + TERMINATED = 3 + + +PlanId = NewType('PlanId', int) + + +class Plan(TypedDict): + totalVestingDuration: int + vestingCliff: int + vestingIntervalTimeUnit: TimeUnit + vestingInterval: int + isDelegationAllowed: bool + isTerminatable: bool + + +class PlanWithId(Plan): + planId: PlanId diff --git a/skale/types/schain.py b/skale/types/schain.py index 44adb26a..c7f07da1 100644 --- a/skale/types/schain.py +++ b/skale/types/schain.py @@ -17,8 +17,41 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from dataclasses import dataclass from typing import NewType +from eth_typing import ChecksumAddress +from web3.types import Wei + +from skale.dataclasses.schain_options import SchainOptions + SchainName = NewType('SchainName', str) SchainHash = NewType('SchainHash', bytes) +SchainOption = tuple[str, bytes] + + +@dataclass +class Schain: + name: SchainName + mainnetOwner: ChecksumAddress + indexInOwnerList: int + partOfNode: int + lifetime: int + startDate: int + startBlock: int + deposit: Wei + index: int + generation: int + originator: ChecksumAddress + + +@dataclass +class SchainStructure(Schain): + chainId: SchainHash + options: SchainOptions + + +@dataclass +class SchainStructureWithStatus(SchainStructure): + active: bool diff --git a/skale/utils/account_tools.py b/skale/utils/account_tools.py index 0a15346c..4cb60e7f 100644 --- a/skale/utils/account_tools.py +++ b/skale/utils/account_tools.py @@ -19,11 +19,14 @@ """ Account utilities """ import logging -from typing import Optional +from typing import Any, Optional +from eth_typing import ChecksumAddress from web3 import Web3 +from web3.types import TxReceipt, Wei from skale.skale_manager import SkaleManager +from skale.transactions.result import TxRes from skale.transactions.tools import compose_eth_transfer_tx from skale.utils.constants import LONG_LINE from skale.wallets import LedgerWallet, Web3Wallet @@ -32,6 +35,7 @@ default_gas_price, wait_for_confirmation_blocks ) +from skale.wallets.common import BaseWallet logger = logging.getLogger(__name__) @@ -48,11 +52,11 @@ def create_wallet(wallet_type='web3', *args, **kwargs): def send_tokens( skale: SkaleManager, - receiver_address, - amount, - *args, - **kwargs -) -> None: + receiver_address: ChecksumAddress, + amount: Wei, + *args: Any, + **kwargs: Any +) -> TxRes: logger.info( f'Sending {amount} SKALE tokens from {skale.wallet.address} => ' f'{receiver_address}' @@ -69,17 +73,17 @@ def send_tokens( def send_eth( web3: Web3, - wallet, - receiver_address, - amount, - *args, + wallet: BaseWallet, + receiver_address: ChecksumAddress, + amount: Wei, + *args: Any, gas_price: Optional[int] = None, wait_for: bool = True, confirmation_blocks: int = 0, multiplier: Optional[int] = None, priority: Optional[int] = None, - **kwargs -): + **kwargs: Any +) -> TxReceipt: logger.info( f'Sending {amount} ETH from {wallet.address} => ' f'{receiver_address}' @@ -111,11 +115,11 @@ def send_eth( return receipt -def account_eth_balance_wei(web3, address): +def account_eth_balance_wei(web3: Web3, address: ChecksumAddress) -> Wei: return web3.eth.get_balance(address) -def check_ether_balance(web3, address): +def check_ether_balance(web3: Web3, address: ChecksumAddress) -> int: balance_wei = account_eth_balance_wei(web3, address) balance = web3.from_wei(balance_wei, 'ether') @@ -123,7 +127,7 @@ def check_ether_balance(web3, address): return balance -def check_skale_balance(skale, address): +def check_skale_balance(skale: SkaleManager, address: ChecksumAddress) -> int: balance_wei = skale.token.get_balance(address) balance = skale.web3.from_wei(balance_wei, 'ether') logger.info(f'{address} balance: {balance} SKALE') diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index 5dab6798..6292d2bc 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -171,7 +171,7 @@ def cleanup_nodes(skale: SkaleManager, ids: list[int] = []) -> None: def cleanup_schains(skale: SkaleManager) -> None: for schain_id in skale.schains_internal.get_all_schains_ids(): schain_data = skale.schains.get(schain_id) - schain_name = schain_data.get('name', None) + schain_name = schain_data.name if schain_name is not None: skale.manager.delete_schain_by_root(schain_name, wait_for=True) diff --git a/tests/manager/schains_internal_test.py b/tests/manager/schains_internal_test.py index 1abc952d..4dc519e6 100644 --- a/tests/manager/schains_internal_test.py +++ b/tests/manager/schains_internal_test.py @@ -1,5 +1,6 @@ """ SKALE chain internal test """ +from dataclasses import astuple, fields from skale.contracts.manager.schains import FIELDS from tests.constants import ( DEFAULT_SCHAIN_ID, @@ -9,14 +10,14 @@ def test_get_raw(skale): - schain_arr = skale.schains_internal.get_raw(DEFAULT_SCHAIN_ID) - assert len(FIELDS) == len(schain_arr) + 3 # +1 for chainId + options + schain = skale.schains_internal.get_raw(DEFAULT_SCHAIN_ID) + assert len(FIELDS) == len(fields(schain)) + 2 # +2 for chainId + options def test_get_raw_not_exist(skale): not_exist_schain_id = skale.schains.name_to_id('unused_hash') schain_arr = skale.schains_internal.get_raw(not_exist_schain_id) - assert schain_arr == EMPTY_SCHAIN_ARR + assert list(astuple(schain_arr)) == EMPTY_SCHAIN_ARR def test_get_schains_number(skale, schain): @@ -36,7 +37,7 @@ def test_get_schain_id_by_index_for_owner(skale, schain): skale.wallet.address, 0 ) schain = skale.schains.get(schain_id) - assert schain['mainnetOwner'] == skale.wallet.address + assert schain.mainnetOwner == skale.wallet.address def test_get_node_ids_for_schain(skale, schain): diff --git a/tests/manager/schains_test.py b/tests/manager/schains_test.py index 4b2dd78a..d62c2d30 100644 --- a/tests/manager/schains_test.py +++ b/tests/manager/schains_test.py @@ -1,6 +1,8 @@ """ SKALE chain test """ +from dataclasses import fields from hexbytes import HexBytes +from web3 import Web3 from skale.contracts.manager.schains import FIELDS, SchainStructure from skale.dataclasses.schain_options import SchainOptions @@ -15,22 +17,16 @@ def test_get(skale): schain = skale.schains.get(DEFAULT_SCHAIN_ID) - assert list(schain.keys()) == FIELDS - assert [k for k, v in schain.items() if v is None] == [] - - -def test_get_object(skale): - schain = skale.schains.get(DEFAULT_SCHAIN_ID, obj=True) assert isinstance(schain, SchainStructure) assert isinstance(schain.options, SchainOptions) def test_get_by_name(skale): schain = skale.schains.get(DEFAULT_SCHAIN_ID) - schain_name = schain['name'] + schain_name = schain.name schain_by_name = skale.schains.get_by_name(schain_name) - assert list(schain_by_name.keys()) == FIELDS + assert [field.name for field in fields(schain_by_name)] == FIELDS assert schain == schain_by_name @@ -38,7 +34,7 @@ def test_get_schains_for_owner(skale, schain, empty_account): schains = skale.schains.get_schains_for_owner(skale.wallet.address) assert isinstance(schains, list) assert len(schains) > 0 - assert set(schains[-1].keys()) == set(FIELDS) + assert set([field.name for field in fields(schains[-1])]) == set(FIELDS) schains = skale.schains.get_schains_for_owner(empty_account.address) assert schains == [] @@ -57,7 +53,7 @@ def test_get_schains_for_node(skale, schain): test_schain = schains_for_node[0] schain_node_ids = skale.schains_internal.get_node_ids_for_schain( - test_schain['name'] + test_schain.name ) assert node_id in schain_node_ids @@ -65,13 +61,13 @@ def test_get_schains_for_node(skale, schain): def test_name_to_id(skale): schain_id = skale.schains.name_to_id(DEFAULT_SCHAIN_NAME) - assert schain_id == DEFAULT_SCHAIN_ID + assert schain_id == Web3.to_bytes(hexstr=DEFAULT_SCHAIN_ID) def test_get_all_schains_ids(skale, schain): schains_ids = skale.schains_internal.get_all_schains_ids() schain = skale.schains.get(schains_ids[-1]) - assert list(schain.keys()) == FIELDS + assert [field.name for field in fields(schain)] == FIELDS def test_get_schain_price(skale): @@ -93,19 +89,19 @@ def test_add_schain_by_foundation(skale, nodes): ) schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name in schains_names new_schain = skale.schains.get_by_name(name) - assert new_schain['mainnetOwner'] == skale.wallet.address + assert new_schain.mainnetOwner == skale.wallet.address finally: skale.manager.delete_schain(name, wait_for=True) schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name not in schains_names @@ -130,7 +126,7 @@ def test_add_schain_by_foundation_with_options(skale, nodes): ), wait_for=True ) - schain = skale.schains.get_by_name(name, obj=True) + schain = skale.schains.get_by_name(name) assert schain.options.multitransaction_mode is True assert schain.options.threshold_encryption is False @@ -158,8 +154,8 @@ def test_add_schain_by_foundation_custom_owner(skale, nodes): ) new_schain = skale.schains.get_by_name(name) - assert new_schain['mainnetOwner'] != skale.wallet.address - assert new_schain['mainnetOwner'] == custom_wallet.address + assert new_schain.mainnetOwner != skale.wallet.address + assert new_schain.mainnetOwner == custom_wallet.address skale.wallet = custom_wallet finally: @@ -169,7 +165,7 @@ def test_add_schain_by_foundation_custom_owner(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name not in schains_names @@ -198,8 +194,8 @@ def test_add_schain_by_foundation_custom_originator(skale, nodes): ) new_schain = skale.schains.get_by_name(name) - assert new_schain['originator'] != skale.wallet.address - assert new_schain['originator'] == custom_originator.address + assert new_schain.originator != skale.wallet.address + assert new_schain.originator == custom_originator.address finally: if name: @@ -208,7 +204,7 @@ def test_add_schain_by_foundation_custom_originator(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name not in schains_names @@ -222,7 +218,7 @@ def test_get_active_schains_for_node(skale, nodes, schain): active_schains = skale.schains.get_active_schains_for_node(node_id) all_schains = skale.schains.get_schains_for_node(node_id) all_active_schains = [ - schain for schain in all_schains if schain['active']] + schain for schain in all_schains if schain.active] for active_schain in all_active_schains: assert active_schain in active_schains finally: From 0d339b422ea6e4b439a194d4b6e25c3c016e993b Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 30 Apr 2024 16:50:41 +0300 Subject: [PATCH 076/100] Add type hints to account tools --- skale/utils/account_tools.py | 45 +++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/skale/utils/account_tools.py b/skale/utils/account_tools.py index 4cb60e7f..f6e728b7 100644 --- a/skale/utils/account_tools.py +++ b/skale/utils/account_tools.py @@ -18,8 +18,10 @@ # along with SKALE.py. If not, see . """ Account utilities """ +from __future__ import annotations +from decimal import Decimal import logging -from typing import Any, Optional +from typing import Any, Dict, List, Literal, Optional, TYPE_CHECKING, Type, TypedDict from eth_typing import ChecksumAddress from web3 import Web3 @@ -35,18 +37,31 @@ default_gas_price, wait_for_confirmation_blocks ) -from skale.wallets.common import BaseWallet + +if TYPE_CHECKING: + from skale.skale_base import SkaleBase + from skale.wallets.common import BaseWallet + logger = logging.getLogger(__name__) -WALLET_TYPE_TO_CLASS = { +class AccountData(TypedDict): + address: ChecksumAddress + private_key: str + + +WALLET_TYPE_TO_CLASS: Dict[str, Type[LedgerWallet] | Type[Web3Wallet]] = { 'ledger': LedgerWallet, 'web3': Web3Wallet } -def create_wallet(wallet_type='web3', *args, **kwargs): +def create_wallet( + wallet_type: Literal['web3'] | Literal['ledger'] = 'web3', + *args: Any, + **kwargs: Any +) -> LedgerWallet | Web3Wallet: return WALLET_TYPE_TO_CLASS[wallet_type](*args, **kwargs) @@ -119,7 +134,7 @@ def account_eth_balance_wei(web3: Web3, address: ChecksumAddress) -> Wei: return web3.eth.get_balance(address) -def check_ether_balance(web3: Web3, address: ChecksumAddress) -> int: +def check_ether_balance(web3: Web3, address: ChecksumAddress) -> int | Decimal: balance_wei = account_eth_balance_wei(web3, address) balance = web3.from_wei(balance_wei, 'ether') @@ -127,26 +142,28 @@ def check_ether_balance(web3: Web3, address: ChecksumAddress) -> int: return balance -def check_skale_balance(skale: SkaleManager, address: ChecksumAddress) -> int: +def check_skale_balance(skale: SkaleManager, address: ChecksumAddress) -> int | Decimal: balance_wei = skale.token.get_balance(address) balance = skale.web3.from_wei(balance_wei, 'ether') logger.info(f'{address} balance: {balance} SKALE') return balance -def generate_account(web3): +def generate_account(web3: Web3) -> AccountData: account = web3.eth.account.create() private_key = account.key.hex() logger.info(f'Generated account: {account.address}') - return {'address': account.address, 'private_key': private_key} + return AccountData({'address': account.address, 'private_key': private_key}) -def generate_accounts(skale, - base_wallet, - n_wallets, - skale_amount, - eth_amount, - debug=False): +def generate_accounts( + skale: SkaleBase, + base_wallet: BaseWallet, + n_wallets: int, + skale_amount: Wei, + eth_amount: Wei, + debug: bool = False +) -> List[AccountData]: n_wallets = int(n_wallets) results = [] From 49583c08b3db706123c1b9c5b2ee6e3bef234788 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 30 Apr 2024 17:19:44 +0300 Subject: [PATCH 077/100] Add type hints to fake_multisig_contract --- skale/utils/account_tools.py | 5 ++--- .../contracts_provision/fake_multisig_contract.py | 14 +++++++------- skale/utils/contracts_provision/utils.py | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/skale/utils/account_tools.py b/skale/utils/account_tools.py index f6e728b7..6b057aeb 100644 --- a/skale/utils/account_tools.py +++ b/skale/utils/account_tools.py @@ -27,7 +27,6 @@ from web3 import Web3 from web3.types import TxReceipt, Wei -from skale.skale_manager import SkaleManager from skale.transactions.result import TxRes from skale.transactions.tools import compose_eth_transfer_tx from skale.utils.constants import LONG_LINE @@ -39,7 +38,7 @@ ) if TYPE_CHECKING: - from skale.skale_base import SkaleBase + from skale.skale_manager import SkaleManager from skale.wallets.common import BaseWallet @@ -157,7 +156,7 @@ def generate_account(web3: Web3) -> AccountData: def generate_accounts( - skale: SkaleBase, + skale: SkaleManager, base_wallet: BaseWallet, n_wallets: int, skale_amount: Wei, diff --git a/skale/utils/contracts_provision/fake_multisig_contract.py b/skale/utils/contracts_provision/fake_multisig_contract.py index 915e0f99..8277b341 100644 --- a/skale/utils/contracts_provision/fake_multisig_contract.py +++ b/skale/utils/contracts_provision/fake_multisig_contract.py @@ -20,11 +20,13 @@ import os import json -from skale.transactions.tools import transaction_from_method +from web3 import Web3 + from skale.utils.web3_utils import ( get_eth_nonce, wait_for_receipt_by_blocks ) +from skale.wallets.common import BaseWallet # Usage note: to change this contract update the code, compile it and put the new bytecode and # new ABI below @@ -76,22 +78,20 @@ FAKE_MULTISIG_CONSTRUCTOR_GAS = 1000000 -def deploy_fake_multisig_contract(web3, wallet): +def deploy_fake_multisig_contract(web3: Web3, wallet: BaseWallet) -> None: print('Going to deploy simple payable contract') FakeMultisigContract = web3.eth.contract(abi=FAKE_MULTISIG_ABI, bytecode=FAKE_MULTISIG_BYTECODE) constructor = FakeMultisigContract.constructor() - constructor.fn_name = 'fake_multisig_constructor' - tx = transaction_from_method( - constructor, + tx = constructor.build_transaction( nonce=get_eth_nonce(web3, wallet.address), gas_price=3 * 10 ** 9, gas_limit=FAKE_MULTISIG_CONSTRUCTOR_GAS ) tx_hash = wallet.sign_and_send(tx) receipt = wait_for_receipt_by_blocks(web3, tx_hash) - print(f'Sample contract successfully deployed: {receipt.contractAddress}') + print(f"Sample contract successfully deployed: {receipt['contractAddress']}") content = { - 'address': receipt.contractAddress, + 'address': receipt['contractAddress'], 'abi': FAKE_MULTISIG_ABI } with open(FAKE_MULTISIG_DATA_PATH, 'w') as outfile: diff --git a/skale/utils/contracts_provision/utils.py b/skale/utils/contracts_provision/utils.py index aae78c0d..08ef8b71 100644 --- a/skale/utils/contracts_provision/utils.py +++ b/skale/utils/contracts_provision/utils.py @@ -35,7 +35,7 @@ def generate_random_name(length: int = 8) -> str: def generate_random_port() -> Port: - return random.randint(0, 60000) + return Port(random.randint(0, 60000)) def generate_random_node_data() -> tuple[str, str, int, str]: From 44692032c5b1c3667a47a0e756f557379e16095f Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 2 May 2024 17:44:57 +0300 Subject: [PATCH 078/100] Add type hints to rotation_history --- skale/contracts/manager/node_rotation.py | 19 +--- skale/schain_config/rotation_history.py | 77 +++++++-------- skale/skale_manager.py | 96 ++++++++++++++++++- skale/types/dkg.py | 6 +- skale/types/rotation.py | 60 ++++++++++++ .../fake_multisig_contract.py | 10 +- .../rotation_history/rotation_history_test.py | 4 +- tests/rotation_history/utils.py | 13 ++- 8 files changed, 210 insertions(+), 75 deletions(-) create mode 100644 skale/types/rotation.py diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index 7662b602..5bed75a7 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -21,8 +21,7 @@ from __future__ import annotations import logging import functools -from typing import TYPE_CHECKING, List, TypedDict -from dataclasses import dataclass +from typing import TYPE_CHECKING, List from eth_typing import ChecksumAddress @@ -32,6 +31,7 @@ from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.node import NodeId +from skale.types.rotation import Rotation, RotationSwap from skale.types.schain import SchainHash, SchainName if TYPE_CHECKING: @@ -44,19 +44,6 @@ NO_PREVIOUS_NODE_EXCEPTION_TEXT = 'No previous node' -@dataclass -class Rotation: - leaving_node_id: int - new_node_id: int - freeze_until: int - rotation_counter: int - - -class RotationSwap(TypedDict): - schain_id: SchainHash - finished_rotation: int - - class NodeRotation(SkaleManagerContract): """Wrapper for NodeRotation.sol functions""" @@ -107,7 +94,7 @@ def is_rotation_active(self, schain_name: SchainName) -> bool: return self.is_rotation_in_progress(schain_name) and not finish_ts_reached def is_finish_ts_reached(self, schain_name: SchainName) -> bool: - rotation = self.skale.node_rotation.get_rotation_obj(schain_name) + rotation = self.skale.node_rotation.get_rotation(schain_name) schain_finish_ts = self.get_schain_finish_ts(rotation.leaving_node_id, schain_name) if not schain_finish_ts: diff --git a/skale/schain_config/rotation_history.py b/skale/schain_config/rotation_history.py index 81226708..4f4aeaa5 100644 --- a/skale/schain_config/rotation_history.py +++ b/skale/schain_config/rotation_history.py @@ -19,44 +19,26 @@ from __future__ import annotations import logging -from collections import namedtuple -from typing import TYPE_CHECKING, Optional, TypedDict +from typing import TYPE_CHECKING, Dict, List, TypedDict from skale import Skale from skale.contracts.manager.node_rotation import Rotation +from skale.types.rotation import BlsPublicKey, NodesGroup, RotationNodeData if TYPE_CHECKING: from skale.contracts.manager.key_storage import G2Point + from skale.skale_manager import SkaleManager + from skale.types.node import NodeId + from skale.types.schain import SchainName logger = logging.getLogger(__name__) -RotationNodeData = namedtuple('RotationNodeData', ['index', 'node_id', 'public_key']) - - -class NodesSwap(TypedDict): - leaving_node_id: int - new_node_id: int - - -class BlsPublicKey(TypedDict): - blsPublicKey0: str - blsPublicKey1: str - blsPublicKey2: str - blsPublicKey3: str - - -class NodesGroup(TypedDict): - rotation: NodesSwap | None - nodes: dict[int, RotationNodeData] - finish_ts: int | None - bls_public_key: BlsPublicKey | None - def get_previous_schain_groups( - skale: Skale, - schain_name: str, - leaving_node_id=None, -) -> dict: + skale: SkaleManager, + schain_name: SchainName, + leaving_node_id: NodeId | None = None, +) -> Dict[int, NodesGroup]: """ Returns all previous node groups with public keys and finish timestamps. In case of no rotations returns the current state. @@ -64,12 +46,12 @@ def get_previous_schain_groups( logger.info(f'Collecting rotation history for {schain_name}...') node_groups: dict[int, NodesGroup] = {} - group_id = skale.schains.name_to_group_id(schain_name) + group_id = skale.schains.name_to_id(schain_name) previous_public_keys = skale.key_storage.get_all_previous_public_keys(group_id) current_public_key = skale.key_storage.get_common_public_key(group_id) - rotation = skale.node_rotation.get_rotation_obj(schain_name) + rotation = skale.node_rotation.get_rotation(schain_name) logger.info(f'Rotation data for {schain_name}: {rotation}') @@ -93,9 +75,9 @@ def _add_current_schain_state( skale: Skale, node_groups: dict[int, NodesGroup], rotation: Rotation, - schain_name: str, + schain_name: SchainName, current_public_key: G2Point -): +) -> None: """ Internal function, composes the initial info about the current sChain state and adds it to the node_groups dictionary @@ -115,18 +97,23 @@ def _add_current_schain_state( def _add_previous_schain_rotations_state( - skale: Skale, - node_groups: dict, + skale: SkaleManager, + node_groups: dict[int, NodesGroup], rotation: Rotation, - schain_name: str, - previous_public_keys: list, - leaving_node_id=None -): + schain_name: SchainName, + previous_public_keys: list[G2Point], + leaving_node_id: NodeId | None = None +) -> None: """ Internal function, handles rotations from (rotation_counter - 2) to 0 and adds them to the node_groups dictionary """ - previous_nodes = {} + + class PreviousNodeData(TypedDict): + finish_ts: int + previous_node_id: NodeId + + previous_nodes: Dict[NodeId, PreviousNodeData] = {} for rotation_id in range(rotation.rotation_counter - 1, -1, -1): nodes = node_groups[rotation_id + 1]['nodes'].copy() @@ -136,7 +123,7 @@ def _add_previous_schain_rotations_state( if previous_node is not None: finish_ts = skale.node_rotation.get_schain_finish_ts(previous_node, schain_name) previous_nodes[node_id] = { - 'finish_ts': finish_ts, + 'finish_ts': finish_ts or 0, 'previous_node_id': previous_node } @@ -182,7 +169,7 @@ def _add_previous_schain_rotations_state( break -def _pop_previous_bls_public_key(previous_public_keys): +def _pop_previous_bls_public_key(previous_public_keys: List[G2Point]) -> BlsPublicKey | None: """ Returns BLS public key for the group and removes it from the list, returns None if node with provided node_id was kicked out of the chain because of failed DKG. @@ -193,7 +180,7 @@ def _pop_previous_bls_public_key(previous_public_keys): return bls_keys -def _compose_bls_public_key_info(bls_public_key: G2Point) -> Optional[BlsPublicKey]: +def _compose_bls_public_key_info(bls_public_key: G2Point) -> BlsPublicKey | None: if bls_public_key: return { 'blsPublicKey0': str(bls_public_key[0][0]), @@ -204,10 +191,14 @@ def _compose_bls_public_key_info(bls_public_key: G2Point) -> Optional[BlsPublicK return None -def get_new_nodes_list(skale: Skale, name: str, node_groups) -> list: +def get_new_nodes_list( + skale: SkaleManager, + name: SchainName, + node_groups: Dict[int, NodesGroup] +) -> list[NodeId]: """Returns list of new nodes in for the latest rotation""" logger.info(f'Getting new nodes list for chain {name}') - rotation = skale.node_rotation.get_rotation_obj(name) + rotation = skale.node_rotation.get_rotation(name) current_group_ids = node_groups[rotation.rotation_counter]['nodes'].keys() new_nodes = [] for index in node_groups: diff --git a/skale/skale_manager.py b/skale/skale_manager.py index dfa51e55..cf8599c3 100644 --- a/skale/skale_manager.py +++ b/skale/skale_manager.py @@ -27,10 +27,27 @@ from skale.utils.helper import get_contracts_info if TYPE_CHECKING: - from skale.contracts.manager.node_rotation import NodeRotation - from skale.contracts.manager.schains import SChains - from skale.contracts.manager.schains_internal import SChainsInternal - from skale.contracts.manager.token import Token + from skale.contracts.manager import BountyV2 + from skale.contracts.manager import ConstantsHolder + from skale.contracts.manager import ContractManager + from skale.contracts.manager import DelegationController + from skale.contracts.manager import DelegationPeriodManager + from skale.contracts.manager import Distributor + from skale.contracts.manager import DKG + from skale.contracts.manager import KeyStorage + from skale.contracts.manager import Manager + from skale.contracts.manager import NodeRotation + from skale.contracts.manager import Nodes + from skale.contracts.manager import Punisher + from skale.contracts.manager import SChains + from skale.contracts.manager import SChainsInternal + from skale.contracts.manager import SlashingTable + from skale.contracts.manager import SyncManager + from skale.contracts.manager import TimeHelpersWithDebug + from skale.contracts.manager import Token + from skale.contracts.manager import TokenState + from skale.contracts.manager import ValidatorService + from skale.contracts.manager import Wallets logger = logging.getLogger(__name__) @@ -42,7 +59,8 @@ class SkaleManager(SkaleBase): def project_name(self) -> str: return 'skale-manager' - def contracts_info(self) -> List[ContractInfo[SkaleManager]]: + @staticmethod + def contracts_info() -> List[ContractInfo[SkaleManager]]: import skale.contracts.manager as contracts return [ ContractInfo('contract_manager', 'ContractManager', @@ -88,10 +106,54 @@ def contracts_info(self) -> List[ContractInfo[SkaleManager]]: contracts.TimeHelpersWithDebug, ContractTypes.API, False) ] + @property + def bounty_v2(self) -> BountyV2: + return cast('BountyV2', self._get_contract('bounty_v2')) + + @property + def constants_holder(self) -> ConstantsHolder: + return cast('ConstantsHolder', self._get_contract('constants_holder')) + + @property + def contract_manager(self) -> ContractManager: + return cast('ContractManager', self._get_contract('contract_manager')) + + @property + def delegation_controller(self) -> DelegationController: + return cast('DelegationController', self._get_contract('delegation_controller')) + + @property + def delegation_period_manager(self) -> DelegationPeriodManager: + return cast('DelegationPeriodManager', self._get_contract('delegation_period_manager')) + + @property + def distributor(self) -> Distributor: + return cast('Distributor', self._get_contract('distributor')) + + @property + def dkg(self) -> DKG: + return cast('DKG', self._get_contract('dkg')) + + @property + def key_storage(self) -> KeyStorage: + return cast('KeyStorage', self._get_contract('key_storage')) + + @property + def manager(self) -> Manager: + return cast('Manager', self._get_contract('manager')) + @property def node_rotation(self) -> NodeRotation: return cast('NodeRotation', self._get_contract('node_rotation')) + @property + def nodes(self) -> Nodes: + return cast('Nodes', self._get_contract('nodes')) + + @property + def punisher(self) -> Punisher: + return cast('Punisher', self._get_contract('punisher')) + @property def schains(self) -> SChains: return cast('SChains', self._get_contract('schains')) @@ -100,10 +162,34 @@ def schains(self) -> SChains: def schains_internal(self) -> SChainsInternal: return cast('SChainsInternal', self._get_contract('schains_internal')) + @property + def slashing_table(self) -> SlashingTable: + return cast('SlashingTable', self._get_contract('slashing_table')) + + @property + def sync_manager(self) -> SyncManager: + return cast('SyncManager', self._get_contract('sync_manager')) + + @property + def time_helpers_with_debug(self) -> TimeHelpersWithDebug: + return cast('TimeHelpersWithDebug', self._get_contract('time_helpers_with_debug')) + @property def token(self) -> Token: return cast('Token', self._get_contract('token')) + @property + def token_state(self) -> TokenState: + return cast('TokenState', self._get_contract('token_state')) + + @property + def validator_service(self) -> ValidatorService: + return cast('ValidatorService', self._get_contract('validator_service')) + + @property + def wallets(self) -> Wallets: + return cast('Wallets', self._get_contract('wallets')) + def init_contract_manager(self) -> None: from skale.contracts.manager.contract_manager import ContractManager self.add_lib_contract('contract_manager', ContractManager, 'ContractManager') diff --git a/skale/types/dkg.py b/skale/types/dkg.py index a6b8f50e..27d13b45 100644 --- a/skale/types/dkg.py +++ b/skale/types/dkg.py @@ -20,6 +20,8 @@ from collections import namedtuple from typing import List, NamedTuple, NewType, Tuple +from eth_typing import HexStr + Fp2Point = namedtuple('Fp2Point', ['a', 'b']) @@ -33,5 +35,5 @@ class G2Point(NamedTuple): class KeyShare(NamedTuple): - publicKey: Tuple[bytes, bytes] - share: bytes + publicKey: Tuple[bytes | HexStr, bytes | HexStr] + share: bytes | HexStr diff --git a/skale/types/rotation.py b/skale/types/rotation.py new file mode 100644 index 00000000..78e22ad7 --- /dev/null +++ b/skale/types/rotation.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- +# +# This file is part of SKALE.py +# +# Copyright (C) 2024-Present SKALE Labs +# +# SKALE.py is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SKALE.py is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with SKALE.py. If not, see . + +from collections import namedtuple +from dataclasses import dataclass +from typing import TypedDict + +from skale.types.node import NodeId +from skale.types.schain import SchainHash + + +RotationNodeData = namedtuple('RotationNodeData', ['index', 'node_id', 'public_key']) + + +class NodesSwap(TypedDict): + leaving_node_id: NodeId + new_node_id: NodeId + + +class BlsPublicKey(TypedDict): + blsPublicKey0: str + blsPublicKey1: str + blsPublicKey2: str + blsPublicKey3: str + + +class NodesGroup(TypedDict): + rotation: NodesSwap | None + nodes: dict[NodeId, RotationNodeData] + finish_ts: int | None + bls_public_key: BlsPublicKey | None + + +@dataclass +class Rotation: + leaving_node_id: NodeId + new_node_id: NodeId + freeze_until: int + rotation_counter: int + + +class RotationSwap(TypedDict): + schain_id: SchainHash + finished_rotation: int diff --git a/skale/utils/contracts_provision/fake_multisig_contract.py b/skale/utils/contracts_provision/fake_multisig_contract.py index 8277b341..19ac2c25 100644 --- a/skale/utils/contracts_provision/fake_multisig_contract.py +++ b/skale/utils/contracts_provision/fake_multisig_contract.py @@ -82,11 +82,11 @@ def deploy_fake_multisig_contract(web3: Web3, wallet: BaseWallet) -> None: print('Going to deploy simple payable contract') FakeMultisigContract = web3.eth.contract(abi=FAKE_MULTISIG_ABI, bytecode=FAKE_MULTISIG_BYTECODE) constructor = FakeMultisigContract.constructor() - tx = constructor.build_transaction( - nonce=get_eth_nonce(web3, wallet.address), - gas_price=3 * 10 ** 9, - gas_limit=FAKE_MULTISIG_CONSTRUCTOR_GAS - ) + tx = constructor.build_transaction({ + 'nonce': get_eth_nonce(web3, wallet.address), + 'gasPrice': 3 * 10 ** 9, + 'gas': FAKE_MULTISIG_CONSTRUCTOR_GAS + }) tx_hash = wallet.sign_and_send(tx) receipt = wait_for_receipt_by_blocks(web3, tx_hash) print(f"Sample contract successfully deployed: {receipt['contractAddress']}") diff --git a/tests/rotation_history/rotation_history_test.py b/tests/rotation_history/rotation_history_test.py index a3c57725..4d39610b 100644 --- a/tests/rotation_history/rotation_history_test.py +++ b/tests/rotation_history/rotation_history_test.py @@ -237,7 +237,7 @@ def test_get_new_nodes_list(skale, four_node_schain): rotation_id=1 ) - rotation = skale.node_rotation.get_rotation_obj(name) + rotation = skale.node_rotation.get_rotation(name) node_groups = get_previous_schain_groups( skale=skale, schain_name=name, @@ -251,7 +251,7 @@ def test_get_new_nodes_list(skale, four_node_schain): exiting_node_index = 3 rotate_node(skale, group_index, nodes, skale_instances, exiting_node_index, rotation_id=4) - rotation = skale.node_rotation.get_rotation_obj(name) + rotation = skale.node_rotation.get_rotation(name) node_groups = get_previous_schain_groups( skale=skale, schain_name=name, diff --git a/tests/rotation_history/utils.py b/tests/rotation_history/utils.py index 0091dd13..1c9a0e9e 100644 --- a/tests/rotation_history/utils.py +++ b/tests/rotation_history/utils.py @@ -4,6 +4,7 @@ import json import logging +from skale.types.dkg import Fp2Point, G2Point, KeyShare from skale.utils.contracts_provision.main import _skip_evm_time from skale.utils.contracts_provision import DEFAULT_DOMAIN_NAME @@ -113,8 +114,16 @@ def send_broadcasts(nodes, skale_instances, group_index, skip_node_index=None, r skale_instances[i].dkg.broadcast( group_index, node['node_id'], - TEST_DKG_DATA['test_verification_vectors'][i], - TEST_DKG_DATA['test_encrypted_secret_key_contributions'][i], + [ + G2Point(*[ + Fp2Point(*fp2_point) for fp2_point in g2_point + ]) + for g2_point in TEST_DKG_DATA['test_verification_vectors'][i] + ], + [ + KeyShare(tuple(key_share[0]), key_share[1]) + for key_share in TEST_DKG_DATA['test_encrypted_secret_key_contributions'][i] + ], rotation_id ) else: From 6791264b024023a1c4409f1997653339e776b788 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 2 May 2024 17:47:50 +0300 Subject: [PATCH 079/100] Add type hints to ports_allocation --- skale/contracts/manager/delegation/delegation_controller.py | 4 ++-- skale/schain_config/ports_allocation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/skale/contracts/manager/delegation/delegation_controller.py b/skale/contracts/manager/delegation/delegation_controller.py index f95028c6..b54853fe 100644 --- a/skale/contracts/manager/delegation/delegation_controller.py +++ b/skale/contracts/manager/delegation/delegation_controller.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from typing import Any, Dict, List, cast +from typing import Any, Dict, List from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction @@ -120,7 +120,7 @@ def get_all_delegations(self, delegation_ids: List[DelegationId]) -> List[FullDe :rtype: list """ return [ - cast(DelegationController, self.skale.delegation_controller).get_delegation_full(_id) + self.skale.delegation_controller.get_delegation_full(_id) for _id in delegation_ids ] diff --git a/skale/schain_config/ports_allocation.py b/skale/schain_config/ports_allocation.py index 577cd7fc..70bbc716 100644 --- a/skale/schain_config/ports_allocation.py +++ b/skale/schain_config/ports_allocation.py @@ -25,12 +25,12 @@ def calc_schain_base_port(node_base_port: Port, schain_index: int) -> Port: - return node_base_port + schain_index * PORTS_PER_SCHAIN + return Port(node_base_port + schain_index * PORTS_PER_SCHAIN) def get_schain_index_in_node(schain_name: SchainName, node_schains: list[SchainStructure]) -> int: for index, schain in enumerate(node_schains): - if schain_name == schain['name']: + if schain_name == schain.name: return index raise SChainNotFoundException(f'sChain {schain_name} is not found in the list: {node_schains}') From 471ed0f69241b801aa809d3423728c494e769da7 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 2 May 2024 17:48:43 +0300 Subject: [PATCH 080/100] Add type hints to ports_allocation --- skale/schain_config/generator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skale/schain_config/generator.py b/skale/schain_config/generator.py index 3e9f66e5..8a52c96e 100644 --- a/skale/schain_config/generator.py +++ b/skale/schain_config/generator.py @@ -19,9 +19,10 @@ from skale.skale_manager import SkaleManager +from skale.types.schain import SchainName -def get_nodes_for_schain(skale: SkaleManager, name: str) -> list[int]: +def get_nodes_for_schain(skale: SkaleManager, name: SchainName) -> list[int]: nodes = [] ids = skale.schains_internal.get_node_ids_for_schain(name) for id_ in ids: From 3c6570fdd7eb3be3dda4a980a1d8b4dad898f7db Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 2 May 2024 18:06:22 +0300 Subject: [PATCH 081/100] Add type hints to generator --- skale/contracts/manager/nodes.py | 26 ++----------------- skale/schain_config/generator.py | 18 ++++++++----- skale/types/node.py | 37 ++++++++++++++++++++++++++- tests/schain_config/generator_test.py | 4 +-- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/skale/contracts/manager/nodes.py b/skale/contracts/manager/nodes.py index 5afac6af..2ca59a0f 100644 --- a/skale/contracts/manager/nodes.py +++ b/skale/contracts/manager/nodes.py @@ -19,8 +19,7 @@ """ Nodes.sol functions """ import socket -from enum import IntEnum -from typing import Any, Dict, List, Tuple, TypedDict, cast +from typing import Any, Dict, List, Tuple, cast from Crypto.Hash import keccak from eth_typing import BlockNumber, ChecksumAddress @@ -30,7 +29,7 @@ from skale.contracts.base_contract import transaction_method from skale.contracts.skale_manager_contract import SkaleManagerContract -from skale.types.node import NodeId, Port +from skale.types.node import Node, NodeId, NodeStatus, Port from skale.types.validator import ValidatorId from skale.utils.exceptions import InvalidNodeIdError from skale.utils.helper import format_fields @@ -41,27 +40,6 @@ ] -class NodeStatus(IntEnum): - ACTIVE = 0 - LEAVING = 1 - LEFT = 2 - IN_MAINTENANCE = 3 - - -class Node(TypedDict): - name: str - ip: bytes - publicIP: bytes - port: Port - start_block: BlockNumber - last_reward_date: int - finish_time: int - status: NodeStatus - validator_id: ValidatorId - publicKey: str - domain_name: str - - class Nodes(SkaleManagerContract): def __get_raw(self, node_id: NodeId) -> List[Any]: try: diff --git a/skale/schain_config/generator.py b/skale/schain_config/generator.py index 8a52c96e..0a6e5c20 100644 --- a/skale/schain_config/generator.py +++ b/skale/schain_config/generator.py @@ -19,22 +19,26 @@ from skale.skale_manager import SkaleManager +from skale.types.node import NodeWithId, NodeWithSchains from skale.types.schain import SchainName -def get_nodes_for_schain(skale: SkaleManager, name: SchainName) -> list[int]: +def get_nodes_for_schain(skale: SkaleManager, name: SchainName) -> list[NodeWithId]: nodes = [] ids = skale.schains_internal.get_node_ids_for_schain(name) for id_ in ids: node = skale.nodes.get(id_) - node['id'] = id_ - nodes.append(node) + nodes.append(NodeWithId(id=id_, **node)) return nodes -def get_schain_nodes_with_schains(skale: SkaleManager, schain_name: str) -> list: +def get_schain_nodes_with_schains( + skale: SkaleManager, + schain_name: SchainName +) -> list[NodeWithSchains]: """Returns list of nodes for schain with schains for all nodes""" nodes = get_nodes_for_schain(skale, schain_name) - for node in nodes: - node['schains'] = skale.schains.get_schains_for_node(node['id']) - return nodes + return [ + NodeWithSchains(schains=skale.schains.get_schains_for_node(node['id']), **node) + for node in nodes + ] diff --git a/skale/types/node.py b/skale/types/node.py index 3e51984b..8f91316a 100644 --- a/skale/types/node.py +++ b/skale/types/node.py @@ -17,8 +17,43 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from typing import NewType +from enum import IntEnum +from typing import List, NewType, TypedDict + +from eth_typing import BlockNumber + +from skale.types.schain import SchainStructureWithStatus +from skale.types.validator import ValidatorId NodeId = NewType("NodeId", int) Port = NewType("Port", int) + + +class NodeStatus(IntEnum): + ACTIVE = 0 + LEAVING = 1 + LEFT = 2 + IN_MAINTENANCE = 3 + + +class Node(TypedDict): + name: str + ip: bytes + publicIP: bytes + port: Port + start_block: BlockNumber + last_reward_date: int + finish_time: int + status: NodeStatus + validator_id: ValidatorId + publicKey: str + domain_name: str + + +class NodeWithId(Node): + id: NodeId + + +class NodeWithSchains(NodeWithId): + schains: List[SchainStructureWithStatus] diff --git a/tests/schain_config/generator_test.py b/tests/schain_config/generator_test.py index 4cfefb9b..aaebd186 100644 --- a/tests/schain_config/generator_test.py +++ b/tests/schain_config/generator_test.py @@ -10,11 +10,11 @@ def test_get_nodes_for_schain(skale, schain): fields_with_id.append('id') assert len(schain_nodes) >= MIN_NODES_IN_SCHAIN - assert list(schain_nodes[0].keys()) == fields_with_id + assert set(schain_nodes[0].keys()) == set(fields_with_id) def test_get_schain_nodes_with_schains(skale, schain): schain_name = schain nodes_with_schains = get_schain_nodes_with_schains(skale, schain_name) assert isinstance(nodes_with_schains[0]['schains'], list) - assert isinstance(nodes_with_schains[0]['schains'][0]['mainnetOwner'], str) + assert isinstance(nodes_with_schains[0]['schains'][0].mainnetOwner, str) From 804d0d5adbbc7cc55ba7eba5bea664336144a020 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 2 May 2024 18:19:33 +0300 Subject: [PATCH 082/100] Add type hints to contracts_provision --- skale/utils/contracts_provision/__init__.py | 7 +++++-- skale/utils/contracts_provision/allocator.py | 4 ++-- skale/utils/contracts_provision/main.py | 18 +++++++++++++----- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/skale/utils/contracts_provision/__init__.py b/skale/utils/contracts_provision/__init__.py index bf403f69..c4f6b32e 100644 --- a/skale/utils/contracts_provision/__init__.py +++ b/skale/utils/contracts_provision/__init__.py @@ -17,7 +17,10 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from web3.types import Wei + from skale.contracts.allocator.allocator import TimeUnit +from skale.types.validator import ValidatorId # manager test constants @@ -29,7 +32,7 @@ DEFAULT_DOMAIN_NAME = 'skale.test' INITIAL_DELEGATION_PERIOD = 2 -D_VALIDATOR_ID = 1 +D_VALIDATOR_ID = ValidatorId(1) D_VALIDATOR_NAME = 'test' D_VALIDATOR_DESC = 'test' D_VALIDATOR_FEE = 10 @@ -60,6 +63,6 @@ POLL_INTERVAL = 2 -TEST_SKALE_AMOUNT = 100000 +TEST_SKALE_AMOUNT = Wei(100000) D_PLAN_ID = 1 diff --git a/skale/utils/contracts_provision/allocator.py b/skale/utils/contracts_provision/allocator.py index ef30d181..b7d4dc15 100644 --- a/skale/utils/contracts_provision/allocator.py +++ b/skale/utils/contracts_provision/allocator.py @@ -20,7 +20,7 @@ from time import sleep from web3.contract.contract import ContractEvent -from web3.types import LogReceipt +from web3.types import LogReceipt, Wei from web3._utils.filters import LogFilter from skale.skale_allocator import SkaleAllocator @@ -48,7 +48,7 @@ def _catch_event(event_obj: ContractEvent) -> LogReceipt: def transfer_tokens_to_allocator( skale_manager: SkaleManager, skale_allocator: SkaleAllocator, - amount: int = TEST_SKALE_AMOUNT + amount: Wei = TEST_SKALE_AMOUNT ) -> None: send_tokens(skale_manager, skale_allocator.allocator.address, amount) diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index 6292d2bc..a1e64c94 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -17,6 +17,8 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . +from typing import Tuple +from eth_typing import ChecksumAddress from web3 import Web3 from web3.types import RPCEndpoint @@ -24,6 +26,8 @@ from skale.dataclasses.schain_options import SchainOptions from skale.skale_manager import SkaleManager from skale.transactions.result import TxRes +from skale.types.node import NodeId +from skale.types.validator import ValidatorId from skale.utils.contracts_provision import ( D_VALIDATOR_ID, D_VALIDATOR_MIN_DEL, @@ -75,7 +79,7 @@ def add_test_permissions(skale: SkaleManager) -> None: add_all_permissions(skale, skale.wallet.address) -def add_all_permissions(skale: SkaleManager, address: str) -> None: +def add_all_permissions(skale: SkaleManager, address: ChecksumAddress) -> None: default_admin_role = skale.manager.default_admin_role() if not skale.manager.has_role(default_admin_role, address): skale.manager.grant_role(default_admin_role, address) @@ -157,7 +161,7 @@ def add_test4_schain_type(skale: SkaleManager) -> TxRes: ) -def cleanup_nodes(skale: SkaleManager, ids: list[int] = []) -> None: +def cleanup_nodes(skale: SkaleManager, ids: list[NodeId] = []) -> None: active_ids = filter( lambda i: skale.nodes.get_node_status(i) == NodeStatus.ACTIVE, ids or skale.nodes.get_active_node_ids() @@ -249,7 +253,11 @@ def link_address_to_validator(skale: SkaleManager) -> None: tx_res.raise_for_status() -def link_nodes_to_validator(skale: SkaleManager, validator_id: int, node_skale_objs=()) -> None: +def link_nodes_to_validator( + skale: SkaleManager, + validator_id: ValidatorId, + node_skale_objs: Tuple[SkaleManager] | None = None +) -> None: print('Linking address to validator') node_skale_objs = node_skale_objs or (skale,) validator_id = validator_id or D_VALIDATOR_ID @@ -322,11 +330,11 @@ def create_validator(skale: SkaleManager) -> None: ) -def create_nodes(skales, names: tuple[str, str] | None = None) -> list[int]: +def create_nodes(skale: SkaleManager, names: tuple[str, str] | None = None) -> list[int]: # create couple of nodes print('Creating two nodes') node_names = names or (DEFAULT_NODE_NAME, SECOND_NODE_NAME) - for skale, name in zip(skales, node_names): + for skale, name in zip((skale, skale), node_names): ip, public_ip, port, _ = generate_random_node_data() skale.manager.create_node( ip=ip, From a602983b1083f7387e99684d8cf98f99f38fd702 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Thu, 2 May 2024 18:50:15 +0300 Subject: [PATCH 083/100] Fix imports --- skale/contracts/allocator/__init__.py | 2 + skale/contracts/allocator/allocator.py | 2 +- skale/contracts/base_contract.py | 4 +- skale/contracts/ima/__init__.py | 4 + skale/contracts/manager/__init__.py | 24 +++++ skale/contracts/manager/key_storage.py | 2 +- skale/contracts/manager/manager.py | 2 +- skale/schain_config/ports_allocation.py | 3 +- skale/schain_config/rotation_history.py | 9 +- skale/skale_manager.py | 109 +++++++++----------- skale/utils/contracts_provision/__init__.py | 2 +- skale/utils/contracts_provision/main.py | 3 +- skale/utils/web3_utils.py | 10 +- skale/wallets/__init__.py | 6 ++ skale/wallets/web3_wallet.py | 2 +- 15 files changed, 98 insertions(+), 86 deletions(-) diff --git a/skale/contracts/allocator/__init__.py b/skale/contracts/allocator/__init__.py index 5d14ee33..6365e525 100644 --- a/skale/contracts/allocator/__init__.py +++ b/skale/contracts/allocator/__init__.py @@ -2,3 +2,5 @@ from skale.contracts.allocator.escrow import Escrow from skale.contracts.allocator.allocator import Allocator + +__all__ = ['Allocator', 'Escrow'] diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index 882aa30d..51b8dc35 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -23,11 +23,11 @@ from eth_typing import ChecksumAddress from web3 import Web3 from web3.contract.contract import ContractFunction +from web3.exceptions import ContractLogicError from web3.types import Wei from skale.contracts.allocator_contract import AllocatorContract from skale.contracts.base_contract import transaction_method -from skale.transactions.exceptions import ContractLogicError from skale.types.allocation import BeneficiaryStatus, Plan, PlanId, PlanWithId, TimeUnit from skale.utils.helper import format_fields diff --git a/skale/contracts/base_contract.py b/skale/contracts/base_contract.py index 27b4dd0d..48323c44 100644 --- a/skale/contracts/base_contract.py +++ b/skale/contracts/base_contract.py @@ -28,8 +28,8 @@ from web3.types import ABI, Nonce, Wei import skale.config as config -from skale.transactions.result import TxRes -from skale.transactions.tools import make_dry_run_call, transaction_from_method, TxStatus +from skale.transactions.result import TxRes, TxStatus +from skale.transactions.tools import make_dry_run_call, transaction_from_method from skale.utils.web3_utils import ( DEFAULT_BLOCKS_TO_WAIT, get_eth_nonce, diff --git a/skale/contracts/ima/__init__.py b/skale/contracts/ima/__init__.py index 8395fab4..465bdd4d 100644 --- a/skale/contracts/ima/__init__.py +++ b/skale/contracts/ima/__init__.py @@ -4,3 +4,7 @@ from skale.contracts.base_contract import BaseContract, transaction_method from skale.contracts.ima.linker import Linker + +__all__ = [ + 'Linker' +] diff --git a/skale/contracts/manager/__init__.py b/skale/contracts/manager/__init__.py index eb0d530f..93f126e2 100644 --- a/skale/contracts/manager/__init__.py +++ b/skale/contracts/manager/__init__.py @@ -31,3 +31,27 @@ from skale.contracts.manager.sync_manager import SyncManager from skale.contracts.manager.test.time_helpers_with_debug import TimeHelpersWithDebug + +__all__ = [ + 'BountyV2', + 'ConstantsHolder', + 'ContractManager', + 'DelegationController', + 'DelegationPeriodManager', + 'Distributor', + 'DKG', + 'KeyStorage', + 'Manager', + 'NodeRotation', + 'Nodes', + 'Punisher', + 'SChains', + 'SChainsInternal', + 'SlashingTable', + 'SyncManager', + 'TimeHelpersWithDebug', + 'Token', + 'TokenState', + 'ValidatorService', + 'Wallets' +] diff --git a/skale/contracts/manager/key_storage.py b/skale/contracts/manager/key_storage.py index ff4a3a2f..575655ae 100644 --- a/skale/contracts/manager/key_storage.py +++ b/skale/contracts/manager/key_storage.py @@ -18,7 +18,7 @@ # along with SKALE.py. If not, see . from typing import List -from skale.contracts.manager.dkg import G2Point +from skale.types.dkg import G2Point from skale.contracts.skale_manager_contract import SkaleManagerContract from skale.types.schain import SchainHash diff --git a/skale/contracts/manager/manager.py b/skale/contracts/manager/manager.py index 3e9156db..5c364fa9 100644 --- a/skale/contracts/manager/manager.py +++ b/skale/contracts/manager/manager.py @@ -22,7 +22,7 @@ import logging import socket -from eth_abi import encode +from eth_abi.abi import encode from eth_typing import ChecksumAddress from web3.contract.contract import ContractFunction diff --git a/skale/schain_config/ports_allocation.py b/skale/schain_config/ports_allocation.py index 70bbc716..6c6526f0 100644 --- a/skale/schain_config/ports_allocation.py +++ b/skale/schain_config/ports_allocation.py @@ -17,9 +17,8 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from skale.contracts.manager.schains import SchainStructure from skale.types.node import Port -from skale.types.schain import SchainName +from skale.types.schain import SchainName, SchainStructure from skale.utils.exceptions import SChainNotFoundException from skale.schain_config import PORTS_PER_SCHAIN diff --git a/skale/schain_config/rotation_history.py b/skale/schain_config/rotation_history.py index 4f4aeaa5..7c826ba0 100644 --- a/skale/schain_config/rotation_history.py +++ b/skale/schain_config/rotation_history.py @@ -21,14 +21,11 @@ import logging from typing import TYPE_CHECKING, Dict, List, TypedDict -from skale import Skale -from skale.contracts.manager.node_rotation import Rotation -from skale.types.rotation import BlsPublicKey, NodesGroup, RotationNodeData - if TYPE_CHECKING: - from skale.contracts.manager.key_storage import G2Point from skale.skale_manager import SkaleManager + from skale.types.dkg import G2Point from skale.types.node import NodeId + from skale.types.rotation import BlsPublicKey, NodesGroup, Rotation, RotationNodeData from skale.types.schain import SchainName logger = logging.getLogger(__name__) @@ -72,7 +69,7 @@ def get_previous_schain_groups( def _add_current_schain_state( - skale: Skale, + skale: SkaleManager, node_groups: dict[int, NodesGroup], rotation: Rotation, schain_name: SchainName, diff --git a/skale/skale_manager.py b/skale/skale_manager.py index cf8599c3..ce12c927 100644 --- a/skale/skale_manager.py +++ b/skale/skale_manager.py @@ -27,27 +27,7 @@ from skale.utils.helper import get_contracts_info if TYPE_CHECKING: - from skale.contracts.manager import BountyV2 - from skale.contracts.manager import ConstantsHolder - from skale.contracts.manager import ContractManager - from skale.contracts.manager import DelegationController - from skale.contracts.manager import DelegationPeriodManager - from skale.contracts.manager import Distributor - from skale.contracts.manager import DKG - from skale.contracts.manager import KeyStorage - from skale.contracts.manager import Manager - from skale.contracts.manager import NodeRotation - from skale.contracts.manager import Nodes - from skale.contracts.manager import Punisher - from skale.contracts.manager import SChains - from skale.contracts.manager import SChainsInternal - from skale.contracts.manager import SlashingTable - from skale.contracts.manager import SyncManager - from skale.contracts.manager import TimeHelpersWithDebug - from skale.contracts.manager import Token - from skale.contracts.manager import TokenState - from skale.contracts.manager import ValidatorService - from skale.contracts.manager import Wallets + import skale.contracts.manager as contracts logger = logging.getLogger(__name__) @@ -107,88 +87,91 @@ def contracts_info() -> List[ContractInfo[SkaleManager]]: ] @property - def bounty_v2(self) -> BountyV2: - return cast('BountyV2', self._get_contract('bounty_v2')) + def bounty_v2(self) -> contracts.BountyV2: + return cast('contracts.BountyV2', self._get_contract('bounty_v2')) @property - def constants_holder(self) -> ConstantsHolder: - return cast('ConstantsHolder', self._get_contract('constants_holder')) + def constants_holder(self) -> contracts.ConstantsHolder: + return cast('contracts.ConstantsHolder', self._get_contract('constants_holder')) @property - def contract_manager(self) -> ContractManager: - return cast('ContractManager', self._get_contract('contract_manager')) + def contract_manager(self) -> contracts.ContractManager: + return cast('contracts.ContractManager', self._get_contract('contract_manager')) @property - def delegation_controller(self) -> DelegationController: - return cast('DelegationController', self._get_contract('delegation_controller')) + def delegation_controller(self) -> contracts.DelegationController: + return cast('contracts.DelegationController', self._get_contract('delegation_controller')) @property - def delegation_period_manager(self) -> DelegationPeriodManager: - return cast('DelegationPeriodManager', self._get_contract('delegation_period_manager')) + def delegation_period_manager(self) -> contracts.DelegationPeriodManager: + return cast( + 'contracts.DelegationPeriodManager', + self._get_contract('delegation_period_manager') + ) @property - def distributor(self) -> Distributor: - return cast('Distributor', self._get_contract('distributor')) + def distributor(self) -> contracts.Distributor: + return cast('contracts.Distributor', self._get_contract('distributor')) @property - def dkg(self) -> DKG: - return cast('DKG', self._get_contract('dkg')) + def dkg(self) -> contracts.DKG: + return cast('contracts.DKG', self._get_contract('dkg')) @property - def key_storage(self) -> KeyStorage: - return cast('KeyStorage', self._get_contract('key_storage')) + def key_storage(self) -> contracts.KeyStorage: + return cast('contracts.KeyStorage', self._get_contract('key_storage')) @property - def manager(self) -> Manager: - return cast('Manager', self._get_contract('manager')) + def manager(self) -> contracts.Manager: + return cast('contracts.Manager', self._get_contract('manager')) @property - def node_rotation(self) -> NodeRotation: - return cast('NodeRotation', self._get_contract('node_rotation')) + def node_rotation(self) -> contracts.NodeRotation: + return cast('contracts.NodeRotation', self._get_contract('node_rotation')) @property - def nodes(self) -> Nodes: - return cast('Nodes', self._get_contract('nodes')) + def nodes(self) -> contracts.Nodes: + return cast('contracts.Nodes', self._get_contract('nodes')) @property - def punisher(self) -> Punisher: - return cast('Punisher', self._get_contract('punisher')) + def punisher(self) -> contracts.Punisher: + return cast('contracts.Punisher', self._get_contract('punisher')) @property - def schains(self) -> SChains: - return cast('SChains', self._get_contract('schains')) + def schains(self) -> contracts.SChains: + return cast('contracts.SChains', self._get_contract('schains')) @property - def schains_internal(self) -> SChainsInternal: - return cast('SChainsInternal', self._get_contract('schains_internal')) + def schains_internal(self) -> contracts.SChainsInternal: + return cast('contracts.SChainsInternal', self._get_contract('schains_internal')) @property - def slashing_table(self) -> SlashingTable: - return cast('SlashingTable', self._get_contract('slashing_table')) + def slashing_table(self) -> contracts.SlashingTable: + return cast('contracts.SlashingTable', self._get_contract('slashing_table')) @property - def sync_manager(self) -> SyncManager: - return cast('SyncManager', self._get_contract('sync_manager')) + def sync_manager(self) -> contracts.SyncManager: + return cast('contracts.SyncManager', self._get_contract('sync_manager')) @property - def time_helpers_with_debug(self) -> TimeHelpersWithDebug: - return cast('TimeHelpersWithDebug', self._get_contract('time_helpers_with_debug')) + def time_helpers_with_debug(self) -> contracts.TimeHelpersWithDebug: + return cast('contracts.TimeHelpersWithDebug', self._get_contract('time_helpers_with_debug')) @property - def token(self) -> Token: - return cast('Token', self._get_contract('token')) + def token(self) -> contracts.Token: + return cast('contracts.Token', self._get_contract('token')) @property - def token_state(self) -> TokenState: - return cast('TokenState', self._get_contract('token_state')) + def token_state(self) -> contracts.TokenState: + return cast('contracts.TokenState', self._get_contract('token_state')) @property - def validator_service(self) -> ValidatorService: - return cast('ValidatorService', self._get_contract('validator_service')) + def validator_service(self) -> contracts.ValidatorService: + return cast('contracts.ValidatorService', self._get_contract('validator_service')) @property - def wallets(self) -> Wallets: - return cast('Wallets', self._get_contract('wallets')) + def wallets(self) -> contracts.Wallets: + return cast('contracts.Wallets', self._get_contract('wallets')) def init_contract_manager(self) -> None: from skale.contracts.manager.contract_manager import ContractManager diff --git a/skale/utils/contracts_provision/__init__.py b/skale/utils/contracts_provision/__init__.py index c4f6b32e..645f9aa5 100644 --- a/skale/utils/contracts_provision/__init__.py +++ b/skale/utils/contracts_provision/__init__.py @@ -19,7 +19,7 @@ from web3.types import Wei -from skale.contracts.allocator.allocator import TimeUnit +from skale.types.allocation import TimeUnit from skale.types.validator import ValidatorId # manager test constants diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index a1e64c94..17508312 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -22,11 +22,10 @@ from web3 import Web3 from web3.types import RPCEndpoint -from skale.contracts.manager.nodes import NodeStatus from skale.dataclasses.schain_options import SchainOptions from skale.skale_manager import SkaleManager from skale.transactions.result import TxRes -from skale.types.node import NodeId +from skale.types.node import NodeId, NodeStatus from skale.types.validator import ValidatorId from skale.utils.contracts_provision import ( D_VALIDATOR_ID, diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index 65214a55..9d4f4c95 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -24,15 +24,13 @@ from typing import Any, Callable, Dict, Iterable from urllib.parse import urlparse -from eth_keys import keys +from eth_keys.main import lazy_key_api as keys from eth_typing import Address, AnyAddress, BlockNumber, ChecksumAddress, HexStr from web3 import Web3, WebsocketProvider, HTTPProvider from web3.exceptions import TransactionNotFound -from web3.middleware import ( - attrdict_middleware, - geth_poa_middleware, - http_retry_request_middleware -) +from web3.middleware.attrdict import attrdict_middleware +from web3.middleware.exception_retry_request import http_retry_request_middleware +from web3.middleware.geth_poa import geth_poa_middleware from web3.providers.base import JSONBaseProvider from web3.types import ( _Hash32, diff --git a/skale/wallets/__init__.py b/skale/wallets/__init__.py index a5d7b685..dcced7a2 100644 --- a/skale/wallets/__init__.py +++ b/skale/wallets/__init__.py @@ -5,3 +5,9 @@ from skale.wallets.redis_wallet import RedisWalletAdapter from skale.wallets.sgx_wallet import SgxWallet from skale.wallets.web3_wallet import Web3Wallet + +__all__ = [ + 'BaseWallet', + 'LedgerWallet', + 'Web3Wallet' +] diff --git a/skale/wallets/web3_wallet.py b/skale/wallets/web3_wallet.py index fc81716e..9b822e56 100644 --- a/skale/wallets/web3_wallet.py +++ b/skale/wallets/web3_wallet.py @@ -18,7 +18,7 @@ # along with SKALE.py. If not, see . from typing import cast -from eth_keys import keys +from eth_keys.main import lazy_key_api as keys from eth_keys.datatypes import PublicKey from web3 import Web3 from web3.types import _Hash32, TxParams, TxReceipt From 0452f86220b7158e1968535acbbc618d434e22a1 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 3 May 2024 17:19:39 +0300 Subject: [PATCH 084/100] Fix main_test --- tests/main_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/main_test.py b/tests/main_test.py index f4408e30..b24d82ad 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -82,8 +82,8 @@ def test_get_contract_address(skale): def test_get_attr(skale): - random_attr = skale.t123_random_attr - assert random_attr is None + with pytest.raises(ValueError): + random_attr = skale.t123_random_attr skale_py_nodes_contract = skale.nodes assert issubclass(type(skale_py_nodes_contract), BaseContract) assert isinstance(skale_py_nodes_contract, Nodes) From 7c8f23b9b86087aa6db56f0b6e534493bf9c5d48 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 3 May 2024 18:16:07 +0300 Subject: [PATCH 085/100] Fix dkg_test --- skale/utils/contracts_provision/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index 17508312..4ca0f4bc 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Affero General Public License # along with SKALE.py. If not, see . -from typing import Tuple +from typing import List, Tuple from eth_typing import ChecksumAddress from web3 import Web3 from web3.types import RPCEndpoint @@ -187,7 +187,7 @@ def cleanup_nodes_schains(skale: SkaleManager) -> None: def create_clean_schain(skale: SkaleManager) -> str: cleanup_nodes_schains(skale) - create_nodes(skale) + create_nodes([skale]) return create_schain(skale, random_name=True) @@ -329,11 +329,11 @@ def create_validator(skale: SkaleManager) -> None: ) -def create_nodes(skale: SkaleManager, names: tuple[str, str] | None = None) -> list[int]: +def create_nodes(skales: List[SkaleManager], names: List[str] | None = None) -> list[int]: # create couple of nodes print('Creating two nodes') node_names = names or (DEFAULT_NODE_NAME, SECOND_NODE_NAME) - for skale, name in zip((skale, skale), node_names): + for skale, name in zip(skales, node_names): ip, public_ip, port, _ = generate_random_node_data() skale.manager.create_node( ip=ip, @@ -344,7 +344,7 @@ def create_nodes(skale: SkaleManager, names: tuple[str, str] | None = None) -> l wait_for=True ) ids = [ - skale.nodes.node_name_to_index(name) + skales[0].nodes.node_name_to_index(name) for name in node_names ] return ids From d77c6079837cad2d1d150f032eefb842a1f5e008 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 3 May 2024 18:57:54 +0300 Subject: [PATCH 086/100] Fix node_rotation_test --- skale/contracts/manager/node_rotation.py | 6 ++++-- tests/rotation_history/utils.py | 22 ++++++++++++---------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/skale/contracts/manager/node_rotation.py b/skale/contracts/manager/node_rotation.py index 5bed75a7..66db202e 100644 --- a/skale/contracts/manager/node_rotation.py +++ b/skale/contracts/manager/node_rotation.py @@ -69,10 +69,12 @@ def get_leaving_history(self, node_id: NodeId) -> List[RotationSwap]: return history def get_schain_finish_ts(self, node_id: NodeId, schain_name: SchainName) -> int | None: - raw_history = self.contract.functions.getLeavingHistory(node_id).call() + history = self.get_leaving_history(node_id) schain_id = self.skale.schains.name_to_id(schain_name) finish_ts = next( - (schain[1] for schain in raw_history if '0x' + schain[0].hex() == schain_id), None) + (swap['finished_rotation'] for swap in history if swap['schain_id'] == schain_id), + None + ) if not finish_ts: return None return int(finish_ts) diff --git a/tests/rotation_history/utils.py b/tests/rotation_history/utils.py index 1c9a0e9e..2791b9b9 100644 --- a/tests/rotation_history/utils.py +++ b/tests/rotation_history/utils.py @@ -111,19 +111,21 @@ def init_skale_from_wallet(wallet) -> Skale: def send_broadcasts(nodes, skale_instances, group_index, skip_node_index=None, rotation_id=0): for i, node in enumerate(nodes): if i != skip_node_index: + verification_vector = [ + G2Point(*[ + Fp2Point(*fp2_point) for fp2_point in g2_point + ]) + for g2_point in TEST_DKG_DATA['test_verification_vectors'][i] + ] + secret_key_contribution = [ + KeyShare(tuple(key_share[0]), key_share[1]) + for key_share in TEST_DKG_DATA['test_encrypted_secret_key_contributions'][i] + ] skale_instances[i].dkg.broadcast( group_index, node['node_id'], - [ - G2Point(*[ - Fp2Point(*fp2_point) for fp2_point in g2_point - ]) - for g2_point in TEST_DKG_DATA['test_verification_vectors'][i] - ], - [ - KeyShare(tuple(key_share[0]), key_share[1]) - for key_share in TEST_DKG_DATA['test_encrypted_secret_key_contributions'][i] - ], + verification_vector, + secret_key_contribution, rotation_id ) else: From 24fc09286be67644513801bb3513827d13b4816d Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 7 May 2024 16:44:25 +0300 Subject: [PATCH 087/100] Remove unused variable --- tests/main_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main_test.py b/tests/main_test.py index b24d82ad..45d6a445 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -83,7 +83,7 @@ def test_get_contract_address(skale): def test_get_attr(skale): with pytest.raises(ValueError): - random_attr = skale.t123_random_attr + skale.t123_random_attr skale_py_nodes_contract = skale.nodes assert issubclass(type(skale_py_nodes_contract), BaseContract) assert isinstance(skale_py_nodes_contract, Nodes) From 2ff9ba133e047fe12b2d700abbdf15d191a7e7fe Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 20 May 2024 16:42:48 +0300 Subject: [PATCH 088/100] Update skale manager tests --- tests/manager/manager_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/manager/manager_test.py b/tests/manager/manager_test.py index 2c7349e9..6ecdc5c7 100644 --- a/tests/manager/manager_test.py +++ b/tests/manager/manager_test.py @@ -69,7 +69,7 @@ def test_create_delete_schain(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name in schains_names @@ -81,7 +81,7 @@ def test_create_delete_schain(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name not in schains_names @@ -100,7 +100,7 @@ def test_delete_schain_by_root(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name not in schains_names @@ -117,7 +117,7 @@ def test_create_delete_default_schain(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name in schains_names @@ -129,7 +129,7 @@ def test_create_delete_default_schain(skale, nodes): schains_ids_after = skale.schains_internal.get_all_schains_ids() schains_names = [ - skale.schains.get(sid)['name'] + skale.schains.get(sid).name for sid in schains_ids_after ] assert name not in schains_names From d75f9f542bb44ae0196b230f1182c72f5f57e58f Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 20 May 2024 17:35:16 +0300 Subject: [PATCH 089/100] Fix rotation history tests --- skale/schain_config/rotation_history.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skale/schain_config/rotation_history.py b/skale/schain_config/rotation_history.py index 7c826ba0..2c123afb 100644 --- a/skale/schain_config/rotation_history.py +++ b/skale/schain_config/rotation_history.py @@ -20,12 +20,13 @@ from __future__ import annotations import logging from typing import TYPE_CHECKING, Dict, List, TypedDict +from skale.types.rotation import RotationNodeData if TYPE_CHECKING: from skale.skale_manager import SkaleManager from skale.types.dkg import G2Point from skale.types.node import NodeId - from skale.types.rotation import BlsPublicKey, NodesGroup, Rotation, RotationNodeData + from skale.types.rotation import BlsPublicKey, NodesGroup, Rotation from skale.types.schain import SchainName logger = logging.getLogger(__name__) From 10b1762a190d08cfeaccab73f072497a5e5059a2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 20 May 2024 17:55:10 +0300 Subject: [PATCH 090/100] Fix redis adapter test --- tests/wallets/redis_adapter_test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/wallets/redis_adapter_test.py b/tests/wallets/redis_adapter_test.py index 7bc8d879..fb954ef6 100644 --- a/tests/wallets/redis_adapter_test.py +++ b/tests/wallets/redis_adapter_test.py @@ -3,6 +3,7 @@ from unittest import mock import pytest from freezegun import freeze_time +from web3 import Web3 from skale.wallets.redis_wallet import ( RedisWalletNotSentError, @@ -64,8 +65,8 @@ def test_sign_and_send(rdp): 'nonce': 1, 'chainId': 1 } - tx_id = rdp.sign_and_send(tx, multiplier=2, priority=5) - assert tx_id.startswith('tx-') and len(tx_id) == 19 + tx_id = Web3.to_bytes(hexstr=rdp.sign_and_send(tx, multiplier=2, priority=5)) + assert tx_id.startswith(b'tx-') and len(tx_id) == 19 rdp.rs.pipeline = mock.Mock(side_effect=RedisTestError('rtest')) with pytest.raises(RedisWalletNotSentError): From a8efa3f14e2550efda25361e52705db4f8cf3c8a Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 20 May 2024 18:06:58 +0300 Subject: [PATCH 091/100] Update SGX test --- tests/wallets/sgx_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/wallets/sgx_test.py b/tests/wallets/sgx_test.py index b1fe7132..71dcc8b8 100644 --- a/tests/wallets/sgx_test.py +++ b/tests/wallets/sgx_test.py @@ -44,6 +44,7 @@ def test_sgx_sign(wallet): def test_sgx_sign_and_send_without_nonce(wallet): send_tx_mock = mock.Mock() + send_tx_mock.return_value = HexBytes('') wallet._web3.eth.send_raw_transaction = send_tx_mock wallet._web3.eth.get_transaction_count = mock.Mock(return_value=0) tx_dict = { From 038b99c452aaf79ea477f2675988c8dc57fe53b2 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 20 May 2024 19:37:16 +0300 Subject: [PATCH 092/100] Fix bug with beneficiary plan --- skale/contracts/allocator/allocator.py | 25 +++++++++++++++++++++---- skale/types/allocation.py | 11 +++++++++++ tests/allocator/escrow_test.py | 5 +++-- 3 files changed, 35 insertions(+), 6 deletions(-) diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index 51b8dc35..184b21c9 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -28,7 +28,14 @@ from skale.contracts.allocator_contract import AllocatorContract from skale.contracts.base_contract import transaction_method -from skale.types.allocation import BeneficiaryStatus, Plan, PlanId, PlanWithId, TimeUnit +from skale.types.allocation import ( + BeneficiaryStatus, + BeneficiaryPlan, + Plan, + PlanId, + PlanWithId, + TimeUnit +) from skale.utils.helper import format_fields @@ -134,17 +141,17 @@ def __get_beneficiary_plan_params_raw(self, beneficiary_address: ChecksumAddress def get_beneficiary_plan_params_dict(self, beneficiary_address: ChecksumAddress) -> List[Any]: return self.__get_beneficiary_plan_params_raw(beneficiary_address) - def get_beneficiary_plan_params(self, beneficiary_address: ChecksumAddress) -> Plan: + def get_beneficiary_plan_params(self, beneficiary_address: ChecksumAddress) -> BeneficiaryPlan: plan_params = self.get_beneficiary_plan_params_dict(beneficiary_address) if plan_params is None: raise ValueError('Plan for ', beneficiary_address, ' is missing') if isinstance(plan_params, list): - return self._to_plan({ + return self._to_beneficiary_plan({ **plan_params[0], 'statusName': BeneficiaryStatus(plan_params[0]['status']).name }) if isinstance(plan_params, dict): - return self._to_plan({ + return self._to_beneficiary_plan({ **plan_params, 'statusName': BeneficiaryStatus(plan_params['status']).name }) @@ -199,3 +206,13 @@ def _to_plan(self, untyped_plan: Dict[str, Any]) -> Plan: 'isDelegationAllowed': bool(untyped_plan['isDelegationAllowed']), 'isTerminatable': bool(untyped_plan['isTerminatable']) }) + + def _to_beneficiary_plan(self, untyped_beneficiary_plan: Dict[str, Any]) -> BeneficiaryPlan: + return BeneficiaryPlan({ + 'status': BeneficiaryStatus(untyped_beneficiary_plan['status']), + 'statusName': str(untyped_beneficiary_plan['statusName']), + 'planId': PlanId(untyped_beneficiary_plan['planId']), + 'startMonth': int(untyped_beneficiary_plan['startMonth']), + 'fullAmount': Wei(untyped_beneficiary_plan['fullAmount']), + 'amountAfterLockup': Wei(untyped_beneficiary_plan['amountAfterLockup']) + }) diff --git a/skale/types/allocation.py b/skale/types/allocation.py index 5b8ef712..b61765c9 100644 --- a/skale/types/allocation.py +++ b/skale/types/allocation.py @@ -20,6 +20,8 @@ from enum import IntEnum from typing import NewType, TypedDict +from web3.types import Wei + class TimeUnit(IntEnum): DAY = 0 @@ -48,3 +50,12 @@ class Plan(TypedDict): class PlanWithId(Plan): planId: PlanId + + +class BeneficiaryPlan(TypedDict): + status: BeneficiaryStatus + statusName: str + planId: PlanId + startMonth: int + fullAmount: Wei + amountAfterLockup: Wei diff --git a/tests/allocator/escrow_test.py b/tests/allocator/escrow_test.py index 89eda5c7..1d44db54 100644 --- a/tests/allocator/escrow_test.py +++ b/tests/allocator/escrow_test.py @@ -1,5 +1,6 @@ """ Tests for skale/allocator/escrow.py """ +from skale.types.delegation import DelegationStatus from skale.wallets.web3_wallet import generate_wallet from skale.utils.account_tools import send_eth, check_skale_balance @@ -87,7 +88,7 @@ def test_request_undelegate(skale, skale_allocator): validator_id=D_VALIDATOR_ID ) assert delegations[-1]['id'] == delegation_id - assert delegations[-1]['status'] == 'UNDELEGATION_REQUESTED' + assert delegations[-1]['status'] == DelegationStatus.UNDELEGATION_REQUESTED def test_retrieve(skale, skale_allocator): @@ -169,4 +170,4 @@ def test_cancel_pending_delegation(skale_allocator, skale): validator_id=D_VALIDATOR_ID ) assert delegations[-1]['id'] == delegation_id - assert delegations[-1]['status'] == 'CANCELED' + assert delegations[-1]['status'] == DelegationStatus.CANCELED From 52c91ce24fcfbedb5002bd79134b161cc7bebecb Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 20 May 2024 19:58:32 +0300 Subject: [PATCH 093/100] Fix codacy --- skale/contracts/allocator/allocator.py | 2 +- skale/utils/contracts_provision/main.py | 2 +- skale/utils/helper.py | 4 ++-- skale/utils/web3_utils.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index 184b21c9..76e3c495 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -153,7 +153,7 @@ def get_beneficiary_plan_params(self, beneficiary_address: ChecksumAddress) -> B if isinstance(plan_params, dict): return self._to_beneficiary_plan({ **plan_params, - 'statusName': BeneficiaryStatus(plan_params['status']).name + 'statusName': BeneficiaryStatus(plan_params.get('status', 0)).name }) raise TypeError(beneficiary_address) diff --git a/skale/utils/contracts_provision/main.py b/skale/utils/contracts_provision/main.py index 4ca0f4bc..b554903e 100644 --- a/skale/utils/contracts_provision/main.py +++ b/skale/utils/contracts_provision/main.py @@ -160,7 +160,7 @@ def add_test4_schain_type(skale: SkaleManager) -> TxRes: ) -def cleanup_nodes(skale: SkaleManager, ids: list[NodeId] = []) -> None: +def cleanup_nodes(skale: SkaleManager, ids: list[NodeId] | None = None) -> None: active_ids = filter( lambda i: skale.nodes.get_node_status(i) == NodeStatus.ACTIVE, ids or skale.nodes.get_active_node_ids() diff --git a/skale/utils/helper.py b/skale/utils/helper.py index 4d8e6797..2d52c40c 100644 --- a/skale/utils/helper.py +++ b/skale/utils/helper.py @@ -151,9 +151,9 @@ def generate_random_ip() -> str: # pragma: no cover return '.'.join('%s' % random.randint(0, 255) for i in range(4)) -def generate_random_name(len: int = 8) -> str: # pragma: no cover +def generate_random_name(length: int = 8) -> str: # pragma: no cover return ''.join( - random.choices(string.ascii_uppercase + string.digits, k=len)) + random.choices(string.ascii_uppercase + string.digits, k=length)) def generate_random_port() -> Port: # pragma: no cover diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index 9d4f4c95..a8f72a99 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -63,7 +63,7 @@ def get_provider( endpoint: str, timeout: int = DEFAULT_HTTP_TIMEOUT, - request_kwargs: Dict[str, Any] = {} + request_kwargs: Dict[str, Any] = {'max_size': WS_MAX_MESSAGE_DATA_BYTES} ) -> JSONBaseProvider: scheme = urlparse(endpoint).scheme if scheme == 'ws' or scheme == 'wss': From f340829083e79c4beb5c6339fae26229badcc0ad Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 21 May 2024 11:06:03 +0300 Subject: [PATCH 094/100] Fix default argument --- skale/utils/web3_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skale/utils/web3_utils.py b/skale/utils/web3_utils.py index a8f72a99..06fd6ce7 100644 --- a/skale/utils/web3_utils.py +++ b/skale/utils/web3_utils.py @@ -63,7 +63,7 @@ def get_provider( endpoint: str, timeout: int = DEFAULT_HTTP_TIMEOUT, - request_kwargs: Dict[str, Any] = {'max_size': WS_MAX_MESSAGE_DATA_BYTES} + request_kwargs: Dict[str, Any] | None = None ) -> JSONBaseProvider: scheme = urlparse(endpoint).scheme if scheme == 'ws' or scheme == 'wss': @@ -72,7 +72,7 @@ def get_provider( websocket_kwargs=kwargs) if scheme == 'http' or scheme == 'https': - kwargs = {'timeout': timeout, **request_kwargs} + kwargs = {'timeout': timeout, **(request_kwargs or {})} return HTTPProvider(endpoint, request_kwargs=kwargs) raise Exception( From 2d641cbbc26d1f0a6d6432a2cef7544c7941c9a9 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 21 May 2024 12:10:25 +0300 Subject: [PATCH 095/100] Fix delegation test --- tests/manager/delegation/delegation_controller_test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/manager/delegation/delegation_controller_test.py b/tests/manager/delegation/delegation_controller_test.py index 2e1e425e..3885298c 100644 --- a/tests/manager/delegation/delegation_controller_test.py +++ b/tests/manager/delegation/delegation_controller_test.py @@ -35,7 +35,7 @@ def _delegate_and_activate(skale, validator_id=D_VALIDATOR_ID): delegations[-1]['id'], wait_for=True ) - _skip_evm_time(skale.web3, MONTH_IN_SECONDS, mine=False) + _skip_evm_time(skale.web3, MONTH_IN_SECONDS) def _get_number_of_delegations(skale, validator_id=D_VALIDATOR_ID): @@ -85,9 +85,6 @@ def test_delegate(skale, validator): ) assert delegations[-1]['info'] == D_DELEGATION_INFO - delegated_now_after = skale.delegation_controller.get_delegated_to_validator_now( - validator_id - ) delegated_now_after = skale.delegation_controller.get_delegated_to_validator_now( validator_id ) From 4916eda1cc91880dd9add88746877a0d65d9e060 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Tue, 21 May 2024 12:42:26 +0300 Subject: [PATCH 096/100] Fix runtime type checking --- skale/wallets/redis_wallet.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/skale/wallets/redis_wallet.py b/skale/wallets/redis_wallet.py index 9cbf2902..95b34f6c 100644 --- a/skale/wallets/redis_wallet.py +++ b/skale/wallets/redis_wallet.py @@ -26,8 +26,7 @@ from typing import Optional, Tuple, TypedDict from eth_account.datastructures import SignedMessage, SignedTransaction -from eth_typing import ChecksumAddress, Hash32, HexStr -from hexbytes import HexBytes +from eth_typing import ChecksumAddress, HexStr from redis import Redis from web3 import Web3 from web3.types import _Hash32, TxParams, TxReceipt @@ -146,13 +145,9 @@ def _make_record( @classmethod def _to_raw_id(cls, tx_id: _Hash32) -> bytes: - if type(tx_id) == HexStr: - return tx_id.encode('utf-8') - if type(tx_id) == Hash32: - return bytes(tx_id) - if type(tx_id) == HexBytes: - return bytes(tx_id) - raise ValueError('tx_id has unknown type', type(tx_id)) + if isinstance(tx_id, str): + return Web3.to_bytes(hexstr=tx_id) + return Web3.to_bytes(tx_id) @classmethod def _to_id(cls, raw_id: bytes) -> HexStr: From d3c6fbc9caa814a9f2370a57a664c7f4dd2c6c18 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 24 May 2024 15:15:42 +0300 Subject: [PATCH 097/100] Improve exception description --- skale/contracts/allocator/allocator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skale/contracts/allocator/allocator.py b/skale/contracts/allocator/allocator.py index 76e3c495..22496a2f 100644 --- a/skale/contracts/allocator/allocator.py +++ b/skale/contracts/allocator/allocator.py @@ -155,7 +155,7 @@ def get_beneficiary_plan_params(self, beneficiary_address: ChecksumAddress) -> B **plan_params, 'statusName': BeneficiaryStatus(plan_params.get('status', 0)).name }) - raise TypeError(beneficiary_address) + raise TypeError(f'Internal error on getting plan params for ${beneficiary_address}') def __get_plan_raw(self, plan_id: PlanId) -> List[Any]: return list(self.contract.functions.getPlan(plan_id).call()) From 9487667872ce91566eb06376eac300b193dccd27 Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 24 May 2024 15:16:40 +0300 Subject: [PATCH 098/100] Remove unneeded comments --- skale/contracts/manager/delegation/validator_service.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/skale/contracts/manager/delegation/validator_service.py b/skale/contracts/manager/delegation/validator_service.py index fd157b81..534e759a 100644 --- a/skale/contracts/manager/delegation/validator_service.py +++ b/skale/contracts/manager/delegation/validator_service.py @@ -77,8 +77,6 @@ def get_with_id(self, _id: ValidatorId) -> ValidatorWithId: """ validator = self.get(_id) return ValidatorWithId({'id': _id, **validator}) - # validator['id'] = _id - # return validator def number_of_validators(self) -> int: """Returns number of registered validators. From 04a831c066c9c15584e54227d1a14b1b2e6e3c9f Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Fri, 24 May 2024 15:17:55 +0300 Subject: [PATCH 099/100] Move declaration --- skale/schain_config/rotation_history.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/skale/schain_config/rotation_history.py b/skale/schain_config/rotation_history.py index 2c123afb..daebea61 100644 --- a/skale/schain_config/rotation_history.py +++ b/skale/schain_config/rotation_history.py @@ -32,6 +32,11 @@ logger = logging.getLogger(__name__) +class PreviousNodeData(TypedDict): + finish_ts: int + previous_node_id: NodeId + + def get_previous_schain_groups( skale: SkaleManager, schain_name: SchainName, @@ -107,10 +112,6 @@ def _add_previous_schain_rotations_state( node_groups dictionary """ - class PreviousNodeData(TypedDict): - finish_ts: int - previous_node_id: NodeId - previous_nodes: Dict[NodeId, PreviousNodeData] = {} for rotation_id in range(rotation.rotation_counter - 1, -1, -1): From f408e018465f86b27e09887770765284229b865d Mon Sep 17 00:00:00 2001 From: Dmytro Stebaiev Date: Mon, 27 May 2024 14:59:10 +0300 Subject: [PATCH 100/100] Fix indention --- skale/schain_config/rotation_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skale/schain_config/rotation_history.py b/skale/schain_config/rotation_history.py index daebea61..93213b92 100644 --- a/skale/schain_config/rotation_history.py +++ b/skale/schain_config/rotation_history.py @@ -33,8 +33,8 @@ class PreviousNodeData(TypedDict): - finish_ts: int - previous_node_id: NodeId + finish_ts: int + previous_node_id: NodeId def get_previous_schain_groups(