Skip to content

Commit

Permalink
Support Foreign objects as ABI arguments and address ARC-4 changes (#251
Browse files Browse the repository at this point in the history
)

* Start ABI JSON interaction

* Add static annoation

* Fix Method argument parsing

* Add ABI Typing to Method arguments

* [WIP] Add AtomicTransactionComposer build functions

* [WIP] Sign and send atomic transaction groups

* Add unit tests for object parsing

* Clean up method calls

* Address PR comments on JSON objects

* Refactor ABI Type to ABIType so it can be exposed to outside world

* Add cucumber steps for ABI tests and update existing implementation so it can pass these tests

* Refactor TransactionSigner to Abstract class and merge signatures when signing

* Update testing to reflect json unit tests and composer tests

* Formatting and docstring fixes

* Add foreign types for method arguments

* Clean up imports

* Fix unit test for appId

* Add unit test for foreign array

* Refactor some names and add txn as an arg type

* Partially address PR comments

* Fix encoding args for foreign types

* Add some additional checks for safety

* Fix a step so we check for empty string instead of None

* Correct foreign app and account indices accounting for the implicit argument

* Resolve formatting

* Fix unit tests

* Fix foreign objects to compact duplicates and special values

* Refactor foreign objects, transactions, and address some new ABI changes

* ABI composer modifications and test updates

* Change Interface and Contract to newest ABI changes

* Fix some integration tests for composer

* Fix remaining composer tests

* Formatting changes

* Fix method json tests

* Address PR Comments, clean up and refactor composer and contract

* Create helper function for populating foreign objects

* Change type hints on reference and transaction checks

* Add generics and fix dictifying network info

* Fix step for cucumber test contract parsing
  • Loading branch information
algochoi authored and aldur committed Feb 8, 2022
1 parent 8da3ffd commit 7c975a7
Show file tree
Hide file tree
Showing 9 changed files with 554 additions and 133 deletions.
12 changes: 9 additions & 3 deletions algosdk/abi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
from algosdk.abi.array_dynamic_type import ArrayDynamicType
from algosdk.abi.array_static_type import ArrayStaticType
from algosdk.abi.tuple_type import TupleType
from .method import Method, Argument, Returns
from .interface import Interface
from .contract import Contract
from algosdk.abi.method import Method, Argument, Returns
from algosdk.abi.interface import Interface
from algosdk.abi.contract import Contract, NetworkInfo
from algosdk.abi.transaction import (
ABITransactionType,
is_abi_transaction_type,
check_abi_transaction_type,
)
from algosdk.abi.reference import ABIReferenceType, is_abi_reference_type

name = "abi"
56 changes: 48 additions & 8 deletions algosdk/abi/contract.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import json
from typing import List, Union
from typing import Dict, List, Union

from algosdk.abi.method import Method

Expand All @@ -10,22 +10,32 @@ class Contract:
Args:
name (string): name of the contract
app_id (int): application id associated with the contract
methods (list): list of Method objects
desc (string, optional): description of the contract
networks (dict, optional): information about the contract in a
particular network, such as an app-id.
"""

def __init__(self, name: str, app_id: int, methods: List[Method]) -> None:
def __init__(
self,
name: str,
methods: List[Method],
desc: str = None,
networks: Dict[str, "NetworkInfo"] = None,
) -> None:
self.name = name
self.app_id = int(app_id)
self.methods = methods
self.desc = desc
self.networks = networks if networks else {}

def __eq__(self, o: object) -> bool:
if not isinstance(o, Contract):
return False
return (
self.name == o.name
and self.app_id == o.app_id
and self.methods == o.methods
and self.desc == o.desc
and self.networks == o.networks
)

@staticmethod
Expand All @@ -36,13 +46,43 @@ def from_json(resp: Union[str, bytes, bytearray]) -> "Contract":
def dictify(self) -> dict:
d = {}
d["name"] = self.name
d["appId"] = self.app_id
d["methods"] = [m.dictify() for m in self.methods]
d["desc"] = self.desc
d["networks"] = {k: v.dictify() for k, v in self.networks.items()}
return d

@staticmethod
def undictify(d: dict) -> "Contract":
name = d["name"]
app_id = d["appId"]
method_list = [Method.undictify(method) for method in d["methods"]]
return Contract(name=name, app_id=app_id, methods=method_list)
desc = d["desc"] if "desc" in d else None
networks = d["networks"] if "networks" in d else {}
for k, v in networks.items():
networks[k] = NetworkInfo.undictify(v)
return Contract(
name=name, desc=desc, networks=networks, methods=method_list
)


class NetworkInfo:
"""
Represents network information.
Args:
app_id (int): application ID on a particular network
"""

def __init__(self, app_id: int) -> None:
self.app_id = app_id

def __eq__(self, o: object) -> bool:
if not isinstance(o, NetworkInfo):
return False
return self.app_id == o.app_id

def dictify(self) -> dict:
return {"appID": self.app_id}

@staticmethod
def undictify(d: dict) -> "NetworkInfo":
return NetworkInfo(app_id=d["appID"])
17 changes: 14 additions & 3 deletions algosdk/abi/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,24 @@ class Interface:
Args:
name (string): name of the interface
methods (list): list of Method objects
desc (string, optional): description of the interface
"""

def __init__(self, name: str, methods: List[Method]) -> None:
def __init__(
self, name: str, methods: List[Method], desc: str = None
) -> None:
self.name = name
self.methods = methods
self.desc = desc

def __eq__(self, o: object) -> bool:
if not isinstance(o, Interface):
return False
return self.name == o.name and self.methods == o.methods
return (
self.name == o.name
and self.methods == o.methods
and self.desc == o.desc
)

@staticmethod
def from_json(resp: Union[str, bytes, bytearray]) -> "Interface":
Expand All @@ -31,10 +39,13 @@ def dictify(self) -> dict:
d = {}
d["name"] = self.name
d["methods"] = [m.dictify() for m in self.methods]
if self.desc:
d["desc"] = self.desc
return d

@staticmethod
def undictify(d: dict) -> "Interface":
name = d["name"]
method_list = [Method.undictify(method) for method in d["methods"]]
return Interface(name=name, methods=method_list)
desc = d["desc"] if "desc" in d else None
return Interface(name=name, desc=desc, methods=method_list)
17 changes: 4 additions & 13 deletions algosdk/abi/method.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,6 @@
from algosdk import abi, constants, error


TRANSACTION_ARGS = (
"txn", # Denotes a placeholder for any of the six transaction types below
constants.PAYMENT_TXN,
constants.KEYREG_TXN,
constants.ASSETCONFIG_TXN,
constants.ASSETTRANSFER_TXN,
constants.ASSETFREEZE_TXN,
constants.APPCALL_TXN,
)


class Method:
"""
Represents a ABI method description.
Expand Down Expand Up @@ -44,7 +33,7 @@ def __init__(
# add one for this method call itself.
txn_count = 1
for arg in self.args:
if arg.type in TRANSACTION_ARGS:
if abi.is_abi_transaction_type(arg.type):
txn_count += 1
self.txn_calls = txn_count

Expand Down Expand Up @@ -150,7 +139,9 @@ class Argument:
def __init__(
self, arg_type: str, name: str = None, desc: str = None
) -> None:
if arg_type in TRANSACTION_ARGS:
if abi.is_abi_transaction_type(arg_type) or abi.is_abi_reference_type(
arg_type
):
self.type = arg_type
else:
# If the type cannot be parsed into an ABI type, it will error
Expand Down
20 changes: 20 additions & 0 deletions algosdk/abi/reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from typing import Any


class ABIReferenceType:
# Account reference type
ACCOUNT = "account"

# Application reference type
APPLICATION = "application"

# Asset reference type
ASSET = "asset"


def is_abi_reference_type(t: Any) -> bool:
return t in (
ABIReferenceType.ACCOUNT,
ABIReferenceType.APPLICATION,
ABIReferenceType.ASSET,
)
45 changes: 45 additions & 0 deletions algosdk/abi/transaction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from typing import Any

from algosdk import constants
from algosdk.future.transaction import Transaction


class ABITransactionType:
# Any transaction type
ANY = "txn"

# Payment transaction type
PAY = constants.PAYMENT_TXN

# Key registration transaction type
KEYREG = constants.KEYREG_TXN

# Asset configuration transaction type
ACFG = constants.ASSETCONFIG_TXN

# Asset transfer transaction type
AXFER = constants.ASSETTRANSFER_TXN

# Asset freeze transaction type
AFRZ = constants.ASSETFREEZE_TXN

# Application transaction type
APPL = constants.APPCALL_TXN


def is_abi_transaction_type(t: Any) -> bool:
return t in (
ABITransactionType.ANY,
ABITransactionType.PAY,
ABITransactionType.KEYREG,
ABITransactionType.ACFG,
ABITransactionType.AXFER,
ABITransactionType.AFRZ,
ABITransactionType.APPL,
)


def check_abi_transaction_type(t: Any, txn: Transaction) -> bool:
if t == ABITransactionType.ANY:
return True
return txn.type and txn.type == t
Loading

0 comments on commit 7c975a7

Please sign in to comment.