-
Notifications
You must be signed in to change notification settings - Fork 143
ABI Type encoding support #238
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
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
21f01cf
Add initial support for ABI types
algochoi b7089eb
Finish up abi types and values
algochoi 5e4bd2b
Add doc strings
algochoi 5428035
Merge branch 'develop' of https://github.com/algorand/py-algorand-sdk…
algochoi f2ee5d3
Refactor and finish encoding functions
algochoi 9aa7984
More cleanup
algochoi 867d285
Fix minor bugs and add number decoding
algochoi a8f0a9e
Bump version to 1.8.0 and add changelog
bricerisingalgorand 23d0d3b
Add change to changelog
bricerisingalgorand 7b62bab
Merge branch 'release/v1.8.0'
bricerisingalgorand 77c111b
Add decoding for arrays and dynamic types
algochoi 053d263
Merge branch 'master' of https://github.com/algorand/py-algorand-sdk …
algochoi 0577857
Add more unit tests
algochoi 75e901a
Minor change from PR comment
algochoi 1e338f8
Another small PR comment change
algochoi 8f622a8
Split up files and remove circular imports
algochoi e4fc085
Address PR comments
algochoi 70ac9f5
Have arrays accept bytes and add docstrings
algochoi 7191b12
Minor clean up
algochoi 821cc81
Fix error messages for negative uint
algochoi 6aa4c2a
Remove unnecessary imports
algochoi 0c6a635
Refactor out abi_types since it is implicit by the class
algochoi 7fc99ab
Tuples don't need static lengths...
algochoi d6015ee
Address PR comments 1
algochoi b9f7ff4
Fix head encoding placeholders
algochoi fadabcb
Fix the tuple docstring
algochoi d52ebb6
Formatting fixes
algochoi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| from .util import type_from_string | ||
| from .uint_type import UintType | ||
| from .ufixed_type import UfixedType | ||
| from .bool_type import BoolType | ||
| from .byte_type import ByteType | ||
| from .address_type import AddressType | ||
| from .string_type import StringType | ||
| from .array_dynamic_type import ArrayDynamicType | ||
| from .array_static_type import ArrayStaticType | ||
| from .tuple_type import TupleType | ||
|
|
||
| name = "abi" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| from .base_type import Type | ||
| from .byte_type import ByteType | ||
| from .tuple_type import TupleType | ||
| from .. import error | ||
|
|
||
| from algosdk import encoding | ||
|
|
||
|
|
||
| class AddressType(Type): | ||
| """ | ||
| Represents an Address ABI Type for encoding. | ||
| """ | ||
|
|
||
| def __init__(self) -> None: | ||
| super().__init__() | ||
|
|
||
| def __eq__(self, other) -> bool: | ||
| if not isinstance(other, AddressType): | ||
| return False | ||
| return True | ||
|
|
||
| def __str__(self): | ||
| return "address" | ||
|
|
||
| def byte_len(self): | ||
| return 32 | ||
|
|
||
| def is_dynamic(self): | ||
| 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): | ||
| """ | ||
| 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): | ||
| """ | ||
| 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| from .base_type import ABI_LENGTH_SIZE, Type | ||
| from .byte_type import ByteType | ||
| from .tuple_type import TupleType | ||
| from .. import error | ||
|
|
||
|
|
||
| class ArrayDynamicType(Type): | ||
| """ | ||
| Represents a ArrayDynamic ABI Type for encoding. | ||
|
|
||
| Args: | ||
| child_type (Type): the type of the dynamic array. | ||
|
|
||
| Attributes: | ||
| child_type (Type) | ||
| """ | ||
|
|
||
| def __init__(self, arg_type) -> None: | ||
| super().__init__() | ||
| self.child_type = arg_type | ||
|
|
||
| def __eq__(self, other) -> bool: | ||
| if not isinstance(other, ArrayDynamicType): | ||
| return False | ||
| return self.child_type == other.child_type | ||
|
|
||
| def __str__(self): | ||
| return "{}[]".format(self.child_type) | ||
|
|
||
| def byte_len(self): | ||
| raise error.ABITypeError( | ||
| "cannot get length of a dynamic type: {}".format(self) | ||
| ) | ||
|
|
||
| def is_dynamic(self): | ||
| return True | ||
|
|
||
| def _to_tuple_type(self, length): | ||
| child_type_array = [self.child_type] * length | ||
| return TupleType(child_type_array) | ||
|
|
||
| def encode(self, value_array): | ||
| """ | ||
| 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): | ||
| """ | ||
| 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:]) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import math | ||
|
|
||
| from .base_type import Type | ||
| from .bool_type import BoolType | ||
| from .byte_type import ByteType | ||
| from .tuple_type import TupleType | ||
| from .. import error | ||
|
|
||
|
|
||
| class ArrayStaticType(Type): | ||
| """ | ||
| Represents a ArrayStatic ABI Type for encoding. | ||
|
|
||
| Args: | ||
| child_type (Type): the type of the child_types array. | ||
| static_length (int): length of the static array. | ||
|
|
||
| Attributes: | ||
| child_type (Type) | ||
| static_length (int) | ||
| """ | ||
|
|
||
| def __init__(self, arg_type, array_len) -> None: | ||
| if array_len < 1: | ||
| raise error.ABITypeError( | ||
| "static array length must be a positive integer: {}".format( | ||
| len(array_len) | ||
| ) | ||
| ) | ||
| super().__init__() | ||
| self.child_type = arg_type | ||
| self.static_length = array_len | ||
|
|
||
| def __eq__(self, other) -> 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): | ||
| return "{}[{}]".format(self.child_type, self.static_length) | ||
|
|
||
| def byte_len(self): | ||
| 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): | ||
| return self.child_type.is_dynamic() | ||
|
|
||
| def _to_tuple_type(self): | ||
| child_type_array = [self.child_type] * self.static_length | ||
| return TupleType(child_type_array) | ||
|
|
||
| def encode(self, value_array): | ||
| """ | ||
| 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): | ||
| """ | ||
| 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) | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| from abc import ABC, abstractmethod | ||
| from enum import IntEnum | ||
|
|
||
| # Globals | ||
| ABI_LENGTH_SIZE = 2 # We use 2 bytes to encode the length of a dynamic element | ||
|
|
||
|
|
||
| class Type(ABC): | ||
| """ | ||
| Represents an ABI Type for encoding. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| ) -> None: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def __str__(self): | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def __eq__(self, other) -> bool: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def is_dynamic(self): | ||
| """ | ||
| Return whether the ABI type is dynamic. | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def byte_len(self): | ||
| """ | ||
| Return the length in bytes of the ABI type. | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def encode(self, value): | ||
| """ | ||
| Serialize the ABI value into a byte string using ABI encoding rules. | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def decode(self, value_string): | ||
| """ | ||
| Deserialize the ABI type and value from a byte string using ABI encoding rules. | ||
| """ | ||
| pass |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.