Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FOR REVIEW ONLY - py-algorand-sdk v1.9.0b1 #257

Merged
merged 7 commits into from
Nov 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2.1
jobs:
build:
machine:
image: "ubuntu-2004:202104-01"
steps:
- checkout
- run:
command: |
pip3 install -r requirements.txt
black --check .
set -e
python3 test_unit.py
make docker-test
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## 1.9.0b1
### Added

- ABI Interaction Support for Python SDK (#247)
- ABI Type encoding support (#238)
- Add type hints and clean up ABI code (#253)
- Add CircleCI configs to the Python SDK repo (#246)

## 1.8.0
### Added

Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
unit:
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
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

integration:
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
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

docker-test:
./run_integration.sh
1 change: 1 addition & 0 deletions algosdk/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from . import abi
from . import account
from . import algod
from . import auction
Expand Down
15 changes: 15 additions & 0 deletions algosdk/abi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from algosdk.abi.uint_type import UintType
from algosdk.abi.ufixed_type import UfixedType
from algosdk.abi.base_type import ABIType
from algosdk.abi.bool_type import BoolType
from algosdk.abi.byte_type import ByteType
from algosdk.abi.address_type import AddressType
from algosdk.abi.string_type import StringType
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

name = "abi"
90 changes: 90 additions & 0 deletions algosdk/abi/address_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
from typing import Union

from algosdk.abi.base_type import ABIType
from algosdk.abi.byte_type import ByteType
from algosdk.abi.tuple_type import TupleType
from algosdk import error

from algosdk import encoding


class AddressType(ABIType):
"""
Represents an Address ABI Type for encoding.
"""

def __init__(self) -> None:
super().__init__()

def __eq__(self, other: object) -> bool:
if not isinstance(other, AddressType):
return False
return True

def __str__(self) -> str:
return "address"

def byte_len(self) -> int:
return 32

def is_dynamic(self) -> bool:
return False

def _to_tuple_type(self):
child_type_array = list()
for _ in range(self.byte_len()):
child_type_array.append(ByteType())
return TupleType(child_type_array)

def encode(self, value: Union[str, bytes]) -> bytes:
"""
Encode an address string or a 32-byte public key into a Address ABI bytestring.

Args:
value (str | bytes): value to be encoded. It can be either a base32
address string or a 32-byte public key.

Returns:
bytes: encoded bytes of the address
"""
# Check that the value is an address in string or the public key in bytes
if isinstance(value, str):
try:
value = encoding.decode_address(value)
except Exception as e:
raise error.ABIEncodingError(
"cannot encode the following address: {}".format(value)
) from e
elif (
not (isinstance(value, bytes) or isinstance(value, bytearray))
or len(value) != 32
):
raise error.ABIEncodingError(
"cannot encode the following public key: {}".format(value)
)
return bytes(value)

def decode(self, bytestring: Union[bytearray, bytes]) -> str:
"""
Decodes a bytestring to a base32 encoded address string.

Args:
bytestring (bytes | bytearray): bytestring to be decoded

Returns:
str: base32 encoded address from the encoded bytestring
"""
if (
not (
isinstance(bytestring, bytearray)
or isinstance(bytestring, bytes)
)
or len(bytestring) != 32
):
raise error.ABIEncodingError(
"address string must be in bytes and correspond to a byte[32]: {}".format(
bytestring
)
)
# Return the base32 encoded address string
return encoding.encode_address(bytestring)
100 changes: 100 additions & 0 deletions algosdk/abi/array_dynamic_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
from typing import Any, List, NoReturn, Union

from algosdk.abi.base_type import ABI_LENGTH_SIZE, ABIType
from algosdk.abi.byte_type import ByteType
from algosdk.abi.tuple_type import TupleType
from algosdk import error


class ArrayDynamicType(ABIType):
"""
Represents a ArrayDynamic ABI Type for encoding.

Args:
child_type (ABIType): the type of the dynamic array.

Attributes:
child_type (ABIType)
"""

def __init__(self, arg_type: ABIType) -> None:
super().__init__()
self.child_type = arg_type

def __eq__(self, other: object) -> bool:
if not isinstance(other, ArrayDynamicType):
return False
return self.child_type == other.child_type

def __str__(self) -> str:
return "{}[]".format(self.child_type)

def byte_len(self) -> NoReturn:
raise error.ABITypeError(
"cannot get length of a dynamic type: {}".format(self)
)

def is_dynamic(self) -> bool:
return True

def _to_tuple_type(self, length: int) -> TupleType:
child_type_array = [self.child_type] * length
return TupleType(child_type_array)

def encode(self, value_array: Union[List[Any], bytes, bytearray]) -> bytes:
"""
Encodes a list of values into a ArrayDynamic ABI bytestring.

Args:
value_array (list | bytes | bytearray): list of values to be encoded.
If the child types are ByteType, then bytes or bytearray can be
passed in to be encoded as well.

Returns:
bytes: encoded bytes of the dynamic array
"""
if (
isinstance(value_array, bytes)
or isinstance(value_array, bytearray)
) and not isinstance(self.child_type, ByteType):
raise error.ABIEncodingError(
"cannot pass in bytes when the type of the array is not ByteType: {}".format(
value_array
)
)
converted_tuple = self._to_tuple_type(len(value_array))
length_to_encode = len(converted_tuple.child_types).to_bytes(
2, byteorder="big"
)
encoded = converted_tuple.encode(value_array)
return bytes(length_to_encode) + encoded

def decode(self, array_bytes: Union[bytes, bytearray]) -> list:
"""
Decodes a bytestring to a dynamic list.

Args:
array_bytes (bytes | bytearray): bytestring to be decoded

Returns:
list: values from the encoded bytestring
"""
if not (
isinstance(array_bytes, bytearray)
or isinstance(array_bytes, bytes)
):
raise error.ABIEncodingError(
"value to be decoded must be in bytes: {}".format(array_bytes)
)
if len(array_bytes) < ABI_LENGTH_SIZE:
raise error.ABIEncodingError(
"dynamic array is too short to be decoded: {}".format(
len(array_bytes)
)
)

byte_length = int.from_bytes(
array_bytes[:ABI_LENGTH_SIZE], byteorder="big"
)
converted_tuple = self._to_tuple_type(byte_length)
return converted_tuple.decode(array_bytes[ABI_LENGTH_SIZE:])
109 changes: 109 additions & 0 deletions algosdk/abi/array_static_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import math
from typing import Any, List, Union

from algosdk.abi.base_type import ABIType
from algosdk.abi.bool_type import BoolType
from algosdk.abi.byte_type import ByteType
from algosdk.abi.tuple_type import TupleType
from algosdk import error


class ArrayStaticType(ABIType):
"""
Represents a ArrayStatic ABI Type for encoding.

Args:
child_type (ABIType): the type of the child_types array.
array_len (int): length of the static array.

Attributes:
child_type (ABIType)
static_length (int)
"""

def __init__(self, arg_type: ABIType, array_len: int) -> None:
if array_len < 1:
raise error.ABITypeError(
"static array length must be a positive integer: {}".format(
array_len
)
)
super().__init__()
self.child_type = arg_type
self.static_length = array_len

def __eq__(self, other: object) -> bool:
if not isinstance(other, ArrayStaticType):
return False
return (
self.child_type == other.child_type
and self.static_length == other.static_length
)

def __str__(self) -> str:
return "{}[{}]".format(self.child_type, self.static_length)

def byte_len(self) -> int:
if isinstance(self.child_type, BoolType):
# 8 Boolean values can be encoded into 1 byte
return math.ceil(self.static_length / 8)
element_byte_length = self.child_type.byte_len()
return self.static_length * element_byte_length

def is_dynamic(self) -> bool:
return self.child_type.is_dynamic()

def _to_tuple_type(self) -> TupleType:
child_type_array = [self.child_type] * self.static_length
return TupleType(child_type_array)

def encode(self, value_array: Union[List[Any], bytes, bytearray]) -> bytes:
"""
Encodes a list of values into a ArrayStatic ABI bytestring.

Args:
value_array (list | bytes | bytearray): list of values to be encoded.
The number of elements must match the predefined length of array.
If the child types are ByteType, then bytes or bytearray can be
passed in to be encoded as well.

Returns:
bytes: encoded bytes of the static array
"""
if len(value_array) != self.static_length:
raise error.ABIEncodingError(
"value array length does not match static array length: {}".format(
len(value_array)
)
)
if (
isinstance(value_array, bytes)
or isinstance(value_array, bytearray)
) and not isinstance(self.child_type, ByteType):
raise error.ABIEncodingError(
"cannot pass in bytes when the type of the array is not ByteType: {}".format(
value_array
)
)
converted_tuple = self._to_tuple_type()
return converted_tuple.encode(value_array)

def decode(self, array_bytes: Union[bytes, bytearray]) -> list:
"""
Decodes a bytestring to a static list.

Args:
array_bytes (bytes | bytearray): bytestring to be decoded

Returns:
list: values from the encoded bytestring
"""
if not (
isinstance(array_bytes, bytearray)
or isinstance(array_bytes, bytes)
):
raise error.ABIEncodingError(
"value to be decoded must be in bytes: {}".format(array_bytes)
)
converted_tuple = self._to_tuple_type()
return converted_tuple.decode(array_bytes)
Loading