Skip to content

Commit d8b29d9

Browse files
authored
ABI Interaction Support for Python SDK (#247)
* 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 * Clean up imports * Fix unit test for appId * Refactor some names and add txn as an arg type * Partially address PR comments * Add some additional checks for safety * Fix a step so we check for empty string instead of None * MInclude returns tests and check for a valid returns from a ABI call * Addressing PR comments about type hints and returning self * Ensure group ids are zero when adding transactions
1 parent 8cd3bb6 commit d8b29d9

19 files changed

+1391
-33
lines changed

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
unit:
2-
behave --tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.responses.231 or @unit.feetest or @unit.indexer.logs" test -f progress2
2+
behave --tags="@unit.offline or @unit.algod or @unit.indexer or @unit.rekey or @unit.tealsign or @unit.dryrun or @unit.applications or @unit.responses or @unit.transactions or @unit.transactions.payment or @unit.responses.231 or @unit.feetest or @unit.indexer.logs or @unit.abijson or @unit.atomic_transaction_composer" test -f progress2
33

44
integration:
5-
behave --tags="@algod or @assets or @auction or @kmd or @send or @template or @indexer or @indexer.applications or @rekey or @compile or @dryrun or @dryrun.testing or @applications or @applications.verified or @indexer.231" test -f progress2
5+
behave --tags="@algod or @assets or @auction or @kmd or @send or @template or @indexer or @indexer.applications or @rekey or @compile or @dryrun or @dryrun.testing or @applications or @applications.verified or @indexer.231 or @abi" test -f progress2
66

77
docker-test:
88
./run_integration.sh

algosdk/abi/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
from .util import type_from_string
22
from .uint_type import UintType
33
from .ufixed_type import UfixedType
4+
from .base_type import ABIType
45
from .bool_type import BoolType
56
from .byte_type import ByteType
67
from .address_type import AddressType
78
from .string_type import StringType
89
from .array_dynamic_type import ArrayDynamicType
910
from .array_static_type import ArrayStaticType
1011
from .tuple_type import TupleType
12+
from .method import Method, Argument, Returns
13+
from .interface import Interface
14+
from .contract import Contract
1115

1216
name = "abi"

algosdk/abi/address_type.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
from .base_type import Type
1+
from .base_type import ABIType
22
from .byte_type import ByteType
33
from .tuple_type import TupleType
44
from .. import error
55

66
from algosdk import encoding
77

88

9-
class AddressType(Type):
9+
class AddressType(ABIType):
1010
"""
1111
Represents an Address ABI Type for encoding.
1212
"""

algosdk/abi/array_dynamic_type.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
from .base_type import ABI_LENGTH_SIZE, Type
1+
from .base_type import ABI_LENGTH_SIZE, ABIType
22
from .byte_type import ByteType
33
from .tuple_type import TupleType
44
from .. import error
55

66

7-
class ArrayDynamicType(Type):
7+
class ArrayDynamicType(ABIType):
88
"""
99
Represents a ArrayDynamic ABI Type for encoding.
1010

algosdk/abi/array_static_type.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import math
22

3-
from .base_type import Type
3+
from .base_type import ABIType
44
from .bool_type import BoolType
55
from .byte_type import ByteType
66
from .tuple_type import TupleType
77
from .. import error
88

99

10-
class ArrayStaticType(Type):
10+
class ArrayStaticType(ABIType):
1111
"""
1212
Represents a ArrayStatic ABI Type for encoding.
1313

algosdk/abi/base_type.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
from abc import ABC, abstractmethod
2-
from enum import IntEnum
32

43
# Globals
54
ABI_LENGTH_SIZE = 2 # We use 2 bytes to encode the length of a dynamic element
65

76

8-
class Type(ABC):
7+
class ABIType(ABC):
98
"""
109
Represents an ABI Type for encoding.
1110
"""
@@ -45,7 +44,7 @@ def encode(self, value):
4544
pass
4645

4746
@abstractmethod
48-
def decode(self, value_string):
47+
def decode(self, bytestring):
4948
"""
5049
Deserialize the ABI type and value from a byte string using ABI encoding rules.
5150
"""

algosdk/abi/bool_type.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from .base_type import Type
1+
from .base_type import ABIType
22
from .. import error
33

44

5-
class BoolType(Type):
5+
class BoolType(ABIType):
66
"""
77
Represents a Bool ABI Type for encoding.
88
"""

algosdk/abi/byte_type.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
from .base_type import Type
1+
from .base_type import ABIType
22
from .. import error
33

44

5-
class ByteType(Type):
5+
class ByteType(ABIType):
66
"""
77
Represents a Byte ABI Type for encoding.
88
"""

algosdk/abi/contract.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import json
2+
3+
from algosdk.abi.method import Method
4+
5+
6+
class Contract:
7+
"""
8+
Represents a ABI contract description.
9+
10+
Args:
11+
name (string): name of the contract
12+
app_id (int): application id associated with the contract
13+
methods (list): list of Method objects
14+
"""
15+
16+
def __init__(self, name, app_id, methods) -> None:
17+
self.name = name
18+
self.app_id = int(app_id)
19+
self.methods = methods
20+
21+
def __eq__(self, o) -> bool:
22+
if not isinstance(o, Contract):
23+
return False
24+
return (
25+
self.name == o.name
26+
and self.app_id == o.app_id
27+
and self.methods == o.methods
28+
)
29+
30+
@staticmethod
31+
def from_json(resp):
32+
d = json.loads(resp)
33+
return Contract.undictify(d)
34+
35+
def dictify(self):
36+
d = {}
37+
d["name"] = self.name
38+
d["appId"] = self.app_id
39+
d["methods"] = [m.dictify() for m in self.methods]
40+
return d
41+
42+
@staticmethod
43+
def undictify(d):
44+
name = d["name"]
45+
app_id = d["appId"]
46+
method_list = [Method.undictify(method) for method in d["methods"]]
47+
return Contract(name=name, app_id=app_id, methods=method_list)

algosdk/abi/interface.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
3+
from algosdk.abi.method import Method
4+
5+
6+
class Interface:
7+
"""
8+
Represents a ABI interface description.
9+
10+
Args:
11+
name (string): name of the interface
12+
methods (list): list of Method objects
13+
"""
14+
15+
def __init__(self, name, methods):
16+
self.name = name
17+
self.methods = methods
18+
19+
def __eq__(self, o):
20+
if not isinstance(o, Interface):
21+
return False
22+
return self.name == o.name and self.methods == o.methods
23+
24+
@staticmethod
25+
def from_json(resp):
26+
d = json.loads(resp)
27+
return Interface.undictify(d)
28+
29+
def dictify(self):
30+
d = {}
31+
d["name"] = self.name
32+
d["methods"] = [m.dictify() for m in self.methods]
33+
return d
34+
35+
@staticmethod
36+
def undictify(d):
37+
name = d["name"]
38+
method_list = [Method.undictify(method) for method in d["methods"]]
39+
return Interface(name=name, methods=method_list)

0 commit comments

Comments
 (0)