Skip to content

Commit

Permalink
Merge branch 'release/v1.9.0b1'
Browse files Browse the repository at this point in the history
  • Loading branch information
onetechnical committed Nov 26, 2021
2 parents 7b62bab + f0f7cc1 commit 6785baa
Show file tree
Hide file tree
Showing 23 changed files with 3,198 additions and 13 deletions.
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

0 comments on commit 6785baa

Please sign in to comment.