From f90f96b52b1dd3a0023aef4ffd5d98fba62496ff Mon Sep 17 00:00:00 2001 From: Dalena Date: Wed, 1 Feb 2023 12:23:44 -0800 Subject: [PATCH] chore: upgrade to 0.6 (#128) --- .github/workflows/commitlint.yaml | 2 +- .gitignore | 5 +++-- .pre-commit-config.yaml | 2 +- README.md | 10 ++-------- ape_starknet/accounts/__init__.py | 29 ++++++++++++++++++++--------- ape_starknet/ecosystems.py | 29 +++++++++++++++++------------ ape_starknet/provider.py | 21 +++++++++++++-------- ape_starknet/tokens.py | 6 ++---- ape_starknet/transactions.py | 15 +++++++++------ pyproject.toml | 2 +- setup.py | 12 ++++++------ 11 files changed, 75 insertions(+), 58 deletions(-) diff --git a/.github/workflows/commitlint.yaml b/.github/workflows/commitlint.yaml index 142ebb94..8e6c7580 100644 --- a/.github/workflows/commitlint.yaml +++ b/.github/workflows/commitlint.yaml @@ -21,7 +21,7 @@ jobs: - name: Install Dependencies run: | python -m pip install --upgrade pip - pip install .[dev] + pip install commitizen - name: Check commit history run: cz check --rev-range $(git rev-list --all --reverse | head -1)..HEAD diff --git a/.gitignore b/.gitignore index 7273276d..7622e4e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -.DS_Store - # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -125,3 +123,6 @@ version.py # IDEs stuff .vscode +**/.DS_Store +*.swp +*.swo diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d559331..33cf3cd1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: isort - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black name: black diff --git a/README.md b/README.md index 93fa62eb..9b2f38bd 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ This only works if the account has been funded. For convenience purposes, if you have another account with funds, you can use that account to fund the deployment of this one using the `--funder` option: ```bash -ape starknet account deploy --network testnet --funder +ape starknet accounts deploy --network testnet --funder ``` **NOTE**: You cannot use an Ethereum account to send funds to a Starknet account directly. @@ -152,7 +152,7 @@ The `delete` command differs based on its values of `--network` and `--address`: - To delete all deployments on a given network, use the `--network` option without `--address`. - To delete all deployments matching an address (regardless of network), use the `--address` option without `--network`. -- To delete a deployment on a network with a particular address, use both `--network` and \`--address. +- To delete a deployment on a network with a particular address, use both `--network` and `--address`. - Exclude both options to delete the whole account. Note you can also specify multiple networks, the same as `import`. @@ -385,12 +385,6 @@ Via an Environment Variable: export ALPHA_MAINNET_WL_DEPLOY_TOKEN="MY_TOKEN" ``` -Or, via the `--token` flag when deploying an account: - -```bash -ape starknet accounts create MY_ACCOUNT --token MY_TOKEN -``` - ## Development This project is in development and should be considered a beta. diff --git a/ape_starknet/accounts/__init__.py b/ape_starknet/accounts/__init__.py index 511dee7d..4ec975ed 100644 --- a/ape_starknet/accounts/__init__.py +++ b/ape_starknet/accounts/__init__.py @@ -538,7 +538,9 @@ def __repr__(self): def add_deployment(self, network_name: str, contract_address: int, salt: int): pass - def call(self, txn: TransactionAPI, send_everything: bool = False) -> ReceiptAPI: + def call( + self, txn: TransactionAPI, send_everything: bool = False, **signer_options + ) -> ReceiptAPI: if send_everything: raise NotImplementedError("send_everything currently isn't implemented in Starknet.") @@ -612,8 +614,9 @@ def deploy_account( if balance < txn.max_fee: # Use funder to provide the rest. - amount = txn.max_fee - balance + amount = ceil((txn.max_fee - balance) * FEE_MARGIN_OF_ESTIMATION) self.tokens.transfer(funder, txn.contract_address, amount) + logger.success("Account has been funded.") elif balance < txn.max_fee: raise StarknetAccountsError("Unable to afford transaction.") @@ -629,7 +632,9 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: txn.max_fee = txn.max_fee or MAX_FEE txn = self._prepare_transaction(txn) - txn.signature = self.sign_transaction(txn) + signed_txn = self.sign_transaction(txn) + if signed_txn is not None: + return cast(TransactionAPI, signed_txn) return txn def _prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: @@ -647,7 +652,7 @@ def _prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: def get_fee_estimate(self, txn: TransactionAPI) -> int: return self.provider.estimate_gas_cost(txn) - def handle_signature(self, sign_result, txn: TransactionAPI) -> TransactionSignature: + def handle_signature(self, sign_result, txn: TransactionAPI) -> TransactionAPI: if not sign_result: raise SignatureError("Failed to sign transaction.") @@ -655,7 +660,7 @@ def handle_signature(self, sign_result, txn: TransactionAPI) -> TransactionSigna s = to_bytes(sign_result[1]) txn.signature = TransactionSignature(v=0, r=r, s=s) self.check_signature(txn) - return txn.signature + return txn def transfer( self, @@ -790,7 +795,7 @@ def deployments(self) -> List[StarknetAccountDeployment]: def constructor_calldata(self) -> List[Any]: return self.custom_constructor_calldata or super().constructor_calldata - def sign_transaction(self, txn: TransactionAPI) -> TransactionSignature: + def sign_transaction(self, txn: TransactionAPI, **signer_options) -> Optional[TransactionAPI]: if not isinstance(txn, AccountTransaction): raise StarknetAccountsError( f"This account can only sign Starknet transactions (received={type(txn)}." @@ -908,7 +913,9 @@ def public_key(self) -> str: def nonce(self) -> int: return super().nonce if self.deployed else 0 - def sign_transaction(self, txn: TransactionAPI) -> TransactionSignature: + def sign_transaction( + self, txn: TransactionAPI, **signer_optins: Any + ) -> Optional[TransactionAPI]: if not isinstance(txn, AccountTransaction): raise StarknetAccountsError( f"This account can only sign Starknet transactions (received={type(txn)}." @@ -972,14 +979,18 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: logger.debug("Fee-estimation related autosign enabled temporarily.") original_value = self.__autosign self.__autosign = True - txn.signature = self.sign_transaction(txn) + signed_txn = self.sign_transaction(txn) + if signed_txn is not None: + signed_txn = cast(TransactionAPI, signed_txn) self.__autosign = original_value txn.max_fee = ceil(self.get_fee_estimate(txn) * FEE_MARGIN_OF_ESTIMATION) # Only sign the transaction if not aborting. # This is the real and final signature. - txn.signature = self.sign_transaction(txn) + signed_txn = self.sign_transaction(txn) + if signed_txn is not None: + return cast(TransactionAPI, signed_txn) return txn def write( diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index ad6a8b8e..41751909 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -134,22 +134,24 @@ def decode_returndata(self, abi: MethodABI, raw_data: List[int]) -> Any: # type return decoded - def encode_calldata( + def encode_calldata(self, abi: Union[ConstructorABI, MethodABI], *args) -> List: # type: ignore + full_abi = abi.contract_type.abi if abi.contract_type is not None else [abi] + return self._encode_calldata(full_abi=full_abi, abi=abi, call_args=args) + + def _encode_calldata( self, full_abi: List, - method_abi: Union[ConstructorABI, MethodABI], - call_args: Union[List, Tuple], + abi: Union[ConstructorABI, MethodABI], + call_args, ) -> List: full_abi = [abi.dict() if hasattr(abi, "dict") else abi for abi in full_abi] - call_serializer = FunctionCallSerializer( - method_abi.dict(), identifier_manager_from_abi(full_abi) - ) + call_serializer = FunctionCallSerializer(abi.dict(), identifier_manager_from_abi(full_abi)) pre_encoded_args: List[Any] = [] index = 0 - last_index = min(len(method_abi.inputs), len(call_args)) - 1 + last_index = min(len(abi.inputs), len(call_args)) - 1 did_process_array_during_arr_len = False - for call_arg, input_type in zip(call_args, method_abi.inputs): + for call_arg, input_type in zip(call_args, abi.inputs): if str(input_type.type).endswith("*"): if did_process_array_during_arr_len: did_process_array_during_arr_len = False @@ -161,7 +163,7 @@ def encode_calldata( input_type.name is not None and input_type.name.endswith("_len") and index < last_index - and str(method_abi.inputs[index + 1].type).endswith("*") + and str(abi.inputs[index + 1].type).endswith("*") ): pre_encoded_arg = self._pre_encode_value(call_arg) @@ -180,7 +182,7 @@ def encode_calldata( index += 1 calldata, _ = call_serializer.from_python(*pre_encoded_args) - return calldata + return list(calldata) def _pre_encode_value(self, value: Any) -> Any: if isinstance(value, dict): @@ -263,7 +265,7 @@ def encode_deployment( "Unable to encode deployment - missing full contract type for constructor." ) - constructor_arguments = self.encode_calldata(contract_type.abi, abi, args) + constructor_arguments = self._encode_calldata(contract_type.abi, abi, args) return self.universal_deployer.create_deploy(class_hash, constructor_arguments, **kwargs) def encode_transaction( @@ -275,7 +277,7 @@ def encode_transaction( raise ContractTypeNotFoundError(address) arguments = list(args) - encoded_calldata = self.encode_calldata(contract_type.abi, abi, arguments) + encoded_calldata = self._encode_calldata(contract_type.abi, abi, arguments) return InvokeFunctionTransaction( receiver=address, method_abi=abi, @@ -450,3 +452,6 @@ def _get_proxy_info( def decode_primitive_value(self, value: Any, output_type: Union[str, Tuple, List]) -> int: return to_int(value) + + def decode_calldata(self, abi: Union[ConstructorABI, MethodABI], calldata: bytes) -> Dict: + raise NotImplementedError() diff --git a/ape_starknet/provider.py b/ape_starknet/provider.py index 9f0098cd..6fa314f5 100644 --- a/ape_starknet/provider.py +++ b/ape_starknet/provider.py @@ -1,16 +1,17 @@ import os from dataclasses import asdict -from typing import Dict, Iterator, List, Optional, Union, cast +from typing import Any, Dict, Iterator, List, Optional, Union, cast from urllib.error import HTTPError from urllib.parse import urlparse from urllib.request import urlopen from ape.api import BlockAPI, ProviderAPI, ReceiptAPI, SubprocessProvider, TransactionAPI from ape.api.networks import LOCAL_NETWORK_NAME -from ape.exceptions import ProviderNotConnectedError, TransactionError +from ape.exceptions import ProviderNotConnectedError, TransactionError, VirtualMachineError from ape.logging import logger from ape.types import AddressType, BlockID, ContractLog, LogFilter, RawAddress from ape.utils import DEFAULT_NUMBER_OF_TEST_ACCOUNTS, cached_property, raises_not_implemented +from hexbytes import HexBytes from requests import Session from starknet_py.net.client_errors import ContractNotFoundError from starknet_py.net.client_models import ( @@ -42,7 +43,6 @@ get_chain_id, get_class_hash, get_dict_from_tx_info, - handle_client_error, handle_client_errors, run_until_complete, to_checksum_address, @@ -157,9 +157,10 @@ def get_balance(self, address: AddressType) -> int: return self.tokens.get_balance(address) @handle_client_errors - def get_code(self, address: str) -> List[int]: + def get_code(self, address: str) -> bytes: # NOTE: Always return truthy value for code so that Ape core works properly - return self.get_code_and_abi(address).bytecode or [ord(c) for c in "PROXY"] + code = self.get_code_and_abi(address).bytecode or [ord(c) for c in "PROXY"] + return b"".join([HexBytes(x) for x in code]) @handle_client_errors def get_abi(self, address: str) -> List[Dict]: @@ -189,7 +190,9 @@ def estimate_gas_cost(self, txn: StarknetTransaction) -> int: if not txn.signature: # Signature is required to estimate gas, unfortunately. # the transaction is typically signed by this point, but not always. - txn.signature = self.account_manager[txn.receiver].sign_transaction(txn) + signed_txn = self.account_manager[txn.receiver].sign_transaction(txn) + if signed_txn is not None: + txn = cast(StarknetTransaction, signed_txn) starknet_object = txn.as_starknet_object() estimated_fee = self.connected_client.estimate_fee_sync(starknet_object) @@ -263,6 +266,7 @@ def get_receipt( self.starknet_client.get_transaction(txn_hash), self.starknet_client.get_transaction_receipt(tx_hash=txn_hash), ) + data = {**asdict(receipt), **get_dict_from_tx_info(txn_info)} was_deploy = False # Handle __execute__ overhead. User only cares for target ABI. @@ -389,8 +393,9 @@ def prepare_transaction(self, txn: TransactionAPI) -> TransactionAPI: return txn - def get_virtual_machine_error(self, exception: Exception): - return handle_client_error(exception) + def get_virtual_machine_error(self, exception: Exception, **kwargs: Any) -> VirtualMachineError: + txn = kwargs.get("txn") + return VirtualMachineError(base_err=exception, txn=txn) def get_code_and_abi(self, address: Union[str, AddressType, int]) -> ContractCode: address_int = parse_address(address) diff --git a/ape_starknet/tokens.py b/ape_starknet/tokens.py index 1c13bc82..5e498b41 100644 --- a/ape_starknet/tokens.py +++ b/ape_starknet/tokens.py @@ -248,9 +248,6 @@ def get_balance( address = cast(AddressType, account) address_int = to_int(address) - - # Al - network = self.provider.network.name if not self.cache_enabled.get(network, False): # Strictly use provider. @@ -291,13 +288,14 @@ def transfer( receiver: Union[int, AddressType, "BaseStarknetAccount"], amount: int, token: str = STARKNET_FEE_TOKEN_SYMBOL.lower(), + **kwargs, ): receiver_int = to_int(receiver) sender_account = cast( "BaseStarknetAccount", (self.account_container[sender] if isinstance(sender, (int, str)) else sender), ) - result = self[token].transfer(receiver_int, amount, sender=sender_account) + result = self[token].transfer(receiver_int, amount, sender=sender_account, **kwargs) # NOTE: the fees paid by the sender get updated in `provider.send_transaction()`. amount_int = self._convert_amount_to_int(amount) diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index 5c50252f..e5615c1e 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -1,6 +1,6 @@ from copy import deepcopy from dataclasses import asdict -from typing import Any, Dict, Iterator, List, Optional +from typing import Any, Dict, List, Optional from ape.api import ReceiptAPI, TransactionAPI from ape.exceptions import APINotImplementedError, TransactionError @@ -236,7 +236,7 @@ def as_execute(self) -> "InvokeFunctionTransaction": } full_abi = OPEN_ZEPPELIN_ACCOUNT_CONTRACT_TYPE.abi entire_call_data = [[account_call], stark_tx.calldata] - new_tx.data = new_tx.starknet.encode_calldata(full_abi, EXECUTE_ABI, entire_call_data) + new_tx.data = new_tx.starknet._encode_calldata(full_abi, EXECUTE_ABI, entire_call_data) if new_tx.sender: new_tx.receiver = new_tx.sender @@ -330,7 +330,7 @@ def total_fees_paid(self) -> int: @raises_not_implemented def decode_logs( # type: ignore[empty-body] self, abi: Optional[ContractEventABI] = None - ) -> Iterator[ContractLog]: + ) -> List[ContractLog]: # Overriden in InvocationReceipt pass @@ -391,7 +391,7 @@ def return_value(self) -> Any: def decode_logs( self, abi: Optional[ContractEventABI] = None, - ) -> Iterator[ContractLog]: + ) -> List[ContractLog]: log_data_items: List[Dict] = [] for log in self.logs: @@ -408,7 +408,7 @@ def decode_logs( abi = [abi] event_abis: List[EventABI] = [a.abi if not isinstance(a, EventABI) else a for a in abi] - yield from self.starknet.decode_logs(log_data_items, *event_abis) + return list(self.starknet.decode_logs(log_data_items, *event_abis)) else: # If ABI is not provided, decode all events @@ -422,6 +422,8 @@ def decode_logs( address: {get_selector_from_name(e.name): e for e in contract.events} for address, contract in contract_types.items() } + + decoded_logs: List[ContractLog] = [] for log in log_data_items: contract_address = address_map[log["from_address"]] if contract_address not in selectors: @@ -429,7 +431,8 @@ def decode_logs( for event_key in log.get("keys", []): event_abi = selectors[contract_address][event_key] - yield from self.starknet.decode_logs([log], event_abi) + decoded_logs.extend(list(self.starknet.decode_logs([log], event_abi))) + return decoded_logs class ContractDeclaration(AccountTransactionReceipt): diff --git a/pyproject.toml b/pyproject.toml index e6a9cb89..b4c3f9e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ write_to = "ape_starknet/version.py" [tool.black] line-length = 100 -target-version = ['py38', 'py39'] +target-version = ['py38', 'py39', 'py310'] include = '\.pyi?$' [tool.pytest.ini_options] diff --git a/setup.py b/setup.py index bdc1077c..c4987bfa 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ "ape-cairo", # For compiling contracts in tests ], "lint": [ - "black>=22.10.0", # auto-formatter and linter + "black>=22.12", # auto-formatter and linter "mypy>=0.991", # Static type analyzer "types-requests", # Needed due to mypy typeshed "types-setuptools", # Needed due to mypy typeshed @@ -30,7 +30,7 @@ "twine", # Package upload tool ], "dev": [ - "commitizen>=2.19,<2.20", # Manage commits and publishing releases + "commitizen", # Manage commits and publishing releases "pre-commit", # Ensure that linters are run prior to committing "pytest-watch", # `ptw` test watcher/runner "IPython", # Console for interacting @@ -66,12 +66,12 @@ "hexbytes", # Use same version as eth-ape "pydantic", # Use same version as eth-ape # ** ApeWorX maintained ** - "eth-ape>=0.5.9,<0.6", + "eth-ape>=0.6.1,<0.7", "ethpm-types", # Use same version as eth-ape # ** Starknet Ecosystem ** "cairo-lang>=0.10.3,<0.11", - "starknet.py>=0.13.a0,<0.14", - "starknet-devnet>=0.4.3,<0.5", + "starknet_py>=0.14.0a0,<0.15", + "starknet-devnet>=0.4.4,<0.5", ], entry_points={"ape_cli_subcommands": ["ape_starknet=ape_starknet._cli:cli"]}, python_requires=">=3.8,<3.11", @@ -81,7 +81,7 @@ zip_safe=False, keywords="ethereum starknet", packages=find_packages(exclude=["tests", "tests.*"]), - package_data={"": ["py.typed"]}, + package_data={"ape_starknet": ["py.typed"]}, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers",