Skip to content

Commit

Permalink
feat: add contract schema and validate msgs if present (#271)
Browse files Browse the repository at this point in the history
  • Loading branch information
jrriehl authored Sep 21, 2022
1 parent b83f1ae commit da9778d
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 8 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ exclude tox.ini
exclude .firebaserc
recursive-exclude contracts *.txt
recursive-exclude contracts *.wasm
recursive-exclude contracts *.json
recursive-exclude docs *.*
recursive-exclude docs *
recursive-exclude examples *.py
Expand Down
39 changes: 39 additions & 0 deletions contracts/simple/schema/execute_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "ExecuteMsg",
"anyOf": [
{
"type": "object",
"required": [
"increment"
],
"properties": {
"increment": {
"type": "object"
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"reset"
],
"properties": {
"reset": {
"type": "object",
"required": [
"count"
],
"properties": {
"count": {
"type": "integer",
"format": "int32"
}
}
}
},
"additionalProperties": false
}
]
}
14 changes: 14 additions & 0 deletions contracts/simple/schema/instantiate_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "InstantiateMsg",
"type": "object",
"required": [
"count"
],
"properties": {
"count": {
"type": "integer",
"format": "int32"
}
}
}
18 changes: 18 additions & 0 deletions contracts/simple/schema/query_msg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "QueryMsg",
"anyOf": [
{
"type": "object",
"required": [
"get_count"
],
"properties": {
"get_count": {
"type": "object"
}
},
"additionalProperties": false
}
]
}
File renamed without changes.
42 changes: 40 additions & 2 deletions cosmpy/aerial/contract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,12 @@
"""cosmwasm contract functionality."""

import json
import os
from collections import UserString
from datetime import datetime
from typing import Any, Optional
from typing import Any, Dict, Optional

from jsonschema import validate

from cosmpy.aerial.client import LedgerClient, prepare_and_broadcast_basic_transaction
from cosmpy.aerial.contract.cosmwasm import (
Expand All @@ -42,6 +45,10 @@
QuerySmartContractStateRequest,
)

INSTANTIATE_MSG = "instantiate_msg"
EXECUTE_MSG = "execute_msg"
QUERY_MSG = "query_msg"


def _compute_digest(path: str) -> bytes:
with open(path, "rb") as input_file:
Expand All @@ -53,6 +60,21 @@ def _generate_label(digest: bytes) -> str:
return f"{digest.hex()[:14]}-{now.strftime('%Y%m%d%H%M%S')}"


def _load_contract_schema(schema_path: str) -> Optional[Dict[Any, Any]]:
if not os.path.isdir(schema_path):
return None

schema = {}
for filename in os.listdir(schema_path):
if filename.endswith(".json"):
msg_name = os.path.splitext(os.path.basename(filename))[0]
full_path = os.path.join(schema_path, filename)
with open(full_path, "r", encoding="utf-8") as msg_schema_file:
msg_schema = json.load(msg_schema_file)
schema[msg_name] = msg_schema
return schema


class LedgerContract(UserString):
"""Ledger contract."""

Expand All @@ -62,19 +84,26 @@ def __init__(
client: LedgerClient,
address: Optional[Address] = None,
digest: Optional[bytes] = None,
schema_path: Optional[str] = None,
):
"""Initialize the Ledger contract.
:param path: Path
:param client: Ledger client
:param address: address, defaults to None
:param digest: digest, defaults to None
:param schema_path: path to contract schema, defaults to None
"""
# pylint: disable=super-init-not-called
self._path = path
self._client = client
self._address = address

if schema_path is not None:
self._schema = _load_contract_schema(schema_path)
else:
self._schema = None

# select the digest either by computing it from the provided contract or by the value specified by
# the user
self._digest: Optional[bytes] = digest
Expand Down Expand Up @@ -175,6 +204,9 @@ def instantiate(
"""
assert self._digest is not None

if self._schema is not None:
validate(args, self._schema[INSTANTIATE_MSG])

label = label or _generate_label(bytes(self._digest))

# build up the store transaction
Expand Down Expand Up @@ -262,7 +294,10 @@ def execute(
if self._address is None:
raise RuntimeError("Contract appears not to be deployed currently")

# build up the store transaction
if self._schema is not None:
validate(args, self._schema[EXECUTE_MSG])

# build up the execute transaction
tx = Transaction()
tx.add_message(
create_cosmwasm_execute_msg(
Expand All @@ -284,6 +319,9 @@ def query(self, args: Any) -> Any:
if self._address is None:
raise RuntimeError("Contract appears not to be deployed currently")

if self._schema is not None:
validate(args, self._schema[QUERY_MSG])

req = QuerySmartContractStateRequest(
address=str(self._address), query_data=json_encode(args).encode("UTF8")
)
Expand Down
4 changes: 3 additions & 1 deletion docs/api/aerial/contract/__init__.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ Ledger contract.
def __init__(path: Optional[str],
client: LedgerClient,
address: Optional[Address] = None,
digest: Optional[bytes] = None)
digest: Optional[bytes] = None,
schema_path: Optional[str] = None)
```

Initialize the Ledger contract.
Expand All @@ -33,6 +34,7 @@ Initialize the Ledger contract.
- `client`: Ledger client
- `address`: address, defaults to None
- `digest`: digest, defaults to None
- `schema_path`: path to contract schema, defaults to None

<a id="cosmpy.aerial.contract.__init__.LedgerContract.path"></a>

Expand Down
2 changes: 1 addition & 1 deletion docs/deploy-a-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ You can deploy smart contracts in CosmPy using `LedgerContract`. For this, you w
```python
from cosmpy.aerial.contract import LedgerContract

PATH = "../simple.wasm"
PATH = "contracts/simple/simple.wasm"

contract = LedgerContract(PATH, ledger_client)
contract.deploy({}, wallet)
Expand Down
90 changes: 87 additions & 3 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ grpcio = "==1.47.0"
bip-utils = "*"
blspy = "*"
google-api-python-client = "*"
jsonschema = "^4.16.0"

[tool.poetry.group.dev.dependencies]
check-manifest = "*"
Expand Down
3 changes: 3 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ ignore_missing_imports = True
[mypy-mbedtls.*]
ignore_missing_imports = True

[mypy-jsonschema.*]
ignore_missing_imports = True

[isort]
profile = black
skip = protos
Expand Down
Loading

0 comments on commit da9778d

Please sign in to comment.