From 5655ced07db411e995a17e87654231826661dfa6 Mon Sep 17 00:00:00 2001 From: 0xca11ab1e <105989135+ca11ab1e@users.noreply.github.com> Date: Fri, 17 Jun 2022 10:13:32 +0200 Subject: [PATCH 01/15] feat: add declare transactions --- ape_starknet/_utils.py | 23 +++++++++++++++++++++++ ape_starknet/ecosystems.py | 7 ++++++- ape_starknet/provider.py | 3 +++ ape_starknet/transactions.py | 17 +++++++++++++++++ tests/functional/test_contract.py | 15 +++++++++++++++ 5 files changed, 64 insertions(+), 1 deletion(-) diff --git a/ape_starknet/_utils.py b/ape_starknet/_utils.py index 16dfbfb0..f112a461 100644 --- a/ape_starknet/_utils.py +++ b/ape_starknet/_utils.py @@ -1,3 +1,5 @@ +import json +from pathlib import Path import re from typing import Any, Optional, Union @@ -21,7 +23,12 @@ to_hex, ) from starknet_py.net.client import BadRequest # type: ignore +from starkware.cairo.lang.compiler.program import Program # type: ignore +from starkware.starknet.compiler.compile import get_entry_points_by_type # type: ignore +from starkware.starknet.core.os.class_hash import compute_class_hash # type: ignore from starkware.starknet.definitions.general_config import StarknetChainId # type: ignore +from starkware.starknet.services.api.contract_class import ContractClass # type: ignore +from starkware.starknet.testing.contract_utils import get_contract_class # type: ignore PLUGIN_NAME = "starknet" NETWORKS = { @@ -117,3 +124,19 @@ def get_virtual_machine_error(err: Exception) -> Optional[VirtualMachineError]: # Fix escaping newline issue with error message. err_msg = err_msg.replace("\\n", "").strip() return ContractLogicError(revert_message=err_msg) + + +def compute_contract_class_hash(path: Path) -> int: + """ + Compute contract classes hash off-chain. + `path` is the full path to the JSON compiled contract. + """ + contract = json.loads(path.read_text()) + program = Program.load(data=contract["program"]) + real_contract = ContractClass( + program=program, + entry_points_by_type=get_entry_points_by_type(program=program), + abi=contract["abi"], + ) + contract_cls = get_contract_class(contract_class=real_contract) + return compute_class_hash(contract_cls) diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index daf93ab0..a2973c39 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -23,6 +23,7 @@ from ape_starknet._utils import to_checksum_address from ape_starknet.exceptions import StarknetEcosystemError from ape_starknet.transactions import ( + DeclareTransaction, DeployTransaction, InvokeFunctionTransaction, StarknetReceipt, @@ -195,11 +196,15 @@ def encode_transaction( def create_transaction(self, **kwargs) -> TransactionAPI: txn_type = kwargs.pop("type") - txn_cls: Union[Type[InvokeFunctionTransaction], Type[DeployTransaction]] + txn_cls: Union[ + Type[InvokeFunctionTransaction], Type[DeployTransaction], Type[DeclareTransaction] + ] if txn_type == TransactionType.INVOKE_FUNCTION: txn_cls = InvokeFunctionTransaction elif txn_type == TransactionType.DEPLOY: txn_cls = DeployTransaction + elif txn_type == TransactionType.DECLARE: + txn_cls = DeclareTransaction return txn_cls(**kwargs) diff --git a/ape_starknet/provider.py b/ape_starknet/provider.py index 991118d0..15ec45b9 100644 --- a/ape_starknet/provider.py +++ b/ape_starknet/provider.py @@ -17,6 +17,7 @@ from starkware.starknet.definitions.transaction_type import TransactionType # type: ignore from starkware.starknet.services.api.contract_class import ContractClass # type: ignore from starkware.starknet.services.api.feeder_gateway.response_objects import ( # type: ignore + DeclareSpecificInfo, DeploySpecificInfo, InvokeSpecificInfo, ) @@ -205,6 +206,8 @@ def get_transaction(self, txn_hash: str) -> ReceiptAPI: txn_type = TransactionType.DEPLOY elif isinstance(txn_info, InvokeSpecificInfo): txn_type = TransactionType.INVOKE_FUNCTION + elif isinstance(txn_info, DeclareSpecificInfo): + txn_type = TransactionType.DECLARE else: raise ValueError(f"No value found for '{txn_info}'.") diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index b820c96f..5d7800a1 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -10,6 +10,7 @@ from pydantic import Field from starknet_py.constants import TxStatus # type: ignore from starknet_py.net.models.transaction import ( # type: ignore + Declare, Deploy, InvokeFunction, Transaction, @@ -47,6 +48,22 @@ def as_starknet_object(self) -> Transaction: """ +class DeclareTransaction(StarknetTransaction): + type: TransactionType = TransactionType.DECLARE + sender: Optional[AddressType] = None + max_fee: int = 0 + + def as_starknet_object(self) -> Declare: + contract = ContractClass.deserialize(self.data) + return Declare( + contract, + sender_address=self.sender, + signature=[], # NOTE: Signatures are not supported on signing transactions + max_fee=self.max_fee, + version=self.version, + ) + + class DeployTransaction(StarknetTransaction): type: TransactionType = TransactionType.DEPLOY salt: int diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py index f19b33f0..19ef5756 100644 --- a/tests/functional/test_contract.py +++ b/tests/functional/test_contract.py @@ -7,6 +7,21 @@ def connection(provider): yield +def test_declare_and_deploy(project): + # This also tests `get_class_hash()`. + + # Declare + + # Get class hash + + # Check calls are not possible on the declared contract + + # Deploy the declared contract + + # Check calls are possible on the deployed contract + pass + + def test_deploy(project): assert project.MyContract, "Unable to access contract when needing to compile" From 428372a462a1ac4c9b3b249f238ef4a0c29553f1 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Sun, 19 Jun 2022 19:45:33 -0500 Subject: [PATCH 02/15] fix: rm unneeded code --- ape_starknet/_utils.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ape_starknet/_utils.py b/ape_starknet/_utils.py index f112a461..16dfbfb0 100644 --- a/ape_starknet/_utils.py +++ b/ape_starknet/_utils.py @@ -1,5 +1,3 @@ -import json -from pathlib import Path import re from typing import Any, Optional, Union @@ -23,12 +21,7 @@ to_hex, ) from starknet_py.net.client import BadRequest # type: ignore -from starkware.cairo.lang.compiler.program import Program # type: ignore -from starkware.starknet.compiler.compile import get_entry_points_by_type # type: ignore -from starkware.starknet.core.os.class_hash import compute_class_hash # type: ignore from starkware.starknet.definitions.general_config import StarknetChainId # type: ignore -from starkware.starknet.services.api.contract_class import ContractClass # type: ignore -from starkware.starknet.testing.contract_utils import get_contract_class # type: ignore PLUGIN_NAME = "starknet" NETWORKS = { @@ -124,19 +117,3 @@ def get_virtual_machine_error(err: Exception) -> Optional[VirtualMachineError]: # Fix escaping newline issue with error message. err_msg = err_msg.replace("\\n", "").strip() return ContractLogicError(revert_message=err_msg) - - -def compute_contract_class_hash(path: Path) -> int: - """ - Compute contract classes hash off-chain. - `path` is the full path to the JSON compiled contract. - """ - contract = json.loads(path.read_text()) - program = Program.load(data=contract["program"]) - real_contract = ContractClass( - program=program, - entry_points_by_type=get_entry_points_by_type(program=program), - abi=contract["abi"], - ) - contract_cls = get_contract_class(contract_class=real_contract) - return compute_class_hash(contract_cls) From afc21788b2652da9fb85438aacd06fe2ae651f9f Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Sun, 19 Jun 2022 20:28:02 -0500 Subject: [PATCH 03/15] feat: wip --- ape_starknet/accounts/__init__.py | 7 ++++--- ape_starknet/ecosystems.py | 27 +++++++++++++++++++-------- ape_starknet/transactions.py | 22 ++++++++++++++++------ tests/functional/test_contract.py | 5 +++++ tests/functional/test_transactions.py | 3 +-- 5 files changed, 45 insertions(+), 19 deletions(-) diff --git a/ape_starknet/accounts/__init__.py b/ape_starknet/accounts/__init__.py index 6f3493e3..36bf0cb5 100644 --- a/ape_starknet/accounts/__init__.py +++ b/ape_starknet/accounts/__init__.py @@ -8,7 +8,7 @@ from ape.api import AccountAPI, AccountContainerAPI, ReceiptAPI, TransactionAPI from ape.api.address import BaseAddress from ape.api.networks import LOCAL_NETWORK_NAME -from ape.contracts import ContractContainer, ContractInstance +from ape.contracts import ContractContainer from ape.exceptions import AccountsError, ProviderError, SignatureError from ape.logging import logger from ape.types import AddressType, SignableMessage, TransactionSignature @@ -385,8 +385,9 @@ def transfer( sender = self.starknet.encode_address(self.contract_address) return self.token_manager.transfer(sender, account, value, **kwargs) # type: ignore - def deploy(self, contract: ContractContainer, *args, **kwargs) -> ContractInstance: - return contract.deploy(sender=self) + def declare_class(self, contract: ContractContainer): + transaction = self.starknet.encode_contract_declaration(contract) + return self.provider.send_transaction(transaction) def get_deployment(self, network_name: str) -> Optional[StarknetAccountDeployment]: return next( diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index ea3d02c5..397acd74 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -1,8 +1,10 @@ from typing import Any, Dict, Iterator, List, Tuple, Type, Union from ape.api import BlockAPI, EcosystemAPI, ReceiptAPI, TransactionAPI +from ape.contracts import ContractContainer from ape.types import AddressType, ContractLog, RawAddress from eth_utils import is_0x_prefixed, to_bytes +from ethpm_types import ContractType from ethpm_types.abi import ConstructorABI, EventABI, MethodABI from hexbytes import HexBytes from starknet_py.net.models.address import parse_address # type: ignore @@ -243,16 +245,9 @@ def encode_deployment( def encode_transaction( self, address: AddressType, abi: MethodABI, *args, **kwargs ) -> TransactionAPI: + # NOTE: This method only works for invoke-transactions contract_type = self.chain_manager.contracts[address] encoded_calldata = self.encode_calldata(contract_type.abi, abi, list(args)) - type_kwarg = kwargs.get("type") - - if type_kwarg in ("declare", "0", 0, "0x0", "0x00", b"\x00"): - # a 'delcare' transaction - return DeclareTransaction( - sender=kwargs.get("sender"), - max_fee=kwargs.get("max_fee", 0), - ) return InvokeFunctionTransaction( contract_address=address, @@ -262,6 +257,22 @@ def encode_transaction( max_fee=kwargs.get("max_fee", 0), ) + def encode_contract_declaration( + self, contract: Union[ContractContainer, ContractType], *args, **kwargs + ) -> DeclareTransaction: + if isinstance(contract, ContractContainer): + contract_type = contract.contract_type + else: + contract_type = contract + + code = contract_type.get_deployment_bytecode() or b"" + contract = ContractClass.deserialize(code) + return DeclareTransaction( + data=contract.dumps(), + max_fee=kwargs.get("max_fee", 0), + sender=kwargs.get("sender"), + ) + def create_transaction(self, **kwargs) -> TransactionAPI: txn_type = kwargs.pop("type", kwargs.pop("tx_type", "")) txn_cls: Union[Type[InvokeFunctionTransaction], Type[DeployTransaction]] diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index c5f845ee..b242d476 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -21,6 +21,7 @@ ) from starkware.starknet.core.os.transaction_hash.transaction_hash import ( # type: ignore TransactionHashPrefix, + calculate_declare_transaction_hash, calculate_deploy_transaction_hash, calculate_transaction_hash_common, ) @@ -63,10 +64,21 @@ class DeclareTransaction(StarknetTransaction): sender: Optional[AddressType] = None max_fee: int = 0 + @property + def starknet_contract(self) -> ContractClass: + return ContractClass.deserialize(self.data) + + @property + def txn_hash(self) -> HexBytes: + return calculate_declare_transaction_hash( + self.starknet_contract, + self.provider.chain_id, + self.sender, + ) + def as_starknet_object(self) -> Declare: - contract = ContractClass.deserialize(self.data) return Declare( - contract, + self.starknet_contract, sender_address=self.sender, signature=[], # NOTE: Signatures are not supported on signing transactions max_fee=self.max_fee, @@ -99,9 +111,8 @@ def txn_hash(self) -> HexBytes: deployer_address=self.caller_address, salt=self.salt, ) - chain_id = self.provider.chain_id hash_int = calculate_deploy_transaction_hash( - chain_id=chain_id, + chain_id=self.provider.chain_id, contract_address=contract_address, constructor_calldata=self.constructor_calldata, version=self.version, @@ -109,11 +120,10 @@ def txn_hash(self) -> HexBytes: return HexBytes(to_bytes(hash_int)) def as_starknet_object(self) -> Deploy: - contract = ContractClass.deserialize(self.data) return Deploy( constructor_calldata=self.constructor_calldata, contract_address_salt=self.salt, - contract_definition=contract, + contract_definition=self.starknet_contract, version=self.version, ) diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py index 30f553c3..3af89d27 100644 --- a/tests/functional/test_contract.py +++ b/tests/functional/test_contract.py @@ -32,6 +32,11 @@ def test_deploy(project): assert deployment +def test_declare(account, project): + declare_receipt = account.declare_class(project.MyContract) + assert declare_receipt + + def test_contract_transaction_handles_non_felt_arguments(contract, account, initial_balance): # NOTE: This test validates the account signature but the transaction # is not directly sent from the account. diff --git a/tests/functional/test_transactions.py b/tests/functional/test_transactions.py index 84cb0159..a104f6a4 100644 --- a/tests/functional/test_transactions.py +++ b/tests/functional/test_transactions.py @@ -4,8 +4,7 @@ def test_deploy_txn_hash(project, convert, provider): contract_type = project.MyContract.contract_type constructor = ContractConstructor( # type: ignore - abi=contract_type.constructor, - deployment_bytecode=contract_type.get_deployment_bytecode() or b"", # type: ignore + abi=contract_type.constructor, deployment_bytecode=contract_type.get_deployment_bytecode() ) deploy_txn = constructor.serialize_transaction() receipt = provider.send_transaction(deploy_txn) From 4394ab5a44c2c51468f5a1b08155028ce771df23 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Mon, 20 Jun 2022 07:26:04 -0500 Subject: [PATCH 04/15] feat: wip --- ape_starknet/accounts/__init__.py | 32 ++++++++++++++++++------------- ape_starknet/ecosystems.py | 20 ++++++++++++++++--- ape_starknet/tokens.py | 10 ++++++++-- ape_starknet/transactions.py | 12 +++++++----- tests/functional/test_contract.py | 20 +++---------------- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/ape_starknet/accounts/__init__.py b/ape_starknet/accounts/__init__.py index 36bf0cb5..874b8453 100644 --- a/ape_starknet/accounts/__init__.py +++ b/ape_starknet/accounts/__init__.py @@ -30,7 +30,6 @@ from ape_starknet.tokens import TokenManager from ape_starknet.transactions import InvokeFunctionTransaction -from ape_starknet.utils import PLUGIN_NAME, get_chain_id from ape_starknet.utils.basemodel import StarknetMixin APP_KEY_FILE_KEY = "ape-starknet" @@ -263,10 +262,10 @@ def address(self) -> AddressType: @cached_property def signer(self) -> StarkCurveSigner: key_pair = KeyPair.from_private_key(self._get_key()) - network = self.provider.network - chain_id = get_chain_id(network.name) return StarkCurveSigner( - account_address=self.contract_address, key_pair=key_pair, chain_id=chain_id + account_address=self.contract_address, + key_pair=key_pair, + chain_id=self.provider.chain_id, ) @cached_property @@ -374,19 +373,26 @@ def transfer( raise ValueError("value is not an integer.") if not isinstance(account, str) and hasattr(account, "contract_address"): - account = account.contract_address # type: ignore - - if not isinstance(account, int): - account = self.starknet.encode_address(account) # type: ignore + receiver = account.contract_address # type: ignore + elif isinstance(account, str): + checksummed_address = self.starknet.decode_address(account) + receiver = self.starknet.encode_address(checksummed_address) + elif isinstance(account, int): + receiver = account + else: + raise TypeError(f"Unable to handle account type '{type(account)}'.") if self.contract_address is None: raise ValueError("Contract address cannot be None") - sender = self.starknet.encode_address(self.contract_address) - return self.token_manager.transfer(sender, account, value, **kwargs) # type: ignore + return self.token_manager.transfer( + self.contract_address, receiver, value, **kwargs + ) # type: ignore - def declare_class(self, contract: ContractContainer): - transaction = self.starknet.encode_contract_declaration(contract) + def declare(self, contract: ContractContainer): + transaction = self.starknet.encode_contract_declaration( + contract, sender=self, nonce=self.nonce, + ) return self.provider.send_transaction(transaction) def get_deployment(self, network_name: str) -> Optional[StarknetAccountDeployment]: @@ -404,7 +410,7 @@ def check_signature( # type: ignore data: int, signature: Optional[ECSignature] = None, # TransactionAPI doesn't need it ) -> bool: - int_address = self.network_manager.get_ecosystem(PLUGIN_NAME).encode_address(self.address) + int_address = self.starknet.encode_address(self.address) return verify_ecdsa_sig(int_address, data, signature) def get_deployments(self) -> List[StarknetAccountDeployment]: diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index 397acd74..d3b6fd83 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -265,17 +265,31 @@ def encode_contract_declaration( else: contract_type = contract + if "sender" in kwargs: + if hasattr(kwargs["sender"], "contract_address"): + account = kwargs["sender"] + kwargs["sender"] = account.contract_address + if not kwargs.get("nonce"): + kwargs["nonce"] = account.nonce + + if isinstance(kwargs["sender"], str): + kwargs["sender"] = self.encode_address(kwargs["sender"]) + + if not kwargs.get("nonce") and self.network_manager.active_provider: + kwargs["nonce"] = self.provider.get_nonce(kwargs["sender"]) + code = contract_type.get_deployment_bytecode() or b"" - contract = ContractClass.deserialize(code) + starknet_contract = ContractClass.deserialize(code) return DeclareTransaction( - data=contract.dumps(), + data=starknet_contract.dumps(), max_fee=kwargs.get("max_fee", 0), sender=kwargs.get("sender"), + nonce=kwargs["nonce"] ) def create_transaction(self, **kwargs) -> TransactionAPI: txn_type = kwargs.pop("type", kwargs.pop("tx_type", "")) - txn_cls: Union[Type[InvokeFunctionTransaction], Type[DeployTransaction]] + txn_cls: Union[Type[InvokeFunctionTransaction], Type[DeployTransaction], Type[DeclareTransaction]] invoking = txn_type == TransactionType.INVOKE_FUNCTION if invoking: txn_cls = InvokeFunctionTransaction diff --git a/ape_starknet/tokens.py b/ape_starknet/tokens.py index da8e1b88..6dfb5a7d 100644 --- a/ape_starknet/tokens.py +++ b/ape_starknet/tokens.py @@ -1,4 +1,4 @@ -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Union from ape.contracts import ContractInstance from ape.contracts.base import ContractCall @@ -56,7 +56,13 @@ def get_balance(self, account: AddressType, token: str = "eth") -> int: method_abi_obj = MethodABI.parse_obj(method_abi) return ContractCall(method_abi_obj, contract_address)() - def transfer(self, sender: int, receiver: int, amount: int, token: str = "eth"): + def transfer(self, sender: Union[int, AddressType], receiver: Union[int, AddressType], amount: int, token: str = "eth"): + if not isinstance(sender, int): + sender = self.starknet.encode_address(sender) + + if not isinstance(receiver, int): + receiver = self.starknet.encode_address(receiver) + contract_address = self._get_contract_address(token=token) if not contract_address: return diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index b242d476..50c3d756 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -31,7 +31,7 @@ from ape_starknet.utils.basemodel import StarknetMixin -class StarknetTransaction(TransactionAPI): +class StarknetTransaction(TransactionAPI, StarknetMixin): """ A base transaction class for all Starknet transactions. """ @@ -77,11 +77,13 @@ def txn_hash(self) -> HexBytes: ) def as_starknet_object(self) -> Declare: + sender_int = self.starknet.encode_address(self.sender) return Declare( - self.starknet_contract, - sender_address=self.sender, - signature=[], # NOTE: Signatures are not supported on signing transactions + contract_class=self.starknet_contract, max_fee=self.max_fee, + nonce=self.nonce, + sender_address=sender_int, + signature=[], # NOTE: Signatures are not supported on signing transactions version=self.version, ) @@ -128,7 +130,7 @@ def as_starknet_object(self) -> Deploy: ) -class InvokeFunctionTransaction(StarknetTransaction, StarknetMixin): +class InvokeFunctionTransaction(StarknetTransaction): type: TransactionType = TransactionType.INVOKE_FUNCTION method_abi: MethodABI max_fee: int = 0 diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py index 3af89d27..13d72a2d 100644 --- a/tests/functional/test_contract.py +++ b/tests/functional/test_contract.py @@ -7,21 +7,6 @@ def connection(provider): yield -def test_declare_and_deploy(project): - # This also tests `get_class_hash()`. - - # Declare - - # Get class hash - - # Check calls are not possible on the declared contract - - # Deploy the declared contract - - # Check calls are possible on the deployed contract - pass - - def test_deploy(project): assert project.MyContract, "Unable to access contract when needing to compile" @@ -33,8 +18,9 @@ def test_deploy(project): def test_declare(account, project): - declare_receipt = account.declare_class(project.MyContract) - assert declare_receipt + declared_contract = account.declare(project.MyContract) + contract_instance = account.deploy(declared_contract) + assert declared_contract def test_contract_transaction_handles_non_felt_arguments(contract, account, initial_balance): From 8556903cce293871ffb94aee475a22593f6b5028 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 12:28:21 -0500 Subject: [PATCH 05/15] fix: issue with getting txns by block --- ape_starknet/ecosystems.py | 46 +++++---------------------- ape_starknet/provider.py | 8 +++-- ape_starknet/transactions.py | 52 ++++++++++++++++++++++++++++--- ape_starknet/utils/__init__.py | 15 +++++++-- tests/functional/test_contract.py | 5 +-- 5 files changed, 76 insertions(+), 50 deletions(-) diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index c79be189..ee053607 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -183,37 +183,16 @@ def encode_primitive_value(self, value: Any) -> int: def decode_receipt(self, data: dict) -> ReceiptAPI: txn_type = TransactionType(data["type"]) cls: Union[Type[ContractDeclaration], Type[DeployReceipt], Type[InvocationReceipt]] - receipt_kwargs = { - "block_hash": data.get("block_hash"), - "block_number": data["block_number"], - "provider": data.get("provider"), - "receiver": data.get("receiver"), - "type": txn_type, - "transaction_hash": data["transaction_hash"], - } - if txn_type == TransactionType.INVOKE_FUNCTION: - receipt_kwargs["receiver"] = data.pop("contract_address") - receipt_kwargs["max_fee"] = data.get("max_fee", 0) - receipt_kwargs["receiver"] = self.decode_address(data["receiver"]) - receipt_kwargs["actual_fee"] = data["actual_fee"] - receipt_kwargs["events"] = data["events"] - receipt_kwargs["max_fee"] = data["max_fee"] - receipt_kwargs["status"] = data["status"].value cls = InvocationReceipt - elif txn_type == TransactionType.DEPLOY: - receipt_kwargs["contract_address"] = self.decode_address(data["contract_address"]) cls = DeployReceipt - elif txn_type == TransactionType.DECLARE: - receipt_kwargs["class_hash"] = data["class_hash"] cls = ContractDeclaration - else: raise ValueError(f"Unable to handle contract type '{txn_type.value}'.") - return cls(**receipt_kwargs) + return cls.parse_obj(data) def decode_block(self, data: dict) -> BlockAPI: return StarknetBlock( @@ -262,12 +241,16 @@ def encode_contract_declaration( contract_type = ( contract.contract_type if isinstance(contract, ContractContainer) else contract ) - code = contract_type.get_deployment_bytecode() or b"" - starknet_contract = ContractClass.deserialize(code) + code = ( + (contract_type.deployment_bytecode.bytecode or 0) + if contract_type.deployment_bytecode + else 0 + ) + starknet_contract = ContractClass.deserialize(HexBytes(code)) return DeclareTransaction(contract_type=contract_type, data=starknet_contract.dumps()) def create_transaction(self, **kwargs) -> TransactionAPI: - txn_type = kwargs.pop("type", kwargs.pop("tx_type", "")) + txn_type = TransactionType(kwargs.pop("type", kwargs.pop("tx_type", ""))) txn_cls: Union[ Type[InvokeFunctionTransaction], Type[DeployTransaction], Type[DeclareTransaction] ] @@ -292,19 +275,6 @@ def create_transaction(self, **kwargs) -> TransactionAPI: """ ~ Invoke transactions ~ """ - if "receiver" in txn_data: - # Model expects 'contract_address' key during serialization. - # NOTE: Deploy transactions have a different 'contract_address' and that is handled - # above before getting to the 'Invoke transactions' section. - txn_data["contract_address"] = self.decode_address(txn_data["receiver"]) - - if ( - "max_fee" in txn_data - and not isinstance(txn_data["max_fee"], int) - and txn_data["max_fee"] is not None - ): - txn_data["max_fee"] = self.encode_primitive_value(txn_data["max_fee"]) - if "method_abi" not in txn_data: contract_int = txn_data["contract_address"] contract_str = self.decode_address(contract_int) diff --git a/ape_starknet/provider.py b/ape_starknet/provider.py index f0859600..b508a98f 100644 --- a/ape_starknet/provider.py +++ b/ape_starknet/provider.py @@ -229,9 +229,11 @@ def send_call(self, txn: TransactionAPI) -> bytes: @handle_client_errors def get_transaction(self, txn_hash: str) -> ReceiptAPI: self.starknet_client.wait_for_tx_sync(txn_hash) - receipt = self.starknet_client.get_transaction_receipt_sync(tx_hash=txn_hash) - receipt_dict: Dict[str, Any] = {"provider": self, **vars(receipt)} txn_info = self.starknet_client.get_transaction_sync(tx_hash=txn_hash).transaction + receipt = self.starknet_client.get_transaction_receipt_sync( + tx_hash=txn_info.transaction_hash + ) + receipt_dict: Dict[str, Any] = {"provider": self, **vars(receipt)} receipt_dict = get_dict_from_tx_info(txn_info, **receipt_dict) return self.starknet.decode_receipt(receipt_dict) @@ -239,7 +241,7 @@ def get_transactions_by_block(self, block_id: BlockID) -> Iterator[TransactionAP block = self._get_block(block_id) for txn_info in block.transactions: txn_dict = get_dict_from_tx_info(txn_info) - yield self.network.ecosystem.create_transaction(**txn_dict) + yield self.starknet.create_transaction(**txn_dict) @handle_client_errors def send_transaction(self, txn: TransactionAPI, token: Optional[str] = None) -> ReceiptAPI: diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index d7f9639a..513303ae 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -57,6 +57,16 @@ class Config: def serialize_transaction(self) -> dict: # type: ignore return self.dict() + @validator("status", pre=True, allow_reuse=True) + def validate_status(cls, value): + if isinstance(value, TxStatus): + return value.value + + elif isinstance(value, str): + return int(value, 16) + + return value + @abstractmethod def as_starknet_object(self) -> Transaction: """ @@ -141,9 +151,8 @@ def as_starknet_object(self) -> Deploy: class InvokeFunctionTransaction(StarknetTransaction): - method_abi: MethodABI - max_fee: int = 0 + method_abi: MethodABI sender: Optional[AddressType] = None type: TransactionType = TransactionType.INVOKE_FUNCTION @@ -151,6 +160,20 @@ class InvokeFunctionTransaction(StarknetTransaction): data: List[Any] = Field(alias="calldata") # type: ignore receiver: AddressType = Field(alias="contract_address") + @validator("receiver", pre=True, allow_reuse=True) + def validate_receiver(cls, value): + if isinstance(value, int): + return to_checksum_address(value) + + return value + + @validator("max_fee", pre=True, allow_reuse=True) + def validate_max_fee(cls, value): + if isinstance(value, str): + return int(value, 16) + + return value + @property def receiver_int(self) -> int: return self.starknet.encode_address(self.receiver) @@ -238,10 +261,20 @@ class DeployReceipt(StarknetReceipt): # Only get a receipt if deploy was accepted status: TxStatus = TxStatus.ACCEPTED_ON_L2 + @validator("contract_address", pre=True, allow_reuse=True) + def validate_contract_address(cls, value): + if isinstance(value, int): + return to_checksum_address(value) + + return value + class InvocationReceipt(StarknetReceipt): actual_fee: int + entry_point_selector: Optional[int] = None # Either has this or method_abi max_fee: int + method_abi: Optional[MethodABI] = None # Either has this or entry_point_selector + receiver: str = Field(alias="contract_address") return_value: List[int] = [] """Aliased""" @@ -252,6 +285,13 @@ def validate_max_fee(cls, value): if isinstance(value, str): return int(value, 16) + @validator("entry_point_selector", pre=True, allow_reuse=True) + def validate_entry_point_selector(cls, value): + if isinstance(value, str): + return int(value, 16) + + return value + @property def ran_out_of_gas(self) -> bool: return self.actual_fee >= self.max_fee @@ -317,8 +357,12 @@ def contract_type( if not contract_type.source_id: continue - code = contract_type.get_deployment_bytecode() or b"" - contract_class = ContractClass.deserialize(code) + code = ( + (contract_type.deployment_bytecode.bytecode or 0) + if contract_type.deployment_bytecode + else 0 + ) + contract_class = ContractClass.deserialize(HexBytes(code)) contract_cls = get_contract_class(contract_class=contract_class) computed_class_hash = compute_class_hash(contract_cls) if computed_class_hash == self.class_hash: diff --git a/ape_starknet/utils/__init__.py b/ape_starknet/utils/__init__.py index 66f31d59..b0919978 100644 --- a/ape_starknet/utils/__init__.py +++ b/ape_starknet/utils/__init__.py @@ -133,19 +133,28 @@ def get_virtual_machine_error(err: Exception) -> Optional[VirtualMachineError]: def get_dict_from_tx_info( txn_info: Union[DeploySpecificInfo, InvokeSpecificInfo], **extra_kwargs ) -> Dict: - txn_dict = txn_info.dump() + txn_dict = {**txn_info.dump(), **extra_kwargs} if isinstance(txn_info, DeploySpecificInfo): txn_dict["contract_address"] = to_checksum_address(txn_info.contract_address) txn_dict["max_fee"] = 0 txn_dict["type"] = TransactionType.DEPLOY elif isinstance(txn_info, InvokeSpecificInfo): txn_dict["contract_address"] = to_checksum_address(txn_info.contract_address) - txn_dict["events"] = [vars(e) for e in txn_dict["events"]] + + if "events" in txn_dict: + txn_dict["events"] = [vars(e) for e in txn_dict["events"]] + txn_dict["max_fee"] = txn_dict["max_fee"] + + if "method_abi" in txn_dict: + txn_dict["method_abi"] = txn_dict.get("method_abi") + + if "entry_point_selector" in txn_dict: + txn_dict["entry_point_selector"] = txn_dict.get("entry_point_selector") + txn_dict["type"] = TransactionType.INVOKE_FUNCTION elif isinstance(txn_info, DeclareSpecificInfo): txn_dict["sender"] = to_checksum_address(txn_info.sender_address) txn_dict["type"] = TransactionType.DECLARE - txn_dict = {**txn_dict, **extra_kwargs} return txn_dict diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py index 2a9d712b..fe9375f8 100644 --- a/tests/functional/test_contract.py +++ b/tests/functional/test_contract.py @@ -28,9 +28,10 @@ def test_declare_then_deploy(account, project, provider): assert isinstance(contract, ContractInstance) # Ensure can interact with deployed contract from 'class_hash'. - balance_pre_call = contract.get_balance(account) + contract.initialize() + balance_pre_call = contract.get_balance(account.contract_address) contract.increase_balance(account.contract_address, 9, sender=account) - assert contract.get_balance(account) == balance_pre_call + 9 + assert contract.get_balance(account.contract_address) == balance_pre_call + 9 def test_contract_transaction_handles_non_felt_arguments(contract, account, initial_balance): From cdb54ae4ac78c60df51c20205f8d1af07d4e97d3 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 12:39:08 -0500 Subject: [PATCH 06/15] fix: issue with str nonce --- README.md | 5 ++++- ape_starknet/ecosystems.py | 2 ++ ape_starknet/provider.py | 18 ++++++------------ ape_starknet/transactions.py | 6 ++++++ 4 files changed, 18 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 8745de42..5e57bd11 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,10 @@ python3 setup.py install ### Account Management -Deploy a new account: +Accounts are used to execute transactions and sign call data. +Accounts are smart contracts in Starknet. + +To deploy a new account: ```bash ape starknet accounts create --network starknet:testnet diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index ee053607..7336f9bf 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -76,6 +76,8 @@ def serialize_transaction(self, transaction: TransactionAPI) -> bytes: return starknet_object.deserialize() def decode_returndata(self, abi: MethodABI, raw_data: List[int]) -> List[Any]: # type: ignore + raw_data = [self.encode_primitive_value(v) if isinstance(v, str) else v for v in raw_data] + def clear_lengths(arr): arr_len = arr[0] rest = arr[1:] diff --git a/ape_starknet/provider.py b/ape_starknet/provider.py index b508a98f..43468292 100644 --- a/ape_starknet/provider.py +++ b/ape_starknet/provider.py @@ -59,7 +59,6 @@ class StarknetProvider(SubprocessProvider, ProviderAPI, StarknetBase): client: Optional[StarknetClient] = None token_manager: TokenManager = TokenManager() default_gas_cost: int = 0 - contract_type_by_class_hash: Dict[int, ContractType] = {} @property def process_name(self) -> str: @@ -256,21 +255,16 @@ def send_transaction(self, txn: TransactionAPI, token: Optional[str] = None) -> message = error.get("message", error) raise ProviderError(message) - if invoking: - # Return felts as ints and let calling context decide if hexstr is more appropriate. - return_value = [ - self.starknet.encode_primitive_value(v) if isinstance(v, str) else v - for v in txn_info.get("result", []) - ] - if return_value and isinstance(txn, InvokeFunctionTransaction): - return_value = self.starknet.decode_returndata(txn.method_abi, return_value) - if isinstance(return_value, (list, tuple)) and len(return_value) == 1: - return_value = return_value[0] - txn_hash = txn_info["transaction_hash"] receipt = self.get_transaction(txn_hash) if invoking: + return_value = self.starknet.decode_returndata( + txn.method_abi, txn_info.get("result", []) + ) + if isinstance(return_value, (list, tuple)) and len(return_value) == 1: + return_value = return_value[0] + receipt.return_value = return_value return receipt diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index 513303ae..955ecd94 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -234,6 +234,12 @@ class StarknetReceipt(ReceiptAPI, StarknetBase): """Aliased""" txn_hash: str = Field(alias="transaction_hash") + @validator("nonce", pre=True, allow_reuse=True) + def validate(cls, value): + if isinstance(value, str): + breakpoint() + return int(value, 16) + @validator("block_hash", pre=True, allow_reuse=True) def validate_block_hash(cls, value): return HexBytes(value).hex() if value else value From eaf4b7bd0a1ce43716f7a395f96fc32308a2e8c0 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 12:49:03 -0500 Subject: [PATCH 07/15] docs: fix docs --- README.md | 35 ++++++++++++++++++++++++++++++++--- ape_starknet/transactions.py | 1 - 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5e57bd11..d19cf1a0 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,30 @@ ape starknet accounts delete --network starknet:testnet **NOTE**: You don't have to specify the network if your account is only deployed to a single network. -### Contract Interaction +### Declare and Deploy Contracts + +In Starknet, you can declare contract types by publishing them to the chain. +This allows other contracts to create instances of them using the [deploy system call](https://www.cairo-lang.org/docs/hello_starknet/deploying_from_contracts.html). + +To declare a contract using `ape-starknet`, do the following (in a script or console): + +```python +from ape import project, networks -First, deploy your contract: +provider = networks.active_provider +declaration = provider.declare(project.MyContract) +print(declaration.class_hash) +``` + +You can also `deploy()` from the declaration receipt: + +```python +from ape import accounts + +receipt = declaration.deploy(1, 2, sender=accounts.load("MyAccount")) +``` + +Otherwise, you can use the legacy deploy system which works the same as Ethereum in ape: ```python from ape import project @@ -90,9 +111,17 @@ from ape import project contract = project.MyContract.deploy() ``` -The ``deploy`` method returns a contract instance from which you can call methods on: +### Contract Interaction + +After you have deployed your contracts, you can begin interacting with them. +``deploy`` methods return a contract instance from which you can call methods on: ```python +from ape import project + +contract = project.MyContract.deploy() + +# Interact with deployed contract receipt = contract.my_mutable_method(123) value = contract.my_view_method() ``` diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index 955ecd94..0ae597f2 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -237,7 +237,6 @@ class StarknetReceipt(ReceiptAPI, StarknetBase): @validator("nonce", pre=True, allow_reuse=True) def validate(cls, value): if isinstance(value, str): - breakpoint() return int(value, 16) @validator("block_hash", pre=True, allow_reuse=True) From db3dfee85104eddf1ccb28d7a363d788faadbebb Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 13:00:47 -0500 Subject: [PATCH 08/15] fix: decoding issue --- ape_starknet/ecosystems.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ape_starknet/ecosystems.py b/ape_starknet/ecosystems.py index 7336f9bf..45dcef13 100644 --- a/ape_starknet/ecosystems.py +++ b/ape_starknet/ecosystems.py @@ -84,7 +84,11 @@ def clear_lengths(arr): num_rest = len(rest) return clear_lengths(rest) if arr_len == num_rest else arr - is_arr = abi.outputs[0].name == "arr_len" and abi.outputs[1].type == "felt*" + is_arr = ( + len(abi.outputs) >= 2 + and abi.outputs[0].name == "arr_len" + and abi.outputs[1].type == "felt*" + ) has_leftover_length = len(raw_data) > 1 and not is_arr if ( len(abi.outputs) == 2 From fba6aa6faacc210b5cc361aaebdd20b8dfe6e5c0 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 14:05:56 -0500 Subject: [PATCH 09/15] feat: example with deploy contract call --- README.md | 33 ++++++++++++++++- tests/conftest.py | 15 +++++--- tests/functional/test_contract.py | 21 +++++++++-- .../project/contracts/ContractFactory.cairo | 36 +++++++++++++++++++ .../project/contracts/MyContract.cairo | 1 - 5 files changed, 97 insertions(+), 9 deletions(-) create mode 100644 tests/projects/project/contracts/ContractFactory.cairo diff --git a/README.md b/README.md index d19cf1a0..05fb4eba 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,38 @@ declaration = provider.declare(project.MyContract) print(declaration.class_hash) ``` -You can also `deploy()` from the declaration receipt: +Then, you can use the class hash in a deploy system call in a Factory contract: + +```cairo +@external +func deploy_my_contract{ + syscall_ptr : felt*, + pedersen_ptr : HashBuiltin*, + range_check_ptr, +}(): + let (current_salt) = salt.read() + let (class_hash) = ownable_class_hash.read() + let (calldata_ptr) = alloc() + let (contract_address) = deploy( + class_hash=class_hash, + contract_address_salt=current_salt, + constructor_calldata_size=0, + constructor_calldata=calldata_ptr, + ) + salt.write(value=current_salt + 1) +``` + +Then, you can make calls to the factory method using `ape` and create contract instances that way: + +```python +from ape import Contract, project + +factory = project.ContractFactory.deploy(declaration.class_hash) +contract_address = project.starknet.decode_address(contract_address_int) +contract = Contract(contract_address, contract_address) +``` + +You can also `deploy()` from the declaration receipt (which uses the legacy Deploy transaction): ```python from ape import accounts diff --git a/tests/conftest.py b/tests/conftest.py index 42926247..90510870 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,18 +78,23 @@ def project(request, config): os.chdir(_HERE) -@pytest.fixture(scope="module") -def contract_type(project): +@pytest.fixture(scope="session") +def contract_container(project): return project.MyContract -@pytest.fixture(scope="module") -def contract(contract_type, provider): - deployed_contract = contract_type.deploy() +@pytest.fixture(scope="session") +def contract(contract_container, provider): + deployed_contract = contract_container.deploy() deployed_contract.initialize() return deployed_contract +@pytest.fixture(scope="session") +def factory_contract_container(provider, project): + return project.ContractFactory + + @pytest.fixture def initial_balance(contract, account): return contract.get_balance(account.address) diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py index fe9375f8..b5844b3d 100644 --- a/tests/functional/test_contract.py +++ b/tests/functional/test_contract.py @@ -1,4 +1,5 @@ import pytest +from ape import Contract from ape.contracts import ContractInstance from ape.exceptions import AccountsError, ContractLogicError @@ -18,7 +19,7 @@ def test_deploy(project): assert deployment -def test_declare_then_deploy(account, project, provider): +def test_declare_then_deploy(account, chain, project, provider, factory_contract_container): # Declare contract type. The result should contain a 'class_hash'. declaration = provider.declare(project.MyContract) assert declaration.class_hash @@ -27,12 +28,28 @@ def test_declare_then_deploy(account, project, provider): contract = declaration.deploy() assert isinstance(contract, ContractInstance) - # Ensure can interact with deployed contract from 'class_hash'. + # Ensure can interact with deployed contract from declaration. contract.initialize() balance_pre_call = contract.get_balance(account.contract_address) contract.increase_balance(account.contract_address, 9, sender=account) assert contract.get_balance(account.contract_address) == balance_pre_call + 9 + # Ensure can use class_hash in factory contract + factory = factory_contract_container.deploy(declaration.class_hash) + receipt = factory.create_my_contract() + logs = list(receipt.decode_logs(factory.contract_deployed)) + new_contract_address = provider.starknet.decode_address(logs[0].contract_address) + + # TODO: Can remove after ape 0.3.2 release + chain.contracts._local_contracts[new_contract_address] = contract.contract_type + + # Ensure can interact with deployed contract from 'class_hash'. + new_contract_instance = Contract(new_contract_address, contract_type=contract.contract_type) + new_contract_instance.initialize() + balance_pre_call = new_contract_instance.get_balance(account.contract_address) + new_contract_instance.increase_balance(account.contract_address, 9, sender=account) + assert new_contract_instance.get_balance(account.contract_address) == balance_pre_call + 9 + def test_contract_transaction_handles_non_felt_arguments(contract, account, initial_balance): # NOTE: This test validates the account signature but the transaction diff --git a/tests/projects/project/contracts/ContractFactory.cairo b/tests/projects/project/contracts/ContractFactory.cairo new file mode 100644 index 00000000..0cc37f30 --- /dev/null +++ b/tests/projects/project/contracts/ContractFactory.cairo @@ -0,0 +1,36 @@ +%lang starknet + +from starkware.cairo.common.alloc import alloc +from starkware.starknet.common.syscalls import deploy +from starkware.cairo.common.cairo_builtins import HashBuiltin + +@storage_var +func class_hash() -> (class_hash : felt): +end + +@event +func contract_deployed(contract_address : felt): +end + +@constructor +func constructor{ + syscall_ptr : felt*, + pedersen_ptr : HashBuiltin*, + range_check_ptr, +}(cls_hash : felt): + class_hash.write(value=cls_hash) + return () +end + +@external +func create_my_contract{ + syscall_ptr : felt*, + pedersen_ptr : HashBuiltin*, + range_check_ptr, +}(): + let (cls_hash) = class_hash.read() + let (ptr) = alloc() + let (contract_addr) = deploy(cls_hash, 123, 0, ptr) + contract_deployed.emit(contract_address=contract_addr) + return () +end diff --git a/tests/projects/project/contracts/MyContract.cairo b/tests/projects/project/contracts/MyContract.cairo index 47d2dbab..89480c30 100644 --- a/tests/projects/project/contracts/MyContract.cairo +++ b/tests/projects/project/contracts/MyContract.cairo @@ -1,4 +1,3 @@ -# Declare this file as a StarkNet contract. %lang starknet from starkware.cairo.common.alloc import alloc From aad30c792493a75de0965fc294d552443b4159b0 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 14:56:35 -0500 Subject: [PATCH 10/15] test: fix issue with tests --- ape_starknet/provider.py | 2 +- tests/functional/test_contract.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ape_starknet/provider.py b/ape_starknet/provider.py index 43468292..5e3a799b 100644 --- a/ape_starknet/provider.py +++ b/ape_starknet/provider.py @@ -258,7 +258,7 @@ def send_transaction(self, txn: TransactionAPI, token: Optional[str] = None) -> txn_hash = txn_info["transaction_hash"] receipt = self.get_transaction(txn_hash) - if invoking: + if invoking and isinstance(txn, InvokeFunctionTransaction): return_value = self.starknet.decode_returndata( txn.method_abi, txn_info.get("result", []) ) diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py index b5844b3d..58fdea74 100644 --- a/tests/functional/test_contract.py +++ b/tests/functional/test_contract.py @@ -110,6 +110,9 @@ def test_revert_no_message(contract, account): assert "An ASSERT_EQ instruction failed" in str(err.value) + # Re-initialize (re-store state) + contract.initialize() + def test_array_inputs(contract, account): # This test makes sure we can pass python lists as arguments From eb177546fb1dc49489891650120ffa12e20aa5ae Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 15:55:25 -0500 Subject: [PATCH 11/15] docs: revise --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 05fb4eba..a991587c 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,14 @@ func deploy_my_contract{ salt.write(value=current_salt + 1) ``` -Then, you can make calls to the factory method using `ape` and create contract instances that way: +After deploying the factory contract, you can use it to create contract instances: ```python from ape import Contract, project factory = project.ContractFactory.deploy(declaration.class_hash) -contract_address = project.starknet.decode_address(contract_address_int) +call_result = factory.deploy_my_contract() +contract_address = project.starknet.decode_address(call_result) contract = Contract(contract_address, contract_address) ``` From 8c1d509c74e92c60244dbae5c8a4b475eaf786f5 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 16:33:22 -0500 Subject: [PATCH 12/15] docs: revise --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a991587c..1405980f 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ declaration = provider.declare(project.MyContract) print(declaration.class_hash) ``` -Then, you can use the class hash in a deploy system call in a Factory contract: +Then, you can use the class hash in a deploy system call in a factory contract: ```cairo @external @@ -127,7 +127,7 @@ contract_address = project.starknet.decode_address(call_result) contract = Contract(contract_address, contract_address) ``` -You can also `deploy()` from the declaration receipt (which uses the legacy Deploy transaction): +You can also `deploy()` from the declaration receipt (which uses the legacy deploy transaction): ```python from ape import accounts @@ -135,7 +135,7 @@ from ape import accounts receipt = declaration.deploy(1, 2, sender=accounts.load("MyAccount")) ``` -Otherwise, you can use the legacy deploy system which works the same as Ethereum in ape: +Otherwise, you can use the legacy deploy system which works the same as Ethereum in ape except no sender is needed: ```python from ape import project From 5a5dee1e7408b691f4a605724bbf3f07aa6be9a6 Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Tue, 21 Jun 2022 16:40:21 -0500 Subject: [PATCH 13/15] fix: ignore nocodes --- ape_starknet/transactions.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ape_starknet/transactions.py b/ape_starknet/transactions.py index 0ae597f2..25c03b8e 100644 --- a/ape_starknet/transactions.py +++ b/ape_starknet/transactions.py @@ -362,11 +362,14 @@ def contract_type( if not contract_type.source_id: continue - code = ( - (contract_type.deployment_bytecode.bytecode or 0) - if contract_type.deployment_bytecode - else 0 - ) + program = contract_type.deployment_bytecode + if not program: + continue + + code = program.bytecode + if not code: + continue + contract_class = ContractClass.deserialize(HexBytes(code)) contract_cls = get_contract_class(contract_class=contract_class) computed_class_hash = compute_class_hash(contract_cls) From f7cbecb7287a30eba9d5eb6149632085990f3ac3 Mon Sep 17 00:00:00 2001 From: Juliya Smith Date: Wed, 22 Jun 2022 09:47:34 -0500 Subject: [PATCH 14/15] docs: d --- ape_starknet/provider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ape_starknet/provider.py b/ape_starknet/provider.py index 5e3a799b..03ac12c5 100644 --- a/ape_starknet/provider.py +++ b/ape_starknet/provider.py @@ -248,7 +248,7 @@ def send_transaction(self, txn: TransactionAPI, token: Optional[str] = None) -> invoking = txn.type == TransactionType.INVOKE_FUNCTION if "code" in txn_info and txn_info["code"] != StarkErrorCode.TRANSACTION_RECEIVED.name: - raise TransactionError(message="Transaction not receiver.") + raise TransactionError(message="Transaction not received.") error = txn_info.get("error", {}) if error: From be9814e14d9023cd6f900eaa0c2694f9a1d2407a Mon Sep 17 00:00:00 2001 From: unparalleled-js Date: Wed, 22 Jun 2022 10:00:29 -0500 Subject: [PATCH 15/15] docs: fille out imports more --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1405980f..9a8f180a 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,10 @@ print(declaration.class_hash) Then, you can use the class hash in a deploy system call in a factory contract: ```cairo +from starkware.cairo.common.alloc import alloc +from starkware.starknet.common.syscalls import deploy +from starkware.cairo.common.cairo_builtins import HashBuiltin + @external func deploy_my_contract{ syscall_ptr : felt*, @@ -121,6 +125,7 @@ After deploying the factory contract, you can use it to create contract instance ```python from ape import Contract, project +declaration = project.provider.declare(project.MyContract) factory = project.ContractFactory.deploy(declaration.class_hash) call_result = factory.deploy_my_contract() contract_address = project.starknet.decode_address(call_result) @@ -130,8 +135,9 @@ contract = Contract(contract_address, contract_address) You can also `deploy()` from the declaration receipt (which uses the legacy deploy transaction): ```python -from ape import accounts +from ape import accounts, project +declaration = project.provider.declare(project.MyContract) receipt = declaration.deploy(1, 2, sender=accounts.load("MyAccount")) ```