Skip to content

Commit

Permalink
feat: add highload wallet v3 (initial support)
Browse files Browse the repository at this point in the history
  • Loading branch information
gtors committed Apr 24, 2024
1 parent bbf36fe commit 1ab6c84
Show file tree
Hide file tree
Showing 10 changed files with 491 additions and 94 deletions.
6 changes: 0 additions & 6 deletions devtools/dev-requirements.txt

This file was deleted.

2 changes: 0 additions & 2 deletions devtools/requirements.txt

This file was deleted.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ classifiers = [
python = ">=3.10"
pynacl = { version = ">=1.4.0" }
bitarray = { version = "==2.6.0" }
httpx = { version = "*", optional = true }
httpj = { version = "*", optional = true }

[tool.poetry.extras]
http_api = ["httpx"]
full = ["httpx"]
http_api = ["httpj"]
full = ["httpj"]

[tool.poetry.group.dev.dependencies]
black = "*"
Expand Down
35 changes: 35 additions & 0 deletions tests/test_highload_query_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from tonsdk_ng.contract.wallet import HighloadQueryId


def test_highload_query_id_seqno():
assert HighloadQueryId.from_seqno(0).to_seqno() == 0
assert HighloadQueryId.from_seqno(1022).to_seqno() == 1022
assert HighloadQueryId.from_seqno(1023).to_seqno() == 1023
assert HighloadQueryId.from_seqno(1024).to_seqno() == 1024
assert HighloadQueryId.from_seqno(8380415).to_seqno() == 8380415


def test_highload_query_id():
MAX = (2**13) * 1023 - 2
query_id = HighloadQueryId()
for i in range(MAX):
query_id = query_id.get_next()

q = query_id.query_id
q2 = HighloadQueryId.from_query_id(q)

assert query_id.shift == q2.shift
assert query_id.bit_number == q2.bit_number
assert q2.query_id == q

q3 = HighloadQueryId.from_shift_and_bit_number(
query_id.shift, query_id.bit_number
)
assert query_id.shift == q3.shift
assert query_id.bit_number == q3.bit_number
assert q3.query_id == q

if i == MAX - 1:
assert not query_id.has_next()
else:
assert query_id.has_next()
25 changes: 25 additions & 0 deletions tonsdk_ng/contract/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,31 @@ def create_internal_message_header(
message.bits.write_uint(created_at, 32)
return message

@classmethod
def create_out_msg(
cls,
address: str,
amount: int,
payload: str | bytes | Cell | None = None,
state_init=None,
):
payload_cell = Cell()
if payload:
if isinstance(payload, Cell):
payload_cell = payload
elif isinstance(payload, str):
if len(payload) > 0:
payload_cell.bits.write_uint(0, 32)
payload_cell.bits.write_string(payload)
else:
payload_cell.bits.write_bytes(payload)

order_header = cls.create_internal_message_header(address, amount)
order = cls.create_common_msg_info(
order_header, state_init, payload_cell
)
return order

@classmethod
def create_common_msg_info(cls, header, state_init=None, body=None):
common_msg_info = Cell()
Expand Down
22 changes: 15 additions & 7 deletions tonsdk_ng/contract/wallet/__init__.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
from enum import Enum

from ...crypto import (
mnemonic_from_password,
mnemonic_is_valid,
mnemonic_new,
mnemonic_to_wallet_key,
private_key_to_public_key,
)
from ...crypto.exceptions import InvalidMnemonicsError
from ._highload_wallet_contract import HighloadWalletV2Contract
from ._highload_query_id import HighloadQueryId
from ._highload_wallet_contract_v2 import HighloadWalletV2Contract
from ._highload_wallet_contract_v3 import HighloadWalletV3Contract
from ._multisig_wallet_contract import (
MultiSigOrder,
MultiSigOrderBuilder,
Expand All @@ -27,6 +30,7 @@ class WalletVersionEnum(str, Enum):
v4r1 = "v4r1"
v4r2 = "v4r2"
hv2 = "hv2"
hv3 = "hv3"


class Wallets:
Expand All @@ -39,6 +43,7 @@ class Wallets:
WalletVersionEnum.v4r1: WalletV4ContractR1,
WalletVersionEnum.v4r2: WalletV4ContractR2,
WalletVersionEnum.hv2: HighloadWalletV2Contract,
WalletVersionEnum.hv3: HighloadWalletV3Contract,
}

@classmethod
Expand All @@ -49,7 +54,9 @@ def create(
password: str | None = None,
**kwargs,
) -> tuple[list[str], bytes, bytes, WalletContract]:
mnemonics = mnemonic_new(password=password)
mnemonics = (
mnemonic_from_password(password) if password else mnemonic_new()
)
pub_k, priv_k = mnemonic_to_wallet_key(mnemonics)
wallet = cls.ALL[version](
public_key=pub_k, private_key=priv_k, wc=workchain, **kwargs
Expand Down Expand Up @@ -92,15 +99,13 @@ def from_private_key(
@classmethod
def to_addr_pk(
cls,
mnemonics: WalletContract,
mnemonics: list[str],
version: WalletVersionEnum = default_version,
workchain: int = 0,
**kwargs,
) -> tuple[bytes, bytes]:
_mnemonics, _pub_k, priv_k, wallet = cls.from_mnemonics(
mnemonics, version, workchain, **kwargs
)

wallet = cls.from_mnemonics(mnemonics, version, workchain, **kwargs)
pub_k, priv_k = mnemonic_to_wallet_key(mnemonics)
return wallet.address.to_buffer(), priv_k[:32]


Expand All @@ -111,6 +116,9 @@ def to_addr_pk(
"WalletV3ContractR2",
"WalletV4ContractR1",
"WalletV4ContractR2",
"HighloadWalletV2Contract",
"HighloadWalletV3Contract",
"HighloadQueryId",
"WalletContract",
"SendModeEnum",
"WalletVersionEnum",
Expand Down
132 changes: 132 additions & 0 deletions tonsdk_ng/contract/wallet/_highload_query_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
BIT_NUMBER_SIZE = 10 # 10 bit
SHIFT_SIZE = 13 # 13 bit
MAX_BIT_NUMBER = 1022
MAX_SHIFT = 8191 # 2^13 = 8192


class HighloadQueryId:
def __init__(self):
"""
Initializes a HighloadQueryId instance with default values.
:ivar _shift: Internal state for shift (bigint) [0 .. 8191]
:ivar _bit_number: Internal state for bit number (bigint) [0 .. 1022]
"""
self._shift = 0
self._bit_number = 0

@staticmethod
def from_shift_and_bit_number(shift, bit_number):
"""
Creates a new HighloadQueryId object with specified shift and bit number.
:param shift: The shift value (int)
:param bit_number: The bit number value (int)
:return: A new instance of HighloadQueryId
:raises ValueError: If the shift or bit number is out of valid range
"""
if not (0 <= shift <= MAX_SHIFT):
raise ValueError("invalid shift")
if not (0 <= bit_number <= MAX_BIT_NUMBER):
raise ValueError("invalid bitnumber")

q = HighloadQueryId()
q._shift = shift
q._bit_number = bit_number
return q

def get_next(self):
"""
Calculates the next HighloadQueryId based on the current state.
:return: HighloadQueryId representing the next ID
:raises ValueError: If the current ID is at the maximum capacity
"""
new_bit_number = self._bit_number + 1
new_shift = self._shift

if new_shift == MAX_SHIFT and new_bit_number > (MAX_BIT_NUMBER - 1):
# we left one queryId for emergency withdraw
raise ValueError("Overload")

if new_bit_number > MAX_BIT_NUMBER:
new_bit_number = 0
new_shift += 1
if new_shift > MAX_SHIFT:
raise ValueError("Overload")

return HighloadQueryId.from_shift_and_bit_number(
new_shift, new_bit_number
)

def has_next(self):
"""
Checks if there is a next HighloadQueryId available.
:return: True if there is a next ID available, False otherwise
"""
# we left one queryId for emergency withdraw
is_end = (
self._bit_number >= (MAX_BIT_NUMBER - 1)
and self._shift == MAX_SHIFT
)
return not is_end

@property
def shift(self):
"""
Gets the current shift value.
:return: The current shift value (int)
"""
return self._shift

@property
def bit_number(self):
"""
Gets the current bit number value.
:return: The current bit number value (int)
"""
return self._bit_number

@property
def query_id(self):
"""
Computes the query ID based on the current shift and bit number.
:return: The computed query ID (int)
"""
return (self._shift << BIT_NUMBER_SIZE) + self._bit_number

@staticmethod
def from_query_id(query_id: int):
"""
Creates a new HighloadQueryId object from a given query ID.
:param query_id: The query ID to parse (int)
:return: A new instance of HighloadQueryId
"""
shift = query_id >> BIT_NUMBER_SIZE
bit_number = query_id & 1023
return HighloadQueryId.from_shift_and_bit_number(shift, bit_number)

@staticmethod
def from_seqno(i: int):
"""
Creates a HighloadQueryId from a sequence number.
:param i: The sequence number (int)
:return: A new HighloadQueryId
"""
shift = i // 1023
bit_number = i % 1023
return HighloadQueryId.from_shift_and_bit_number(shift, bit_number)

def to_seqno(self) -> int:
"""
Converts the current HighloadQueryId to a sequence number.
:return: The sequence number (int)
"""
return self._bit_number + self._shift * 1023
Loading

0 comments on commit 1ab6c84

Please sign in to comment.