diff --git a/docs/userguides/contracts.md b/docs/userguides/contracts.md index d5c97e34e6..d75dd23e73 100644 --- a/docs/userguides/contracts.md +++ b/docs/userguides/contracts.md @@ -98,6 +98,32 @@ from ape import Contract contract = Contract("v2.registry.ychad.eth") ``` +## From ABIs + +You can load contracts using their ABIs: + +```python +from ape import Contract + +address = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45" + +# Using a JSON str: +contract = Contract( + address, abi='[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}]' +) + +# Using a JSON file path: +contract = Contract(address, abi="abi.json") + +# Using a Python dictionary from JSON: +contract = Contract( + address, + abi=[{"name":"foo","type":"fallback", "stateMutability":"nonpayable"}] +) +``` + +This will create the Contract instance from the given ABI. + ## From Previous Deployment Ape keeps track of your deployments for you so you can always refer back to a version that you deployed previously. diff --git a/src/ape/managers/chain.py b/src/ape/managers/chain.py index 63694ad4bb..c449feb43f 100644 --- a/src/ape/managers/chain.py +++ b/src/ape/managers/chain.py @@ -7,7 +7,7 @@ from typing import IO, Collection, Dict, Iterator, List, Optional, Set, Type, Union, cast import pandas as pd -from ethpm_types import ContractType +from ethpm_types import ABI, ContractType from rich import get_console from rich.console import Console as RichConsole @@ -1154,6 +1154,7 @@ def instance_at( address: Union[str, AddressType], contract_type: Optional[ContractType] = None, txn_hash: Optional[str] = None, + abi: Optional[Union[Union[List[ABI], Dict], str, Path]] = None, ) -> ContractInstance: """ Get a contract at the given address. If the contract type of the contract is known, @@ -1173,6 +1174,8 @@ def instance_at( in case it is not already known. txn_hash (Optional[str]): The hash of the transaction responsible for deploying the contract, if known. Useful for publishing. Defaults to ``None``. + abi (Optional[Union[Union[List[ABI], Dict], str, Path]]): Use an ABI str, dict, path, + or ethpm models to create a contract instance class. Returns: :class:`~ape.contracts.base.ContractInstance` @@ -1196,6 +1199,45 @@ def instance_at( else: raise # Current exception + if abi: + # if the ABI is a str then convert it to a JSON dictionary. + if isinstance(abi, Path) or ( + isinstance(abi, str) and "{" not in abi and Path(abi).is_file() + ): + # Handle both absolute and relative paths + abi = Path(abi) + if not abi.is_absolute(): + abi = self.project_manager.path / abi + + try: + abi = json.loads(abi.read_text()) + except Exception as err: + if contract_type: + # If a default contract type was provided, don't error and use it. + logger.error(str(err)) + else: + raise # Current exception + + elif isinstance(abi, str): + # JSON str + try: + abi = json.loads(abi) + except Exception as err: + if contract_type: + # If a default contract type was provided, don't error and use it. + logger.error(str(err)) + else: + raise # Current exception + + # If the ABI was a str, it should be a list now. + if isinstance(abi, list): + contract_type = ContractType(abi=abi) + + else: + raise TypeError( + f"Invalid ABI type '{type(abi)}', expecting str, List[ABI] or a JSON file." + ) + if not contract_type: raise ContractNotFoundError( contract_address, diff --git a/tests/functional/data/contracts/ethereum/abi/contract_abi.json b/tests/functional/data/contracts/ethereum/abi/contract_abi.json new file mode 100644 index 0000000000..29d7730554 --- /dev/null +++ b/tests/functional/data/contracts/ethereum/abi/contract_abi.json @@ -0,0 +1,205 @@ +[{"inputs":[{"internalType":"uint256","name":"num" + ,"type":"uint256"}],"stateMutability":"nonpayable" + ,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true + ,"internalType":"address","name":"newAddress","type":"address"}] + ,"name":"AddressChange","type":"event"},{"anonymous":false + ,"inputs":[{"indexed":true,"internalType":"uint256" + ,"name":"bar","type":"uint256"}],"name":"BarHappened" + ,"type":"event"},{"anonymous":false,"inputs":[{"indexed":true + ,"internalType":"uint256","name":"foo","type":"uint256"}] + ,"name":"FooHappened","type":"event"},{"anonymous":false + ,"inputs":[{"indexed":false,"internalType":"bytes32" + ,"name":"b","type":"bytes32"},{"indexed":false,"internalType":"uint256" + ,"name":"prevNum","type":"uint256"},{"indexed":false + ,"internalType":"string","name":"dynData","type":"string"} + ,{"indexed":true,"internalType":"uint256","name":"newNum" + ,"type":"uint256"},{"indexed":true,"internalType":"string" + ,"name":"dynIndexed","type":"string"}],"name":"NumberChange" + ,"type":"event"},{"inputs":[{"internalType":"address" + ,"name":"","type":"address"}],"name":"balances","outputs":[{"internalType":"uint256" + ,"name":"","type":"uint256"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"fooAndBar" + ,"outputs":[],"stateMutability":"nonpayable","type":"function"} + ,{"inputs":[{"internalType":"uint256","name":"a0","type":"uint256"} + ,{"internalType":"uint256","name":"a1","type":"uint256"} + ,{"internalType":"uint256","name":"a2","type":"uint256"} + ,{"internalType":"uint256","name":"a3","type":"uint256"} + ,{"internalType":"uint256","name":"a4","type":"uint256"} + ,{"internalType":"uint256","name":"a5","type":"uint256"} + ,{"internalType":"uint256","name":"a6","type":"uint256"} + ,{"internalType":"uint256","name":"a7","type":"uint256"} + ,{"internalType":"uint256","name":"a8","type":"uint256"} + ,{"internalType":"uint256","name":"a9","type":"uint256"}] + ,"name":"functionWithUniqueAmountOfArguments","outputs":[] + ,"stateMutability":"view","type":"function"},{"inputs":[] + ,"name":"getAddressArray","outputs":[{"internalType":"address[2]" + ,"name":"","type":"address[2]"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getArrayWithBiggerSize" + ,"outputs":[{"internalType":"uint256[20]","name":"" + ,"type":"uint256[20]"}],"stateMutability":"pure","type":"function"} + ,{"inputs":[],"name":"getDynamicStructArray","outputs": + [{"components":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"t","type":"tuple"} + ,{"internalType":"uint256","name":"foo","type":"uint256"}] + ,"internalType":"struct TestContractSol.NestedStruct1[]" + ,"name":"","type":"tuple[]"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getEmptyArray" + ,"outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}] + ,"stateMutability":"pure","type":"function"},{"inputs":[] + ,"name":"getEmptyDynArrayOfStructs","outputs":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[]","name":"","type":"tuple[]"}] + ,"stateMutability":"pure","type":"function"},{"inputs":[] + ,"name":"getEmptyTupleOfArrayOfStructsAndDynArrayOfStructs" + ,"outputs":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[3]","name":"","type":"tuple[3]"} + ,{"components":[{"internalType":"address","name":"a" + ,"type":"address"},{"internalType":"bytes32","name":"b" + ,"type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[]" + ,"name":"","type":"tuple[]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getEmptyTupleOfDynArrayStructs" + ,"outputs":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[]","name":"","type":"tuple[]"} + ,{"components":[{"internalType":"address","name":"a" + ,"type":"address"},{"internalType":"bytes32","name":"b" + ,"type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[]" + ,"name":"","type":"tuple[]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getEmptyTupleOfIntAndDynArray" + ,"outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"} + ,{"components":[{"internalType":"address","name":"a" + ,"type":"address"},{"internalType":"bytes32","name":"b" + ,"type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[]" + ,"name":"","type":"tuple[]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getFilledArray" + ,"outputs":[{"internalType":"uint256[3]","name":"" + ,"type":"uint256[3]"}],"stateMutability":"pure","type":"function"} + ,{"inputs":[],"name":"getNamedSingleItem","outputs":[{"internalType":"uint256" + ,"name":"foo","type":"uint256"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getNestedAddressArray" + ,"outputs":[{"internalType":"address[3][]","name":"" + ,"type":"address[3][]"}],"stateMutability":"view","type":"function"} + ,{"inputs":[],"name":"getNestedArrayDynamicFixed","outputs":[{"internalType":"uint256[2][]" + ,"name":"","type":"uint256[2][]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getNestedArrayFixedDynamic" + ,"outputs":[{"internalType":"uint256[][3]","name":"" + ,"type":"uint256[][3]"}],"stateMutability":"view","type":"function"} + ,{"inputs":[],"name":"getNestedArrayFixedFixed","outputs":[{"internalType":"uint256[2][3]" + ,"name":"","type":"uint256[2][3]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getNestedArrayMixedDynamic" + ,"outputs":[{"internalType":"uint256[][3][][5]","name":"" + ,"type":"uint256[][3][][5]"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getNestedStruct1" + ,"outputs":[{"components":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"t","type":"tuple"} + ,{"internalType":"uint256","name":"foo","type":"uint256"}] + ,"internalType":"struct TestContractSol.NestedStruct1" + ,"name":"","type":"tuple"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getNestedStruct2" + ,"outputs":[{"components":[{"internalType":"uint256" + ,"name":"foo","type":"uint256"},{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"t","type":"tuple"}] + ,"internalType":"struct TestContractSol.NestedStruct2" + ,"name":"","type":"tuple"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getNestedStructWithTuple1" + ,"outputs":[{"components":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"t","type":"tuple"} + ,{"internalType":"uint256","name":"foo","type":"uint256"}] + ,"internalType":"struct TestContractSol.NestedStruct1" + ,"name":"","type":"tuple"},{"internalType":"uint256" + ,"name":"","type":"uint256"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getNestedStructWithTuple2" + ,"outputs":[{"internalType":"uint256","name":"","type":"uint256"} + ,{"components":[{"internalType":"uint256","name":"foo" + ,"type":"uint256"},{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"t","type":"tuple"}] + ,"internalType":"struct TestContractSol.NestedStruct2" + ,"name":"","type":"tuple"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getPartiallyNamedTuple" + ,"outputs":[{"internalType":"uint256","name":"foo" + ,"type":"uint256"},{"internalType":"uint256","name":"" + ,"type":"uint256"}],"stateMutability":"pure","type":"function"} + ,{"inputs":[],"name":"getSingleItemArray","outputs":[{"internalType":"uint256[1]" + ,"name":"","type":"uint256[1]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getStaticStructArray" + ,"outputs":[{"components":[{"internalType":"uint256" + ,"name":"foo","type":"uint256"},{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"t","type":"tuple"}] + ,"internalType":"struct TestContractSol.NestedStruct2[3]" + ,"name":"","type":"tuple[3]"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getStruct" + ,"outputs":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"","type":"tuple"}] + ,"stateMutability":"view","type":"function"},{"inputs":[] + ,"name":"getStructWithArray","outputs":[{"components":[{"internalType":"uint256" + ,"name":"foo","type":"uint256"},{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[2]","name":"arr","type":"tuple[2]"} + ,{"internalType":"uint256","name":"bar","type":"uint256"}] + ,"internalType":"struct TestContractSol.WithArray" + ,"name":"","type":"tuple"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getTupleAllNamed" + ,"outputs":[{"internalType":"uint256","name":"foo" + ,"type":"uint256"},{"internalType":"uint256","name":"bar" + ,"type":"uint256"}],"stateMutability":"pure","type":"function"} + ,{"inputs":[],"name":"getTupleOfAddressArray","outputs":[{"internalType":"address[20]" + ,"name":"","type":"address[20]"},{"internalType":"int128[20]" + ,"name":"","type":"int128[20]"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"getTupleOfArrays" + ,"outputs":[{"internalType":"uint256[20]","name":"" + ,"type":"uint256[20]"},{"internalType":"uint256[20]" + ,"name":"","type":"uint256[20]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getTupleOfIntAndStructArray" + ,"outputs":[{"internalType":"uint256","name":"","type":"uint256"} + ,{"components":[{"internalType":"uint256","name":"one" + ,"type":"uint256"},{"internalType":"uint256","name":"two" + ,"type":"uint256"},{"internalType":"uint256","name":"three" + ,"type":"uint256"},{"internalType":"uint256","name":"four" + ,"type":"uint256"},{"internalType":"uint256","name":"five" + ,"type":"uint256"},{"internalType":"uint256","name":"six" + ,"type":"uint256"}],"internalType":"struct TestContractSol.IntStruct[5]" + ,"name":"","type":"tuple[5]"}],"stateMutability":"pure" + ,"type":"function"},{"inputs":[],"name":"getUnnamedTuple" + ,"outputs":[{"internalType":"uint256","name":"","type":"uint256"} + ,{"internalType":"uint256","name":"","type":"uint256"}] + ,"stateMutability":"pure","type":"function"},{"inputs":[] + ,"name":"myNumber","outputs":[{"internalType":"uint256" + ,"name":"","type":"uint256"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address" + ,"name":"","type":"address"}],"stateMutability":"view" + ,"type":"function"},{"inputs":[],"name":"prevNumber" + ,"outputs":[{"internalType":"uint256","name":"","type":"uint256"}] + ,"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address" + ,"name":"_address","type":"address"}],"name":"setAddress" + ,"outputs":[],"stateMutability":"nonpayable","type":"function"} + ,{"inputs":[{"internalType":"address","name":"_address" + ,"type":"address"},{"internalType":"uint256","name":"bal" + ,"type":"uint256"}],"name":"setBalance","outputs":[] + ,"stateMutability":"nonpayable","type":"function"} + ,{"inputs":[{"internalType":"uint256","name":"num" + ,"type":"uint256"}],"name":"setNumber","outputs":[] + ,"stateMutability":"nonpayable","type":"function"} + ,{"inputs":[{"internalType":"uint256","name":"num" + ,"type":"uint256"},{"internalType":"address","name":"_address" + ,"type":"address"}],"name":"setNumber","outputs":[] + ,"stateMutability":"nonpayable","type":"function"} + ,{"inputs":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct","name":"_my_struct","type":"tuple"}] + ,"name":"setStruct","outputs":[],"stateMutability":"pure" + ,"type":"function"},{"inputs":[{"components":[{"internalType":"address" + ,"name":"a","type":"address"},{"internalType":"bytes32" + ,"name":"b","type":"bytes32"}],"internalType":"struct TestContractSol.MyStruct[2]","name":"_my_struct_array" + ,"type":"tuple[2]"}],"name":"setStructArray","outputs":[] + ,"stateMutability":"pure","type":"function"},{"inputs":[] + ,"name":"theAddress","outputs":[{"internalType":"address" + ,"name":"","type":"address"}],"stateMutability":"view" + ,"type":"function"}] diff --git a/tests/functional/test_contract.py b/tests/functional/test_contract.py new file mode 100644 index 0000000000..ed22e31397 --- /dev/null +++ b/tests/functional/test_contract.py @@ -0,0 +1,50 @@ +import json +from pathlib import Path + +from ape import Contract +from ape.contracts import ContractInstance + + +def test_contract_from_abi(contract_instance): + contract = Contract(contract_instance.address, abi=contract_instance.contract_type.abi) + assert isinstance(contract, ContractInstance) + assert contract.address == contract_instance.address + assert contract.myNumber() == 0 + assert contract.balance == 0 + + +def test_contract_from_abi_list(contract_instance): + contract = Contract( + contract_instance.address, abi=[abi.dict() for abi in contract_instance.contract_type.abi] + ) + + assert isinstance(contract, ContractInstance) + assert contract.address == contract_instance.address + assert contract.myNumber() == 0 + + +def test_contract_from_json_str(contract_instance): + contract = Contract( + contract_instance.address, + abi=json.dumps([abi.dict() for abi in contract_instance.contract_type.abi]), + ) + + assert isinstance(contract, ContractInstance) + assert contract.address == contract_instance.address + assert contract.myNumber() == 0 + + +def test_contract_from_file(contract_instance): + """ + need feedback about the json file specifications + """ + PROJECT_PATH = Path(__file__).parent + CONTRACTS_FOLDER = PROJECT_PATH / "data" / "contracts" / "ethereum" / "abi" + json_abi_file = f"{CONTRACTS_FOLDER}/contract_abi.json" + + address = contract_instance.address + contract = Contract(address, abi=json_abi_file) + + assert isinstance(contract, ContractInstance) + assert contract.address == address + assert contract.myNumber() == 0