From 3686314bd1cbc12911129e6d1e204faafe11b9f3 Mon Sep 17 00:00:00 2001 From: Arterialist Date: Fri, 9 Jun 2023 14:17:47 +0400 Subject: [PATCH 1/3] feat: implement long string packing/unpacking --- tonsdk/utils/_utils.py | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/tonsdk/utils/_utils.py b/tonsdk/utils/_utils.py index c9414a6..a34a0fb 100644 --- a/tonsdk/utils/_utils.py +++ b/tonsdk/utils/_utils.py @@ -7,6 +7,57 @@ from nacl.bindings import crypto_sign, crypto_sign_BYTES from nacl.signing import SignedMessage +from tonsdk.boc import Builder, Slice, Cell + + +def read_string_tail(string_slice: Slice): + if len(string_slice) % 8 != 0: + raise Exception(f"Invalid string length: {len(string_slice) // 8}") + + if len(string_slice.refs) > 1: + raise Exception("Too many refs in string tail") + + if len(string_slice.refs) == 1 and 1023 - len(string_slice) > 7: + raise Exception(f"Invalid string length: {len(string_slice) // 8}") + + text_bytes = bytes() + + if len(string_slice) != 0: + text_bytes = string_slice.read_bytes(len(string_slice) // 8) + + if len(string_slice.refs) - string_slice.ref_offset == 1: + text_bytes += read_string_tail(string_slice.read_ref().begin_parse()) + + return text_bytes + + +def write_string_tail(text: bytes, builder: Builder): + if len(text) <= 0: + return + + free_bytes = builder.bits.get_free_bits() // 8 + + if len(text) <= free_bytes: + builder.store_bytes(text) + return + + available = text[:free_bytes] + tail = text[free_bytes:] + builder.store_bytes(available) + new_builder = Builder() + write_string_tail(tail, new_builder) + builder.store_ref(new_builder.end_cell()) + + +def string_to_cell(text: str): + builder = Builder() + write_string_tail(bytes(text, encoding="utf8"), builder) + return builder.end_cell() + + +def cell_to_string(string_cell: Cell): + return read_string_tail(string_cell.begin_parse()).decode("utf8") + def concat_bytes(a, b): return a + b # ? From 254a68d16f208ab18dfec7a8169d19ea3a3e629e Mon Sep 17 00:00:00 2001 From: Arterialist Date: Fri, 9 Jun 2023 14:39:23 +0400 Subject: [PATCH 2/3] fix: resolve circular dependency --- tonsdk/boc/__init__.py | 2 ++ tonsdk/boc/_string_utils.py | 52 +++++++++++++++++++++++++++++++++++++ tonsdk/utils/_utils.py | 51 ------------------------------------ 3 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 tonsdk/boc/_string_utils.py diff --git a/tonsdk/boc/__init__.py b/tonsdk/boc/__init__.py index 7bf6bec..a69fabf 100644 --- a/tonsdk/boc/__init__.py +++ b/tonsdk/boc/__init__.py @@ -2,6 +2,7 @@ from ._builder import Builder, begin_cell from ._dict_builder import DictBuilder, begin_dict from ._slice import Slice +from ._string_utils import string_to_cell, cell_to_string, read_string_tail, write_string_tail __all__ = [ 'Cell', 'Slice', @@ -9,4 +10,5 @@ 'DictBuilder', 'begin_dict', 'deserialize_cell_data', 'parse_boc_header', + 'string_to_cell', 'cell_to_string', 'read_string_tail', 'write_string_tail' ] diff --git a/tonsdk/boc/_string_utils.py b/tonsdk/boc/_string_utils.py new file mode 100644 index 0000000..a1f8891 --- /dev/null +++ b/tonsdk/boc/_string_utils.py @@ -0,0 +1,52 @@ +from ._builder import Builder +from ._cell import Cell +from ._slice import Slice + + +def read_string_tail(string_slice: Slice): + if len(string_slice) % 8 != 0: + raise Exception(f"Invalid string length: {len(string_slice) // 8}") + + if len(string_slice.refs) > 1: + raise Exception("Too many refs in string tail") + + if len(string_slice.refs) == 1 and 1023 - len(string_slice) > 7: + raise Exception(f"Invalid string length: {len(string_slice) // 8}") + + text_bytes = bytes() + + if len(string_slice) != 0: + text_bytes = string_slice.read_bytes(len(string_slice) // 8) + + if len(string_slice.refs) - string_slice.ref_offset == 1: + text_bytes += read_string_tail(string_slice.read_ref().begin_parse()) + + return text_bytes + + +def write_string_tail(text: bytes, builder: Builder): + if len(text) <= 0: + return + + free_bytes = builder.bits.get_free_bits() // 8 + + if len(text) <= free_bytes: + builder.store_bytes(text) + return + + available = text[:free_bytes] + tail = text[free_bytes:] + builder.store_bytes(available) + new_builder = Builder() + write_string_tail(tail, new_builder) + builder.store_ref(new_builder.end_cell()) + + +def string_to_cell(text: str): + builder = Builder() + write_string_tail(bytes(text, encoding="utf8"), builder) + return builder.end_cell() + + +def cell_to_string(string_cell: Cell): + return read_string_tail(string_cell.begin_parse()).decode("utf8") diff --git a/tonsdk/utils/_utils.py b/tonsdk/utils/_utils.py index a34a0fb..c9414a6 100644 --- a/tonsdk/utils/_utils.py +++ b/tonsdk/utils/_utils.py @@ -7,57 +7,6 @@ from nacl.bindings import crypto_sign, crypto_sign_BYTES from nacl.signing import SignedMessage -from tonsdk.boc import Builder, Slice, Cell - - -def read_string_tail(string_slice: Slice): - if len(string_slice) % 8 != 0: - raise Exception(f"Invalid string length: {len(string_slice) // 8}") - - if len(string_slice.refs) > 1: - raise Exception("Too many refs in string tail") - - if len(string_slice.refs) == 1 and 1023 - len(string_slice) > 7: - raise Exception(f"Invalid string length: {len(string_slice) // 8}") - - text_bytes = bytes() - - if len(string_slice) != 0: - text_bytes = string_slice.read_bytes(len(string_slice) // 8) - - if len(string_slice.refs) - string_slice.ref_offset == 1: - text_bytes += read_string_tail(string_slice.read_ref().begin_parse()) - - return text_bytes - - -def write_string_tail(text: bytes, builder: Builder): - if len(text) <= 0: - return - - free_bytes = builder.bits.get_free_bits() // 8 - - if len(text) <= free_bytes: - builder.store_bytes(text) - return - - available = text[:free_bytes] - tail = text[free_bytes:] - builder.store_bytes(available) - new_builder = Builder() - write_string_tail(tail, new_builder) - builder.store_ref(new_builder.end_cell()) - - -def string_to_cell(text: str): - builder = Builder() - write_string_tail(bytes(text, encoding="utf8"), builder) - return builder.end_cell() - - -def cell_to_string(string_cell: Cell): - return read_string_tail(string_cell.begin_parse()).decode("utf8") - def concat_bytes(a, b): return a + b # ? From 253ac660790b7bca582c82f8841931c993e072e2 Mon Sep 17 00:00:00 2001 From: Arterialist Date: Mon, 12 Jun 2023 11:11:20 +0400 Subject: [PATCH 3/3] refactor: add more general class for snake data manipulations --- tonsdk/boc/_string_utils.py | 52 ----------------------------------- tonsdk/utils/_data.py | 54 +++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 52 deletions(-) delete mode 100644 tonsdk/boc/_string_utils.py create mode 100644 tonsdk/utils/_data.py diff --git a/tonsdk/boc/_string_utils.py b/tonsdk/boc/_string_utils.py deleted file mode 100644 index a1f8891..0000000 --- a/tonsdk/boc/_string_utils.py +++ /dev/null @@ -1,52 +0,0 @@ -from ._builder import Builder -from ._cell import Cell -from ._slice import Slice - - -def read_string_tail(string_slice: Slice): - if len(string_slice) % 8 != 0: - raise Exception(f"Invalid string length: {len(string_slice) // 8}") - - if len(string_slice.refs) > 1: - raise Exception("Too many refs in string tail") - - if len(string_slice.refs) == 1 and 1023 - len(string_slice) > 7: - raise Exception(f"Invalid string length: {len(string_slice) // 8}") - - text_bytes = bytes() - - if len(string_slice) != 0: - text_bytes = string_slice.read_bytes(len(string_slice) // 8) - - if len(string_slice.refs) - string_slice.ref_offset == 1: - text_bytes += read_string_tail(string_slice.read_ref().begin_parse()) - - return text_bytes - - -def write_string_tail(text: bytes, builder: Builder): - if len(text) <= 0: - return - - free_bytes = builder.bits.get_free_bits() // 8 - - if len(text) <= free_bytes: - builder.store_bytes(text) - return - - available = text[:free_bytes] - tail = text[free_bytes:] - builder.store_bytes(available) - new_builder = Builder() - write_string_tail(tail, new_builder) - builder.store_ref(new_builder.end_cell()) - - -def string_to_cell(text: str): - builder = Builder() - write_string_tail(bytes(text, encoding="utf8"), builder) - return builder.end_cell() - - -def cell_to_string(string_cell: Cell): - return read_string_tail(string_cell.begin_parse()).decode("utf8") diff --git a/tonsdk/utils/_data.py b/tonsdk/utils/_data.py new file mode 100644 index 0000000..e044969 --- /dev/null +++ b/tonsdk/utils/_data.py @@ -0,0 +1,54 @@ +# https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md#data-serialization +from ctypes import Union + +from tonsdk.boc import begin_cell, Builder, Slice + + +class SnakeData: + prefix = 0x00 + prefix_len = 8 + + @classmethod + def write(cls, builder: Builder, data: Union[bytes, bytearray], prefixed=False): + if prefixed: + builder.store_uint(cls.prefix, cls.prefix_len) + + # todo: implement data serialization logic to a given builder (now data is a bytes sequence) + # implementation example + builders = [] + + while len(data) > 0: + max_bytes = builder.builder_rembits >> 3 + bits, data = data[:max_bytes], data[max_bytes:] + + builder.store_bytes(bits) + builders.append(builder) + + builder = begin_cell() + + if len(builders) > 1: + last_builder = builders[-1] + + for builder in reversed(builders[:-1]): + builder.store_ref(last_builder.end_cell()) + last_builder = builder + + return builders[0] + + @classmethod + def read(cls, cs: Slice, prefixed=False): + data = bytearray() + + if prefixed: + assert cs.preload_uint(cls.prefix_len) == cls.prefix + + while True: + data.extend(cs.load_bytes(cs.slice_bits >> 3)) + + if cs.slice_refs > 0: + cs = cs.load_ref().begin_parse() + continue + + break + + return data