Skip to content

Commit

Permalink
Merge pull request #262 from valory-xyz/feat/auto-contract-methods
Browse files Browse the repository at this point in the history
[WIP - 1.21.0] Feat/auto contract methods
  • Loading branch information
DavidMinarsch authored Sep 29, 2022
2 parents 7267518 + 734af7f commit 1f33a54
Show file tree
Hide file tree
Showing 17 changed files with 1,004 additions and 29 deletions.
17 changes: 12 additions & 5 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

## 1.21.0 (2022-09-28)

AEA:
- Updates `aea scaffold contract` to include contract ABIs

Packages:
- Adds support for running local ACN nodes
- Converts `ledger` and `http_client` connections and `http`, `ledger_api` and `contract_api` protocols to valory packages and syncs them with `open-autonomy` versions of the same packages
- Extends `ledger` connection to automatically handle contract calls to methods not implemented in the contract package, redirecting them to a default contract method.

Plugins:
- Introduces test tools module for IPFS cli plugin
Expand All @@ -14,17 +18,20 @@ Tests:
- Fixes flaky `DHT (ACN/Libp2p)` tests on windows
- Introduces test for assessing robustness of the ACN setup without agents

Docs:
- Adds a guide on implementing contract packages


## 1.20.0 (2022-09-20)

AEA:
- Ensures author and year in copyright headers are updated in scaffolded components
- Updates `check-packages`
- Updates `check-packages`
- to check the presence of the constant `PUBLIC_ID` for connections and skills.
- to validate author
- Fixes CLI help message for `aea config set` command
- Extends test command to support consistency check skips and to run tests for a specific author
- Adds proper exception raising and error handling
- Adds proper exception raising and error handling
- Exception handling when downloading from IPFS nodes
- Better error message when the `--aev` flag is not provided
- Fixes file sorting to maintain consistency of links on `PBNode` object on `IPFSHashOnly` tool
Expand All @@ -43,15 +50,15 @@ Chores:
- Fixes docstring formatting to make sure doc generator works fine
- Updated `check_ipfs_hashes.py` script to use `packages.json` instead of `hashes.csv`
- Updates the command regex to align with the latest version

## 1.19.0 (2022-09-14)

AEA:
- Updates the `aea init` command to set the local as default registry and IPFS as default remote registry
- Updates the `aea test packages` to include the agent tests
- Introduces
- Introduces
- `aea packages` command group to manage local packages repository
- `aea packages lock` command to lock all available packages and create `packages.json` file
- `aea packages lock` command to lock all available packages and create `packages.json` file
- `aea packages sync` command to synchronize the local packages repository

Chores:
Expand Down
66 changes: 63 additions & 3 deletions aea/cli/scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
import shutil
from datetime import datetime
from pathlib import Path
from typing import cast
from typing import Optional, cast

import click
from jsonschema import ValidationError
Expand All @@ -39,10 +39,12 @@
create_symlink_vendor_to_local,
validate_package_name,
)
from aea.configurations.base import PublicId
from aea.configurations.base import PackageType, PublicId
from aea.configurations.constants import ( # noqa: F401 # pylint: disable=unused-import
BUILD,
CONNECTION,
CONTRACT,
CONTRACTS,
DEFAULT_AEA_CONFIG_FILE,
DEFAULT_CONNECTION_CONFIG_FILE,
DEFAULT_CONTRACT_CONFIG_FILE,
Expand All @@ -53,7 +55,9 @@
PROTOCOL,
SCAFFOLD_PUBLIC_ID,
SKILL,
_ETHEREUM_IDENTIFIER,
)
from aea.configurations.loader import ConfigLoader
from aea.helpers.io import open_file
from aea.helpers.ipfs.base import IPFSHashOnly

Expand Down Expand Up @@ -137,10 +141,17 @@ def connection(ctx: Context, connection_name: str) -> None:

@scaffold.command()
@click.argument("contract_name", type=str, required=True)
@click.argument(
"contract_abi_path", type=click.Path(dir_okay=True, exists=True), required=False
)
@pass_ctx
def contract(ctx: Context, contract_name: str) -> None:
def contract(
ctx: Context, contract_name: str, contract_abi_path: Optional[Path] = None
) -> None:
"""Add a contract scaffolding to the configuration file and agent."""
scaffold_item(ctx, CONTRACT, contract_name)
if contract_abi_path:
add_contract_abi(ctx, contract_name, Path(contract_abi_path))


@scaffold.command()
Expand Down Expand Up @@ -360,3 +371,52 @@ def _scaffold_non_package_item(
except Exception as e:
os.remove(dest)
raise click.ClickException(str(e))


def add_contract_abi(ctx: Context, contract_name: str, contract_abi_path: Path) -> None:
"""
Add the contract ABI to a contract scaffold.
:param ctx: the CLI context.
:param contract_name: the contract name.
:param contract_abi_path: the contract ABI path.
"""

# Get some data from the context
to_local_registry = ctx.config.get("to_local_registry")
author_name = ctx.agent_config.author

# Create the build directory and copy the ABI file
contract_dir_root = (
Path(str(ctx.agent_config.directory)) if to_local_registry else Path()
)

contract_dir = contract_dir_root / Path(CONTRACTS) / Path(contract_name)
abi_dest = contract_dir / Path(BUILD) / Path(contract_abi_path.name)

click.echo(f"Updating contract scaffold '{contract_name}' to include ABI...")

os.makedirs(os.path.dirname(abi_dest), exist_ok=True)
shutil.copyfile(str(contract_abi_path), abi_dest)

# Load configuration (contract.yaml)
config_file = Path(contract_dir / DEFAULT_CONTRACT_CONFIG_FILE)
config_loader = ConfigLoader.from_configuration_type(PackageType.CONTRACT)
contract_config = config_loader.load(config_file.open("r"))

# Add ABI to the configuration
# Can't use contract_config.update() here. Since contract_interface_paths is empty during instantiation,
# it cannot be validated afterwards even if we add the correct values to ContractConfig.FIELDS_ALLOWED_TO_UPDATE
contract_config.contract_interface_paths = {
_ETHEREUM_IDENTIFIER: f"{BUILD}/{contract_abi_path.name}"
}

# Write the new configuration
config_loader.dump(contract_config, config_file.open("w+"))

# Fingerprint again
new_public_id = PublicId(author_name, contract_name, DEFAULT_VERSION)

if to_local_registry:
ctx.cwd = str(ctx.agent_config.directory)
fingerprint_item(ctx, CONTRACT, new_public_id)
1 change: 1 addition & 0 deletions aea/configurations/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
VENDOR = "vendor"
AGENT = "agent"
AGENTS = "agents"
BUILD = "build"
CONNECTION = "connection"
CONNECTIONS = "connections"
CONTRACT = "contract"
Expand Down
25 changes: 25 additions & 0 deletions aea/contracts/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,31 @@ def build_transaction(
)
return tx

@classmethod
def default_method_call(
cls,
ledger_api: LedgerApi,
contract_address: str,
method_name: str,
**kwargs: Any,
) -> Optional[JSONLike]:
"""
Make a contract call.
:param ledger_api: the ledger apis.
:param contract_address: the contract address.
:param method_name: the method to call.
:param kwargs: keyword arguments.
:return: the call result
"""

contract_instance = cls.get_instance(ledger_api, contract_address)

result = ledger_api.contract_method_call(
contract_instance, method_name, **kwargs
)
return result

@classmethod
def get_transaction_transfer_logs(
cls,
Expand Down
23 changes: 23 additions & 0 deletions docs/api/contracts/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,29 @@ Build a transaction.

the transaction

<a id="aea.contracts.base.Contract.default_method_call"></a>

#### default`_`method`_`call

```python
@classmethod
def default_method_call(cls, ledger_api: LedgerApi, contract_address: str,
method_name: str, **kwargs: Any) -> Optional[JSONLike]
```

Make a contract call.

**Arguments**:

- `ledger_api`: the ledger apis.
- `contract_address`: the contract address.
- `method_name`: the method to call.
- `kwargs`: keyword arguments.

**Returns**:

the call result

<a id="aea.contracts.base.Contract.get_transaction_transfer_logs"></a>

#### get`_`transaction`_`transfer`_`logs
Expand Down
17 changes: 17 additions & 0 deletions docs/api/plugins/aea_cli_ipfs/test_tools/fixture_helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<a id="plugins.aea-cli-ipfs.aea_cli_ipfs.test_tools.fixture_helpers"></a>

# plugins.aea-cli-ipfs.aea`_`cli`_`ipfs.test`_`tools.fixture`_`helpers

Fixture helpers.

<a id="plugins.aea-cli-ipfs.aea_cli_ipfs.test_tools.fixture_helpers.ipfs_daemon"></a>

#### ipfs`_`daemon

```python
@pytest.fixture(scope="module")
def ipfs_daemon() -> Iterator[bool]
```

Starts an IPFS daemon for the tests.

2 changes: 1 addition & 1 deletion packages/fetchai/skills/erc1155_client/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fingerprint:
tests/test_strategy.py: bafybeicbxie3v6vue3gcnru6vsvggcgy3shxwrldis5gppizbuhooslcqa
fingerprint_ignore_patterns: []
connections:
- valory/ledger:0.19.0:bafybeigpltrga4ggf4nejvl7l32zioyk77jzodvhthjwd3uvdkuxedvnz4
- valory/ledger:0.19.0:bafybeic34ll726rlh4bwaeiuzmw5aoiw54ikvdgznlc5q2njv43affylpe
contracts:
- fetchai/erc1155:0.22.0:bafybeidw2vhsh3ifmg5sxnbxhpu4ygosov5jtzjhilsfkdhoanvwiu7dyi
protocols:
Expand Down
2 changes: 1 addition & 1 deletion packages/fetchai/skills/erc1155_deploy/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fingerprint:
tests/test_strategy.py: bafybeigxtw2j2c7vl6xhdwos62jbtmx62xfgdyadptm5eewmkesmcooyea
fingerprint_ignore_patterns: []
connections:
- valory/ledger:0.19.0:bafybeigpltrga4ggf4nejvl7l32zioyk77jzodvhthjwd3uvdkuxedvnz4
- valory/ledger:0.19.0:bafybeic34ll726rlh4bwaeiuzmw5aoiw54ikvdgznlc5q2njv43affylpe
contracts:
- fetchai/erc1155:0.22.0:bafybeidw2vhsh3ifmg5sxnbxhpu4ygosov5jtzjhilsfkdhoanvwiu7dyi
protocols:
Expand Down
2 changes: 1 addition & 1 deletion packages/fetchai/skills/generic_buyer/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fingerprint:
tests/test_models.py: bafybeibh72j3n72yseqvmpppucpu5wtidf6ebxbxkfnmrnlh4zv5y5apei
fingerprint_ignore_patterns: []
connections:
- valory/ledger:0.19.0:bafybeigpltrga4ggf4nejvl7l32zioyk77jzodvhthjwd3uvdkuxedvnz4
- valory/ledger:0.19.0:bafybeic34ll726rlh4bwaeiuzmw5aoiw54ikvdgznlc5q2njv43affylpe
contracts: []
protocols:
- fetchai/default:1.0.0:bafybeihzesahyayexkhk26fg7rqnjuqaab3bmcijtjekvskvs4xw6ecyuu
Expand Down
2 changes: 1 addition & 1 deletion packages/fetchai/skills/generic_seller/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fingerprint:
tests/test_models.py: bafybeihabrc22zqssit3fmqhxptosy6qz6mx65ukhf5iayvirfv42xrhoq
fingerprint_ignore_patterns: []
connections:
- valory/ledger:0.19.0:bafybeigpltrga4ggf4nejvl7l32zioyk77jzodvhthjwd3uvdkuxedvnz4
- valory/ledger:0.19.0:bafybeic34ll726rlh4bwaeiuzmw5aoiw54ikvdgznlc5q2njv43affylpe
contracts: []
protocols:
- fetchai/default:1.0.0:bafybeihzesahyayexkhk26fg7rqnjuqaab3bmcijtjekvskvs4xw6ecyuu
Expand Down
10 changes: 5 additions & 5 deletions packages/packages.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"protocol/valory/ledger_api/1.0.0": "bafybeih7rhi5zvfvwakx5ifgxsz2cfipeecsh7bm3gnudjxtvhrygpcftq",
"connection/fetchai/http_server/0.22.0": "bafybeifbdjgzroxywqu5bimvj373yduhnc5n27vxbjf3fqs3mx7s6a5etq",
"connection/fetchai/stub/0.21.0": "bafybeieqlozydyvdxmjxhqygwq27djecpiftoqwlcpcr4qpotomwnh66yy",
"connection/valory/ledger/0.19.0": "bafybeigpltrga4ggf4nejvl7l32zioyk77jzodvhthjwd3uvdkuxedvnz4",
"connection/valory/ledger/0.19.0": "bafybeic34ll726rlh4bwaeiuzmw5aoiw54ikvdgznlc5q2njv43affylpe",
"connection/valory/p2p_libp2p/0.1.0": "bafybeibjirmffnyih5gplt2uu6ojvta25w7up6yt447unungn4xpgqn5ca",
"connection/valory/p2p_libp2p_client/0.1.0": "bafybeihf35zfr35qsvfte4vbi7njvuzfx4httysw7owmlux53gvxh2or54",
"connection/valory/p2p_libp2p_mailbox/0.1.0": "bafybeibptb6lzhyknd265jc2n33r25g4a6fuhmh4pdmkmf4dy4o5pkv6vi",
Expand All @@ -31,11 +31,11 @@
"connection/valory/http_client/0.23.0": "bafybeihz3tubwado7j3wlivndzzuj3c6fdsp4ra5r3nqixn3ufawzo3wii",
"connection/valory/test_libp2p/0.1.0": "bafybeiapa25o6vzyuyvdcfad7bgvhknaa2he6rkyp2sdicne7j446hj6ya",
"protocol/fetchai/tac/1.0.0": "bafybeiaew226n32rwp3h57zl4b2mmbrhjbyrdjbl2evnxf2tmmi4vrls7a",
"skill/fetchai/erc1155_client/0.28.0": "bafybeiaxla5l367xokpjjtnqt5z5jfcghafelrxsvr73kbavhn72wfazsi",
"skill/fetchai/erc1155_deploy/0.30.0": "bafybeieu4zmaxvvnldetqt6656wxdrxzo6r7wmix3wwjtshhnvdsojozbe",
"skill/fetchai/erc1155_client/0.28.0": "bafybeigcmxyzdblcbhsnzptyj2hgcjhfmvdelu5mfguumasoc3kbq72ufy",
"skill/fetchai/erc1155_deploy/0.30.0": "bafybeigj7ymiq6mjkhjxhabf5gj42u3xcl2nqwxi23henx7hdsz2tmno6y",
"skill/fetchai/error/0.17.0": "bafybeib7nhokw3bc46oxuk5mjazan42evipowmka2ikfcs6drcdz4mwkjm",
"skill/fetchai/fipa_dummy_buyer/0.2.0": "bafybeiha4jultg5srhr2ijplvubeo7esv4raq2cjlggmyzcaimop2ggg2m",
"skill/fetchai/generic_buyer/0.26.0": "bafybeibeif2qi7ckrt7hiv3ejt4wszblhmqiztomegebxlkoc625msnn7i",
"skill/fetchai/generic_seller/0.27.0": "bafybeifxkhcyll4skuooap7mukqpnvivtypovlr7pyrc3ieads3ya2v3ji",
"skill/fetchai/generic_buyer/0.26.0": "bafybeigjobpo33znfokdktmrbobnh3ygzvb54d6bpcjpdll3ooomd66whm",
"skill/fetchai/generic_seller/0.27.0": "bafybeiflkay5u3nrxdysob6vjzee6w5dznr2hnbwxnyqgwfcykcyf7c3qa",
"skill/fetchai/task_test_skill/0.1.0": "bafybeidv77u2xl52mnxakwvh7fuh46aiwfpteyof4eaptfd4agoi6cdble"
}
2 changes: 1 addition & 1 deletion packages/valory/connections/ledger/connection.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fingerprint:
__init__.py: bafybeierqitcqk7oy6m3qp7jgs67lcg55mzt3arltkwimuii2ynfejccwi
base.py: bafybeicpyhus3h2t5urzldnjns2sfwae64uinethqnlunudclbdg4xftnq
connection.py: bafybeiehfn2chbgeat5mj23mcelfrfifiezvrwwucdpaz7ku2ygo7dxd5y
contract_dispatcher.py: bafybeiatma5jzq3ynh52t5r7ibq4k6qzs3c5opkf4ct67qkmq7l2mccmxq
contract_dispatcher.py: bafybeigqgqe6zef335t2ygp4celx7445etwjsr42yroc2qmrynwfslgjhq
ledger_dispatcher.py: bafybeibh2d5qgj76stlkiiynu2irvcohntf27f3zrqwlkxwz5zjz67u4qm
tests/__init__.py: bafybeieyhttiwruutk6574yzj7dk2afamgdum5vktyv54gsax7dlkuqtc4
tests/conftest.py: bafybeihqsdoamxlgox2klpjwmyrylrycyfon3jldvmr24q4ai33h24llpi
Expand Down
29 changes: 25 additions & 4 deletions packages/valory/connections/ledger/contract_dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

from aea.common import JSONLike
from aea.contracts import Contract, contract_registry
from aea.contracts.base import snake_to_camel
from aea.crypto.base import LedgerApi
from aea.crypto.registries import Registry
from aea.exceptions import AEAException, parse_exception
Expand Down Expand Up @@ -357,12 +358,32 @@ def _validate_and_call_callable(
:param contract: the contract instance.
:return: the data generated by the method.
"""
method_to_call: Optional[Callable] = None
try:
method_to_call = getattr(contract, message.callable)
except AttributeError as exception:
raise AEAException(
f"Cannot find {message.callable} in contract {type(contract)}"
) from exception
except AttributeError:
_default_logger.info(
f"Contract method {message.callable} not found in the contract package {contract.contract_id}. Checking in the ABI..."
)

# Check for the method in the ABI
if not method_to_call:
try:
contract_instance = contract.get_instance(api, message.contract_address)
contract_instance.get_function_by_name(message.callable)
except ValueError:
raise AEAException(
f"Contract method {message.callable} not found in ABI of contract {type(contract)}"
)

default_method_call = contract.default_method_call
return default_method_call( # type: ignore
api,
message.contract_address,
snake_to_camel(message.callable),
**message.kwargs.body,
)

full_args_spec = inspect.getfullargspec(method_to_call)
if message.performative in [
ContractApiMessage.Performative.GET_STATE,
Expand Down
1 change: 1 addition & 0 deletions scripts/whitelist.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,4 @@
remove_test_directory # unused function (aea/test_tools/utils.py:44)
execinfo # unused variable (aea/test_tools/utils.py:55)
ACNWithBootstrappedEntryNodesDockerImage # unused class (aea/test_tools/acn_image.py:166)
default_method_call # unused method (aea/contracts/base.py:260)
Loading

0 comments on commit 1f33a54

Please sign in to comment.