Skip to content

Commit

Permalink
feat: option to load contracts using ABIs [APE-1334] (ApeWorX#1635)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aviksaikat authored Sep 6, 2023
1 parent 28a8249 commit 26d22cc
Show file tree
Hide file tree
Showing 4 changed files with 324 additions and 1 deletion.
26 changes: 26 additions & 0 deletions docs/userguides/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
44 changes: 43 additions & 1 deletion src/ape/managers/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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,
Expand All @@ -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`
Expand All @@ -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,
Expand Down
205 changes: 205 additions & 0 deletions tests/functional/data/contracts/ethereum/abi/contract_abi.json
Original file line number Diff line number Diff line change
@@ -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"}]
50 changes: 50 additions & 0 deletions tests/functional/test_contract.py
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 26d22cc

Please sign in to comment.