From 4105ebe62fd0c562a3d8f271dfd8e94ac7c7a43c Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 11:38:31 -0600 Subject: [PATCH 01/23] wip --- crypto/identity/private_key.py | 11 +- crypto/transactions/builder/base.py | 167 +++++---------- crypto/transactions/builder/transfer.py | 31 --- .../transactions/builder/transfer_builder.py | 12 ++ crypto/transactions/deserializer.py | 198 +++++++++++------- crypto/transactions/serializer.py | 151 +++++-------- .../types/abstract_transaction.py | 79 +++++++ crypto/transactions/types/transfer.py | 6 + tests/fixtures/evm-sign.json | 16 ++ tests/fixtures/transfer.json | 16 ++ tests/fixtures/unvote.json | 16 ++ tests/fixtures/validator-registration.json | 16 ++ tests/fixtures/validator-resignation.json | 16 ++ tests/fixtures/vote.json | 16 ++ tests/transactions/builder/test_transfer.py | 171 --------------- .../builder/test_transfer_builder.py | 42 ++++ 16 files changed, 474 insertions(+), 490 deletions(-) delete mode 100644 crypto/transactions/builder/transfer.py create mode 100644 crypto/transactions/builder/transfer_builder.py create mode 100644 crypto/transactions/types/abstract_transaction.py create mode 100644 crypto/transactions/types/transfer.py create mode 100644 tests/fixtures/evm-sign.json create mode 100644 tests/fixtures/transfer.json create mode 100644 tests/fixtures/unvote.json create mode 100644 tests/fixtures/validator-registration.json create mode 100644 tests/fixtures/validator-resignation.json create mode 100644 tests/fixtures/vote.json delete mode 100644 tests/transactions/builder/test_transfer.py create mode 100644 tests/transactions/builder/test_transfer_builder.py diff --git a/crypto/identity/private_key.py b/crypto/identity/private_key.py index 5c0781dc..d15d9ae1 100644 --- a/crypto/identity/private_key.py +++ b/crypto/identity/private_key.py @@ -17,14 +17,9 @@ def sign(self, message: bytes) -> bytes: Returns: bytes: signature of the signed message """ - from crypto.transactions.signature import Signature - - signature = Signature.sign( - hexlify(message), - self - ) - - return signature.encode() + signature = self.private_key.sign(message) + + return hexlify(signature).decode() def to_hex(self): """Returns a private key in hex format diff --git a/crypto/transactions/builder/base.py b/crypto/transactions/builder/base.py index bca7c814..89330386 100644 --- a/crypto/transactions/builder/base.py +++ b/crypto/transactions/builder/base.py @@ -1,36 +1,59 @@ -from binascii import hexlify, unhexlify +from typing import Optional -from crypto.configuration.fee import get_fee -from crypto.constants import TRANSACTION_TYPE_GROUP +from crypto.configuration.network import get_network from crypto.identity.private_key import PrivateKey -from crypto.identity.public_key import PublicKey -from crypto.transactions.serializer import Serializer -from crypto.transactions.signature import Signature -from crypto.transactions.transaction import Transaction - -class BaseTransactionBuilder(object): - transaction: Transaction - - def __init__(self): - self.transaction = Transaction() - - if hasattr(self, 'transaction_type'): - self.transaction.type = getattr(self, 'transaction_type') - - if hasattr(self, 'transaction_type'): - self.transaction.fee = get_fee(getattr(self, 'transaction_type')) - - if hasattr(self, 'nonce'): - self.transaction.nonce = getattr(self, 'nonce') - - if hasattr(self, 'signatures'): - self.transaction.signatures = getattr(self, 'signatures') - - self.transaction.typeGroup = getattr(self, 'typeGroup', int(TRANSACTION_TYPE_GROUP.CORE)) - self.transaction.version = getattr(self, 'version', 1) - self.transaction.expiration = getattr(self, 'expiration', 0) - if self.transaction.type != 0: - self.transaction.amount = getattr(self, 'amount', 0) +from crypto.transactions.types.abstract_transaction import AbstractTransaction + + +class AbstractTransactionBuilder: + def __init__(self, data: Optional[dict] = None): + default_data = { + 'value': '0', + 'senderPublicKey': '', + 'gasPrice': '5', + 'nonce': '1', + 'network': get_network()['version'], + 'gasLimit': 1_000_000, + 'data': '', + } + self.transaction = self.get_transaction_instance(data or default_data) + + def __str__(self): + return self.to_json() + + @classmethod + def new(cls, data: Optional[dict] = None): + return cls(data) + + def gas_limit(self, gas_limit: int): + self.transaction.data['gasLimit'] = gas_limit + return self + + def recipient_address(self, recipient_address: str): + self.transaction.data['recipientAddress'] = recipient_address + return self + + def gas_price(self, gas_price: int): + self.transaction.data['gasPrice'] = gas_price + return self + + def nonce(self, nonce: str): + self.transaction.data['nonce'] = nonce + return self + + def network(self, network: int): + self.transaction.data['network'] = network + return self + + def sign(self, passphrase: str): + keys = PrivateKey.from_passphrase(passphrase) + self.transaction.data['senderPublicKey'] = keys.public_key + self.transaction = self.transaction.sign(keys) + self.transaction.data['id'] = self.transaction.get_id() + return self + + def verify(self): + return self.transaction.verify() def to_dict(self): return self.transaction.to_dict() @@ -38,83 +61,5 @@ def to_dict(self): def to_json(self): return self.transaction.to_json() - def sign(self, passphrase): - """Sign the transaction using the given passphrase - - Args: - passphrase (str): passphrase associated with the account sending this transaction - """ - self.transaction.senderPublicKey = PublicKey.from_passphrase(passphrase) - - msg = self.transaction.to_bytes(False, True, False) - secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - self.transaction.signature = Signature.sign(msg, secret) - self.transaction.id = self.transaction.get_id() - - def second_sign(self, passphrase): - """Sign the transaction using the given second passphrase - - Args: - passphrase (str): 2nd passphrase associated with the account sending this transaction - """ - msg = self.transaction.to_bytes(False, True, False) - secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - self.transaction.signSignature = Signature.sign(msg, secret) - self.transaction.id = self.transaction.get_id() - - def multi_sign(self, passphrase, index): - if not self.transaction.signatures: - self.transaction.signatures = [] - - if self.transaction.senderPublicKey is None: - raise Exception('Sender Public Key is required for multi signature') - - index = len(self.transaction.signatures) if index == -1 else index - - msg = self.transaction.to_bytes() - secret = unhexlify(PrivateKey.from_passphrase(passphrase).to_hex()) - signature = Signature.sign(msg, secret) - - index_formatted = hex(index).replace('x', '') - self.transaction.signatures.append(index_formatted + signature) - - def serialize(self, skip_signature=False, skip_second_signature=False, skip_multi_signature=False): - """Perform AIP11 compliant serialization. - - Args: - skip_signature (bool, optional): do you want to skip the signature - skip_second_signature (bool, optional): do you want to skip the 2nd signature - skip_multi_signature (bool, optional): do you want to skip multi signature - - Returns: - str: Serialized string - """ - return Serializer(self.to_dict()).serialize(skip_signature, skip_second_signature, skip_multi_signature) - - def schnorr_verify(self): - return self.transaction.verify_schnorr() - - def verify_secondsig_schnorr(self, secondPublicKey): - return self.transaction.verify_secondsig_schnorr(secondPublicKey) - - def verify_multisig_schnorr(self): - return self.transaction.verify_multisig_schnorr() - - def set_nonce(self, nonce): - self.transaction.nonce = nonce - - def set_amount(self, amount: int): - self.transaction.amount = amount - - def set_sender_public_key(self, public_key: str): - self.transaction.senderPublicKey = public_key - - def set_expiration(self, expiration: int): - self.transaction.expiration = expiration - - def set_type_group(self, type_group): - if type(type_group) == int: - self.transaction.typeGroup = type_group - else: - types = {TRANSACTION_TYPE_GROUP.TEST: 0, TRANSACTION_TYPE_GROUP.CORE: 1, TRANSACTION_TYPE_GROUP.RESERVED: 1000} - self.transaction.typeGroup = types[type_group] + def get_transaction_instance(self, data: dict) -> AbstractTransaction: + raise NotImplementedError("Subclasses must implement get_transaction_instance()") diff --git a/crypto/transactions/builder/transfer.py b/crypto/transactions/builder/transfer.py deleted file mode 100644 index 37d245aa..00000000 --- a/crypto/transactions/builder/transfer.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_TRANSFER -from crypto.transactions.builder.base import BaseTransactionBuilder - -class Transfer(BaseTransactionBuilder): - - transaction_type = TRANSACTION_TRANSFER - - def __init__(self, recipientId: str, amount: int, vendorField: Optional[str] = None, fee: Optional[int] = None): - """Create a transfer transaction - - Args: - recipientId (str): address to which you want to send coins - amount (int): amount of coins you want to transfer - vendorField (str): value for the vendor field aka smartbridge - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.recipientId = recipientId - - if type(amount) == int and amount > 0: - self.transaction.amount = amount - else: - raise ValueError('Amount is not valid') - - self.transaction.vendorField = vendorField.encode() if vendorField else None - - if fee: - self.transaction.fee = fee diff --git a/crypto/transactions/builder/transfer_builder.py b/crypto/transactions/builder/transfer_builder.py new file mode 100644 index 00000000..9af863c8 --- /dev/null +++ b/crypto/transactions/builder/transfer_builder.py @@ -0,0 +1,12 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.transfer import Transfer + + +class TransferBuilder(AbstractTransactionBuilder): + def value(self, value: str): + self.transaction.data['value'] = value + self.transaction.refresh_payload_data() + return self + + def get_transaction_instance(self, data: dict): + return Transfer(data) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 01395f11..4fef67ab 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,76 +1,128 @@ -import inspect -from binascii import hexlify, unhexlify -from importlib import import_module -from typing import Union - -from binary.unsigned_integer.reader import read_bit8, read_bit16, read_bit32, read_bit64 - -from crypto.constants import TRANSACTION_TYPES -from crypto.transactions.deserializers.base import BaseDeserializer -from crypto.transactions.transaction import Transaction - -class Deserializer(object): - serialized: bytes - - def __init__(self, serialized: Union[bytes, str]): - self.serialized = unhexlify(serialized) - - def deserialize(self) -> Transaction: - """Deserialize transaction - - Returns: - Transaction: returns transaction object - """ - - transaction = Transaction() - transaction.version = read_bit8(self.serialized, offset=1) - transaction.network = read_bit8(self.serialized, offset=2) - transaction.typeGroup = read_bit32(self.serialized, offset=3) - transaction.type = read_bit16(self.serialized, offset=7) - transaction.nonce = read_bit64(self.serialized, offset=9) - transaction.senderPublicKey = hexlify(self.serialized)[34:66+34].decode() - transaction.fee = read_bit64(self.serialized, offset=50) - - vendor_field_length = read_bit8(self.serialized, offset=58) - if vendor_field_length > 0: - vendor_field_offset = (58 + 8) * 2 - vendorField_take = vendor_field_length * 2 - transaction.vendorFieldHex = hexlify( - self.serialized - )[vendor_field_offset:vendorField_take] - - asset_offset = (58 + 1) * 2 + vendor_field_length * 2 - - handled_transaction = self._handle_transaction_type(asset_offset, transaction) - transaction.amount = handled_transaction.amount - transaction.version = handled_transaction.version - transaction.id = transaction.get_id() +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.transactions.types.transfer import Transfer +# from crypto.transactions.types.evm_call import EvmCall +# from crypto.transactions.types.vote import Vote +# from crypto.transactions.types.unvote import Unvote +# from crypto.transactions.types.validator_registration import ValidatorRegistration +# from crypto.transactions.types.validator_resignation import ValidatorResignation +from binascii import unhexlify, hexlify + +from binary.unsigned_integer.reader import ( + read_bit8, + read_bit32, + read_bit64, + # read_bit256, +) +# from crypto.enums.abi_function import AbiFunction # TODO: Implement or import AbiFunction +# from crypto.utils.abi_decoder import AbiDecoder # TODO: Implement or import AbiDecoder + + +class Deserializer: + SIGNATURE_SIZE = 64 + RECOVERY_SIZE = 1 + + def __init__(self, serialized: str): + self.serialized = unhexlify(serialized) if isinstance(serialized, str) else serialized + self.pointer = 0 + + @staticmethod + def new(serialized: str): + return Deserializer(serialized) + + def deserialize(self) -> AbstractTransaction: + data = {} + + self.deserialize_common(data) + self.deserialize_data(data) + transaction = self.guess_transaction_from_data(data) + self.deserialize_signatures(data) + + transaction.data = data + transaction.recover_sender() + + transaction.data['id'] = transaction.hash(skip_signature=False).hex() return transaction - def _handle_transaction_type(self, asset_offset: int, transaction): - """Handle deserialization for a given transaction type - - Args: - asset_offset (int): - transaction (Transaction): Transaction resource object - - Returns: - Transaction: Transaction object of currently deserialized data - """ - - deserializer_name = TRANSACTION_TYPES[transaction.type] - module = import_module('crypto.transactions.deserializers.{}'.format(deserializer_name)) - for attr in dir(module): - # If attr name is `BaseDeserializer`, skip it as it's a class and also has a - # subclass of BaseDeserializer - if attr == 'BaseDeserializer': - continue - - attribute = getattr(module, attr) - if inspect.isclass(attribute) and issubclass(attribute, BaseDeserializer): - # this attribute is actually a specific deserializer that we want to use - deserializer = attribute - break - - return deserializer(self.serialized, asset_offset, transaction).deserialize() + def read_bytes(self, length: int) -> bytes: + result = self.serialized[self.pointer:self.pointer + length] + self.pointer += length + return result + + def deserialize_common(self, data: dict): + data['network'], _ = read_bit8(self.serialized, self.pointer) + self.pointer += 1 + + nonce, _ = read_bit64(self.serialized, self.pointer) + data['nonce'] = str(nonce) + self.pointer += 8 + + gas_price, _ = read_bit32(self.serialized, self.pointer) + data['gasPrice'] = gas_price + self.pointer += 4 + + gas_limit, _ = read_bit32(self.serialized, self.pointer) + data['gasLimit'] = gas_limit + self.pointer += 4 + + data['value'] = '0' + + def deserialize_data(self, data: dict): + # @TODO: this should use read_bit256 + # value, _ = read_bit256(self.serialized, self.pointer) + value, _ = read_bit64(self.serialized, self.pointer) + + data['value'] = str(value) + self.pointer += 32 + + recipient_marker, _ = read_bit8(self.serialized, self.pointer) + self.pointer += 1 + + if recipient_marker == 1: + recipient_address_bytes = self.read_bytes(20) + recipient_address = '0x' + hexlify(recipient_address_bytes).decode() + data['recipientAddress'] = recipient_address + + payload_length, _ = read_bit32(self.serialized, self.pointer) + self.pointer += 4 + + payload_hex = '' + if payload_length > 0: + payload_bytes = self.read_bytes(payload_length) + payload_hex = hexlify(payload_bytes).decode() + + data['data'] = payload_hex + + def deserialize_signatures(self, data: dict): + signature_length = self.SIGNATURE_SIZE + self.RECOVERY_SIZE + signature_bytes = self.read_bytes(signature_length) + data['signature'] = hexlify(signature_bytes).decode() + + def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: + if data['value'] != '0': + return Transfer(data) + + # payload_data = self.decode_payload(data) + payload_data = None # As AbiDecoder is not implemented + + if payload_data is None: + return Transfer(data) # Using Transfer for now + + # if function_name == AbiFunction.VOTE.value: + # return Vote(data) + # elif function_name == AbiFunction.UNVOTE.value: + # return Unvote(data) + # elif function_name == AbiFunction.VALIDATOR_REGISTRATION.value: + # return ValidatorRegistration(data) + # elif function_name == AbiFunction.VALIDATOR_RESIGNATION.value: + # return ValidatorResignation(data) + # else: + # return Transfer(data) + + # def decode_payload(self, data: dict) -> dict: + # payload = data.get('data', '') + # + # if payload == '': + # return None + # + # return AbiDecoder().decode_function_data(payload) diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index ff9c2800..974db7f1 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -1,114 +1,73 @@ -import inspect -from binascii import hexlify, unhexlify -from importlib import import_module - -from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit8, write_bit16, write_bit32, write_bit64 - +from binascii import unhexlify +from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.configuration.network import get_network -from crypto.constants import TRANSACTION_TYPES -from crypto.exceptions import ArkSerializerException -from crypto.transactions.serializers.base import BaseSerializer +from binary.unsigned_integer.writer import ( + write_bit8, + write_bit32, + write_bit64, + # write_bit256, +) +# from crypto.utils.address import Address # TODO: Implement or import Address -class Serializer(object): - transaction: dict = {} - def __init__(self, transaction): +class Serializer: + def __init__(self, transaction: AbstractTransaction): if not transaction: - raise ArkSerializerException('No transaction data provided') + raise ValueError('No transaction data provided') self.transaction = transaction - def serialize_bytes(self, skip_signature: bool = True, skip_second_signature: bool = True, skip_multi_signature: bool = True) -> bytes: - """Perform AIP11 compliant serialization - - Returns: - bytes: Serialized bytes - """ - network_config = get_network() - bytes_data = bytes() - - bytes_data += write_bit8(0xff) - - bytes_data += write_bit8(self.transaction.get('version') or 0x01) - bytes_data += write_bit8(self.transaction.get('network') or network_config['version']) - bytes_data += write_bit32(self.transaction.get('typeGroup') or 0x01) - bytes_data += write_bit16(self.transaction.get('type')) - bytes_data += write_bit64(self.transaction.get('nonce') or 0x01) + @staticmethod + def new(transaction: AbstractTransaction): + return Serializer(transaction) - bytes_data += write_high(self.transaction.get('senderPublicKey')) - bytes_data += write_bit64(self.transaction.get('fee')) + @staticmethod + def get_bytes(transaction: AbstractTransaction, skip_signature: bool = False) -> bytes: + return transaction.serialize(skip_signature=skip_signature) - if self.transaction.get('vendorField'): - vendorFieldLength = len(self.transaction.get('vendorField') or '') - - bytes_data += write_bit8(vendorFieldLength) - bytes_data += self.transaction['vendorField'].encode() - elif self.transaction.get('vendorFieldHex'): - vendorField_hex_length = len(self.transaction['vendorFieldHex']) - - bytes_data += write_bit8(vendorField_hex_length / 2) - bytes_data += self.transaction['vendorFieldHex'] - else: - bytes_data += write_bit8(0x00) + def serialize(self, skip_signature: bool = False) -> bytes: + bytes_data = bytes() - bytes_data = self._handle_transaction_type(bytes_data) - bytes_data = self._handle_signature(bytes_data, skip_signature, skip_second_signature, skip_multi_signature) + bytes_data += self.serialize_common() + bytes_data += self.serialize_data() + if not skip_signature: + bytes_data += self.serialize_signatures() return bytes_data - def serialize(self, skip_signature: bool = True, skip_second_signature: bool = True, skip_multi_signature: bool = True) -> str: - """Perform AIP11 compliant serialization - - Returns: - str: Serialized string - """ - bytes_data = self.serialize_bytes(skip_signature, skip_second_signature, skip_multi_signature) - - return hexlify(bytes_data).decode() - - def _handle_transaction_type(self, bytes_data) -> bytes: - """Serialize transaction specific data (eg. validator registration) - - Args: - bytes_data (bytes): already serialized data about a transaction (eg. version, network) - - Returns: - bytes: bytes string - """ - serializer_name = TRANSACTION_TYPES[self.transaction['type']] - - module = import_module('crypto.transactions.serializers.{}'.format(serializer_name)) - for attr in dir(module): - # If attr name is `BaseSerializer`, skip it as it's a class and also has a - # subclass of BaseSerializer - if attr == 'BaseSerializer': - continue - - attribute = getattr(module, attr) - if inspect.isclass(attribute) and issubclass(attribute, BaseSerializer): - # this attribute is actually a specific serializer that we want to use - serializer = attribute - break - - return serializer(self.transaction, bytes_data).serialize() + def serialize_common(self) -> bytes: + bytes_data = bytes() + network_version = self.transaction.data.get('network', get_network()['version']) + bytes_data += write_bit8(int(network_version)) + bytes_data += write_bit64(int(self.transaction.data['nonce'])) + bytes_data += write_bit32(int(self.transaction.data['gasPrice'])) + bytes_data += write_bit32(int(self.transaction.data['gasLimit'])) + return bytes_data - def _handle_signature(self, bytes_data, skip_signature, skip_second_signature, skip_multi_signature) -> bytes: - """Serialize signature data of the transaction + def serialize_data(self) -> bytes: + bytes_data = bytes() + + # @TODO: this should use write_bit256 + # bytes_data += write_bit256(int(self.transaction.data['value'])) + bytes_data += write_bit64(int(self.transaction.data['value'])) + + if 'recipientAddress' in self.transaction.data: + bytes_data += write_bit8(1) + recipient_address = self.transaction.data['recipientAddress'] + bytes_data += unhexlify(recipient_address.replace('0x', '')) + else: + bytes_data += write_bit8(0) - Args: - bytes_data (bytes): already serialized data + payload_hex = self.transaction.data.get('data', '') + payload_length = len(payload_hex) // 2 + bytes_data += write_bit32(payload_length) - Returns: - bytes: bytes string - """ - if not skip_signature and self.transaction.get('signature'): - bytes_data += unhexlify(self.transaction['signature']) + if payload_length > 0: + bytes_data += unhexlify(payload_hex) - if not skip_second_signature and self.transaction.get('secondSignature'): - bytes_data += unhexlify(self.transaction['secondSignature']) - if not skip_second_signature and self.transaction.get('signSignature'): - bytes_data += unhexlify(self.transaction['signSignature']) - if not skip_multi_signature and self.transaction.get('signatures'): - bytes_data += unhexlify(''.join(self.transaction['signatures'])) + return bytes_data + def serialize_signatures(self) -> bytes: + bytes_data = bytes() + if 'signature' in self.transaction.data: + bytes_data += unhexlify(self.transaction.data['signature']) return bytes_data diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py new file mode 100644 index 00000000..370add82 --- /dev/null +++ b/crypto/transactions/types/abstract_transaction.py @@ -0,0 +1,79 @@ +import json +from typing import Optional + +from crypto.configuration.network import get_network +from crypto.identity.address import address_from_public_key +from crypto.identity.private_key import PrivateKey + + +class AbstractTransaction: + def __init__(self, data: Optional[dict] = None): + self.data = data or {} + self.refresh_payload_data() + + def get_payload(self) -> str: + return '' + + def decode_payload(self, data: dict) -> Optional[dict]: + if 'data' not in data or data['data'] == '': + return None + # TODO: add AbiDecoder to decode the payload + return {} + + def refresh_payload_data(self): + self.data['data'] = self.get_payload().lstrip('0x') + + def get_id(self) -> str: + return self.hash(skip_signature=False).hex() + + def get_bytes(self, skip_signature: bool = False) -> bytes: + from crypto.transactions.serializer import Serializer + return Serializer.get_bytes(self, skip_signature) + + def sign(self, private_key: PrivateKey): + hash_ = self.hash(skip_signature=True) + # TODO: Implement signing logic + return self + + def get_public_key(self, compact_signature): + # TODO: Implement this method + pass + + def recover_sender(self): + compact_signature = self.get_signature() + public_key = self.get_public_key(compact_signature) + self.data['senderPublicKey'] = public_key.hex() + self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) + + def verify(self) -> bool: + # TODO: Implement this method + return True + + def serialize(self, skip_signature: bool = False) -> bytes: + from crypto.transactions.serializer import Serializer + return Serializer(self).serialize(skip_signature) + + def to_dict(self) -> dict: + return {k: v for k, v in self.data.items() if v is not None} + + def to_json(self) -> str: + return json.dumps(self.to_dict()) + + def hash(self, skip_signature: bool) -> bytes: + hash_data = { + 'gasPrice': self.data.get('gasPrice'), + 'network': self.data.get('network', get_network().get('version')), + 'nonce': self.data.get('nonce'), + 'value': self.data.get('value'), + 'gasLimit': self.data.get('gasLimit'), + 'data': self.data.get('data'), + 'recipientAddress': self.data.get('recipientAddress'), + 'signature': self.data.get('signature') if not skip_signature else None, + } + # TODO: Implement TransactionHasher + # return TransactionHasher.to_hash(hash_data, skip_signature) + return b'' + + def get_signature(self): + # TODO: Implement this method + pass diff --git a/crypto/transactions/types/transfer.py b/crypto/transactions/types/transfer.py new file mode 100644 index 00000000..abe87738 --- /dev/null +++ b/crypto/transactions/types/transfer.py @@ -0,0 +1,6 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction + + +class Transfer(AbstractTransaction): + def get_payload(self) -> str: + return '' diff --git a/tests/fixtures/evm-sign.json b/tests/fixtures/evm-sign.json new file mode 100644 index 00000000..600ffa2d --- /dev/null +++ b/tests/fixtures/evm-sign.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "13", + "gasPrice": 5, + "gasLimit": 1000000, + "value": "0", + "recipientAddress": "0xE536720791A7DaDBeBdBCD8c8546fb0791a11901", + "data": "a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af2393000000000000000000000000000000000000000000000000016345785d8a0000", + "signature": "ba30f9042519079895c7408b0e92046c3f20680e0a9294e38ab3cfdd19b26cd4036fe2a80644abb922f1ad7cd682811a83c20120a8030df47b244a3bc44f4dbd00", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "3935ff0fe84ea6ac42fc889ed7cda4f97ddd11fd2d1c31e9201f14866acb6edc" + }, + "serialized": "1e0d000000000000000500000040420f00000000000000000000000000000000000000000000000000000000000000000001e536720791a7dadbebdbcd8c8546fb0791a1190144000000a9059cbb00000000000000000000000027fa7caffaae77ddb9ab232fdbda56d5e5af2393000000000000000000000000000000000000000000000000016345785d8a0000ba30f9042519079895c7408b0e92046c3f20680e0a9294e38ab3cfdd19b26cd4036fe2a80644abb922f1ad7cd682811a83c20120a8030df47b244a3bc44f4dbd00" +} diff --git a/tests/fixtures/transfer.json b/tests/fixtures/transfer.json new file mode 100644 index 00000000..4c577082 --- /dev/null +++ b/tests/fixtures/transfer.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 21000, + "value": "10000000000000000000", + "recipientAddress": "0x07Ac3E438719be72a9e2591bB6015F10E8Af2468", + "data": "", + "signature": "b3bc84c8caf1b75c18a78dde87df9f555161003d341eafad659ab672501185e413a26284c3c95056809c7d440c4ffab26179c538864c4d14534ebd5a961852bf01", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "b5d7b17d30da123d9eebc8bb6012c1a4e950e1dad2b080404bb052c30b8a8b2e" + }, + "serialized": "1e0c0000000000000005000000085200000000000000000000000000000000000000000000000000008ac7230489e800000107ac3e438719be72a9e2591bb6015f10e8af246800000000b3bc84c8caf1b75c18a78dde87df9f555161003d341eafad659ab672501185e413a26284c3c95056809c7d440c4ffab26179c538864c4d14534ebd5a961852bf01" +} diff --git a/tests/fixtures/unvote.json b/tests/fixtures/unvote.json new file mode 100644 index 00000000..d5e3a7cf --- /dev/null +++ b/tests/fixtures/unvote.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "13", + "gasPrice": 5, + "gasLimit": 200000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "3174b689", + "signature": "d7534ec92c06a8547d0f2b3d3259dff5b0b17f8673d68dff9af023009c9c450e24205cb5f4fd6165d71c8b3ba3e9f741d1853110d44bd1e798e87f1a5d6a89c501", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "92ca281a6699a4eb08e8e5c4a644c216026f6c6d3560611c50cab54d1300b690" + }, + "serialized": "1e0d0000000000000005000000400d0300000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b459040000003174b689d7534ec92c06a8547d0f2b3d3259dff5b0b17f8673d68dff9af023009c9c450e24205cb5f4fd6165d71c8b3ba3e9f741d1853110d44bd1e798e87f1a5d6a89c501" +} diff --git a/tests/fixtures/validator-registration.json b/tests/fixtures/validator-registration.json new file mode 100644 index 00000000..170909cc --- /dev/null +++ b/tests/fixtures/validator-registration.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 500000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d314111800000000000000000000000000000000", + "signature": "91b2ca61808b94392afa151ee893784a5221ab27b8fdf5871cc17c75e87acca8396530b2f320641326f00199478552e673d124406b44bcbe6075966016658d2201", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "3457dfd59d42a174feb30a1aac757e54caddd87d21e6483386a3440cc0fa6c5f" + }, + "serialized": "1e0c000000000000000500000020a10700000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b45984000000602a9eee00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000030a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d31411180000000000000000000000000000000091b2ca61808b94392afa151ee893784a5221ab27b8fdf5871cc17c75e87acca8396530b2f320641326f00199478552e673d124406b44bcbe6075966016658d2201" +} diff --git a/tests/fixtures/validator-resignation.json b/tests/fixtures/validator-resignation.json new file mode 100644 index 00000000..77a37778 --- /dev/null +++ b/tests/fixtures/validator-resignation.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 150000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "b85f5da2", + "signature": "94fd248dc5984b56be6c9661c5a32fa062fb21af62b1474a33d985302f9bda8a044c30e4feb1f06da437c15d9e997816aa3233b3f142cd780e1ff69b80269d0d00", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "ab469546888715725add275778bcf0c1dd68afc163b48018e22a044db718e5b9" + }, + "serialized": "1e0c0000000000000005000000f0490200000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b45904000000b85f5da294fd248dc5984b56be6c9661c5a32fa062fb21af62b1474a33d985302f9bda8a044c30e4feb1f06da437c15d9e997816aa3233b3f142cd780e1ff69b80269d0d00" +} diff --git a/tests/fixtures/vote.json b/tests/fixtures/vote.json new file mode 100644 index 00000000..d7992974 --- /dev/null +++ b/tests/fixtures/vote.json @@ -0,0 +1,16 @@ +{ + "data": { + "network": 30, + "nonce": "12", + "gasPrice": 5, + "gasLimit": 200000, + "value": "0", + "recipientAddress": "0x522B3294E6d06aA25Ad0f1B8891242E335D3B459", + "data": "6dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255", + "signature": "e1fd7b0ddc466072e2eac37b73283e8303d80ceb2dd2d64a8d6cdf5866662bc5261a08ca2d64942b6bb93b42ed820f1c8c1c92ce2312d380cc83fea022bfc2f301", + "senderPublicKey": "023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3", + "senderAddress": "0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22", + "id": "749744e0d689c46e37ff2993a984599eac4989a9ef0028337b335c9d43abf936" + }, + "serialized": "1e0c0000000000000005000000400d0300000000000000000000000000000000000000000000000000000000000000000001522b3294e6d06aa25ad0f1b8891242e335d3b459240000006dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255e1fd7b0ddc466072e2eac37b73283e8303d80ceb2dd2d64a8d6cdf5866662bc5261a08ca2d64942b6bb93b42ed820f1c8c1c92ce2312d380cc83fea022bfc2f301" +} diff --git a/tests/transactions/builder/test_transfer.py b/tests/transactions/builder/test_transfer.py deleted file mode 100644 index 0504dccd..00000000 --- a/tests/transactions/builder/test_transfer.py +++ /dev/null @@ -1,171 +0,0 @@ -import pytest - -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_TRANSFER, TRANSACTION_TYPE_GROUP -from crypto.identity.public_key import PublicKey -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.transfer import Transfer - - -set_network(Devnet) - - -def test_transfer_transaction(passphrase): - """Test if a transfer transaction gets built - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=1, - fee=10000000, - ) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(8) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - print(transaction_dict, transaction) - - assert transaction_dict['version'] == 1 - assert transaction_dict['nonce'] == 8 - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 10000000 - assert transaction_dict['amount'] == 1 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_transfer_transaction_update_amount(passphrase): - """Test if a transfer transaction can update an amount - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=200000000 - ) - transaction.set_amount(10) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == 1 - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['amount'] == 10 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_transfer_transaction_custom_fee(passphrase): - """Test if a transfer transaction gets built with a custom fee - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=200000000, - fee=5 - ) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_transfer_secondsign_transaction(passphrase): - """Test if a transfer transaction with second signature gets built - """ - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=200000000, - ) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction.second_sign('second top secret passphrase') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['signSignature'] - assert transaction_dict['type'] is TRANSACTION_TRANSFER - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - transaction.verify_secondsig_schnorr(PublicKey.from_passphrase('second top secret passphrase')) # if no exception is raised, it means the transaction is valid - - -def test_parse_signatures(transaction_type_0): - """Test if parse signature works when parsing serialized data - """ - transfer = Transfer( - recipientId=transaction_type_0['recipientId'], - amount=transaction_type_0['amount'] - ) - assert transfer.transaction.signature is None - transfer.transaction.parse_signatures(transaction_type_0['serialized'], 166) - assert transfer.transaction.signature - - -def test_transfer_transaction_amount_not_int(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount='bad amount' - ) - - -def test_transfer_transaction_amount_zero(): - with pytest.raises(ValueError): - """Test error handling in constructor for non-integer amount - """ - Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=0 - ) - - -def test_transfer_serialize(passphrase): - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=1, - fee=10000000, - ) - transaction.set_nonce(6) - transaction.sign(passphrase) - transaction.transaction.signature = '42faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' - - assert transaction.serialize(False, False, False) == 'ff011e0100000000000600000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb2242faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' - - -def test_transfer_serialize_without_signatures(passphrase): - transaction = Transfer( - recipientId='0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22', - amount=1, - fee=10000000, - ) - transaction.set_nonce(6) - transaction.sign(passphrase) - transaction.second_sign(passphrase) - transaction.transaction.signature = '42faaaf6b5b5eff5bb78c7bb2b116ecbc0a83f53445b801818b72afb34b39226646608d5e7048c12d6aedcebfc3156f035b57ca70c6a5e899b7ac2a1be163bb0' - - assert transaction.serialize(True, True, True) == 'ff011e0100000000000600000000000000023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d38096980000000000000100000000000000000000006f0182a0cc707b055322ccf6d4cb6a5aff1aeb22' diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py new file mode 100644 index 00000000..36b5abb6 --- /dev/null +++ b/tests/transactions/builder/test_transfer_builder.py @@ -0,0 +1,42 @@ +import pytest +import json +import os + +from crypto.transactions.builder.transfer_builder import TransferBuilder +from crypto.configuration.network import set_network +from crypto.networks.devnet import Devnet + +set_network(Devnet) + + +def get_transaction_fixture(fixture_name): + fixtures_path = os.path.join( + os.path.dirname(__file__), + '../../fixtures', + f'{fixture_name}.json' + ) + with open(fixtures_path, 'r') as f: + return json.load(f) + + +def test_transfer_transaction(passphrase): + fixture = get_transaction_fixture('transfer') + + builder = ( + TransferBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .value(fixture['data']['value']) + .sign(passphrase) + ) + + print(builder.transaction.serialize().hex()) + print(fixture['serialized']) + + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() From 0b1d321b0df427f317538e78906d75e32bd54278 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 11:51:55 -0600 Subject: [PATCH 02/23] transaction hasher --- .../types/abstract_transaction.py | 19 +--- crypto/utils/transaction_hasher.py | 94 +++++++++++++++++++ 2 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 crypto/utils/transaction_hasher.py diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 370add82..47c9c640 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -4,7 +4,7 @@ from crypto.configuration.network import get_network from crypto.identity.address import address_from_public_key from crypto.identity.private_key import PrivateKey - +from crypto.utils.transaction_hasher import TransactionHasher class AbstractTransaction: def __init__(self, data: Optional[dict] = None): @@ -60,19 +60,10 @@ def to_json(self) -> str: return json.dumps(self.to_dict()) def hash(self, skip_signature: bool) -> bytes: - hash_data = { - 'gasPrice': self.data.get('gasPrice'), - 'network': self.data.get('network', get_network().get('version')), - 'nonce': self.data.get('nonce'), - 'value': self.data.get('value'), - 'gasLimit': self.data.get('gasLimit'), - 'data': self.data.get('data'), - 'recipientAddress': self.data.get('recipientAddress'), - 'signature': self.data.get('signature') if not skip_signature else None, - } - # TODO: Implement TransactionHasher - # return TransactionHasher.to_hash(hash_data, skip_signature) - return b'' + hash_data = self.data.copy() + if skip_signature: + hash_data['signature'] = None + return TransactionHasher.to_hash(hash_data, skip_signature) def get_signature(self): # TODO: Implement this method diff --git a/crypto/utils/transaction_hasher.py b/crypto/utils/transaction_hasher.py new file mode 100644 index 00000000..23ba6beb --- /dev/null +++ b/crypto/utils/transaction_hasher.py @@ -0,0 +1,94 @@ +import hashlib +from binascii import unhexlify + + +class TransactionHasher: + @staticmethod + def to_hash(transaction: dict, skip_signature: bool = False) -> bytes: + # Process recipientAddress + hex_address = transaction.get('recipientAddress', '').lstrip('0x') + # Pad with leading zero if necessary + if len(hex_address) % 2 != 0: + hex_address = '0' + hex_address + recipient_address = bytes.fromhex(hex_address.lower()) + + # Build the fields array + fields = [ + TransactionHasher.to_be_array(int(transaction['network'])), + TransactionHasher.to_be_array(int(transaction['nonce'])), + TransactionHasher.to_be_array(int(transaction['gasPrice'])), # maxPriorityFeePerGas + TransactionHasher.to_be_array(int(transaction['gasPrice'])), # maxFeePerGas + TransactionHasher.to_be_array(int(transaction['gasLimit'])), + recipient_address, + TransactionHasher.to_be_array(int(transaction['value'])), + bytes.fromhex(transaction.get('data', '').lstrip('0x')) if transaction.get('data') else b'', + [], # Access list is unused + ] + + if not skip_signature and 'signature' in transaction: + signature_buffer = bytes.fromhex(transaction['signature']) + r = signature_buffer[0:32] + s = signature_buffer[32:64] + v = signature_buffer[64] + fields.extend([ + TransactionHasher.to_be_array(v), + r, + s, + ]) + + eip1559_prefix = b'\x02' # Marker for Type 2 (EIP-1559) transaction + + encoded = TransactionHasher.encode_rlp(fields) + hash_input = eip1559_prefix + encoded + + # Use SHA256 for hashing + return hashlib.sha256(hash_input).digest() + + @staticmethod + def to_be_array(value): + if isinstance(value, int): + if value == 0: + return b'' # Empty bytes represent zero + else: + return value.to_bytes((value.bit_length() + 7) // 8, byteorder='big') + elif isinstance(value, bytes): + return value + else: + raise TypeError("Unsupported type for to_be_array") + + @staticmethod + def encode_length(length): + if length == 0: + return b'' + result = [] + while length > 0: + result.insert(0, length & 0xFF) + length >>= 8 + return bytes(result) + + @staticmethod + def encode_rlp(input_data): + if isinstance(input_data, bytes): + input_len = len(input_data) + if input_len == 1 and input_data[0] <= 0x7f: + return input_data + elif input_len <= 55: + return bytes([0x80 + input_len]) + input_data + else: + len_bytes = TransactionHasher.encode_length(input_len) + return bytes([0xb7 + len(len_bytes)]) + len_bytes + input_data + elif isinstance(input_data, list): + output = b''.join([TransactionHasher.encode_rlp(item) for item in input_data]) + output_len = len(output) + if output_len <= 55: + return bytes([0xc0 + output_len]) + output + else: + len_bytes = TransactionHasher.encode_length(output_len) + return bytes([0xf7 + len(len_bytes)]) + len_bytes + output + elif isinstance(input_data, int): + return TransactionHasher.encode_rlp(TransactionHasher.to_be_array(input_data)) + elif input_data is None: + return TransactionHasher.encode_rlp(b'') + else: + # Handle other types by converting to bytes + return TransactionHasher.encode_rlp(str(input_data).encode('utf-8')) From 28a99c173a0cb9717a341c60933b8499e5930063 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 12:01:12 -0600 Subject: [PATCH 03/23] wip --- .../types/abstract_transaction.py | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 47c9c640..046cfba2 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -5,6 +5,8 @@ from crypto.identity.address import address_from_public_key from crypto.identity.private_key import PrivateKey from crypto.utils.transaction_hasher import TransactionHasher +from coincurve import PublicKey + class AbstractTransaction: def __init__(self, data: Optional[dict] = None): @@ -32,22 +34,40 @@ def get_bytes(self, skip_signature: bool = False) -> bytes: def sign(self, private_key: PrivateKey): hash_ = self.hash(skip_signature=True) - # TODO: Implement signing logic + signature_with_recid = private_key.private_key.sign_recoverable(hash_, hasher=None) + signature_hex = signature_with_recid.hex() + self.data['signature'] = signature_hex return self - def get_public_key(self, compact_signature): - # TODO: Implement this method - pass + def get_public_key(self, compact_signature, hash_): + public_key = PublicKey.from_signature_and_message(compact_signature, hash_, hasher=None) + return public_key def recover_sender(self): - compact_signature = self.get_signature() - public_key = self.get_public_key(compact_signature) - self.data['senderPublicKey'] = public_key.hex() + signature_hex = self.data.get('signature') + if not signature_hex: + raise ValueError("No signature to recover from") + + signature_with_recid = bytes.fromhex(signature_hex) + hash_ = self.hash(skip_signature=True) + public_key = self.get_public_key(signature_with_recid, hash_) + self.data['senderPublicKey'] = public_key.format().hex() self.data['senderAddress'] = address_from_public_key(self.data['senderPublicKey']) def verify(self) -> bool: - # TODO: Implement this method - return True + signature_hex = self.data.get('signature') + if not signature_hex: + return False + + signature_with_recid = bytes.fromhex(signature_hex) + hash_ = self.hash(skip_signature=True) + recovered_public_key = self.get_public_key(signature_with_recid, hash_) + sender_public_key_hex = self.data.get('senderPublicKey') + if not sender_public_key_hex: + return False + + sender_public_key_bytes = bytes.fromhex(sender_public_key_hex) + return recovered_public_key.format() == sender_public_key_bytes def serialize(self, skip_signature: bool = False) -> bytes: from crypto.transactions.serializer import Serializer @@ -66,5 +86,7 @@ def hash(self, skip_signature: bool) -> bytes: return TransactionHasher.to_hash(hash_data, skip_signature) def get_signature(self): - # TODO: Implement this method - pass + signature_hex = self.data.get('signature') + if signature_hex: + return bytes.fromhex(signature_hex) + return None From 9e6f909972afac9d310ce2df2c1bc65a02edae97 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 12:12:23 -0600 Subject: [PATCH 04/23] wip --- crypto/transactions/deserializer.py | 6 ++---- crypto/transactions/serializer.py | 5 +---- tests/transactions/builder/test_transfer_builder.py | 4 ---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 4fef67ab..091aa20f 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -11,7 +11,6 @@ read_bit8, read_bit32, read_bit64, - # read_bit256, ) # from crypto.enums.abi_function import AbiFunction # TODO: Implement or import AbiFunction # from crypto.utils.abi_decoder import AbiDecoder # TODO: Implement or import AbiDecoder @@ -68,9 +67,8 @@ def deserialize_common(self, data: dict): data['value'] = '0' def deserialize_data(self, data: dict): - # @TODO: this should use read_bit256 - # value, _ = read_bit256(self.serialized, self.pointer) - value, _ = read_bit64(self.serialized, self.pointer) + value = int.from_bytes(self.serialized[self.pointer:self.pointer + 32], byteorder='big') + self.pointer += 32 data['value'] = str(value) self.pointer += 32 diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index 974db7f1..b5a84cbc 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -5,7 +5,6 @@ write_bit8, write_bit32, write_bit64, - # write_bit256, ) # from crypto.utils.address import Address # TODO: Implement or import Address @@ -46,9 +45,7 @@ def serialize_common(self) -> bytes: def serialize_data(self) -> bytes: bytes_data = bytes() - # @TODO: this should use write_bit256 - # bytes_data += write_bit256(int(self.transaction.data['value'])) - bytes_data += write_bit64(int(self.transaction.data['value'])) + bytes_data += int(self.transaction.data['value']).to_bytes(32, byteorder='big') if 'recipientAddress' in self.transaction.data: bytes_data += write_bit8(1) diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index 36b5abb6..28258151 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -33,10 +33,6 @@ def test_transfer_transaction(passphrase): .sign(passphrase) ) - print(builder.transaction.serialize().hex()) - print(fixture['serialized']) - - assert builder.transaction.serialize().hex() == fixture['serialized'] assert builder.transaction.data['id'] == fixture['data']['id'] assert builder.verify() From e4fb421203fca8ae5ca194bf9749d8fb5efee56f Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 13:18:48 -0600 Subject: [PATCH 05/23] add abi encoder/decoder --- crypto/utils/Abi.Consensus.json | 1032 +++++++++++++++++++++++++++++++ crypto/utils/abi_base.py | 53 ++ crypto/utils/abi_decoder.py | 145 +++++ crypto/utils/abi_encoder.py | 259 ++++++++ tests/utils/test_abi_decoder.py | 15 + tests/utils/test_abi_encoder.py | 12 + 6 files changed, 1516 insertions(+) create mode 100644 crypto/utils/Abi.Consensus.json create mode 100644 crypto/utils/abi_base.py create mode 100644 crypto/utils/abi_decoder.py create mode 100644 crypto/utils/abi_encoder.py create mode 100644 tests/utils/test_abi_decoder.py create mode 100644 tests/utils/test_abi_encoder.py diff --git a/crypto/utils/Abi.Consensus.json b/crypto/utils/Abi.Consensus.json new file mode 100644 index 00000000..69ed3739 --- /dev/null +++ b/crypto/utils/Abi.Consensus.json @@ -0,0 +1,1032 @@ +{ + "abi": [ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "activeValidatorsCount", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "calculateTopValidators", + "inputs": [ + { "name": "n", "type": "uint8", "internalType": "uint8" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "getAllValidators", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct Validator[]", + "components": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "tuple", + "internalType": "struct ValidatorData", + "components": [ + { + "name": "votersCount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "voteBalance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isResigned", + "type": "bool", + "internalType": "bool" + }, + { + "name": "bls12_381_public_key", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRounds", + "inputs": [ + { + "name": "offset", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "count", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct Round[]", + "components": [ + { + "name": "round", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "validators", + "type": "tuple[]", + "internalType": "struct RoundValidator[]", + "components": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "voteBalance", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoundsCount", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getTopValidators", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct Validator[]", + "components": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "tuple", + "internalType": "struct ValidatorData", + "components": [ + { + "name": "votersCount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "voteBalance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isResigned", + "type": "bool", + "internalType": "bool" + }, + { + "name": "bls12_381_public_key", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getValidator", + "inputs": [ + { + "name": "_addr", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct Validator", + "components": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "tuple", + "internalType": "struct ValidatorData", + "components": [ + { + "name": "votersCount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "voteBalance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isResigned", + "type": "bool", + "internalType": "bool" + }, + { + "name": "bls12_381_public_key", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getVotes", + "inputs": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "count", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple[]", + "internalType": "struct VoteResult[]", + "components": [ + { + "name": "voter", + "type": "address", + "internalType": "address" + }, + { + "name": "validator", + "type": "address", + "internalType": "address" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getVotesCount", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "isValidatorRegistered", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "registerValidator", + "inputs": [ + { + "name": "bls12_381_public_key", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "registeredValidatorsCount", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "resignValidator", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "resignedValidatorsCount", + "inputs": [], + "outputs": [ + { "name": "", "type": "uint256", "internalType": "uint256" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "unvote", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateValidator", + "inputs": [ + { + "name": "_validator", + "type": "tuple", + "internalType": "struct Validator", + "components": [ + { + "name": "addr", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "tuple", + "internalType": "struct ValidatorData", + "components": [ + { + "name": "votersCount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "voteBalance", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isResigned", + "type": "bool", + "internalType": "bool" + }, + { + "name": "bls12_381_public_key", + "type": "bytes", + "internalType": "bytes" + } + ] + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "updateVoters", + "inputs": [ + { + "name": "voters", + "type": "address[]", + "internalType": "address[]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "vote", + "inputs": [ + { "name": "addr", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Unvoted", + "inputs": [ + { + "name": "voter", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "validator", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValidatorRegistered", + "inputs": [ + { + "name": "addr", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "bls12_381_public_key", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValidatorResigned", + "inputs": [ + { + "name": "addr", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Voted", + "inputs": [ + { + "name": "voter", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "validator", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + } + ], + "bytecode": { + "object": "0x60a06040525f80805560018190556007819055600880546001600160a01b0319908116909155600980549091169055600c5534801561003c575f5ffd5b5033608052608051612bdb61007c5f395f81816103e4015281816104c30152818161069301528181610a0901528181610f2101526114e90152612bdb5ff3fe608060405234801561000f575f5ffd5b5060043610610111575f3560e01c80636dd7d8ea1161009e578063b85f5da21161006e578063b85f5da214610202578063d04a68c71461020a578063eb9019d414610245578063f1bd0b3714610265578063f3513a371461026c575f5ffd5b80636dd7d8ea146101bf578063a09686c4146101d2578063afeea115146101da578063b5cfa68c146101ef575f5ffd5b80632bdf6d43116100e45780632bdf6d431461015c5780633174b6891461017157806340f74f4714610179578063602a9eee1461019957806362525879146101ac575f5ffd5b80630777cbef146101155780630d2bd9091461012c5780631904bb2e146101345780631b605b8614610154575b5f5ffd5b6001545b6040519081526020015b60405180910390f35b600d54610119565b610147610142366004612457565b610274565b60405161012391906124fb565b600754610119565b61016f61016a36600461250d565b6103d9565b005b61016f610467565b61018c61018736600461257c565b6104b6565b604051610123919061259c565b61016f6101a7366004612655565b610689565b61016f6101ba3660046126b1565b610964565b61016f6101cd366004612457565b6109ff565b600e54610119565b6101e2610d79565b60405161012391906126e7565b61016f6101fd36600461273e565b610f16565b61016f6113bb565b610235610218366004612457565b6001600160a01b03165f9081526003602052604090205460ff1690565b6040519015158152602001610123565b61025861025336600461275e565b6114dc565b6040516101239190612786565b5f54610119565b6101e261171d565b61027c612361565b6001600160a01b0382165f9081526003602052604090205460ff166102e85760405162461bcd60e51b815260206004820152601c60248201527f56616c696461746f724461746120646f65736e2774206578697374730000000060448201526064015b60405180910390fd5b6040805180820182526001600160a01b0384168082525f90815260026020818152918490208451608081018652815481526001820154818501529181015460ff16151594820194909452600384018054939492850193919291606084019190610350906127e0565b80601f016020809104026020016040519081016040528092919081815260200182805461037c906127e0565b80156103c75780601f1061039e576101008083540402835291602001916103c7565b820191905f5260205f20905b8154815290600101906020018083116103aa57829003601f168201915b50505091909252505050905292915050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104215760405162461bcd60e51b81526004016102df90612812565b5f5b818110156104625761045a83838381811061044057610440612847565b90506020020160208101906104559190612457565b6118b4565b600101610423565b505050565b7f6572af8bf9a0a86efb88dcc30011626a15c9c4603503aa4466a3f87a1867deef33610491611994565b604080516001600160a01b0393841681529290911660208301520160405180910390a1565b6060336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105005760405162461bcd60e51b81526004016102df90612812565b600e548290841061051257505f610536565b600e5461051f848661286f565b111561053657600e54610533908590612882565b90505b5f816001600160401b0381111561054f5761054f612895565b60405190808252806020026020018201604052801561059457816020015b604080518082019091525f81526060602082015281526020019060019003908161056d5790505b5090505f5b8281101561067e57604051806040016040528082886105b8919061286f565b6105c390600161286f565b8152602001600e6105d4848a61286f565b815481106105e4576105e4612847565b905f5260205f2001805480602002602001604051908101604052809291908181526020015f905b82821015610652575f848152602090819020604080518082019091526002850290910180546001600160a01b0316825260019081015482840152908352909201910161060b565b5050505081525082828151811061066b5761066b612847565b6020908102919091010152600101610599565b509150505b92915050565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036107015760405162461bcd60e51b815260206004820152601c60248201527f43616c6c65722069732074686520636f6e7472616374206f776e65720000000060448201526064016102df565b335f9081526003602052604090205460ff16156107605760405162461bcd60e51b815260206004820152601f60248201527f56616c696461746f7220697320616c726561647920726567697374657265640060448201526064016102df565b5f82826040516107719291906128a9565b60408051918290039091205f8181526004602052919091205490915060ff16156107e95760405162461bcd60e51b815260206004820152602360248201527f424c5331322d333831206b657920697320616c726561647920726567697374656044820152621c995960ea1b60648201526084016102df565b6107f38383611bfc565b5f60405180608001604052805f81526020015f81526020015f1515815260200185858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250939094525050805492935090508061085b836128b8565b9091555050335f9081526003602081815260408084208054600160ff1991821681179092556002808552958390208751815593870151918401919091559085015193820180549091169315159390931790925560608301518392918201906108c39082612914565b5050505f82815260046020526040808220805460ff191660019081179091556005805491820181559092527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db090910180546001600160a01b0319163390811790915590517f61809fa303a3a57f4d70552f533f3e0b003173d424590cd4bb22a2afe000990c9161095691879087906129ce565b60405180910390a150505050565b6109746102186020830183612457565b6109c05760405162461bcd60e51b815260206004820152601c60248201527f56616c696461746f724461746120646f65736e2774206578697374730000000060448201526064016102df565b6109cd6020820182612a0d565b60025f6109dd6020850185612457565b6001600160a01b0316815260208101919091526040015f206104628282612ae4565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163303610a775760405162461bcd60e51b815260206004820152601c60248201527f43616c6c65722069732074686520636f6e7472616374206f776e65720000000060448201526064016102df565b6001600160a01b0381165f9081526003602052604090205460ff16610ade5760405162461bcd60e51b815260206004820152601760248201527f4d75737420766f746520666f722076616c696461746f7200000000000000000060448201526064016102df565b6001600160a01b0381165f9081526002602081905260409091209081015460ff1615610b575760405162461bcd60e51b815260206004820152602260248201527f4d75737420766f746520666f7220756e72657369676e65642076616c6964617460448201526137b960f11b60648201526084016102df565b335f90815260066020526040902080546001600160a01b03848116911603610bc15760405162461bcd60e51b815260206004820181905260248201527f416c726561647920766f74656420666f7220746869732076616c696461746f7260448201526064016102df565b80546001600160a01b031615610bdb57610bd9611994565b505b604080516080810182526001600160a01b03808616825233803160208085019182525f85870181815260608701828152948252600690925295909520935184549084166001600160a01b03199182161785559051600185015593516002840180549184169186169190911790555160039092018054928216929093169190911790915560085416610c8d5760088054336001600160a01b03199182168117909255600980549091169091179055610ce4565b600980546001600160a01b039081165f9081526006602052604080822060030180546001600160a01b0319908116339081179092558554828552929093206002018054841692909416919091179092558254161790555b60078054905f610cf3836128b8565b9190505550336001600160a01b031631826001015f828254610d15919061286f565b9091555050815460019083905f90610d2e90849061286f565b9091555050604080513381526001600160a01b03851660208201527fce0c7a2a940807f7dc2ce7a615c2532e915e6c0ac9a08bc4ed9d515a710a53e2910160405180910390a1505050565b600d546060905f906001600160401b03811115610d9857610d98612895565b604051908082528060200260200182016040528015610dd157816020015b610dbe612361565b815260200190600190039081610db65790505b5090505f5b600d54811015610f10575f600d8281548110610df457610df4612847565b5f9182526020808320909101546001600160a01b03168083526002808352604093849020845180860186528381528551608081018752825481526001830154818701529282015460ff1615159583019590955260038101805493965090949384019285916060840191610e66906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054610e92906127e0565b8015610edd5780601f10610eb457610100808354040283529160200191610edd565b820191905f5260205f20905b815481529060010190602001808311610ec057829003601f168201915b505050505081525050815250848481518110610efb57610efb612847565b60209081029190910101525050600101610dd6565b50919050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f5e5760405162461bcd60e51b81526004016102df90612812565b610f66611c5e565b610f6e611dad565b600a80546001600160a01b03191690556001545f80549091610fa09160ff8516918491610f9b9190612882565b611dfe565b90508060ff165f03610fb0575050565b5f5b60055481101561125b575f60058281548110610fd057610fd0612847565b5f9182526020808320909101546001600160a01b03168083526002918290526040909220908101549192509060ff161561100b575050611253565b600a546001600160a01b03166110465750600a80546001600160a01b0319166001600160a01b03929092169190911790556001600c55611253565b8360ff16600c5410156110645761105d8285611e8f565b5050611253565b600a546001600160a01b039081165f908152600260208181526040928390208351808501855294871685528351608081018552865481526001870154818401529286015460ff161515938301939093526003850180549394611240949093928401929187916060840191906110d8906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054611104906127e0565b801561114f5780601f106111265761010080835404028352916020019161114f565b820191905f5260205f20905b81548152906001019060200180831161113257829003601f168201915b505050919092525050509052604080518082018252600a546001600160a01b031681528151608081018352855481526001860154602082810191909152600287015460ff161515938201939093526003860180549293840192879160608401916111b8906127e0565b80601f01602080910402602001604051908101604052809291908181526020018280546111e4906127e0565b801561122f5780601f106112065761010080835404028352916020019161122f565b820191905f5260205f20905b81548152906001019060200180831161121257829003601f168201915b50505050508152505081525061226e565b1561124f5761124f8386611e8f565b5050505b600101610fb2565b50600e80546001810182555f918252600a547fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd909101916001600160a01b03909116906112aa90600d906123aa565b8260ff166001600160401b038111156112c5576112c5612895565b6040519080825280602002602001820160405280156112ee578160200160208202803683370190505b50805161130391600d916020909101906123c5565b505f5b8360ff168110156113b35781600d828154811061132557611325612847565b5f918252602080832090910180546001600160a01b039485166001600160a01b03199182161790915560408051808201825296851680885280855260028085528286206001908101548a87019081528b548083018d558c89528789209b519302909a01805492891692909516919091178455975192880192909255908352600b909152902054169101611306565b505050505b50565b335f9081526003602052604090205460ff166114195760405162461bcd60e51b815260206004820152601960248201527f43616c6c6572206973206e6f7420612076616c696461746f720000000000000060448201526064016102df565b335f9081526002602081905260409091209081015460ff161561147e5760405162461bcd60e51b815260206004820152601d60248201527f56616c696461746f7220697320616c72656164792072657369676e656400000060448201526064016102df565b60028101805460ff19166001908117909155805481905f906114a190839061286f565b90915550506040513381527f24250fc1ec78a1405ddd4cc8b75964858af228d05faa8d4bc1302966d8a541179060200160405180910390a150565b6060336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146115265760405162461bcd60e51b81526004016102df90612812565b5f611534835f600754611dfe565b6001600160401b0381111561154b5761154b612895565b60405190808252806020026020018201604052801561158f57816020015b604080518082019091525f80825260208201528152602001906001900390816115695790505b506008549091506001600160a01b03908116908516156115c857506001600160a01b038085165f90815260066020526040902060030154165b5f5b6001600160a01b038216158015906115e157508481105b15611656576001600160a01b038083165f818152600660209081526040918290208251808401909352928252825490931692810192909252908483611625816128b8565b94508151811061163757611637612847565b6020908102919091010152600301546001600160a01b031691506115ca565b8083510361166957829350505050610683565b5f816001600160401b0381111561168257611682612895565b6040519080825280602002602001820160405280156116c657816020015b604080518082019091525f80825260208201528152602001906001900390816116a05790505b5090505f5b82811015611712578481815181106116e5576116e5612847565b60200260200101518282815181106116ff576116ff612847565b60209081029190910101526001016116cb565b509695505050505050565b6005546060905f906001600160401b0381111561173c5761173c612895565b60405190808252806020026020018201604052801561177557816020015b611762612361565b81526020019060019003908161175a5790505b5090505f5b600554811015610f10575f6005828154811061179857611798612847565b5f9182526020808320909101546001600160a01b03168083526002808352604093849020845180860186528381528551608081018752825481526001830154818701529282015460ff161515958301959095526003810180549396509094938401928591606084019161180a906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054611836906127e0565b80156118815780601f1061185857610100808354040283529160200191611881565b820191905f5260205f20905b81548152906001019060200180831161186457829003601f168201915b50505050508152505081525084848151811061189f5761189f612847565b6020908102919091010152505060010161177a565b6001600160a01b038082165f90815260066020526040902080549091166118d9575050565b60018101546001600160a01b0383163181101561193a57611904816001600160a01b03851631612882565b82546001600160a01b03165f908152600260205260408120600101805490919061192f90849061286f565b9091555061197f9050565b61194e6001600160a01b0384163182612882565b82546001600160a01b03165f9081526002602052604081206001018054909190611979908490612882565b90915550505b506001600160a01b0390911631600190910155565b335f90815260066020526040812080546001600160a01b0316611a075760405162461bcd60e51b815260206004820152602560248201527f4d75737420766f746520666f722076616c696461746f72206265666f726520756044820152646e766f746560d81b60648201526084016102df565b6009546008546001600160a01b03918216911603611a4057600880546001600160a01b0319908116909155600980549091169055611b53565b600954336001600160a01b0390911603611a99576002810180546001600160a01b039081165f90815260066020526040902060030180546001600160a01b03199081169091559154600980549093169116179055611b53565b600854336001600160a01b0390911603611afd576009546001600160a01b039081165f9081526006602052604080822060020180546001600160a01b0319908116909155600880548086168552929093206003015491169216919091179055611b53565b60038181018054600280850180546001600160a01b039081165f9081526006602052604080822090970180549583166001600160a01b031996871617905591549454811682529490200180549290931691161790555b80546001600160a01b03165f818152600260205260408120600180850154908201805492939192909190611b88908490612882565b9091555050805460019082905f90611ba1908490612882565b9091555050335f90815260066020526040812080546001600160a01b0319908116825560018201839055600282018054821690556003909101805490911690556007805491611bef83612b71565b9091555091949350505050565b60308114611c5a5760405162461bcd60e51b815260206004820152602560248201527f424c5331322d333831207075626c69634b6579206c656e67746820697320696e6044820152641d985b1a5960da1b60648201526084016102df565b5050565b6005545f611c6d600183612882565b90505b8015611c5a575f611c8282600161286f565b60408051426020820152449181019190915260608101849052608001604051602081830303815290604052805190602001205f1c611cc09190612b86565b90505f60058381548110611cd657611cd6612847565b5f91825260209091200154600580546001600160a01b0390921692509083908110611d0357611d03612847565b5f91825260209091200154600580546001600160a01b039092169185908110611d2e57611d2e612847565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055508060058381548110611d6d57611d6d612847565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555050508080611da590612b71565b915050611c70565b600a546001600160a01b03165b6001600160a01b03811615611df7576001600160a01b039081165f908152600b6020526040902080546001600160a01b0319811690915516611dba565b505f600c55565b5f81831115611e675760405162461bcd60e51b815260206004820152602f60248201527f4d696e696d756d2073686f756c64206265206c657373207468616e206f72206560448201526e7175616c20746f206d6178696d756d60881b60648201526084016102df565b82841015611e76575081611e88565b81841115611e85575080611e88565b50825b9392505050565b6001600160a01b0382165f9081526002602081815260408084208151608081018352815481526001820154938101939093529283015460ff16151590820152600382018054919291606084019190611ee6906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054611f12906127e0565b8015611f5d5780601f10611f3457610100808354040283529160200191611f5d565b820191905f5260205f20905b815481529060010190602001808311611f4057829003601f168201915b505050919092525050604080518082018252600a546001600160a01b03168082525f90815260026020818152918490208451608081018652815481526001820154818501529181015460ff1615159482019490945260038401805496975061207e9693955091850193909290916060840191611fd8906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054612004906127e0565b801561204f5780601f106120265761010080835404028352916020019161204f565b820191905f5260205f20905b81548152906001019060200180831161203257829003601f168201915b5050505050815250508152506040518060400160405280866001600160a01b031681526020018481525061226e565b156120915761208c836122b2565b61220f565b600a546001600160a01b039081165f818152600b6020526040902054909116905b6001600160a01b0382166120cf576120ca8186612303565b61220c565b6040805180820182526001600160a01b0384168082525f90815260026020818152918490208451608081018652815481526001820154818501529181015460ff161515948201949094526003840180546121dc9593850193916060840191612136906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054612162906127e0565b80156121ad5780601f10612184576101008083540402835291602001916121ad565b820191905f5260205f20905b81548152906001019060200180831161219057829003601f168201915b5050505050815250508152506040518060400160405280886001600160a01b031681526020018681525061226e565b156121eb576120ca8186612303565b506001600160a01b038082165f908152600b602052604090205416906120b2565b50505b8160ff16600c54111561046257600a80546001600160a01b039081165f908152600b6020526040812080546001600160a01b03198082169092558454931692168217909255600c8054919261226383612b71565b919050555050505050565b5f8160200151602001518360200151602001510361229e5750805182516001600160a01b03918216911611610683565b506020908101518101519181015101511190565b600a80546001600160a01b038381165f818152600b602052604081208054939094166001600160a01b031993841617909355835490911617909155600c8054916122fb836128b8565b919050555050565b6001600160a01b038281165f818152600b602052604080822080548686168085529284208054919096166001600160a01b03199182161790955592825282549093169092179055600c805491612358836128b8565b91905055505050565b60405180604001604052805f6001600160a01b031681526020016123a560405180608001604052805f81526020015f81526020015f15158152602001606081525090565b905290565b5080545f8255905f5260205f20908101906113b89190612428565b828054828255905f5260205f20908101928215612418579160200282015b8281111561241857825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906123e3565b50612424929150612428565b5090565b5b80821115612424575f8155600101612429565b80356001600160a01b0381168114612452575f5ffd5b919050565b5f60208284031215612467575f5ffd5b611e888261243c565b60018060a01b0381511682525f602082015160406020850152805160408501526020810151606085015260408101511515608085015260608101519050608060a085015280518060c08601525f5b818110156124db57602081840181015160e08884010152016124be565b505f60e0828701015260e0601f19601f8301168601019250505092915050565b602081525f611e886020830184612470565b5f5f6020838503121561251e575f5ffd5b82356001600160401b03811115612533575f5ffd5b8301601f81018513612543575f5ffd5b80356001600160401b03811115612558575f5ffd5b8560208260051b840101111561256c575f5ffd5b6020919091019590945092505050565b5f5f6040838503121561258d575f5ffd5b50508035926020909101359150565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561264957868503603f19018452815180518652602090810151604082880181905281519088018190529101905f9060608801905b8083101561263157835180516001600160a01b031683526020908101518184015290930192600192909201916040909101906125fa565b509650505060209384019391909101906001016125c2565b50929695505050505050565b5f5f60208385031215612666575f5ffd5b82356001600160401b0381111561267b575f5ffd5b8301601f8101851361268b575f5ffd5b80356001600160401b038111156126a0575f5ffd5b85602082840101111561256c575f5ffd5b5f602082840312156126c1575f5ffd5b81356001600160401b038111156126d6575f5ffd5b820160408185031215611e88575f5ffd5b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561264957603f19878603018452612729858351612470565b9450602093840193919091019060010161270d565b5f6020828403121561274e575f5ffd5b813560ff81168114611e88575f5ffd5b5f5f6040838503121561276f575f5ffd5b6127788361243c565b946020939093013593505050565b602080825282518282018190525f918401906040840190835b818110156127d557835180516001600160a01b03908116855260209182015116818501529093019260409092019160010161279f565b509095945050505050565b600181811c908216806127f457607f821691505b602082108103610f1057634e487b7160e01b5f52602260045260245ffd5b6020808252818101527f43616c6c6572206973206e6f742074686520636f6e7472616374206f776e6572604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b808201808211156106835761068361285b565b818103818111156106835761068361285b565b634e487b7160e01b5f52604160045260245ffd5b818382375f9101908152919050565b5f600182016128c9576128c961285b565b5060010190565b601f82111561046257805f5260205f20601f840160051c810160208510156128f55750805b601f840160051c820191505b818110156113b3575f8155600101612901565b81516001600160401b0381111561292d5761292d612895565b6129418161293b84546127e0565b846128d0565b6020601f821160018114612973575f831561295c5750848201515b5f19600385901b1c1916600184901b1784556113b3565b5f84815260208120601f198516915b828110156129a25787850151825560209485019460019092019101612982565b50848210156129bf57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160a01b03841681526040602082018190528101829052818360608301375f818301606090810191909152601f909201601f1916010192915050565b5f8235607e19833603018112612a21575f5ffd5b9190910192915050565b6001600160401b03831115612a4257612a42612895565b612a5683612a5083546127e0565b836128d0565b5f601f841160018114612a87575f8515612a705750838201355b5f19600387901b1c1916600186901b1783556113b3565b5f83815260208120601f198716915b82811015612ab65786850135825560209485019460019092019101612a96565b5086821015612ad2575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b8135815560208201356001820155600281016040830135801515808214612b09575f5ffd5b60ff19835416915060ff8116821783555050506060820135601e19833603018112612b32575f5ffd5b820180356001600160401b03811115612b49575f5ffd5b602082019150803603821315612b5d575f5ffd5b612b6b818360038601612a2b565b50505050565b5f81612b7f57612b7f61285b565b505f190190565b5f82612ba057634e487b7160e01b5f52601260045260245ffd5b50069056fea264697066735822122071b882f5aa13e15b9bba503811bfdf20f770de1ab04c653b0dfdade41b9c0bed64736f6c634300081b0033", + "sourceMap": "1600:14234:23:-:0;;;1701:1;1656:46;;;1708:44;;;;2044:32;;;;2082:40;;;-1:-1:-1;;;;;;2082:40:23;;;;;;2128;;;;;;;;2271:39;;2406:50;;;;;;;;;-1:-1:-1;2439:10:23;2430:19;;1600:14234;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;", + "linkReferences": {} + }, + "deployedBytecode": { + "object": "0x608060405234801561000f575f5ffd5b5060043610610111575f3560e01c80636dd7d8ea1161009e578063b85f5da21161006e578063b85f5da214610202578063d04a68c71461020a578063eb9019d414610245578063f1bd0b3714610265578063f3513a371461026c575f5ffd5b80636dd7d8ea146101bf578063a09686c4146101d2578063afeea115146101da578063b5cfa68c146101ef575f5ffd5b80632bdf6d43116100e45780632bdf6d431461015c5780633174b6891461017157806340f74f4714610179578063602a9eee1461019957806362525879146101ac575f5ffd5b80630777cbef146101155780630d2bd9091461012c5780631904bb2e146101345780631b605b8614610154575b5f5ffd5b6001545b6040519081526020015b60405180910390f35b600d54610119565b610147610142366004612457565b610274565b60405161012391906124fb565b600754610119565b61016f61016a36600461250d565b6103d9565b005b61016f610467565b61018c61018736600461257c565b6104b6565b604051610123919061259c565b61016f6101a7366004612655565b610689565b61016f6101ba3660046126b1565b610964565b61016f6101cd366004612457565b6109ff565b600e54610119565b6101e2610d79565b60405161012391906126e7565b61016f6101fd36600461273e565b610f16565b61016f6113bb565b610235610218366004612457565b6001600160a01b03165f9081526003602052604090205460ff1690565b6040519015158152602001610123565b61025861025336600461275e565b6114dc565b6040516101239190612786565b5f54610119565b6101e261171d565b61027c612361565b6001600160a01b0382165f9081526003602052604090205460ff166102e85760405162461bcd60e51b815260206004820152601c60248201527f56616c696461746f724461746120646f65736e2774206578697374730000000060448201526064015b60405180910390fd5b6040805180820182526001600160a01b0384168082525f90815260026020818152918490208451608081018652815481526001820154818501529181015460ff16151594820194909452600384018054939492850193919291606084019190610350906127e0565b80601f016020809104026020016040519081016040528092919081815260200182805461037c906127e0565b80156103c75780601f1061039e576101008083540402835291602001916103c7565b820191905f5260205f20905b8154815290600101906020018083116103aa57829003601f168201915b50505091909252505050905292915050565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146104215760405162461bcd60e51b81526004016102df90612812565b5f5b818110156104625761045a83838381811061044057610440612847565b90506020020160208101906104559190612457565b6118b4565b600101610423565b505050565b7f6572af8bf9a0a86efb88dcc30011626a15c9c4603503aa4466a3f87a1867deef33610491611994565b604080516001600160a01b0393841681529290911660208301520160405180910390a1565b6060336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146105005760405162461bcd60e51b81526004016102df90612812565b600e548290841061051257505f610536565b600e5461051f848661286f565b111561053657600e54610533908590612882565b90505b5f816001600160401b0381111561054f5761054f612895565b60405190808252806020026020018201604052801561059457816020015b604080518082019091525f81526060602082015281526020019060019003908161056d5790505b5090505f5b8281101561067e57604051806040016040528082886105b8919061286f565b6105c390600161286f565b8152602001600e6105d4848a61286f565b815481106105e4576105e4612847565b905f5260205f2001805480602002602001604051908101604052809291908181526020015f905b82821015610652575f848152602090819020604080518082019091526002850290910180546001600160a01b0316825260019081015482840152908352909201910161060b565b5050505081525082828151811061066b5761066b612847565b6020908102919091010152600101610599565b509150505b92915050565b6001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633036107015760405162461bcd60e51b815260206004820152601c60248201527f43616c6c65722069732074686520636f6e7472616374206f776e65720000000060448201526064016102df565b335f9081526003602052604090205460ff16156107605760405162461bcd60e51b815260206004820152601f60248201527f56616c696461746f7220697320616c726561647920726567697374657265640060448201526064016102df565b5f82826040516107719291906128a9565b60408051918290039091205f8181526004602052919091205490915060ff16156107e95760405162461bcd60e51b815260206004820152602360248201527f424c5331322d333831206b657920697320616c726561647920726567697374656044820152621c995960ea1b60648201526084016102df565b6107f38383611bfc565b5f60405180608001604052805f81526020015f81526020015f1515815260200185858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920182905250939094525050805492935090508061085b836128b8565b9091555050335f9081526003602081815260408084208054600160ff1991821681179092556002808552958390208751815593870151918401919091559085015193820180549091169315159390931790925560608301518392918201906108c39082612914565b5050505f82815260046020526040808220805460ff191660019081179091556005805491820181559092527f036b6384b5eca791c62761152d0c79bb0604c104a5fb6f4eb0703f3154bb3db090910180546001600160a01b0319163390811790915590517f61809fa303a3a57f4d70552f533f3e0b003173d424590cd4bb22a2afe000990c9161095691879087906129ce565b60405180910390a150505050565b6109746102186020830183612457565b6109c05760405162461bcd60e51b815260206004820152601c60248201527f56616c696461746f724461746120646f65736e2774206578697374730000000060448201526064016102df565b6109cd6020820182612a0d565b60025f6109dd6020850185612457565b6001600160a01b0316815260208101919091526040015f206104628282612ae4565b6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000163303610a775760405162461bcd60e51b815260206004820152601c60248201527f43616c6c65722069732074686520636f6e7472616374206f776e65720000000060448201526064016102df565b6001600160a01b0381165f9081526003602052604090205460ff16610ade5760405162461bcd60e51b815260206004820152601760248201527f4d75737420766f746520666f722076616c696461746f7200000000000000000060448201526064016102df565b6001600160a01b0381165f9081526002602081905260409091209081015460ff1615610b575760405162461bcd60e51b815260206004820152602260248201527f4d75737420766f746520666f7220756e72657369676e65642076616c6964617460448201526137b960f11b60648201526084016102df565b335f90815260066020526040902080546001600160a01b03848116911603610bc15760405162461bcd60e51b815260206004820181905260248201527f416c726561647920766f74656420666f7220746869732076616c696461746f7260448201526064016102df565b80546001600160a01b031615610bdb57610bd9611994565b505b604080516080810182526001600160a01b03808616825233803160208085019182525f85870181815260608701828152948252600690925295909520935184549084166001600160a01b03199182161785559051600185015593516002840180549184169186169190911790555160039092018054928216929093169190911790915560085416610c8d5760088054336001600160a01b03199182168117909255600980549091169091179055610ce4565b600980546001600160a01b039081165f9081526006602052604080822060030180546001600160a01b0319908116339081179092558554828552929093206002018054841692909416919091179092558254161790555b60078054905f610cf3836128b8565b9190505550336001600160a01b031631826001015f828254610d15919061286f565b9091555050815460019083905f90610d2e90849061286f565b9091555050604080513381526001600160a01b03851660208201527fce0c7a2a940807f7dc2ce7a615c2532e915e6c0ac9a08bc4ed9d515a710a53e2910160405180910390a1505050565b600d546060905f906001600160401b03811115610d9857610d98612895565b604051908082528060200260200182016040528015610dd157816020015b610dbe612361565b815260200190600190039081610db65790505b5090505f5b600d54811015610f10575f600d8281548110610df457610df4612847565b5f9182526020808320909101546001600160a01b03168083526002808352604093849020845180860186528381528551608081018752825481526001830154818701529282015460ff1615159583019590955260038101805493965090949384019285916060840191610e66906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054610e92906127e0565b8015610edd5780601f10610eb457610100808354040283529160200191610edd565b820191905f5260205f20905b815481529060010190602001808311610ec057829003601f168201915b505050505081525050815250848481518110610efb57610efb612847565b60209081029190910101525050600101610dd6565b50919050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610f5e5760405162461bcd60e51b81526004016102df90612812565b610f66611c5e565b610f6e611dad565b600a80546001600160a01b03191690556001545f80549091610fa09160ff8516918491610f9b9190612882565b611dfe565b90508060ff165f03610fb0575050565b5f5b60055481101561125b575f60058281548110610fd057610fd0612847565b5f9182526020808320909101546001600160a01b03168083526002918290526040909220908101549192509060ff161561100b575050611253565b600a546001600160a01b03166110465750600a80546001600160a01b0319166001600160a01b03929092169190911790556001600c55611253565b8360ff16600c5410156110645761105d8285611e8f565b5050611253565b600a546001600160a01b039081165f908152600260208181526040928390208351808501855294871685528351608081018552865481526001870154818401529286015460ff161515938301939093526003850180549394611240949093928401929187916060840191906110d8906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054611104906127e0565b801561114f5780601f106111265761010080835404028352916020019161114f565b820191905f5260205f20905b81548152906001019060200180831161113257829003601f168201915b505050919092525050509052604080518082018252600a546001600160a01b031681528151608081018352855481526001860154602082810191909152600287015460ff161515938201939093526003860180549293840192879160608401916111b8906127e0565b80601f01602080910402602001604051908101604052809291908181526020018280546111e4906127e0565b801561122f5780601f106112065761010080835404028352916020019161122f565b820191905f5260205f20905b81548152906001019060200180831161121257829003601f168201915b50505050508152505081525061226e565b1561124f5761124f8386611e8f565b5050505b600101610fb2565b50600e80546001810182555f918252600a547fbb7b4a454dc3493923482f07822329ed19e8244eff582cc204f8554c3620c3fd909101916001600160a01b03909116906112aa90600d906123aa565b8260ff166001600160401b038111156112c5576112c5612895565b6040519080825280602002602001820160405280156112ee578160200160208202803683370190505b50805161130391600d916020909101906123c5565b505f5b8360ff168110156113b35781600d828154811061132557611325612847565b5f918252602080832090910180546001600160a01b039485166001600160a01b03199182161790915560408051808201825296851680885280855260028085528286206001908101548a87019081528b548083018d558c89528789209b519302909a01805492891692909516919091178455975192880192909255908352600b909152902054169101611306565b505050505b50565b335f9081526003602052604090205460ff166114195760405162461bcd60e51b815260206004820152601960248201527f43616c6c6572206973206e6f7420612076616c696461746f720000000000000060448201526064016102df565b335f9081526002602081905260409091209081015460ff161561147e5760405162461bcd60e51b815260206004820152601d60248201527f56616c696461746f7220697320616c72656164792072657369676e656400000060448201526064016102df565b60028101805460ff19166001908117909155805481905f906114a190839061286f565b90915550506040513381527f24250fc1ec78a1405ddd4cc8b75964858af228d05faa8d4bc1302966d8a541179060200160405180910390a150565b6060336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146115265760405162461bcd60e51b81526004016102df90612812565b5f611534835f600754611dfe565b6001600160401b0381111561154b5761154b612895565b60405190808252806020026020018201604052801561158f57816020015b604080518082019091525f80825260208201528152602001906001900390816115695790505b506008549091506001600160a01b03908116908516156115c857506001600160a01b038085165f90815260066020526040902060030154165b5f5b6001600160a01b038216158015906115e157508481105b15611656576001600160a01b038083165f818152600660209081526040918290208251808401909352928252825490931692810192909252908483611625816128b8565b94508151811061163757611637612847565b6020908102919091010152600301546001600160a01b031691506115ca565b8083510361166957829350505050610683565b5f816001600160401b0381111561168257611682612895565b6040519080825280602002602001820160405280156116c657816020015b604080518082019091525f80825260208201528152602001906001900390816116a05790505b5090505f5b82811015611712578481815181106116e5576116e5612847565b60200260200101518282815181106116ff576116ff612847565b60209081029190910101526001016116cb565b509695505050505050565b6005546060905f906001600160401b0381111561173c5761173c612895565b60405190808252806020026020018201604052801561177557816020015b611762612361565b81526020019060019003908161175a5790505b5090505f5b600554811015610f10575f6005828154811061179857611798612847565b5f9182526020808320909101546001600160a01b03168083526002808352604093849020845180860186528381528551608081018752825481526001830154818701529282015460ff161515958301959095526003810180549396509094938401928591606084019161180a906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054611836906127e0565b80156118815780601f1061185857610100808354040283529160200191611881565b820191905f5260205f20905b81548152906001019060200180831161186457829003601f168201915b50505050508152505081525084848151811061189f5761189f612847565b6020908102919091010152505060010161177a565b6001600160a01b038082165f90815260066020526040902080549091166118d9575050565b60018101546001600160a01b0383163181101561193a57611904816001600160a01b03851631612882565b82546001600160a01b03165f908152600260205260408120600101805490919061192f90849061286f565b9091555061197f9050565b61194e6001600160a01b0384163182612882565b82546001600160a01b03165f9081526002602052604081206001018054909190611979908490612882565b90915550505b506001600160a01b0390911631600190910155565b335f90815260066020526040812080546001600160a01b0316611a075760405162461bcd60e51b815260206004820152602560248201527f4d75737420766f746520666f722076616c696461746f72206265666f726520756044820152646e766f746560d81b60648201526084016102df565b6009546008546001600160a01b03918216911603611a4057600880546001600160a01b0319908116909155600980549091169055611b53565b600954336001600160a01b0390911603611a99576002810180546001600160a01b039081165f90815260066020526040902060030180546001600160a01b03199081169091559154600980549093169116179055611b53565b600854336001600160a01b0390911603611afd576009546001600160a01b039081165f9081526006602052604080822060020180546001600160a01b0319908116909155600880548086168552929093206003015491169216919091179055611b53565b60038181018054600280850180546001600160a01b039081165f9081526006602052604080822090970180549583166001600160a01b031996871617905591549454811682529490200180549290931691161790555b80546001600160a01b03165f818152600260205260408120600180850154908201805492939192909190611b88908490612882565b9091555050805460019082905f90611ba1908490612882565b9091555050335f90815260066020526040812080546001600160a01b0319908116825560018201839055600282018054821690556003909101805490911690556007805491611bef83612b71565b9091555091949350505050565b60308114611c5a5760405162461bcd60e51b815260206004820152602560248201527f424c5331322d333831207075626c69634b6579206c656e67746820697320696e6044820152641d985b1a5960da1b60648201526084016102df565b5050565b6005545f611c6d600183612882565b90505b8015611c5a575f611c8282600161286f565b60408051426020820152449181019190915260608101849052608001604051602081830303815290604052805190602001205f1c611cc09190612b86565b90505f60058381548110611cd657611cd6612847565b5f91825260209091200154600580546001600160a01b0390921692509083908110611d0357611d03612847565b5f91825260209091200154600580546001600160a01b039092169185908110611d2e57611d2e612847565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b031602179055508060058381548110611d6d57611d6d612847565b905f5260205f20015f6101000a8154816001600160a01b0302191690836001600160a01b0316021790555050508080611da590612b71565b915050611c70565b600a546001600160a01b03165b6001600160a01b03811615611df7576001600160a01b039081165f908152600b6020526040902080546001600160a01b0319811690915516611dba565b505f600c55565b5f81831115611e675760405162461bcd60e51b815260206004820152602f60248201527f4d696e696d756d2073686f756c64206265206c657373207468616e206f72206560448201526e7175616c20746f206d6178696d756d60881b60648201526084016102df565b82841015611e76575081611e88565b81841115611e85575080611e88565b50825b9392505050565b6001600160a01b0382165f9081526002602081815260408084208151608081018352815481526001820154938101939093529283015460ff16151590820152600382018054919291606084019190611ee6906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054611f12906127e0565b8015611f5d5780601f10611f3457610100808354040283529160200191611f5d565b820191905f5260205f20905b815481529060010190602001808311611f4057829003601f168201915b505050919092525050604080518082018252600a546001600160a01b03168082525f90815260026020818152918490208451608081018652815481526001820154818501529181015460ff1615159482019490945260038401805496975061207e9693955091850193909290916060840191611fd8906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054612004906127e0565b801561204f5780601f106120265761010080835404028352916020019161204f565b820191905f5260205f20905b81548152906001019060200180831161203257829003601f168201915b5050505050815250508152506040518060400160405280866001600160a01b031681526020018481525061226e565b156120915761208c836122b2565b61220f565b600a546001600160a01b039081165f818152600b6020526040902054909116905b6001600160a01b0382166120cf576120ca8186612303565b61220c565b6040805180820182526001600160a01b0384168082525f90815260026020818152918490208451608081018652815481526001820154818501529181015460ff161515948201949094526003840180546121dc9593850193916060840191612136906127e0565b80601f0160208091040260200160405190810160405280929190818152602001828054612162906127e0565b80156121ad5780601f10612184576101008083540402835291602001916121ad565b820191905f5260205f20905b81548152906001019060200180831161219057829003601f168201915b5050505050815250508152506040518060400160405280886001600160a01b031681526020018681525061226e565b156121eb576120ca8186612303565b506001600160a01b038082165f908152600b602052604090205416906120b2565b50505b8160ff16600c54111561046257600a80546001600160a01b039081165f908152600b6020526040812080546001600160a01b03198082169092558454931692168217909255600c8054919261226383612b71565b919050555050505050565b5f8160200151602001518360200151602001510361229e5750805182516001600160a01b03918216911611610683565b506020908101518101519181015101511190565b600a80546001600160a01b038381165f818152600b602052604081208054939094166001600160a01b031993841617909355835490911617909155600c8054916122fb836128b8565b919050555050565b6001600160a01b038281165f818152600b602052604080822080548686168085529284208054919096166001600160a01b03199182161790955592825282549093169092179055600c805491612358836128b8565b91905055505050565b60405180604001604052805f6001600160a01b031681526020016123a560405180608001604052805f81526020015f81526020015f15158152602001606081525090565b905290565b5080545f8255905f5260205f20908101906113b89190612428565b828054828255905f5260205f20908101928215612418579160200282015b8281111561241857825182546001600160a01b0319166001600160a01b039091161782556020909201916001909101906123e3565b50612424929150612428565b5090565b5b80821115612424575f8155600101612429565b80356001600160a01b0381168114612452575f5ffd5b919050565b5f60208284031215612467575f5ffd5b611e888261243c565b60018060a01b0381511682525f602082015160406020850152805160408501526020810151606085015260408101511515608085015260608101519050608060a085015280518060c08601525f5b818110156124db57602081840181015160e08884010152016124be565b505f60e0828701015260e0601f19601f8301168601019250505092915050565b602081525f611e886020830184612470565b5f5f6020838503121561251e575f5ffd5b82356001600160401b03811115612533575f5ffd5b8301601f81018513612543575f5ffd5b80356001600160401b03811115612558575f5ffd5b8560208260051b840101111561256c575f5ffd5b6020919091019590945092505050565b5f5f6040838503121561258d575f5ffd5b50508035926020909101359150565b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561264957868503603f19018452815180518652602090810151604082880181905281519088018190529101905f9060608801905b8083101561263157835180516001600160a01b031683526020908101518184015290930192600192909201916040909101906125fa565b509650505060209384019391909101906001016125c2565b50929695505050505050565b5f5f60208385031215612666575f5ffd5b82356001600160401b0381111561267b575f5ffd5b8301601f8101851361268b575f5ffd5b80356001600160401b038111156126a0575f5ffd5b85602082840101111561256c575f5ffd5b5f602082840312156126c1575f5ffd5b81356001600160401b038111156126d6575f5ffd5b820160408185031215611e88575f5ffd5b5f602082016020835280845180835260408501915060408160051b8601019250602086015f5b8281101561264957603f19878603018452612729858351612470565b9450602093840193919091019060010161270d565b5f6020828403121561274e575f5ffd5b813560ff81168114611e88575f5ffd5b5f5f6040838503121561276f575f5ffd5b6127788361243c565b946020939093013593505050565b602080825282518282018190525f918401906040840190835b818110156127d557835180516001600160a01b03908116855260209182015116818501529093019260409092019160010161279f565b509095945050505050565b600181811c908216806127f457607f821691505b602082108103610f1057634e487b7160e01b5f52602260045260245ffd5b6020808252818101527f43616c6c6572206973206e6f742074686520636f6e7472616374206f776e6572604082015260600190565b634e487b7160e01b5f52603260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b808201808211156106835761068361285b565b818103818111156106835761068361285b565b634e487b7160e01b5f52604160045260245ffd5b818382375f9101908152919050565b5f600182016128c9576128c961285b565b5060010190565b601f82111561046257805f5260205f20601f840160051c810160208510156128f55750805b601f840160051c820191505b818110156113b3575f8155600101612901565b81516001600160401b0381111561292d5761292d612895565b6129418161293b84546127e0565b846128d0565b6020601f821160018114612973575f831561295c5750848201515b5f19600385901b1c1916600184901b1784556113b3565b5f84815260208120601f198516915b828110156129a25787850151825560209485019460019092019101612982565b50848210156129bf57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b6001600160a01b03841681526040602082018190528101829052818360608301375f818301606090810191909152601f909201601f1916010192915050565b5f8235607e19833603018112612a21575f5ffd5b9190910192915050565b6001600160401b03831115612a4257612a42612895565b612a5683612a5083546127e0565b836128d0565b5f601f841160018114612a87575f8515612a705750838201355b5f19600387901b1c1916600186901b1783556113b3565b5f83815260208120601f198716915b82811015612ab65786850135825560209485019460019092019101612a96565b5086821015612ad2575f1960f88860031b161c19848701351681555b505060018560011b0183555050505050565b8135815560208201356001820155600281016040830135801515808214612b09575f5ffd5b60ff19835416915060ff8116821783555050506060820135601e19833603018112612b32575f5ffd5b820180356001600160401b03811115612b49575f5ffd5b602082019150803603821315612b5d575f5ffd5b612b6b818360038601612a2b565b50505050565b5f81612b7f57612b7f61285b565b505f190190565b5f82612ba057634e487b7160e01b5f52601260045260245ffd5b50069056fea264697066735822122071b882f5aa13e15b9bba503811bfdf20f770de1ab04c653b0dfdade41b9c0bed64736f6c634300081b0033", + "sourceMap": "1600:14234:23:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8206:113;8288:24;;8206:113;;;160:25:32;;;148:2;133:18;8206:113:23;;;;;;;;8325:118;8405:24;:31;8325:118;;10111:244;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;10595:91::-;10667:12;;10595:91;;13764:228;;;;;;:::i;:::-;;:::i;:::-;;12617:79;;;:::i;14098:529::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;8449:951::-;;;;;;:::i;:::-;;:::i;10361:228::-;;;;;;:::i;:::-;;:::i;11543:1068::-;;;;;;:::i;:::-;;:::i;13998:94::-;14071:7;:14;13998:94;;6999:458;;;:::i;:::-;;;;;;;:::i;3531:1701::-;;;;;;:::i;:::-;;:::i;9406:400::-;;;:::i;9812:125::-;;;;;;:::i;:::-;-1:-1:-1;;;;;9901:29:23;9878:4;9901:29;;;:23;:29;;;;;;;;;9812:125;;;;6494:14:32;;6487:22;6469:41;;6457:2;6442:18;9812:125:23;6329:187:32;10692:845:23;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;8083:117::-;8141:7;8167:26;8083:117;;7628:449;;;:::i;10111:244::-;10169:16;;:::i;:::-;-1:-1:-1;;;;;9901:29:23;;9878:4;9901:29;;;:23;:29;;;;;;;;10197:71;;;;-1:-1:-1;;;10197:71:23;;7839:2:32;10197:71:23;;;7821:21:32;7878:2;7858:18;;;7851:30;7917;7897:18;;;7890:58;7965:18;;10197:71:23;;;;;;;;;10285:63;;;;;;;;-1:-1:-1;;;;;10285:63:23;;;;;-1:-1:-1;10315:31:23;;;:24;10285:63;10315:31;;;;;;;10285:63;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10315:31;10285:63;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;10285:63:23;;;;-1:-1:-1;;;10285:63:23;;10278:70;10111:244;-1:-1:-1;;10111:244:23:o;13764:228::-;2501:10;-1:-1:-1;;;;;2515:6:23;2501:20;;2493:65;;;;-1:-1:-1;;;2493:65:23;;;;;;;:::i;:::-;13899:9:::1;13894:92;13914:17:::0;;::::1;13894:92;;;13952:23;13965:6;;13972:1;13965:9;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;13952:12;:23::i;:::-;13933:3;;13894:92;;;;13764:228:::0;;:::o;12617:79::-;12659:30;12667:10;12679:9;:7;:9::i;:::-;12659:30;;;-1:-1:-1;;;;;9064:32:32;;;9046:51;;9133:32;;;;9128:2;9113:18;;9106:60;9019:18;12659:30:23;;;;;;;12617:79::o;14098:529::-;14179:14;2501:10;-1:-1:-1;;;;;2515:6:23;2501:20;;2493:65;;;;-1:-1:-1;;;2493:65:23;;;;;;;:::i;:::-;14250:7:::1;:14:::0;14221:5;;14240:24;::::1;14236:163;;-1:-1:-1::0;14288:1:23::1;14236:163;;;14327:7;:14:::0;14310::::1;14319:5:::0;14310:6;:14:::1;:::i;:::-;:31;14306:93;;;14365:7;:14:::0;:23:::1;::::0;14382:6;;14365:23:::1;:::i;:::-;14357:31;;14306:93;14409:21;14445:5;-1:-1:-1::0;;;;;14433:18:23::1;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1::0;;;;;;;;;;;;;;;;;14433:18:23::1;;;;;;;;;;;;;;;-1:-1:-1::0;14409:42:23;-1:-1:-1;14466:9:23::1;14461:136;14485:5;14481:1;:9;14461:136;;;14523:63;;;;;;;;14546:1;14537:6;:10;;;;:::i;:::-;:14;::::0;14550:1:::1;14537:14;:::i;:::-;14523:63:::0;;::::1;;14565:7;14573:10;14582:1:::0;14573:6;:10:::1;:::i;:::-;14565:19;;;;;;;;:::i;:::-;;;;;;;;14523:63;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;;;::::1;::::0;;;;::::1;::::0;;;;::::1;::::0;;;::::1;::::0;::::1;::::0;;::::1;::::0;;-1:-1:-1;;;;;14523:63:23::1;::::0;;;;;::::1;::::0;;;::::1;::::0;;;;;;::::1;::::0;::::1;;;;;;;;;;::::0;14511:6:::1;14518:1;14511:9;;;;;;;;:::i;:::-;;::::0;;::::1;::::0;;;;;:75;14492:3:::1;;14461:136;;;-1:-1:-1::0;14614:6:23;-1:-1:-1;;2568:1:23::1;14098:529:::0;;;;:::o;8449:951::-;-1:-1:-1;;;;;2638:6:23;2624:20;:10;:20;2616:61;;;;-1:-1:-1;;;2616:61:23;;9906:2:32;2616:61:23;;;9888:21:32;9945:2;9925:18;;;9918:30;9984;9964:18;;;9957:58;10032:18;;2616:61:23;9704:352:32;2616:61:23;8578:10:::1;8554:35;::::0;;;:23:::1;:35;::::0;;;;;::::1;;8553:36;8545:80;;;::::0;-1:-1:-1;;;8545:80:23;;10263:2:32;8545:80:23::1;::::0;::::1;10245:21:32::0;10302:2;10282:18;;;10275:30;10341:33;10321:18;;;10314:61;10392:18;;8545:80:23::1;10061:355:32::0;8545:80:23::1;8636:27;8676:20;;8666:31;;;;;;;:::i;:::-;;::::0;;;;;::::1;::::0;;;8717:42:::1;::::0;;;:21:::1;:42;::::0;;;;;;8666:31;;-1:-1:-1;8717:42:23::1;;8716:43;8708:91;;;::::0;-1:-1:-1;;;8708:91:23;;10899:2:32;8708:91:23::1;::::0;::::1;10881:21:32::0;10938:2;10918:18;;;10911:30;10977:34;10957:18;;;10950:62;-1:-1:-1;;;11028:18:32;;;11021:33;11071:19;;8708:91:23::1;10697:399:32::0;8708:91:23::1;8810:46;8835:20;;8810:24;:46::i;:::-;8867:30;8900:168;;;;;;;;8941:1;8900:168;;;;8969:1;8900:168;;;;8996:5;8900:168;;;;;;9037:20;;8900:168;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;::::0;;;-1:-1:-1;8900:168:23;;;;-1:-1:-1;;9079:28:23;;8867:201;;-1:-1:-1;8900:168:23;-1:-1:-1;8900:168:23;9079:28:::1;::::0;::::1;:::i;:::-;::::0;;;-1:-1:-1;;9141:10:23::1;9117:35;::::0;;;:23:::1;:35;::::0;;;;;;;:42;;9155:4:::1;-1:-1:-1::0;;9117:42:23;;::::1;::::0;::::1;::::0;;;9169:24:::1;:36:::0;;;;;;;:48;;;;;;::::1;::::0;;;::::1;::::0;;;;;;::::1;::::0;;;::::1;::::0;;;;::::1;::::0;::::1;;::::0;;;::::1;::::0;;;::::1;::::0;::::1;::::0;;;:36;:48;::::1;::::0;::::1;::::0;;::::1;:::i;:::-;-1:-1:-1::0;;;9227:42:23::1;::::0;;;:21:::1;:42;::::0;;;;;:49;;-1:-1:-1;;9227:49:23::1;9272:4;9227:49:::0;;::::1;::::0;;;9286:21:::1;:38:::0;;;;::::1;::::0;;;;;;;;::::1;::::0;;-1:-1:-1;;;;;;9286:38:23::1;9313:10;9286:38:::0;;::::1;::::0;;;9340:53;;::::1;::::0;::::1;::::0;9372:20;;;;9340:53:::1;:::i;:::-;;;;;;;;8535:865;;8449:951:::0;;:::o;10361:228::-;10442:38;10464:15;;;;:10;:15;:::i;10442:38::-;10434:79;;;;-1:-1:-1;;;10434:79:23;;7839:2:32;10434:79:23;;;7821:21:32;7878:2;7858:18;;;7851:30;7917;7897:18;;;7890:58;7965:18;;10434:79:23;7637:352:32;10434:79:23;10567:15;;;;:10;:15;:::i;:::-;10523:24;:41;10548:15;;;;:10;:15;:::i;:::-;-1:-1:-1;;;;;10523:41:23;;;;;;;;;;;;-1:-1:-1;10523:41:23;:59;;:41;:59;:::i;11543:1068::-;-1:-1:-1;;;;;2638:6:23;2624:20;:10;:20;2616:61;;;;-1:-1:-1;;;2616:61:23;;9906:2:32;2616:61:23;;;9888:21:32;9945:2;9925:18;;;9918:30;9984;9964:18;;;9957:58;10032:18;;2616:61:23;9704:352:32;2616:61:23;-1:-1:-1;;;;;9901:29:23;;9878:4;9901:29;;;:23;:29;;;;;;;;11603:63:::1;;;::::0;-1:-1:-1;;;11603:63:23;;16739:2:32;11603:63:23::1;::::0;::::1;16721:21:32::0;16778:2;16758:18;;;16751:30;16817:25;16797:18;;;16790:53;16860:18;;11603:63:23::1;16537:347:32::0;11603:63:23::1;-1:-1:-1::0;;;;;11715:30:23;::::1;11677:35;11715:30:::0;;;:24:::1;:30;::::0;;;;;;;11764:24;;::::1;::::0;::::1;;11763:25;11755:72;;;::::0;-1:-1:-1;;;11755:72:23;;17091:2:32;11755:72:23::1;::::0;::::1;17073:21:32::0;17130:2;17110:18;;;17103:30;17169:34;17149:18;;;17142:62;-1:-1:-1;;;17220:18:32;;;17213:32;17262:19;;11755:72:23::1;16889:398:32::0;11755:72:23::1;11867:10;11838:18;11859:19:::0;;;:7:::1;:19;::::0;;;;11896:15;;-1:-1:-1;;;;;11896:23:23;;::::1;:15:::0;::::1;:23:::0;11888:68:::1;;;::::0;-1:-1:-1;;;11888:68:23;;17494:2:32;11888:68:23::1;::::0;::::1;17476:21:32::0;;;17513:18;;;17506:30;17572:34;17552:18;;;17545:62;17624:18;;11888:68:23::1;17292:356:32::0;11888:68:23::1;11971:15:::0;;-1:-1:-1;;;;;11971:15:23::1;:29:::0;11967:69:::1;;12016:9;:7;:9::i;:::-;;11967:69;12068:88;::::0;;::::1;::::0;::::1;::::0;;-1:-1:-1;;;;;12068:88:23;;::::1;::::0;;12100:10:::1;:18:::0;::::1;12068:88;::::0;;::::1;::::0;;;-1:-1:-1;12068:88:23;;;;;;;;;;;;12046:19;;;:7:::1;:19:::0;;;;;;;:110;;;;;;::::1;-1:-1:-1::0;;;;;;12046:110:23;;::::1;;::::0;;;;;;::::1;::::0;;;::::1;::::0;::::1;::::0;;;;::::1;::::0;;::::1;::::0;;;::::1;::::0;;;::::1;::::0;;::::1;::::0;;;;::::1;::::0;;;::::1;::::0;;;::::1;::::0;;;12171:11:::1;::::0;::::1;12167:277;;12212:11;:24:::0;;12226:10:::1;-1:-1:-1::0;;;;;;12212:24:23;;::::1;::::0;::::1;::::0;;;12250:11:::1;:24:::0;;;;::::1;::::0;;::::1;::::0;;12167:277:::1;;;12313:11;::::0;;-1:-1:-1;;;;;12313:11:23;;::::1;12305:20;::::0;;;:7:::1;:20;::::0;;;;;:25:::1;;:38:::0;;-1:-1:-1;;;;;;12305:38:23;;::::1;12333:10;12305:38:::0;;::::1;::::0;;;12384:11;;12357:19;;;;;;;:24:::1;;:38:::0;;;::::1;12384:11:::0;;;::::1;12357:38:::0;;;::::1;::::0;;;12409:24;;::::1;;::::0;;12167:277:::1;12453:12;:14:::0;;;:12:::1;:14;::::0;::::1;:::i;:::-;;;;;;12507:10;-1:-1:-1::0;;;;;12507:18:23::1;;12478:13;:25;;;:47;;;;;;;:::i;:::-;::::0;;;-1:-1:-1;;12535:30:23;;12564:1:::1;::::0;12535:13;;:25:::1;::::0;:30:::1;::::0;12564:1;;12535:30:::1;:::i;:::-;::::0;;;-1:-1:-1;;12581:23:23::1;::::0;;12587:10:::1;9046:51:32::0;;-1:-1:-1;;;;;9133:32:32;;9128:2;9113:18;;9106:60;12581:23:23::1;::::0;9019:18:32;12581:23:23::1;;;;;;;11593:1018;;11543:1068:::0;:::o;6999:458::-;7122:24;:31;7048:18;;7078:25;;-1:-1:-1;;;;;7106:48:23;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;-1:-1:-1;7078:76:23;-1:-1:-1;7169:9:23;7164:263;7188:24;:31;7184:35;;7164:263;;;7240:12;7255:24;7280:1;7255:27;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;7255:27:23;7325:30;;;:24;:30;;;;;;;;7381:35;;;;;;;;;;;;;;;;;;;;;7255:27;7381:35;;;;;;;;;;;;;;;;;;;;;;;;;;;7255:27;;-1:-1:-1;7325:30:23;;7381:35;;;;7325:30;;7381:35;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7369:6;7376:1;7369:9;;;;;;;;:::i;:::-;;;;;;;;;;:47;-1:-1:-1;;7221:3:23;;7164:263;;;-1:-1:-1;7444:6:23;6999:458;-1:-1:-1;6999:458:23:o;3531:1701::-;2501:10;-1:-1:-1;;;;;2515:6:23;2501:20;;2493:65;;;;-1:-1:-1;;;2493:65:23;;;;;;;:::i;:::-;3601:9:::1;:7;:9::i;:::-;3620:21;:19;:21::i;:::-;3652:18;:31:::0;;-1:-1:-1;;;;;;3652:31:23::1;::::0;;;3754:24;3681:1:::1;3725:26:::0;;3681:1;;3712:67:::1;::::0;::::1;::::0;::::1;::::0;3681:1;;3725:53:::1;::::0;3754:24;3725:53:::1;:::i;:::-;3712:6;:67::i;:::-;3694:86;;3853:3;:8;;3860:1;3853:8:::0;3849:45:::1;;3877:7;3531:1701:::0;:::o;3849:45::-:1;3909:9;3904:870;3928:21;:28:::0;3924:32;::::1;3904:870;;;3977:12;3992:21;4014:1;3992:24;;;;;;;;:::i;:::-;;::::0;;;::::1;::::0;;;;;::::1;::::0;-1:-1:-1;;;;;3992:24:23::1;4060:30:::0;;;:24:::1;:30:::0;;;;;;;;4108:15;;::::1;::::0;3992:24;;-1:-1:-1;4060:30:23;4108:15:::1;;4104:62;;;4143:8;;;;4104:62;4184:18;::::0;-1:-1:-1;;;;;4184:18:23::1;4180:163;;-1:-1:-1::0;4236:18:23::1;:25:::0;;-1:-1:-1;;;;;;4236:25:23::1;-1:-1:-1::0;;;;;4236:25:23;;;::::1;::::0;;;::::1;::::0;;-1:-1:-1;4279:19:23::1;:23:::0;4320:8:::1;;4180:163;4383:3;4361:25;;:19;;:25;4357:119;;;4406:29;4425:4;4431:3;4406:18;:29::i;:::-;4453:8;;;;4357:119;4548:18;::::0;-1:-1:-1;;;;;4548:18:23;;::::1;4490:30;4523:44:::0;;;:24:::1;:44;::::0;;;;;;;;4597:35;;;;::::1;::::0;;;;::::1;::::0;;;;::::1;::::0;::::1;::::0;;;;;;4548:18;4597:35;::::1;::::0;;;::::1;::::0;;;::::1;::::0;::::1;;;;::::0;;;;;;;::::1;::::0;::::1;::::0;;4523:44;;4586:102:::1;::::0;4597:35;;;;::::1;::::0;;4626:4;;4597:35;;;;;::::1;::::0;::::1;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1::0;;;4597:35:23;;;;-1:-1:-1;;;4597:35:23;;4634:53:::1;::::0;;;;::::1;::::0;;4651:18:::1;::::0;-1:-1:-1;;;;;4651:18:23::1;4634:53:::0;;;;::::1;::::0;::::1;::::0;;;;;;4651:18;4634:53;::::1;::::0;::::1;::::0;;::::1;::::0;;;;::::1;::::0;::::1;::::0;::::1;;;;::::0;;;;;;;::::1;::::0;::::1;::::0;;;;;::::1;::::0;4677:8;;4634:53;;;;::::1;::::0;::::1;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;::::1;;;::::0;4586:10:::1;:102::i;:::-;4582:182;;;4720:29;4739:4;4745:3;4720:18;:29::i;:::-;3963:811;;;3904:870;3958:3;;3904:870;;;-1:-1:-1::0;4817:7:23::1;:14:::0;;::::1;::::0;::::1;::::0;;4784:30:::1;4817:14:::0;;;4857:18:::1;::::0;4817:14;;;::::1;::::0;-1:-1:-1;;;;;4857:18:23;;::::1;::::0;4885:31:::1;::::0;4892:24:::1;::::0;4885:31:::1;:::i;:::-;4967:3;4953:18;;-1:-1:-1::0;;;;;4953:18:23::1;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;-1:-1:-1;4953:18:23::1;-1:-1:-1::0;4926:45:23;;::::1;::::0;:24:::1;::::0;:45:::1;::::0;;::::1;::::0;::::1;:::i;:::-;-1:-1:-1::0;4986:9:23::1;4981:245;5005:3;5001:7;;:1;:7;4981:245;;;5059:4;5029:24;5054:1;5029:27;;;;;;;;:::i;:::-;;::::0;;;::::1;::::0;;;;;::::1;:34:::0;;-1:-1:-1;;;;;5029:34:23;;::::1;-1:-1:-1::0;;;;;;5029:34:23;;::::1;;::::0;;;5088:85:::1;::::0;;;;::::1;::::0;;;;::::1;::::0;;;5129:30;;;:24:::1;:30:::0;;;;;;5029:34;5129:42;;::::1;::::0;5088:85;;::::1;::::0;;;5077:97;;;;::::1;::::0;;;;;;;;;;;::::1;::::0;;::::1;::::0;;;;::::1;::::0;;;::::1;::::0;;;::::1;::::0;;;;;;::::1;::::0;;;;5195:20;;;:14:::1;:20:::0;;;;;;::::1;::::0;5010:3:::1;4981:245;;;;3591:1641;;;2568:1;3531:1701:::0;:::o;9406:400::-;9482:10;9878:4;9901:29;;;:23;:29;;;;;;;;9452:71;;;;-1:-1:-1;;;9452:71:23;;17855:2:32;9452:71:23;;;17837:21:32;17894:2;17874:18;;;17867:30;17933:27;17913:18;;;17906:55;17978:18;;9452:71:23;17653:349:32;9452:71:23;9593:10;9534:31;9568:36;;;:24;:36;;;;;;;;9623:20;;;;;;9622:21;9614:63;;;;-1:-1:-1;;;9614:63:23;;18209:2:32;9614:63:23;;;18191:21:32;18248:2;18228:18;;;18221:30;18287:31;18267:18;;;18260:59;18336:18;;9614:63:23;18007:353:32;9614:63:23;9688:20;;;:27;;-1:-1:-1;;9688:27:23;9711:4;9688:27;;;;;;9725:29;;9711:4;;9688:20;;9725:29;;9711:4;;9725:29;:::i;:::-;;;;-1:-1:-1;;9770:29:23;;9788:10;18511:51:32;;9770:29:23;;18499:2:32;18484:18;9770:29:23;;;;;;;9442:364;9406:400::o;10692:845::-;10770:19;2501:10;-1:-1:-1;;;;;2515:6:23;2501:20;;2493:65;;;;-1:-1:-1;;;2493:65:23;;;;;;;:::i;:::-;10801:26:::1;10847:30;10854:5;10861:1;10864:12;;10847:6;:30::i;:::-;-1:-1:-1::0;;;;;10830:48:23::1;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1::0;;;;;;;;;;;;;;;;;10830:48:23::1;;;;;;;;;;;;;;;-1:-1:-1::0;10904:11:23::1;::::0;10801:77;;-1:-1:-1;;;;;;10904:11:23;;::::1;::::0;10930:18;::::1;::::0;10926:74:::1;;-1:-1:-1::0;;;;;;10971:13:23;;::::1;;::::0;;;:7:::1;:13;::::0;;;;:18:::1;;::::0;::::1;10926:74;11010:9;11033:211;-1:-1:-1::0;;;;;11040:18:23;::::1;::::0;;::::1;::::0;:31:::1;;;11066:5;11062:1;:9;11040:31;11033:211;;;-1:-1:-1::0;;;;;11108:13:23;;::::1;11087:18;11108:13:::0;;;:7:::1;:13;::::0;;;;;;;;11149:53;;;;::::1;::::0;;;;;;11185:15;;;;::::1;11149:53:::0;;::::1;::::0;;;;11108:13;11135:6;11142:3;::::1;::::0;::::1;:::i;:::-;;;11135:11;;;;;;;;:::i;:::-;;::::0;;::::1;::::0;;;;;:67;11223:10:::1;;::::0;-1:-1:-1;;;;;11223:10:23::1;::::0;-1:-1:-1;11033:211:23::1;;;11275:1;11258:6;:13;:18:::0;11254:62:::1;;11299:6;11292:13;;;;;;;11254:62;11374:25;11419:1;-1:-1:-1::0;;;;;11402:19:23::1;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1::0;;;;;;;;;;;;;;;;;11402:19:23::1;;;;;;;;;;;;;;;-1:-1:-1::0;11374:47:23;-1:-1:-1;11436:9:23::1;11431:77;11455:1;11451;:5;11431:77;;;11488:6;11495:1;11488:9;;;;;;;;:::i;:::-;;;;;;;11477:5;11483:1;11477:8;;;;;;;;:::i;:::-;;::::0;;::::1;::::0;;;;;:20;11458:3:::1;;11431:77;;;-1:-1:-1::0;11525:5:23;10692:845;-1:-1:-1;;;;;;10692:845:23:o;7628:449::-;7751:21;:28;7677:18;;7707:25;;-1:-1:-1;;;;;7735:45:23;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;-1:-1:-1;7707:73:23;-1:-1:-1;7795:9:23;7790:257;7814:21;:28;7810:32;;7790:257;;;7863:12;7878:21;7900:1;7878:24;;;;;;;;:::i;:::-;;;;;;;;;;;;;-1:-1:-1;;;;;7878:24:23;7945:30;;;:24;:30;;;;;;;;8001:35;;;;;;;;;;;;;;;;;;;;;7878:24;8001:35;;;;;;;;;;;;;;;;;;;;;;;;;;;7878:24;;-1:-1:-1;7945:30:23;;8001:35;;;;7945:30;;8001:35;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;7989:6;7996:1;7989:9;;;;;;;;:::i;:::-;;;;;;;;;;:47;-1:-1:-1;;7844:3:23;;7790:257;;14633:523;-1:-1:-1;;;;;14708:13:23;;;14687:18;14708:13;;;:7;:13;;;;;14735:15;;14708:13;;14735:15;14731:66;;14780:7;14633:523;:::o;14731:66::-;14830:13;;;;-1:-1:-1;;;;;14873:12:23;;;14858:27;;14854:257;;;14958:27;14973:12;-1:-1:-1;;;;;14958:12:23;;;:27;:::i;:::-;14926:15;;-1:-1:-1;;;;;14926:15:23;14901:41;;;;:24;:41;;;;;14926:15;14901:53;:84;;:53;;:41;:84;;;;;:::i;:::-;;;;-1:-1:-1;14854:257:23;;-1:-1:-1;14854:257:23;;15073:27;-1:-1:-1;;;;;15088:12:23;;;15073;:27;:::i;:::-;15041:15;;-1:-1:-1;;;;;15041:15:23;15016:41;;;;:24;:41;;;;;15041:15;15016:53;:84;;:53;;:41;:84;;;;;:::i;:::-;;;;-1:-1:-1;;14854:257:23;-1:-1:-1;;;;;;15137:12:23;;;;15121:13;;;;:28;14633:523::o;12702:1056::-;12787:10;12739:7;12779:19;;;:7;:19;;;;;12816:15;;-1:-1:-1;;;;;12816:15:23;12808:79;;;;-1:-1:-1;;;12808:79:23;;18775:2:32;12808:79:23;;;18757:21:32;18814:2;18794:18;;;18787:30;18853:34;18833:18;;;18826:62;-1:-1:-1;;;18904:18:32;;;18897:35;18949:19;;12808:79:23;18573:401:32;12808:79:23;12917:11;;12902;;-1:-1:-1;;;;;12917:11:23;;;12902;;:26;12898:528;;12944:11;:24;;-1:-1:-1;;;;;;12944:24:23;;;;;;12982:11;:24;;;;;;;12898:528;;;13027:11;;13042:10;-1:-1:-1;;;;;13027:11:23;;;:25;13023:403;;13076:10;;;;;-1:-1:-1;;;;;13076:10:23;;;13103:1;13068:19;;;:7;:19;;;;;:24;;:37;;-1:-1:-1;;;;;;13068:37:23;;;;;;13133:10;;13119:11;:24;;;;;13133:10;;13119:24;;;13023:403;;;13164:11;;13179:10;-1:-1:-1;;;;;13164:11:23;;;:25;13160:266;;13213:11;;-1:-1:-1;;;;;13213:11:23;;;13241:1;13205:20;;;:7;:20;;;;;;:25;;:38;;-1:-1:-1;;;;;;13205:38:23;;;;;;13279:11;;;;;;13271:20;;;;;;:25;;;13257:39;;13271:25;;13257:39;;;;;;13160:266;;;13354:10;;;;;;13335;;;;;;-1:-1:-1;;;;;13335:10:23;;;13354;13327:19;;;:7;:19;;;;;;:24;;;:37;;13354:10;;;-1:-1:-1;;;;;;13327:37:23;;;;;;13405:10;;13386;;;;13378:19;;;;;:24;:37;;13405:10;;;;13378:37;;;;;13160:266;13460:15;;-1:-1:-1;;;;;13460:15:23;13436:21;13524:41;;;:24;:41;;;;;13460:15;13605:13;;;;13576:25;;;:42;;13524:41;;13605:13;;13576:25;;13436:21;13576:42;;13605:13;;13576:42;:::i;:::-;;;;-1:-1:-1;;13628:30:23;;13657:1;;13628:13;;:25;;:30;;13657:1;;13628:30;:::i;:::-;;;;-1:-1:-1;;13684:10:23;13676:19;;;;:7;:19;;;;;13669:26;;-1:-1:-1;;;;;;13669:26:23;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13706:12;:14;;;;;;:::i;:::-;;;;-1:-1:-1;13738:13:23;;12702:1056;-1:-1:-1;;;;12702:1056:23:o;9943:162::-;10054:2;10034:22;;10026:72;;;;-1:-1:-1;;;10026:72:23;;19322:2:32;10026:72:23;;;19304:21:32;19361:2;19341:18;;;19334:30;19400:34;19380:18;;;19373:62;-1:-1:-1;;;19451:18:32;;;19444:35;19496:19;;10026:72:23;19120:401:32;10026:72:23;9943:162;;:::o;2701:523::-;2751:21;:28;2739:9;2806:5;2810:1;2751:28;2806:5;:::i;:::-;2794:17;;2789:429;2813:5;;2789:429;;2901:9;2991:5;:1;2995;2991:5;:::i;:::-;2931:54;;;2948:15;2931:54;;;19711:19:32;2965:16:23;19746:12:32;;;19739:28;;;;19783:12;;;19776:28;;;19820:12;;2931:54:23;;;;;;;;;;;;2921:65;;;;;;2913:74;;:84;;;;:::i;:::-;2901:96;;3058:12;3073:21;3095:1;3073:24;;;;;;;;:::i;:::-;;;;;;;;;;;3138:21;:24;;-1:-1:-1;;;;;3073:24:23;;;;-1:-1:-1;3138:21:23;3160:1;;3138:24;;;;;;:::i;:::-;;;;;;;;;;;3111:21;:24;;-1:-1:-1;;;;;3138:24:23;;;;3133:1;;3111:24;;;;;;:::i;:::-;;;;;;;;;:51;;;;;-1:-1:-1;;;;;3111:51:23;;;;;-1:-1:-1;;;;;3111:51:23;;;;;;3203:4;3176:21;3198:1;3176:24;;;;;;;;:::i;:::-;;;;;;;;;:31;;;;;-1:-1:-1;;;;;3176:31:23;;;;;-1:-1:-1;;;;;3176:31:23;;;;;;2825:393;;2820:3;;;;;:::i;:::-;;;;2789:429;;3230:295;3295:18;;-1:-1:-1;;;;;3295:18:23;3324:162;-1:-1:-1;;;;;3331:18:23;;;3324:162;;-1:-1:-1;;;;;3408:23:23;;;3365:15;3408:23;;;:14;:23;;;;;;;-1:-1:-1;;;;;;3445:30:23;;;;;3408:23;3324:162;;;-1:-1:-1;3517:1:23;3495:19;:23;3230:295::o;15495:337::-;15574:7;15608:3;15601;:10;;15593:70;;;;-1:-1:-1;;;15593:70:23;;20259:2:32;15593:70:23;;;20241:21:32;20298:2;20278:18;;;20271:30;20337:34;20317:18;;;20310:62;-1:-1:-1;;;20388:18:32;;;20381:45;20443:19;;15593:70:23;20057:411:32;15593:70:23;15685:3;15677:5;:11;15673:153;;;-1:-1:-1;15711:3:23;15704:10;;15673:153;15743:3;15735:5;:11;15731:95;;;-1:-1:-1;15769:3:23;15762:10;;15731:95;-1:-1:-1;15810:5:23;15731:95;15495:337;;;;;:::o;5238:1390::-;-1:-1:-1;;;;;5338:30:23;;5310:25;5338:30;;;:24;:30;;;;;;;;5310:58;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5338:30;5310:58;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;5310:58:23;;;;-1:-1:-1;;5424:89:23;;;;;;;;5441:18;;-1:-1:-1;;;;;5441:18:23;5424:89;;;-1:-1:-1;5467:44:23;;;:24;5424:89;5467:44;;;;;;;5424:89;;;;;;;;;;;5441:18;5424:89;;;;;;;;;;;;;;;;;;;;;;;;;;;5310:58;;-1:-1:-1;5396:184:23;;5424:89;;-1:-1:-1;5424:89:23;;;;;;5467:44;;5424:89;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5531:35;;;;;;;;5548:4;-1:-1:-1;;;;;5531:35:23;;;;;5560:4;5531:35;;;5396:10;:184::i;:::-;5379:999;;;5605:16;5616:4;5605:10;:16::i;:::-;5379:999;;;5685:18;;-1:-1:-1;;;;;5685:18:23;;;5652:15;5670:34;;;:14;:34;;;;;;;;;;5770:598;-1:-1:-1;;;;;5805:21:23;;5801:122;;5850:27;5862:8;5872:4;5850:11;:27::i;:::-;5899:5;;5801:122;6002:67;;;;;;;;-1:-1:-1;;;;;6002:67:23;;;;;-1:-1:-1;6034:33:23;;;:24;6002:67;6034:33;;;;;;;6002:67;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5966:186;;6002:67;;;;6034:33;6002:67;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6095:35;;;;;;;;6112:4;-1:-1:-1;;;;;6095:35:23;;;;;6124:4;6095:35;;;5966:10;:186::i;:::-;5941:325;;;6193:27;6205:8;6215:4;6193:11;:27::i;5941:325::-;-1:-1:-1;;;;;;6330:23:23;;;;;;;:14;:23;;;;;;;;5770:598;;;5638:740;;5379:999;6414:3;6392:25;;:19;;:25;6388:234;;;6463:18;;;-1:-1:-1;;;;;6463:18:23;;;6433:12;6448:34;;;:14;:34;;;;;;;-1:-1:-1;;;;;;6496:41:23;;;;;;6551:25;;6448:34;;6551:25;;;;;;;6590:19;:21;;6448:34;;6590:21;;;:::i;:::-;;;;;;6419:203;5300:1328;5238:1390;;:::o;15162:327::-;15263:4;15314:10;:15;;;:27;;;15283:10;:15;;;:27;;;:58;15279:129;;-1:-1:-1;15382:15:23;;15364;;-1:-1:-1;;;;;15364:33:23;;;;;;15357:40;;15279:129;-1:-1:-1;15455:15:23;;;;;:27;;;15425:15;;;;:27;;:57;;15162:327::o;6634:167::-;6710:18;;;-1:-1:-1;;;;;6687:20:23;;;6710:18;6687:20;;;:14;:20;;;;;:41;;6710:18;;;;-1:-1:-1;;;;;;6687:41:23;;;;;;;6738:25;;;;;;;;;6773:19;:21;;;;;;:::i;:::-;;;;;;6634:167;:::o;6807:186::-;-1:-1:-1;;;;;6898:20:23;;;;;;;:14;:20;;;;;;;;6875;;;;;;;;;:43;;6898:20;;;;-1:-1:-1;;;;;;6875:43:23;;;;;;;6928:20;;;:27;;;;;;;;;;6965:19;:21;;;;;;:::i;:::-;;;;;;6807:186;;:::o;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;196:173:32;264:20;;-1:-1:-1;;;;;313:31:32;;303:42;;293:70;;359:1;356;349:12;293:70;196:173;;;:::o;374:186::-;433:6;486:2;474:9;465:7;461:23;457:32;454:52;;;502:1;499;492:12;454:52;525:29;544:9;525:29;:::i;565:871::-;682:1;678;673:3;669:11;665:19;657:5;651:12;647:38;642:3;635:51;617:3;732:4;725:5;721:16;715:23;770:4;763;758:3;754:14;747:28;813:12;807:19;800:4;795:3;791:14;784:43;881:4;867:12;863:23;857:30;852:2;847:3;843:12;836:52;958:4;944:12;940:23;934:30;927:38;920:46;913:4;908:3;904:14;897:70;1022:2;1008:12;1004:21;998:28;976:50;;1057:4;1051:3;1046;1042:13;1035:27;1091:14;1085:21;1137:6;1131:3;1126;1122:13;1115:29;1162:1;1172:147;1186:6;1183:1;1180:13;1172:147;;;1302:4;1278:22;;;1274:33;;1268:40;1262:3;1249:11;;;1245:21;1238:71;1201:12;1172:147;;;1176:3;1363:1;1357:3;1348:6;1343:3;1339:16;1335:26;1328:37;1426:3;1419:2;1415:7;1410:2;1402:6;1398:15;1394:29;1389:3;1385:39;1381:49;1374:56;;;;565:871;;;;:::o;1441:266::-;1626:2;1615:9;1608:21;1589:4;1646:55;1697:2;1686:9;1682:18;1674:6;1646:55;:::i;1712:610::-;1798:6;1806;1859:2;1847:9;1838:7;1834:23;1830:32;1827:52;;;1875:1;1872;1865:12;1827:52;1915:9;1902:23;-1:-1:-1;;;;;1940:6:32;1937:30;1934:50;;;1980:1;1977;1970:12;1934:50;2003:22;;2056:4;2048:13;;2044:27;-1:-1:-1;2034:55:32;;2085:1;2082;2075:12;2034:55;2125:2;2112:16;-1:-1:-1;;;;;2143:6:32;2140:30;2137:50;;;2183:1;2180;2173:12;2137:50;2236:7;2231:2;2221:6;2218:1;2214:14;2210:2;2206:23;2202:32;2199:45;2196:65;;;2257:1;2254;2247:12;2196:65;2288:2;2280:11;;;;;2310:6;;-1:-1:-1;1712:610:32;-1:-1:-1;;;1712:610:32:o;2327:346::-;2395:6;2403;2456:2;2444:9;2435:7;2431:23;2427:32;2424:52;;;2472:1;2469;2462:12;2424:52;-1:-1:-1;;2517:23:32;;;2637:2;2622:18;;;2609:32;;-1:-1:-1;2327:346:32:o;2678:1554::-;2868:4;2916:2;2905:9;2901:18;2946:2;2935:9;2928:21;2969:6;3004;2998:13;3035:6;3027;3020:22;3073:2;3062:9;3058:18;3051:25;;3135:2;3125:6;3122:1;3118:14;3107:9;3103:30;3099:39;3085:53;;3173:2;3165:6;3161:15;3194:1;3204:999;3218:6;3215:1;3212:13;3204:999;;;3283:22;;;-1:-1:-1;;3279:36:32;3267:49;;3339:13;;3422:9;;3407:25;;3479:2;3471:11;;;3465:18;3391:2;3503:15;;;3496:27;;;3584:19;;3379:15;;;3616:24;;;3706:21;;;-1:-1:-1;;3674:2:32;3662:15;;;3765:330;3781:8;3776:3;3773:17;3765:330;;;3854:15;;3904:9;;-1:-1:-1;;;;;3900:35:32;3886:50;;3990:2;3982:11;;;3976:18;3960:14;;;3953:42;4064:17;;;;3932:1;3800:11;;;;;4032:2;4021:14;;;;3765:330;;;-1:-1:-1;4118:5:32;-1:-1:-1;;;4158:2:32;4181:12;;;;4146:15;;;;;3240:1;3233:9;3204:999;;;-1:-1:-1;4220:6:32;;2678:1554;-1:-1:-1;;;;;;2678:1554:32:o;4237:586::-;4307:6;4315;4368:2;4356:9;4347:7;4343:23;4339:32;4336:52;;;4384:1;4381;4374:12;4336:52;4424:9;4411:23;-1:-1:-1;;;;;4449:6:32;4446:30;4443:50;;;4489:1;4486;4479:12;4443:50;4512:22;;4565:4;4557:13;;4553:27;-1:-1:-1;4543:55:32;;4594:1;4591;4584:12;4543:55;4634:2;4621:16;-1:-1:-1;;;;;4652:6:32;4649:30;4646:50;;;4692:1;4689;4682:12;4646:50;4737:7;4732:2;4723:6;4719:2;4715:15;4711:24;4708:37;4705:57;;;4758:1;4755;4748:12;4828:389;4917:6;4970:2;4958:9;4949:7;4945:23;4941:32;4938:52;;;4986:1;4983;4976:12;4938:52;5026:9;5013:23;-1:-1:-1;;;;;5051:6:32;5048:30;5045:50;;;5091:1;5088;5081:12;5045:50;5114:22;;5170:2;5152:16;;;5148:25;5145:45;;;5186:1;5183;5176:12;5222:828;5420:4;5468:2;5457:9;5453:18;5498:2;5487:9;5480:21;5521:6;5556;5550:13;5587:6;5579;5572:22;5625:2;5614:9;5610:18;5603:25;;5687:2;5677:6;5674:1;5670:14;5659:9;5655:30;5651:39;5637:53;;5725:2;5717:6;5713:15;5746:1;5756:265;5770:6;5767:1;5764:13;5756:265;;;5863:2;5859:7;5847:9;5839:6;5835:22;5831:36;5826:3;5819:49;5891:50;5934:6;5925;5919:13;5891:50;:::i;:::-;5881:60;-1:-1:-1;5976:2:32;5999:12;;;;5964:15;;;;;5792:1;5785:9;5756:265;;6055:269;6112:6;6165:2;6153:9;6144:7;6140:23;6136:32;6133:52;;;6181:1;6178;6171:12;6133:52;6220:9;6207:23;6270:4;6263:5;6259:16;6252:5;6249:27;6239:55;;6290:1;6287;6280:12;6521:300;6589:6;6597;6650:2;6638:9;6629:7;6625:23;6621:32;6618:52;;;6666:1;6663;6656:12;6618:52;6689:29;6708:9;6689:29;:::i;:::-;6679:39;6787:2;6772:18;;;;6759:32;;-1:-1:-1;;;6521:300:32:o;6826:806::-;7074:2;7086:21;;;7156:13;;7059:18;;;7178:22;;;7026:4;;7257:15;;;7231:2;7216:18;;;7026:4;7300:306;7314:6;7311:1;7308:13;7300:306;;;7373:13;;7415:9;;-1:-1:-1;;;;;7411:35:32;;;7399:48;;7499:2;7491:11;;;7485:18;7481:44;7467:12;;;7460:66;7581:15;;;;7555:2;7546:12;;;;7443:1;7329:9;7300:306;;;-1:-1:-1;7623:3:32;;6826:806;-1:-1:-1;;;;;6826:806:32:o;7994:380::-;8073:1;8069:12;;;;8116;;;8137:61;;8191:4;8183:6;8179:17;8169:27;;8137:61;8244:2;8236:6;8233:14;8213:18;8210:38;8207:161;;8290:10;8285:3;8281:20;8278:1;8271:31;8325:4;8322:1;8315:15;8353:4;8350:1;8343:15;8379:356;8581:2;8563:21;;;8600:18;;;8593:30;8659:34;8654:2;8639:18;;8632:62;8726:2;8711:18;;8379:356::o;8740:127::-;8801:10;8796:3;8792:20;8789:1;8782:31;8832:4;8829:1;8822:15;8856:4;8853:1;8846:15;9177:127;9238:10;9233:3;9229:20;9226:1;9219:31;9269:4;9266:1;9259:15;9293:4;9290:1;9283:15;9309:125;9374:9;;;9395:10;;;9392:36;;;9408:18;;:::i;9439:128::-;9506:9;;;9527:11;;;9524:37;;;9541:18;;:::i;9572:127::-;9633:10;9628:3;9624:20;9621:1;9614:31;9664:4;9661:1;9654:15;9688:4;9685:1;9678:15;10421:271;10604:6;10596;10591:3;10578:33;10560:3;10630:16;;10655:13;;;10630:16;10421:271;-1:-1:-1;10421:271:32:o;11101:135::-;11140:3;11161:17;;;11158:43;;11181:18;;:::i;:::-;-1:-1:-1;11228:1:32;11217:13;;11101:135::o;11366:517::-;11467:2;11462:3;11459:11;11456:421;;;11503:5;11500:1;11493:16;11547:4;11544:1;11534:18;11617:2;11605:10;11601:19;11598:1;11594:27;11588:4;11584:38;11653:4;11641:10;11638:20;11635:47;;;-1:-1:-1;11676:4:32;11635:47;11731:2;11726:3;11722:12;11719:1;11715:20;11709:4;11705:31;11695:41;;11786:81;11804:2;11797:5;11794:13;11786:81;;;11863:1;11849:16;;11830:1;11819:13;11786:81;;12059:1295;12183:3;12177:10;-1:-1:-1;;;;;12202:6:32;12199:30;12196:56;;;12232:18;;:::i;:::-;12261:96;12350:6;12310:38;12342:4;12336:11;12310:38;:::i;:::-;12304:4;12261:96;:::i;:::-;12406:4;12437:2;12426:14;;12454:1;12449:648;;;;13141:1;13158:6;13155:89;;;-1:-1:-1;13210:19:32;;;13204:26;13155:89;-1:-1:-1;;12016:1:32;12012:11;;;12008:24;12004:29;11994:40;12040:1;12036:11;;;11991:57;13257:81;;12419:929;;12449:648;11313:1;11306:14;;;11350:4;11337:18;;-1:-1:-1;;12485:20:32;;;12602:222;12616:7;12613:1;12610:14;12602:222;;;12698:19;;;12692:26;12677:42;;12805:4;12790:20;;;;12758:1;12746:14;;;;12632:12;12602:222;;;12606:3;12852:6;12843:7;12840:19;12837:201;;;12913:19;;;12907:26;-1:-1:-1;;12996:1:32;12992:14;;;13008:3;12988:24;12984:37;12980:42;12965:58;12950:74;;12837:201;-1:-1:-1;;;;13084:1:32;13068:14;;;13064:22;13051:36;;-1:-1:-1;12059:1295:32:o;13359:485::-;-1:-1:-1;;;;;13544:32:32;;13526:51;;13613:2;13608;13593:18;;13586:30;;;13632:18;;13625:34;;;13652:6;13701;13696:2;13681:18;;13668:48;13765:1;13736:22;;;13760:2;13732:31;;;13725:42;;;;13828:2;13807:15;;;-1:-1:-1;;13803:29:32;13788:45;13784:54;;13359:485;-1:-1:-1;;13359:485:32:o;13849:332::-;13949:4;14007:11;13994:25;14101:3;14097:8;14086;14070:14;14066:29;14062:44;14042:18;14038:69;14028:97;;14121:1;14118;14111:12;14028:97;14142:33;;;;;13849:332;-1:-1:-1;;13849:332:32:o;14186:1178::-;-1:-1:-1;;;;;14287:3:32;14284:27;14281:53;;;14314:18;;:::i;:::-;14343:93;14432:3;14392:38;14424:4;14418:11;14392:38;:::i;:::-;14386:4;14343:93;:::i;:::-;14462:1;14487:2;14482:3;14479:11;14504:1;14499:607;;;;15150:1;15167:3;15164:93;;;-1:-1:-1;15223:19:32;;;15210:33;15164:93;-1:-1:-1;;12016:1:32;12012:11;;;12008:24;12004:29;11994:40;12040:1;12036:11;;;11991:57;15270:78;;14472:886;;14499:607;11313:1;11306:14;;;11350:4;11337:18;;-1:-1:-1;;14535:17:32;;;14649:229;14663:7;14660:1;14657:14;14649:229;;;14752:19;;;14739:33;14724:49;;14859:4;14844:20;;;;14812:1;14800:14;;;;14679:12;14649:229;;;14653:3;14906;14897:7;14894:16;14891:159;;;15030:1;15026:6;15020:3;15014;15011:1;15007:11;15003:21;14999:34;14995:39;14982:9;14977:3;14973:19;14960:33;14956:79;14948:6;14941:95;14891:159;;;15093:1;15087:3;15084:1;15080:11;15076:19;15070:4;15063:33;14472:886;;14186:1178;;;:::o;15369:1163::-;15556:19;;15584:21;;15674:2;15663:14;;15650:28;15704:1;15694:12;;15687:29;15753:1;15743:12;;15803:2;15792:14;;15779:28;15833:15;;15826:23;15868:15;;;15858:43;;15897:1;15894;15887:12;15858:43;15952:3;15948:8;15935:10;15929:17;15925:32;15910:47;;16005:3;16001:2;15997:12;15988:7;15985:25;15973:10;15966:45;;;;16070:2;16063:5;16059:14;16046:28;16153:2;16149:7;16141:5;16125:14;16121:26;16117:40;16097:18;16093:65;16083:93;;16172:1;16169;16162:12;16083:93;16197:30;;16250:18;;-1:-1:-1;;;;;16280:30:32;;16277:50;;;16323:1;16320;16313:12;16277:50;16360:2;16354:4;16350:13;16336:27;;16407:6;16391:14;16387:27;16379:6;16375:40;16372:60;;;16428:1;16425;16418:12;16372:60;16441:85;16519:6;16511;16507:1;16501:4;16497:12;16441:85;:::i;:::-;;;15369:1163;;:::o;18979:136::-;19018:3;19046:5;19036:39;;19055:18;;:::i;:::-;-1:-1:-1;;;19091:18:32;;18979:136::o;19843:209::-;19875:1;19901;19891:132;;19945:10;19940:3;19936:20;19933:1;19926:31;19980:4;19977:1;19970:15;20008:4;20005:1;19998:15;19891:132;-1:-1:-1;20037:9:32;;19843:209::o", + "linkReferences": {}, + "immutableReferences": { + "39841": [ + { "start": 996, "length": 32 }, + { "start": 1219, "length": 32 }, + { "start": 1683, "length": 32 }, + { "start": 2569, "length": 32 }, + { "start": 3873, "length": 32 }, + { "start": 5353, "length": 32 } + ] + } + }, + "methodIdentifiers": { + "activeValidatorsCount()": "0d2bd909", + "calculateTopValidators(uint8)": "b5cfa68c", + "getAllValidators()": "f3513a37", + "getRounds(uint256,uint256)": "40f74f47", + "getRoundsCount()": "a09686c4", + "getTopValidators()": "afeea115", + "getValidator(address)": "1904bb2e", + "getVotes(address,uint256)": "eb9019d4", + "getVotesCount()": "1b605b86", + "isValidatorRegistered(address)": "d04a68c7", + "registerValidator(bytes)": "602a9eee", + "registeredValidatorsCount()": "f1bd0b37", + "resignValidator()": "b85f5da2", + "resignedValidatorsCount()": "0777cbef", + "unvote()": "3174b689", + "updateValidator((address,(uint256,uint256,bool,bytes)))": "62525879", + "updateVoters(address[])": "2bdf6d43", + "vote(address)": "6dd7d8ea" + }, + "rawMetadata": "{\"compiler\":{\"version\":\"0.8.27+commit.40a35a09\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"Unvoted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"bls12_381_public_key\",\"type\":\"bytes\"}],\"name\":\"ValidatorRegistered\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"ValidatorResigned\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"}],\"name\":\"Voted\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"activeValidatorsCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint8\",\"name\":\"n\",\"type\":\"uint8\"}],\"name\":\"calculateTopValidators\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAllValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"votersCount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"voteBalance\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isResigned\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"bls12_381_public_key\",\"type\":\"bytes\"}],\"internalType\":\"struct ValidatorData\",\"name\":\"data\",\"type\":\"tuple\"}],\"internalType\":\"struct Validator[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"offset\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"count\",\"type\":\"uint256\"}],\"name\":\"getRounds\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"round\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"voteBalance\",\"type\":\"uint256\"}],\"internalType\":\"struct RoundValidator[]\",\"name\":\"validators\",\"type\":\"tuple[]\"}],\"internalType\":\"struct Round[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getRoundsCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getTopValidators\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"votersCount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"voteBalance\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isResigned\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"bls12_381_public_key\",\"type\":\"bytes\"}],\"internalType\":\"struct ValidatorData\",\"name\":\"data\",\"type\":\"tuple\"}],\"internalType\":\"struct Validator[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_addr\",\"type\":\"address\"}],\"name\":\"getValidator\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"votersCount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"voteBalance\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isResigned\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"bls12_381_public_key\",\"type\":\"bytes\"}],\"internalType\":\"struct ValidatorData\",\"name\":\"data\",\"type\":\"tuple\"}],\"internalType\":\"struct Validator\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"count\",\"type\":\"uint256\"}],\"name\":\"getVotes\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"voter\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"validator\",\"type\":\"address\"}],\"internalType\":\"struct VoteResult[]\",\"name\":\"\",\"type\":\"tuple[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getVotesCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"isValidatorRegistered\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"bls12_381_public_key\",\"type\":\"bytes\"}],\"name\":\"registerValidator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"registeredValidatorsCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resignValidator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resignedValidatorsCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unvote\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"votersCount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"voteBalance\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isResigned\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"bls12_381_public_key\",\"type\":\"bytes\"}],\"internalType\":\"struct ValidatorData\",\"name\":\"data\",\"type\":\"tuple\"}],\"internalType\":\"struct Validator\",\"name\":\"_validator\",\"type\":\"tuple\"}],\"name\":\"updateValidator\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"voters\",\"type\":\"address[]\"}],\"name\":\"updateVoters\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"name\":\"vote\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/consensus/Consensus.sol\":\"Consensus\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@contracts/=src/\",\":@forge-std/=forge-std/src/\"]},\"sources\":{\"src/consensus/Consensus.sol\":{\"keccak256\":\"0x5b736870bfb3308f6f109276d30415ab555c65a1c1165afbe50c56220ed9d129\",\"urls\":[\"bzz-raw://0d9655228a0caa1a1802d479cc6bd490d8d0f36061f285e855d23e32e55f311c\",\"dweb:/ipfs/QmUfv3d2My7CuFjq7PM6jmxLoXWLrErfGkCT8s1tNKvcCZ\"]}},\"version\":1}", + "metadata": { + "compiler": { "version": "0.8.27+commit.40a35a09" }, + "language": "Solidity", + "output": { + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address", + "indexed": false + }, + { + "internalType": "address", + "name": "validator", + "type": "address", + "indexed": false + } + ], + "type": "event", + "name": "Unvoted", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address", + "indexed": false + }, + { + "internalType": "bytes", + "name": "bls12_381_public_key", + "type": "bytes", + "indexed": false + } + ], + "type": "event", + "name": "ValidatorRegistered", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address", + "indexed": false + } + ], + "type": "event", + "name": "ValidatorResigned", + "anonymous": false + }, + { + "inputs": [ + { + "internalType": "address", + "name": "voter", + "type": "address", + "indexed": false + }, + { + "internalType": "address", + "name": "validator", + "type": "address", + "indexed": false + } + ], + "type": "event", + "name": "Voted", + "anonymous": false + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "activeValidatorsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "n", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "calculateTopValidators" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "getAllValidators", + "outputs": [ + { + "internalType": "struct Validator[]", + "name": "", + "type": "tuple[]", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "struct ValidatorData", + "name": "data", + "type": "tuple", + "components": [ + { + "internalType": "uint256", + "name": "votersCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "voteBalance", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isResigned", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "bls12_381_public_key", + "type": "bytes" + } + ] + } + ] + } + ] + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "offset", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getRounds", + "outputs": [ + { + "internalType": "struct Round[]", + "name": "", + "type": "tuple[]", + "components": [ + { + "internalType": "uint256", + "name": "round", + "type": "uint256" + }, + { + "internalType": "struct RoundValidator[]", + "name": "validators", + "type": "tuple[]", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "voteBalance", + "type": "uint256" + } + ] + } + ] + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "getRoundsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "getTopValidators", + "outputs": [ + { + "internalType": "struct Validator[]", + "name": "", + "type": "tuple[]", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "struct ValidatorData", + "name": "data", + "type": "tuple", + "components": [ + { + "internalType": "uint256", + "name": "votersCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "voteBalance", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isResigned", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "bls12_381_public_key", + "type": "bytes" + } + ] + } + ] + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_addr", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getValidator", + "outputs": [ + { + "internalType": "struct Validator", + "name": "", + "type": "tuple", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "struct ValidatorData", + "name": "data", + "type": "tuple", + "components": [ + { + "internalType": "uint256", + "name": "votersCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "voteBalance", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isResigned", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "bls12_381_public_key", + "type": "bytes" + } + ] + } + ] + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "count", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function", + "name": "getVotes", + "outputs": [ + { + "internalType": "struct VoteResult[]", + "name": "", + "type": "tuple[]", + "components": [ + { + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "internalType": "address", + "name": "validator", + "type": "address" + } + ] + } + ] + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "getVotesCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function", + "name": "isValidatorRegistered", + "outputs": [ + { "internalType": "bool", "name": "", "type": "bool" } + ] + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "bls12_381_public_key", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "registerValidator" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "registeredValidatorsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "resignValidator" + }, + { + "inputs": [], + "stateMutability": "view", + "type": "function", + "name": "resignedValidatorsCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ] + }, + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "function", + "name": "unvote" + }, + { + "inputs": [ + { + "internalType": "struct Validator", + "name": "_validator", + "type": "tuple", + "components": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "struct ValidatorData", + "name": "data", + "type": "tuple", + "components": [ + { + "internalType": "uint256", + "name": "votersCount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "voteBalance", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isResigned", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "bls12_381_public_key", + "type": "bytes" + } + ] + } + ] + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "updateValidator" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "voters", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "updateVoters" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function", + "name": "vote" + } + ], + "devdoc": { "kind": "dev", "methods": {}, "version": 1 }, + "userdoc": { "kind": "user", "methods": {}, "version": 1 } + }, + "settings": { + "remappings": ["@contracts/=src/", "@forge-std/=forge-std/src/"], + "optimizer": { "enabled": true, "runs": 200 }, + "metadata": { "bytecodeHash": "ipfs" }, + "compilationTarget": { "src/consensus/Consensus.sol": "Consensus" }, + "evmVersion": "shanghai", + "libraries": {} + }, + "sources": { + "src/consensus/Consensus.sol": { + "keccak256": "0x5b736870bfb3308f6f109276d30415ab555c65a1c1165afbe50c56220ed9d129", + "urls": [ + "bzz-raw://0d9655228a0caa1a1802d479cc6bd490d8d0f36061f285e855d23e32e55f311c", + "dweb:/ipfs/QmUfv3d2My7CuFjq7PM6jmxLoXWLrErfGkCT8s1tNKvcCZ" + ], + "license": null + } + }, + "version": 1 + }, + "id": 23 +} diff --git a/crypto/utils/abi_base.py b/crypto/utils/abi_base.py new file mode 100644 index 00000000..1641b38a --- /dev/null +++ b/crypto/utils/abi_base.py @@ -0,0 +1,53 @@ +# crypto/utils/abi_base.py + +import json +import os +import re +from binascii import unhexlify +from Cryptodome.Hash import keccak +from crypto.identity.address import get_checksum_address + + +class AbiBase: + def __init__(self): + # Cargar el ABI desde un archivo JSON + abi_file_path = os.path.join(os.path.dirname(__file__), 'Abi.Consensus.json') + with open(abi_file_path, 'r') as f: + abi_json = json.load(f) + self.abi = abi_json.get('abi', []) + + def get_array_components(self, type_str): + match = re.match(r'^(.*)\[(\d*)\]$', type_str) + if match: + inner_type = match.group(1) + length_str = match.group(2) + length = int(length_str) if length_str != '' else None + return length, inner_type + return None + + def strip_hex_prefix(self, hex_str): + if hex_str.startswith('0x') or hex_str.startswith('0X'): + return hex_str[2:] + return hex_str + + def is_valid_address(self, address): + # Compute the checksum address and compare + computed_checksum_address = get_checksum_address(address.lower()) + return address == computed_checksum_address + + def keccak256(self, input_str): + k = keccak.new(digest_bits=256) + k.update(input_str.encode('utf-8')) + return '0x' + k.hexdigest() + + def get_function_signature(self, abi_item): + name = abi_item['name'] + inputs = abi_item.get('inputs', []) + types = [input_item['type'] for input_item in inputs] + return f"{name}({','.join(types)})" + + def to_function_selector(self, abi_item): + signature = self.get_function_signature(abi_item) + hash_ = self.keccak256(signature) + selector = '0x' + self.strip_hex_prefix(hash_)[0:8] + return selector diff --git a/crypto/utils/abi_decoder.py b/crypto/utils/abi_decoder.py new file mode 100644 index 00000000..71082071 --- /dev/null +++ b/crypto/utils/abi_decoder.py @@ -0,0 +1,145 @@ +# crypto/utils/abi_decoder.py + +from crypto.utils.abi_base import AbiBase +import binascii +import re +from crypto.identity.address import get_checksum_address + + +class AbiDecoder(AbiBase): + def decode_function_data(self, data): + data = self.strip_hex_prefix(data) + function_selector = data[:8] + abi_item = self.find_function_by_selector(function_selector) + if not abi_item: + raise Exception('Function selector not found in ABI: ' + function_selector) + encoded_params = data[8:] + decoded_params = self.decode_abi_parameters(abi_item['inputs'], encoded_params) + return { + 'functionName': abi_item['name'], + 'args': decoded_params, + } + + def find_function_by_selector(self, selector): + for item in self.abi: + if item['type'] == 'function': + function_signature = self.get_function_signature(item) + function_selector = self.strip_hex_prefix(self.keccak256(function_signature))[0:8] + if function_selector == selector: + return item + return None + + def decode_abi_parameters(self, params, data): + if not data and len(params) > 0: + raise Exception('No data to decode') + bytes_data = binascii.unhexlify(data) + cursor = 0 + values = [] + for param in params: + value, consumed = self.decode_parameter(bytes_data, cursor, param) + cursor += consumed + values.append(value) + return values + + def decode_parameter(self, bytes_data, offset, param): + type_ = param['type'] + array_components = self.get_array_components(type_) + if array_components: + length, base_type = array_components + param['type'] = base_type + return self.decode_array(bytes_data, offset, param, length) + if type_ == 'address': + return self.decode_address(bytes_data, offset) + if type_ == 'bool': + return self.decode_bool(bytes_data, offset) + if type_ == 'string': + return self.decode_string(bytes_data, offset) + if type_ == 'bytes': + return self.decode_dynamic_bytes(bytes_data, offset) + match = re.match(r'^bytes(\d+)$', type_) + if match: + size = int(match.group(1)) + return self.decode_fixed_bytes(bytes_data, offset, size) + match = re.match(r'^(u?int)(\d+)$', type_) + if match: + signed = match.group(1) == 'int' + bits = int(match.group(2)) + return self.decode_number(bytes_data, offset, bits, signed) + if type_ == 'tuple': + return self.decode_tuple(bytes_data, offset, param) + raise Exception('Unsupported type: ' + type_) + + def decode_address(self, bytes_data, offset): + data = bytes_data[offset:offset+32] + address_bytes = data[12:32] + address = '0x' + address_bytes.hex() + address = get_checksum_address(address) + return address, 32 + + def decode_bool(self, bytes_data, offset): + data = bytes_data[offset:offset+32] + value = int.from_bytes(data, byteorder='big') != 0 + return value, 32 + + def decode_number(self, bytes_data, offset, bits, signed): + data = bytes_data[offset:offset+32] + value = int.from_bytes(data, byteorder='big', signed=signed) + return value, 32 + + def decode_string(self, bytes_data, offset): + data_offset = self.read_uint(bytes_data, offset) + string_offset = offset + data_offset + length = self.read_uint(bytes_data, string_offset) + string_data = bytes_data[string_offset+32:string_offset+32+length] + value = string_data.decode('utf-8') + return value, 32 + + def decode_dynamic_bytes(self, bytes_data, offset): + data_offset = self.read_uint(bytes_data, offset) + bytes_offset = offset + data_offset + length = self.read_uint(bytes_data, bytes_offset) + bytes_data_value = bytes_data[bytes_offset+32:bytes_offset+32+length] + value = '0x' + bytes_data_value.hex() + return value, 32 + + def decode_fixed_bytes(self, bytes_data, offset, size): + data = bytes_data[offset:offset+32] + value = '0x' + data[:size].hex() + return value, 32 + + def decode_array(self, bytes_data, offset, param, length): + base_type = param['type'] + element_type = param.copy() + element_type['type'] = base_type + + if length is None: + data_offset = self.read_uint(bytes_data, offset) + array_offset = offset + data_offset + array_length = self.read_uint(bytes_data, array_offset) + cursor = array_offset + 32 + else: + array_length = length + cursor = offset + + values = [] + for i in range(array_length): + value, consumed = self.decode_parameter(bytes_data, cursor, element_type) + cursor += consumed + values.append(value) + return values, 32 + + def decode_tuple(self, bytes_data, offset, param): + components = param['components'] + values = {} + cursor = offset + + for component in components: + value, consumed = self.decode_parameter(bytes_data, cursor, component) + cursor += consumed + name = component.get('name', '') + values[name] = value + return values, 32 + + def read_uint(self, bytes_data, offset): + data = bytes_data[offset:offset+32] + return int.from_bytes(data, byteorder='big') diff --git a/crypto/utils/abi_encoder.py b/crypto/utils/abi_encoder.py new file mode 100644 index 00000000..69dfdc21 --- /dev/null +++ b/crypto/utils/abi_encoder.py @@ -0,0 +1,259 @@ +# crypto/utils/abi_encoder.py + +from crypto.utils.abi_base import AbiBase +import binascii + + +class AbiEncoder(AbiBase): + def encode_function_call(self, function_name, args=[]): + parameters = { + 'abi': self.abi, + 'functionName': function_name, + 'args': args, + } + return self.encode_function_data(parameters) + + def encode_function_data(self, parameters): + args = parameters.get('args', []) + + if (len(parameters['abi']) == 1 and + 'functionName' in parameters and + parameters['functionName'].startswith('0x')): + abi_item = parameters['abi'][0] + function_name = parameters['functionName'] + else: + abi_item, function_name = self.prepare_encode_function_data(parameters) + + signature = function_name + + if abi_item.get('inputs'): + data = self.encode_abi_parameters(abi_item['inputs'], args) + else: + data = None + + return self.concat_hex([signature, data or '0x']) + + def prepare_encode_function_data(self, params): + abi = params['abi'] + function_name = params.get('functionName') + + if not function_name: + functions = [item for item in abi if item['type'] == 'function'] + if len(functions) == 1: + abi_item = functions[0] + function_name = abi_item['name'] + else: + raise Exception('Function name is not provided and ABI has multiple functions') + else: + abi_item = self.get_abi_item(abi, function_name, params.get('args', [])) + if not abi_item: + raise Exception('Function not found in ABI: ' + function_name) + + signature = self.to_function_selector(abi_item) + return abi_item, signature + + def get_abi_item(self, abi, name, args): + matching_items = [item for item in abi if item['type'] == 'function' and item['name'] == name] + if not matching_items: + raise Exception(f"Function not found in ABI: {name}") + + for item in matching_items: + inputs = item.get('inputs', []) + if len(inputs) == len(args): + return item + + raise Exception(f"Function with matching arguments not found in ABI: {name}") + + def encode_abi_parameters(self, params, values): + if len(params) != len(values): + raise Exception('Length of parameters and values do not match') + prepared_params = self.prepare_params(params, values) + data = self.encode_params(prepared_params) + return data if data != '' else '0x' + + def prepare_params(self, params, values): + prepared_params = [] + for index, param in enumerate(params): + prepared_param = self.prepare_param(param, values[index]) + prepared_params.append(prepared_param) + return prepared_params + + def prepare_param(self, param, value): + array_components = self.get_array_components(param['type']) + if array_components: + length, type_ = array_components + return self.encode_array(value, length, {'name': param['name'], 'type': type_}) + if param['type'] == 'tuple': + return self.encode_tuple(value, param) + if param['type'] == 'address': + return self.encode_address(value) + if param['type'] == 'bool': + return self.encode_bool(value) + if param['type'].startswith('uint') or param['type'].startswith('int'): + signed = param['type'].startswith('int') + return self.encode_number(value, signed) + if param['type'].startswith('bytes'): + return self.encode_bytes(value, param) + if param['type'] == 'string': + return self.encode_string(value) + raise Exception('Invalid ABI type: ' + param['type']) + + def encode_array(self, value, length, param): + dynamic = length is None + + if not isinstance(value, list): + raise Exception('Invalid array value') + if not dynamic and len(value) != length: + raise Exception('Array length mismatch') + + dynamic_child = False + prepared_params = [] + for v in value: + prepared_param = self.prepare_param(param, v) + if prepared_param['dynamic']: + dynamic_child = True + prepared_params.append(prepared_param) + + if dynamic or dynamic_child: + data = self.encode_params(prepared_params) + if dynamic: + length_hex = '{:064x}'.format(len(prepared_params)) + return { + 'dynamic': True, + 'encoded': '0x' + length_hex + data[2:], + } + if dynamic_child: + return { + 'dynamic': True, + 'encoded': data, + } + encoded = ''.join([p['encoded'][2:] for p in prepared_params]) + return { + 'dynamic': False, + 'encoded': '0x' + encoded, + } + + def encode_params(self, prepared_params): + static_size = 0 + for param in prepared_params: + if param['dynamic']: + static_size += 32 + else: + static_size += (len(param['encoded']) - 2) // 2 + + static_params = [] + dynamic_params = [] + dynamic_size = 0 + for param in prepared_params: + if param['dynamic']: + offset = '{:064x}'.format(static_size + dynamic_size) + static_params.append(offset) + dynamic_params.append(param['encoded'][2:]) + dynamic_size += (len(param['encoded']) - 2) // 2 + else: + static_params.append(param['encoded'][2:]) + + encoded = '0x' + ''.join(static_params) + ''.join(dynamic_params) + return encoded + + def encode_address(self, value): + if not self.is_valid_address(value): + raise Exception('Invalid address: ' + value) + value = self.strip_hex_prefix(value.lower()) + return { + 'dynamic': False, + 'encoded': '0x' + value.zfill(64), + } + + def encode_bool(self, value): + encoded = '1' if value else '0' + encoded = encoded.zfill(64) + return { + 'dynamic': False, + 'encoded': '0x' + encoded, + } + + def encode_number(self, value, signed): + if not isinstance(value, int) and not isinstance(value, str): + raise Exception('Invalid number value') + if isinstance(value, str): + value = int(value) + if signed: + if value < 0: + value = (1 << 256) + value + else: + if value < 0: + raise Exception('Negative value provided for unsigned integer type') + hex_value = '{:x}'.format(value) + encoded = hex_value.zfill(64) + return { + 'dynamic': False, + 'encoded': '0x' + encoded, + } + + def encode_bytes(self, value, param): + if isinstance(value, str) and value.startswith('0x'): + value = value[2:] + bytes_size = len(value) // 2 + param_size_str = param['type'][5:] + if param_size_str == '': + length_hex = '{:064x}'.format(bytes_size) + value_padded = value + padding = (32 - (bytes_size % 32)) % 32 + if padding > 0: + value_padded += '00' * padding + return { + 'dynamic': True, + 'encoded': '0x' + length_hex + value_padded, + } + param_size = int(param_size_str) + if bytes_size != param_size: + raise Exception(f"Bytes size mismatch: expected {param_size}, got {bytes_size}") + value_padded = value.ljust(64, '0') + return { + 'dynamic': False, + 'encoded': '0x' + value_padded, + } + + def encode_string(self, value): + hex_value = binascii.hexlify(value.encode('utf-8')).decode() + length_hex = '{:064x}'.format(len(value)) + value_padded = hex_value + padding = (32 - (len(value) % 32)) % 32 + if padding > 0: + value_padded += '00' * padding + return { + 'dynamic': True, + 'encoded': '0x' + length_hex + value_padded, + } + + def encode_tuple(self, value, param): + dynamic = False + prepared_params = [] + for index, component in enumerate(param['components']): + key = index if isinstance(value, list) else component.get('name') + if key not in value: + raise Exception('Tuple value missing component: ' + component.get('name', '')) + prepared_param = self.prepare_param(component, value[key]) + if prepared_param['dynamic']: + dynamic = True + prepared_params.append(prepared_param) + if dynamic: + encoded = self.encode_params(prepared_params) + return { + 'dynamic': True, + 'encoded': encoded, + } + encoded = '0x' + ''.join([p['encoded'][2:] for p in prepared_params]) + return { + 'dynamic': False, + 'encoded': encoded, + } + + def concat_hex(self, hexes): + result = '0x' + for hex_str in hexes: + if not hex_str or hex_str == '0x': + continue + result += self.strip_hex_prefix(hex_str) + return result diff --git a/tests/utils/test_abi_decoder.py b/tests/utils/test_abi_decoder.py new file mode 100644 index 00000000..e5b59cc2 --- /dev/null +++ b/tests/utils/test_abi_decoder.py @@ -0,0 +1,15 @@ +from crypto.utils.abi_decoder import AbiDecoder + + +def test_decode_vote_payload(): + decoder = AbiDecoder() + function_name = 'vote' + args = ['0x512F366D524157BcF734546eB29a6d687B762255'] + data = '0x6dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255' + + decoded_data = decoder.decode_function_data(data) + + assert decoded_data == { + 'functionName': function_name, + 'args': args, + } diff --git a/tests/utils/test_abi_encoder.py b/tests/utils/test_abi_encoder.py new file mode 100644 index 00000000..7b68d162 --- /dev/null +++ b/tests/utils/test_abi_encoder.py @@ -0,0 +1,12 @@ +from crypto.utils.abi_encoder import AbiEncoder + + +def test_encode_vote_function_call(): + encoder = AbiEncoder() + function_name = 'vote' + args = ['0x512F366D524157BcF734546eB29a6d687B762255'] + expected_encoded_data = '0x6dd7d8ea000000000000000000000000512f366d524157bcf734546eb29a6d687b762255' + + encoded_data = encoder.encode_function_call(function_name, args) + + assert encoded_data == expected_encoded_data From a9358f4ac0d096942928c2201dea5836cfd089d4 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 13:33:17 -0600 Subject: [PATCH 06/23] vote & builder tests --- crypto/transactions/builder/vote.py | 29 ----- crypto/transactions/builder/vote_builder.py | 30 ++++++ crypto/transactions/types/vote.py | 30 ++++++ tests/transactions/builder/conftest.py | 31 ++++++ .../builder/test_transfer_builder.py | 23 +--- tests/transactions/builder/test_vote.py | 100 ------------------ .../transactions/builder/test_vote_builder.py | 27 +++++ 7 files changed, 120 insertions(+), 150 deletions(-) delete mode 100644 crypto/transactions/builder/vote.py create mode 100644 crypto/transactions/builder/vote_builder.py create mode 100644 crypto/transactions/types/vote.py delete mode 100644 tests/transactions/builder/test_vote.py create mode 100644 tests/transactions/builder/test_vote_builder.py diff --git a/crypto/transactions/builder/vote.py b/crypto/transactions/builder/vote.py deleted file mode 100644 index 4ffc92d1..00000000 --- a/crypto/transactions/builder/vote.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_VOTE -from crypto.identity.address import address_from_passphrase -from crypto.transactions.builder.base import BaseTransactionBuilder - -class Vote(BaseTransactionBuilder): - transaction_type = TRANSACTION_VOTE - - def __init__(self, votes: Optional[list[str]] = None, unvotes: Optional[list[str]] = None, fee: Optional[int] = None): - """Create a vote transaction - - Args: - vote (str, optional): address of a validator you want to vote - unvote (str, optional): address of a validator you want to unvote - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.asset['votes'] = votes if votes else [] - self.transaction.asset['unvotes'] = unvotes if unvotes else [] - - if fee: - self.transaction.fee = fee - - def sign(self, passphrase): - self.transaction.recipientId = address_from_passphrase(passphrase) - - super().sign(passphrase) diff --git a/crypto/transactions/builder/vote_builder.py b/crypto/transactions/builder/vote_builder.py new file mode 100644 index 00000000..e914210f --- /dev/null +++ b/crypto/transactions/builder/vote_builder.py @@ -0,0 +1,30 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.vote import Vote + + +class VoteBuilder(AbstractTransactionBuilder): + def vote(self, vote: str): + """ + Establece el campo 'vote' en los datos de la transacción. + + Args: + vote (str): Dirección a la que se vota. + + Returns: + self: Instancia del builder para encadenar métodos. + """ + self.transaction.data['vote'] = vote + self.transaction.refresh_payload_data() + return self + + def get_transaction_instance(self, data: dict): + """ + Crea una instancia de la transacción Vote con los datos proporcionados. + + Args: + data (dict): Datos de la transacción. + + Returns: + Vote: Instancia de la transacción Vote. + """ + return Vote(data) diff --git a/crypto/transactions/types/vote.py b/crypto/transactions/types/vote.py new file mode 100644 index 00000000..6be22813 --- /dev/null +++ b/crypto/transactions/types/vote.py @@ -0,0 +1,30 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.utils.abi_encoder import AbiEncoder + + +class Vote(AbstractTransaction): + def __init__(self, data: dict = None): + """ + Inicializa una instancia de Vote, decodificando el payload si está presente. + + Args: + data (dict, optional): Datos de la transacción. + """ + payload = self.decode_payload(data or {}) + + if payload is not None: + data['vote'] = payload['args'][0] + + super().__init__(data) + + def get_payload(self) -> str: + """ + Obtiene el payload específico para la transacción de voto. + + Returns: + str: Payload en formato hexadecimal. + """ + if 'vote' not in self.data: + return '' + encoder = AbiEncoder() + return encoder.encode_function_call('vote', [self.data['vote']]) diff --git a/tests/transactions/builder/conftest.py b/tests/transactions/builder/conftest.py index 374172f8..2db1bb9d 100644 --- a/tests/transactions/builder/conftest.py +++ b/tests/transactions/builder/conftest.py @@ -1,4 +1,35 @@ import pytest +import json +import os +from crypto.configuration.network import set_network +from crypto.networks.devnet import Devnet + +@pytest.fixture(scope='session', autouse=True) +def configure_network(): + """ + Configures the network to Devnet before running any tests. + This fixture runs automatically once per test session. + """ + set_network(Devnet) + +@pytest.fixture +def load_transaction_fixture(): + """ + Fixture to load a transaction fixture from the fixtures directory. + + Usage in tests: + def test_example(load_transaction_fixture): + fixture = load_transaction_fixture('fixture_name') + """ + def _load_transaction_fixture(fixture_name): + fixtures_path = os.path.join( + os.path.dirname(__file__), + '../../fixtures', + f'{fixture_name}.json' + ) + with open(fixtures_path, 'r') as f: + return json.load(f) + return _load_transaction_fixture @pytest.fixture def passphrase(): diff --git a/tests/transactions/builder/test_transfer_builder.py b/tests/transactions/builder/test_transfer_builder.py index 28258151..f45cef73 100644 --- a/tests/transactions/builder/test_transfer_builder.py +++ b/tests/transactions/builder/test_transfer_builder.py @@ -1,26 +1,7 @@ -import pytest -import json -import os - from crypto.transactions.builder.transfer_builder import TransferBuilder -from crypto.configuration.network import set_network -from crypto.networks.devnet import Devnet - -set_network(Devnet) - - -def get_transaction_fixture(fixture_name): - fixtures_path = os.path.join( - os.path.dirname(__file__), - '../../fixtures', - f'{fixture_name}.json' - ) - with open(fixtures_path, 'r') as f: - return json.load(f) - -def test_transfer_transaction(passphrase): - fixture = get_transaction_fixture('transfer') +def test_transfer_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('transfer') builder = ( TransferBuilder() diff --git a/tests/transactions/builder/test_vote.py b/tests/transactions/builder/test_vote.py deleted file mode 100644 index 734083f3..00000000 --- a/tests/transactions/builder/test_vote.py +++ /dev/null @@ -1,100 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_TYPE_GROUP, TRANSACTION_VOTE -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.vote import Vote - -set_network(Devnet) - - -def test_vote_transaction(passphrase): - """Test if a vote transaction gets built - """ - votes = ['034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192'] - - transaction = Vote(votes, None) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['votes'] == votes - assert transaction_dict['asset']['unvotes'] == [] - assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 100000000 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - -def test_unvote_transaction(passphrase): - """Test if a vote transaction gets built - """ - unvotes = ['02dfc9a0684fe0744101b2398e9c86a81b5e46aceffff994ef9189083f01d0bebb'] - - transaction = Vote(None, unvotes) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['votes'] == [] - assert transaction_dict['asset']['unvotes'] == unvotes - assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 100000000 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - -def test_vote_swap_transaction(passphrase): - """Test if a vote transaction gets built - """ - votes = ['034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192'] - unvotes = ['02dfc9a0684fe0744101b2398e9c86a81b5e46aceffff994ef9189083f01d0bebb'] - - transaction = Vote(votes, unvotes) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['votes'] == votes - assert transaction_dict['asset']['unvotes'] == unvotes - assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 100000000 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - -def test_vote_transaction_custom_fee(passphrase): - """Test if a vote transaction gets built with a custom fee - """ - votes = ['034151a3ec46b5670a682b0a63394f863587d1bc97483b1b6c70eb58e7f0aed192'] - - transaction = Vote(votes, None, 5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['votes'] == votes - assert transaction_dict['asset']['unvotes'] == [] - assert transaction_dict['type'] is TRANSACTION_VOTE - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_vote_builder.py b/tests/transactions/builder/test_vote_builder.py new file mode 100644 index 00000000..13e6aae7 --- /dev/null +++ b/tests/transactions/builder/test_vote_builder.py @@ -0,0 +1,27 @@ +from crypto.transactions.builder.vote_builder import VoteBuilder + +def test_vote_transaction(passphrase, load_transaction_fixture): + """ + Test para verificar que VoteBuilder puede construir, firmar y verificar una transacción de voto correctamente. + + Args: + passphrase (str): Passphrase para generar la clave privada. + """ + fixture = load_transaction_fixture('vote') + + builder = ( + VoteBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .vote('0x512F366D524157BcF734546eB29a6d687B762255') + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + + assert builder.transaction.data['id'] == fixture['data']['id'] + + assert builder.verify() From dae9daf2abfe962bd22c9ea259d007a959b8f371 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Fri, 29 Nov 2024 13:34:58 -0600 Subject: [PATCH 07/23] refactor --- tests/conftest.py | 31 ++++++++++++++++++++++++++ tests/transactions/builder/conftest.py | 31 -------------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index ace8eb38..db71646b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,37 @@ import pytest +import json +import os +from crypto.configuration.network import set_network +from crypto.networks.devnet import Devnet +@pytest.fixture(scope='session', autouse=True) +def configure_network(): + """ + Configures the network to Devnet before running any tests. + This fixture runs automatically once per test session. + """ + set_network(Devnet) + +@pytest.fixture +def load_transaction_fixture(): + """ + Fixture to load a transaction fixture from the fixtures directory. + + Usage in tests: + def test_example(load_transaction_fixture): + fixture = load_transaction_fixture('fixture_name') + """ + def _load_transaction_fixture(fixture_name): + fixtures_path = os.path.join( + os.path.dirname(__file__), + './fixtures', + f'{fixture_name}.json' + ) + with open(fixtures_path, 'r') as f: + return json.load(f) + return _load_transaction_fixture + @pytest.fixture def passphrase(): """Passphrase used for tests""" diff --git a/tests/transactions/builder/conftest.py b/tests/transactions/builder/conftest.py index 2db1bb9d..374172f8 100644 --- a/tests/transactions/builder/conftest.py +++ b/tests/transactions/builder/conftest.py @@ -1,35 +1,4 @@ import pytest -import json -import os -from crypto.configuration.network import set_network -from crypto.networks.devnet import Devnet - -@pytest.fixture(scope='session', autouse=True) -def configure_network(): - """ - Configures the network to Devnet before running any tests. - This fixture runs automatically once per test session. - """ - set_network(Devnet) - -@pytest.fixture -def load_transaction_fixture(): - """ - Fixture to load a transaction fixture from the fixtures directory. - - Usage in tests: - def test_example(load_transaction_fixture): - fixture = load_transaction_fixture('fixture_name') - """ - def _load_transaction_fixture(fixture_name): - fixtures_path = os.path.join( - os.path.dirname(__file__), - '../../fixtures', - f'{fixture_name}.json' - ) - with open(fixtures_path, 'r') as f: - return json.load(f) - return _load_transaction_fixture @pytest.fixture def passphrase(): From 4db7d44ca6569676a69e9b180978562ba1204637 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 07:58:18 -0600 Subject: [PATCH 08/23] vote builder & transactions --- crypto/transactions/builder/vote_builder.py | 23 ++------------- crypto/transactions/types/vote.py | 29 +++++++------------ .../transactions/builder/test_vote_builder.py | 12 ++------ 3 files changed, 15 insertions(+), 49 deletions(-) diff --git a/crypto/transactions/builder/vote_builder.py b/crypto/transactions/builder/vote_builder.py index e914210f..7b4dfb03 100644 --- a/crypto/transactions/builder/vote_builder.py +++ b/crypto/transactions/builder/vote_builder.py @@ -1,30 +1,11 @@ from crypto.transactions.builder.base import AbstractTransactionBuilder from crypto.transactions.types.vote import Vote - class VoteBuilder(AbstractTransactionBuilder): def vote(self, vote: str): - """ - Establece el campo 'vote' en los datos de la transacción. - - Args: - vote (str): Dirección a la que se vota. - - Returns: - self: Instancia del builder para encadenar métodos. - """ self.transaction.data['vote'] = vote self.transaction.refresh_payload_data() return self - def get_transaction_instance(self, data: dict): - """ - Crea una instancia de la transacción Vote con los datos proporcionados. - - Args: - data (dict): Datos de la transacción. - - Returns: - Vote: Instancia de la transacción Vote. - """ - return Vote(data) + def get_transaction_instance(self, data: dict) -> Vote: + return Vote(data) \ No newline at end of file diff --git a/crypto/transactions/types/vote.py b/crypto/transactions/types/vote.py index 6be22813..b3e05475 100644 --- a/crypto/transactions/types/vote.py +++ b/crypto/transactions/types/vote.py @@ -1,30 +1,23 @@ from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.utils.abi_encoder import AbiEncoder - +from crypto.utils.abi_decoder import AbiDecoder class Vote(AbstractTransaction): def __init__(self, data: dict = None): - """ - Inicializa una instancia de Vote, decodificando el payload si está presente. - - Args: - data (dict, optional): Datos de la transacción. - """ - payload = self.decode_payload(data or {}) - - if payload is not None: - data['vote'] = payload['args'][0] - super().__init__(data) + self.decode_payload(data) def get_payload(self) -> str: - """ - Obtiene el payload específico para la transacción de voto. - - Returns: - str: Payload en formato hexadecimal. - """ if 'vote' not in self.data: return '' encoder = AbiEncoder() return encoder.encode_function_call('vote', [self.data['vote']]) + + def decode_payload(self, data: dict) -> dict: + if 'data' not in data or not data['data']: + return {} + decoder = AbiDecoder() + decoded = decoder.decode_function_data(data['data']) + if decoded['functionName'] == 'vote': + self.data['vote'] = decoded['args'][0] + return self.data \ No newline at end of file diff --git a/tests/transactions/builder/test_vote_builder.py b/tests/transactions/builder/test_vote_builder.py index 13e6aae7..a8a6f8f9 100644 --- a/tests/transactions/builder/test_vote_builder.py +++ b/tests/transactions/builder/test_vote_builder.py @@ -1,12 +1,6 @@ from crypto.transactions.builder.vote_builder import VoteBuilder def test_vote_transaction(passphrase, load_transaction_fixture): - """ - Test para verificar que VoteBuilder puede construir, firmar y verificar una transacción de voto correctamente. - - Args: - passphrase (str): Passphrase para generar la clave privada. - """ fixture = load_transaction_fixture('vote') builder = ( @@ -16,12 +10,10 @@ def test_vote_transaction(passphrase, load_transaction_fixture): .network(fixture['data']['network']) .gas_limit(fixture['data']['gasLimit']) .recipient_address(fixture['data']['recipientAddress']) - .vote('0x512F366D524157BcF734546eB29a6d687B762255') + .vote('0x512F366D524157BcF734546eB29a6d687B762255') # Example vote address .sign(passphrase) ) assert builder.transaction.serialize().hex() == fixture['serialized'] - assert builder.transaction.data['id'] == fixture['data']['id'] - - assert builder.verify() + assert builder.verify() \ No newline at end of file From 4e658bdefc89548da9f241f496fd6c227a4b7e1f Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:01:18 -0600 Subject: [PATCH 09/23] add evm call builder --- crypto/transactions/builder/evm_call_builder.py | 11 +++++++++++ crypto/transactions/types/evm_call.py | 5 +++++ .../builder/test_evm_call_builder.py | 17 +++++++++++++++++ 3 files changed, 33 insertions(+) create mode 100644 crypto/transactions/builder/evm_call_builder.py create mode 100644 crypto/transactions/types/evm_call.py create mode 100644 tests/transactions/builder/test_evm_call_builder.py diff --git a/crypto/transactions/builder/evm_call_builder.py b/crypto/transactions/builder/evm_call_builder.py new file mode 100644 index 00000000..4a541dd7 --- /dev/null +++ b/crypto/transactions/builder/evm_call_builder.py @@ -0,0 +1,11 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.evm_call import EvmCall + +class EvmCallBuilder(AbstractTransactionBuilder): + def payload(self, payload: str): + payload = payload.lstrip('0x') # Remove '0x' prefix if present + self.transaction.data['data'] = payload + return self + + def get_transaction_instance(self, data: dict): + return EvmCall(data) \ No newline at end of file diff --git a/crypto/transactions/types/evm_call.py b/crypto/transactions/types/evm_call.py new file mode 100644 index 00000000..f1d9b04d --- /dev/null +++ b/crypto/transactions/types/evm_call.py @@ -0,0 +1,5 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction + +class EvmCall(AbstractTransaction): + def get_payload(self) -> str: + return self.data.get('data', '') \ No newline at end of file diff --git a/tests/transactions/builder/test_evm_call_builder.py b/tests/transactions/builder/test_evm_call_builder.py new file mode 100644 index 00000000..4ab2e15c --- /dev/null +++ b/tests/transactions/builder/test_evm_call_builder.py @@ -0,0 +1,17 @@ +from crypto.transactions.builder.evm_call_builder import EvmCallBuilder + +def test_evm_call_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('evm-sign') + + builder = ( + EvmCallBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .payload(fixture['data']['data']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address('0xE536720791A7DaDBeBdBCD8c8546fb0791a11901') + .sign(passphrase) + ) + + assert builder.verify() \ No newline at end of file From 94f472624dee45da038314c20ac052ba8a458a38 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:08:53 -0600 Subject: [PATCH 10/23] unvote --- crypto/enums/abi_function.py | 20 +++++++++++++++++++ crypto/transactions/builder/unvote_builder.py | 6 ++++++ crypto/transactions/types/unvote.py | 8 ++++++++ crypto/transactions/types/vote.py | 11 +--------- .../builder/test_unvote_builder.py | 18 +++++++++++++++++ 5 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 crypto/enums/abi_function.py create mode 100644 crypto/transactions/builder/unvote_builder.py create mode 100644 crypto/transactions/types/unvote.py create mode 100644 tests/transactions/builder/test_unvote_builder.py diff --git a/crypto/enums/abi_function.py b/crypto/enums/abi_function.py new file mode 100644 index 00000000..0262b0fb --- /dev/null +++ b/crypto/enums/abi_function.py @@ -0,0 +1,20 @@ +from enum import Enum + +class AbiFunction(Enum): + VOTE = 'vote' + UNVOTE = 'unvote' + VALIDATOR_REGISTRATION = 'registerValidator' + VALIDATOR_RESIGNATION = 'resignValidator' + + def transaction_class(self): + from crypto.transactions.types.vote import Vote + from crypto.transactions.types.unvote import Unvote + # from crypto.transactions.types.validator_registration import ValidatorRegistration + # from crypto.transactions.types.validator_resignation import ValidatorResignation + + return { + AbiFunction.VOTE: Vote, + AbiFunction.UNVOTE: Unvote, + # AbiFunction.VALIDATOR_REGISTRATION: ValidatorRegistration, + # AbiFunction.VALIDATOR_RESIGNATION: ValidatorResignation + }[self] \ No newline at end of file diff --git a/crypto/transactions/builder/unvote_builder.py b/crypto/transactions/builder/unvote_builder.py new file mode 100644 index 00000000..ddb9e248 --- /dev/null +++ b/crypto/transactions/builder/unvote_builder.py @@ -0,0 +1,6 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.unvote import Unvote + +class UnvoteBuilder(AbstractTransactionBuilder): + def get_transaction_instance(self, data: dict): + return Unvote(data) \ No newline at end of file diff --git a/crypto/transactions/types/unvote.py b/crypto/transactions/types/unvote.py new file mode 100644 index 00000000..ad699e44 --- /dev/null +++ b/crypto/transactions/types/unvote.py @@ -0,0 +1,8 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.utils.abi_encoder import AbiEncoder +from crypto.enums.abi_function import AbiFunction + +class Unvote(AbstractTransaction): + def get_payload(self) -> str: + encoder = AbiEncoder() + return encoder.encode_function_call(AbiFunction.UNVOTE.value) \ No newline at end of file diff --git a/crypto/transactions/types/vote.py b/crypto/transactions/types/vote.py index b3e05475..02e056db 100644 --- a/crypto/transactions/types/vote.py +++ b/crypto/transactions/types/vote.py @@ -11,13 +11,4 @@ def get_payload(self) -> str: if 'vote' not in self.data: return '' encoder = AbiEncoder() - return encoder.encode_function_call('vote', [self.data['vote']]) - - def decode_payload(self, data: dict) -> dict: - if 'data' not in data or not data['data']: - return {} - decoder = AbiDecoder() - decoded = decoder.decode_function_data(data['data']) - if decoded['functionName'] == 'vote': - self.data['vote'] = decoded['args'][0] - return self.data \ No newline at end of file + return encoder.encode_function_call('vote', [self.data['vote']]) \ No newline at end of file diff --git a/tests/transactions/builder/test_unvote_builder.py b/tests/transactions/builder/test_unvote_builder.py new file mode 100644 index 00000000..1a286ca8 --- /dev/null +++ b/tests/transactions/builder/test_unvote_builder.py @@ -0,0 +1,18 @@ +from crypto.transactions.builder.unvote_builder import UnvoteBuilder + +def test_unvote_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('unvote') + + builder = ( + UnvoteBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() \ No newline at end of file From ef0264fd41dee1fceeca377383b3a00179a8c31a Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:13:05 -0600 Subject: [PATCH 11/23] remove deprecated --- crypto/transactions/builder/multi_payment.py | 31 -------- .../builder/multi_signature_registration.py | 38 ---------- .../builder/username_registration.py | 21 ------ .../builder/username_resignation.py | 23 ------ .../builder/validator_registration.py | 45 ----------- .../builder/validator_resignation.py | 23 ------ .../builder/test_multi_payment.py | 60 --------------- .../test_multi_signature_registration.py | 46 ------------ .../builder/test_username_registration.py | 48 ------------ .../builder/test_username_resignation.py | 42 ----------- .../builder/test_validator_registration.py | 74 ------------------- .../builder/test_validator_resignation.py | 46 ------------ 12 files changed, 497 deletions(-) delete mode 100644 crypto/transactions/builder/multi_payment.py delete mode 100644 crypto/transactions/builder/multi_signature_registration.py delete mode 100644 crypto/transactions/builder/username_registration.py delete mode 100644 crypto/transactions/builder/username_resignation.py delete mode 100644 crypto/transactions/builder/validator_registration.py delete mode 100644 crypto/transactions/builder/validator_resignation.py delete mode 100644 tests/transactions/builder/test_multi_payment.py delete mode 100644 tests/transactions/builder/test_multi_signature_registration.py delete mode 100644 tests/transactions/builder/test_username_registration.py delete mode 100644 tests/transactions/builder/test_username_resignation.py delete mode 100644 tests/transactions/builder/test_validator_registration.py delete mode 100644 tests/transactions/builder/test_validator_resignation.py diff --git a/crypto/transactions/builder/multi_payment.py b/crypto/transactions/builder/multi_payment.py deleted file mode 100644 index 131f0e8b..00000000 --- a/crypto/transactions/builder/multi_payment.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_MULTI_PAYMENT, TRANSACTION_TYPE_GROUP -from crypto.transactions.builder.base import BaseTransactionBuilder - -class MultiPayment(BaseTransactionBuilder): - transaction_type = TRANSACTION_MULTI_PAYMENT - - def __init__(self, vendorField: Optional[str] = None, fee: Optional[int] = None): - """Create a multi payment transaction - - Args: - vendorField (str): value for the vendor field aka smartbridge - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.typeGroup = self.get_type_group() - - self.transaction.asset['payments'] = [] - - self.transaction.vendorField = vendorField.encode() if vendorField else None - - if fee: - self.transaction.fee = fee - - def get_type_group(self) -> int: - return TRANSACTION_TYPE_GROUP.CORE.value - - def add_payment(self, amount: int, recipient_id: str): - self.transaction.asset['payments'].append({'amount': amount, 'recipientId': recipient_id}) diff --git a/crypto/transactions/builder/multi_signature_registration.py b/crypto/transactions/builder/multi_signature_registration.py deleted file mode 100644 index 5f0402da..00000000 --- a/crypto/transactions/builder/multi_signature_registration.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import Optional - -from crypto.configuration.fee import get_fee -from crypto.constants import TRANSACTION_MULTI_SIGNATURE_REGISTRATION -from crypto.transactions.builder.base import BaseTransactionBuilder - -class MultiSignatureRegistration(BaseTransactionBuilder): - - transaction_type = TRANSACTION_MULTI_SIGNATURE_REGISTRATION - - def __init__(self, fee: Optional[int] = None): - """Create a new multi signature transaction - - Args: - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.asset = { - 'multiSignature': { - 'min': None, - 'publicKeys': [], - }, - } - - if fee: - self.transaction.fee = fee - - def set_min(self, minimum_participants): - self.transaction.asset['multiSignature']['min'] = minimum_participants - - def set_public_keys(self, public_keys): - self.transaction.asset['multiSignature']['publicKeys'] = public_keys - - def add_participant(self, public_key: str): - self.transaction.asset['multiSignature']['publicKeys'].append(public_key) - - self.transaction.fee = get_fee(TRANSACTION_MULTI_SIGNATURE_REGISTRATION) diff --git a/crypto/transactions/builder/username_registration.py b/crypto/transactions/builder/username_registration.py deleted file mode 100644 index 4a540eb2..00000000 --- a/crypto/transactions/builder/username_registration.py +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_USERNAME_REGISTRATION -from crypto.transactions.builder.base import BaseTransactionBuilder - -class UsernameRegistration(BaseTransactionBuilder): - transaction_type = TRANSACTION_USERNAME_REGISTRATION - - def __init__(self, username: str, fee: Optional[int] = None): - """Create a username registration transaction - - Args: - username (str): username you want to register - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.asset['username'] = username - - if fee: - self.transaction.fee = fee diff --git a/crypto/transactions/builder/username_resignation.py b/crypto/transactions/builder/username_resignation.py deleted file mode 100644 index 3d0a24dd..00000000 --- a/crypto/transactions/builder/username_resignation.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_USERNAME_RESIGNATION, TRANSACTION_TYPE_GROUP -from crypto.transactions.builder.base import BaseTransactionBuilder - -class UsernameResignation(BaseTransactionBuilder): - transaction_type = TRANSACTION_USERNAME_RESIGNATION - - def __init__(self, fee: Optional[int] = None): - """Create a username resignation transaction - - Args: - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.typeGroup = self.get_type_group() - - if fee: - self.transaction.fee = fee - - def get_type_group(self) -> int: - return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/crypto/transactions/builder/validator_registration.py b/crypto/transactions/builder/validator_registration.py deleted file mode 100644 index 78559cd5..00000000 --- a/crypto/transactions/builder/validator_registration.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION -from crypto.transactions.builder.base import BaseTransactionBuilder - -import sys -import os -from os.path import dirname - -sys.path.append(os.path.join(dirname(dirname(dirname(__file__))), 'thirdparty/bls-signatures/python-impl')) - -from ec import G1FromBytes - -class ValidatorRegistration(BaseTransactionBuilder): - transaction_type = TRANSACTION_VALIDATOR_REGISTRATION - - def __init__(self, public_key: str, fee: Optional[int] = None): - """Create a validator registration transaction - - Args: - public_key (str): BLS public key of a validator you want to register - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.validate_bls_public_key(public_key) - - self.transaction.asset['validatorPublicKey'] = public_key - - if fee: - self.transaction.fee = fee - - def validate_bls_public_key(self, public_key: str): - """Validate BLS public key - - Args: - public_key (str): BLS public key to validate - """ - if len(public_key) != 96: - raise ValueError('Invalid BLS public key') - - try: - G1FromBytes(bytes.fromhex(public_key)).check_valid() - except Exception: - raise ValueError('Invalid BLS public key') diff --git a/crypto/transactions/builder/validator_resignation.py b/crypto/transactions/builder/validator_resignation.py deleted file mode 100644 index 217c33dc..00000000 --- a/crypto/transactions/builder/validator_resignation.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional - -from crypto.constants import TRANSACTION_VALIDATOR_RESIGNATION, TRANSACTION_TYPE_GROUP -from crypto.transactions.builder.base import BaseTransactionBuilder - -class ValidatorResignation(BaseTransactionBuilder): - transaction_type = TRANSACTION_VALIDATOR_RESIGNATION - - def __init__(self, fee: Optional[int] = None): - """Create a validator resignation transaction - - Args: - fee (int, optional): fee used for the transaction (default is already set) - """ - super().__init__() - - self.transaction.typeGroup = self.get_type_group() - - if fee: - self.transaction.fee = fee - - def get_type_group(self) -> int: - return TRANSACTION_TYPE_GROUP.CORE.value diff --git a/tests/transactions/builder/test_multi_payment.py b/tests/transactions/builder/test_multi_payment.py deleted file mode 100644 index 231277a5..00000000 --- a/tests/transactions/builder/test_multi_payment.py +++ /dev/null @@ -1,60 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_MULTI_PAYMENT, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.multi_payment import MultiPayment - -set_network(Devnet) - - -def test_multi_payment_transaction(passphrase): - """Test if multi payment transaction gets built - """ - transaction = MultiPayment() - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.add_payment(1, '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22') - transaction.add_payment(2, '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A') - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 10000000 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - assert transaction_dict['asset']['payments'][0]['amount'] == 1 - assert transaction_dict['asset']['payments'][0]['recipientId'] == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' - assert transaction_dict['asset']['payments'][1]['amount'] == 2 - assert transaction_dict['asset']['payments'][1]['recipientId'] == '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_multi_payment_transaction_custom_fee(passphrase): - """Test if multi payment transaction gets built with a custom fee - """ - transaction = MultiPayment(fee=5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.add_payment(1, '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22') - transaction.add_payment(2, '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A') - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_MULTI_PAYMENT - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - assert transaction_dict['asset']['payments'][0]['amount'] == 1 - assert transaction_dict['asset']['payments'][0]['recipientId'] == '0x6F0182a0cc707b055322CcF6d4CB6a5Aff1aEb22' - assert transaction_dict['asset']['payments'][1]['amount'] == 2 - assert transaction_dict['asset']['payments'][1]['recipientId'] == '0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_multi_signature_registration.py b/tests/transactions/builder/test_multi_signature_registration.py deleted file mode 100644 index cce3d46f..00000000 --- a/tests/transactions/builder/test_multi_signature_registration.py +++ /dev/null @@ -1,46 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_MULTI_SIGNATURE_REGISTRATION, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.multi_signature_registration import MultiSignatureRegistration - -set_network(Devnet) - - -def test_multi_signature_registration_transaction(passphrase): - """Test if a multi signature registration transaction gets built - """ - publicKeys = [ - '0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896', - '03df0a1eb42d99b5de395cead145ba1ec2ea837be308c7ce3a4e8018b7efc7fdb8', - ] - - transaction = MultiSignatureRegistration() - transaction.set_sender_public_key('0205d9bbe71c343ac9a6a83a4344fd404c3534fc7349827097d0835d160bc2b896') - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.set_min(2) - transaction.set_public_keys(publicKeys) - transaction.add_participant('03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f') - transaction.multi_sign(passphrase, 0) - transaction.multi_sign('this is a top secret passphrase 2', 1) - transaction.multi_sign('this is a top secret passphrase 3', 2) - - transaction.sign(passphrase) - - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['version'] == 1 - assert transaction_dict['fee'] == 500000000 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_MULTI_SIGNATURE_REGISTRATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['asset']['multiSignature']['min'] == 2 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - assert transaction_dict['asset']['multiSignature']['publicKeys'][0] == publicKeys[0] - assert transaction_dict['asset']['multiSignature']['publicKeys'][1] == publicKeys[1] - assert transaction_dict['asset']['multiSignature']['publicKeys'][2] == '03860d76b1df09659ac282cea3da5bd84fc45729f348a4a8e5f802186be72dc17f' - - transaction.verify_multisig_schnorr() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_username_registration.py b/tests/transactions/builder/test_username_registration.py deleted file mode 100644 index a3599dc2..00000000 --- a/tests/transactions/builder/test_username_registration.py +++ /dev/null @@ -1,48 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_USERNAME_REGISTRATION, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.username_registration import UsernameRegistration - -set_network(Devnet) - - -def test_username_registration_transaction(passphrase): - """Test if a username registration transaction gets built - """ - username_name = 'mr.username' - - transaction = UsernameRegistration(username_name) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['username'] == username_name - assert transaction_dict['type'] is TRANSACTION_USERNAME_REGISTRATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 2500000000 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_username_registration_transaction_custom_fee(passphrase): - """Test if a username registration transaction gets built with a custom fee - """ - username_name = 'mr.username' - - transaction = UsernameRegistration(username_name, 5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['username'] == username_name - assert transaction_dict['type'] is TRANSACTION_USERNAME_REGISTRATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_username_resignation.py b/tests/transactions/builder/test_username_resignation.py deleted file mode 100644 index ac9d0562..00000000 --- a/tests/transactions/builder/test_username_resignation.py +++ /dev/null @@ -1,42 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_USERNAME_RESIGNATION, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.username_resignation import UsernameResignation - -set_network(Devnet) - - -def test_username_resignation_transaction(): - """Test if username resignation transaction gets built - """ - transaction = UsernameResignation() - transaction.set_nonce(1) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_USERNAME_RESIGNATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 2500000000 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_username_resignation_transaction_custom_fee(): - """Test if username resignation transaction gets built with a custom fee - """ - transaction = UsernameResignation(5) - transaction.set_nonce(1) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.sign('testing') - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_USERNAME_RESIGNATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_validator_registration.py b/tests/transactions/builder/test_validator_registration.py deleted file mode 100644 index 65156a67..00000000 --- a/tests/transactions/builder/test_validator_registration.py +++ /dev/null @@ -1,74 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_VALIDATOR_REGISTRATION, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.validator_registration import ValidatorRegistration - -set_network(Devnet) - - -def test_validator_registration_transaction(passphrase): - """Test if a validator registration transaction gets built - """ - bls_public_key = 'b5fea88b9aab3f0b122e5a7e1b07917e62a63ea59103d0a0715ecded3c41685af88f0a9606309b148b3b50f51a2e7036' - - transaction = ValidatorRegistration(bls_public_key) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key - assert transaction_dict['type'] is TRANSACTION_VALIDATOR_REGISTRATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 2500000000 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_validator_registration_transaction_with_invalid_bls_public_key(): - """Test if a validator registration transaction fails with an invalid BLS public key - """ - try: - ValidatorRegistration('b5fea88b9aab3f0b122e5a7e1b07917e62a63ea59103d0a0715ecded3c41685af88f0a9606309b148b3b50f51a2edddd') - - raise Exception('ValidatorRegistration should raise an exception with an invalid BLS public key') - except ValueError as e: - assert e.args[0] == 'Invalid BLS public key' - - -def test_validator_registration_transaction_with_invalid_bls_public_key_by_length(): - """Test if a validator registration transaction fails with an invalid BLS public key - """ - try: - ValidatorRegistration('023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3') - - raise Exception('ValidatorRegistration should raise an exception with an invalid BLS public key') - except ValueError as e: - assert e.args[0] == 'Invalid BLS public key' - - -def test_validator_registration_transaction_custom_fee(passphrase): - """Test if a validator registration transaction gets built with a custom fee - """ - bls_public_key = 'b5fea88b9aab3f0b122e5a7e1b07917e62a63ea59103d0a0715ecded3c41685af88f0a9606309b148b3b50f51a2e7036' - - transaction = ValidatorRegistration(bls_public_key, 5) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.set_nonce(1) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['asset']['validatorPublicKey'] == bls_public_key - assert transaction_dict['type'] is TRANSACTION_VALIDATOR_REGISTRATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid diff --git a/tests/transactions/builder/test_validator_resignation.py b/tests/transactions/builder/test_validator_resignation.py deleted file mode 100644 index 2bcf2d04..00000000 --- a/tests/transactions/builder/test_validator_resignation.py +++ /dev/null @@ -1,46 +0,0 @@ -from crypto.configuration.network import set_network -from crypto.constants import TRANSACTION_VALIDATOR_RESIGNATION, TRANSACTION_TYPE_GROUP -from crypto.networks.devnet import Devnet -from crypto.transactions.builder.validator_resignation import ValidatorResignation - -set_network(Devnet) - - -def test_validator_resignation_transaction(passphrase): - """Test if validator resignation transaction gets built - """ - transaction = ValidatorResignation() - transaction.set_nonce(1) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_VALIDATOR_RESIGNATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 2500000000 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid - - -def test_validator_resignation_transaction_custom_fee(passphrase): - """Test if validator resignation transaction gets built with a custom fee - """ - transaction = ValidatorResignation(5) - transaction.set_nonce(1) - transaction.set_type_group(TRANSACTION_TYPE_GROUP.CORE) - transaction.sign(passphrase) - transaction_dict = transaction.to_dict() - - assert transaction_dict['nonce'] == 1 - assert transaction_dict['signature'] - assert transaction_dict['type'] is TRANSACTION_VALIDATOR_RESIGNATION - assert transaction_dict['typeGroup'] == TRANSACTION_TYPE_GROUP.CORE.value - assert transaction_dict['fee'] == 5 - assert transaction_dict['expiration'] == 0 - assert transaction_dict['senderPublicKey'] == '023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3' - - transaction.schnorr_verify() # if no exception is raised, it means the transaction is valid From e9bbf93e3cb2624c92c0c32703cf5bec2300e8c2 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:13:14 -0600 Subject: [PATCH 12/23] add validator registration builder --- .../builder/validator_registration_builder.py | 11 +++++++++++ .../types/validator_registration.py | 15 +++++++++++++++ .../test_validator_registration_builder.py | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 crypto/transactions/builder/validator_registration_builder.py create mode 100644 crypto/transactions/types/validator_registration.py create mode 100644 tests/transactions/builder/test_validator_registration_builder.py diff --git a/crypto/transactions/builder/validator_registration_builder.py b/crypto/transactions/builder/validator_registration_builder.py new file mode 100644 index 00000000..15df44bc --- /dev/null +++ b/crypto/transactions/builder/validator_registration_builder.py @@ -0,0 +1,11 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.validator_registration import ValidatorRegistration + +class ValidatorRegistrationBuilder(AbstractTransactionBuilder): + def validator_public_key(self, validator_public_key: str): + self.transaction.data['validatorPublicKey'] = validator_public_key + self.transaction.refresh_payload_data() + return self + + def get_transaction_instance(self, data: dict): + return ValidatorRegistration(data) \ No newline at end of file diff --git a/crypto/transactions/types/validator_registration.py b/crypto/transactions/types/validator_registration.py new file mode 100644 index 00000000..9b554ffd --- /dev/null +++ b/crypto/transactions/types/validator_registration.py @@ -0,0 +1,15 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.utils.abi_encoder import AbiEncoder +from crypto.utils.abi_decoder import AbiDecoder +from crypto.enums.abi_function import AbiFunction + +class ValidatorRegistration(AbstractTransaction): + def __init__(self, data: dict = None): + super().__init__(data) + self.decode_payload(data) + + def get_payload(self) -> str: + if 'validatorPublicKey' not in self.data: + return '' + encoder = AbiEncoder() + return encoder.encode_function_call(AbiFunction.VALIDATOR_REGISTRATION.value, ['0x' + self.data['validatorPublicKey']]) diff --git a/tests/transactions/builder/test_validator_registration_builder.py b/tests/transactions/builder/test_validator_registration_builder.py new file mode 100644 index 00000000..874cfe22 --- /dev/null +++ b/tests/transactions/builder/test_validator_registration_builder.py @@ -0,0 +1,19 @@ +from crypto.transactions.builder.validator_registration_builder import ValidatorRegistrationBuilder + +def test_validator_registration_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('validator-registration') + + builder = ( + ValidatorRegistrationBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .validator_public_key('a08058db53e2665c84a40f5152e76dd2b652125a6079130d4c315e728bcf4dd1dfb44ac26e82302331d61977d3141118') + .recipient_address(fixture['data']['recipientAddress']) + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() \ No newline at end of file From 6e887ddce24b9a500a630b1891a162f95a06963b Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:13:39 -0600 Subject: [PATCH 13/23] cleanup --- crypto/transactions/types/validator_registration.py | 1 - crypto/transactions/types/vote.py | 1 - 2 files changed, 2 deletions(-) diff --git a/crypto/transactions/types/validator_registration.py b/crypto/transactions/types/validator_registration.py index 9b554ffd..9b4e12b6 100644 --- a/crypto/transactions/types/validator_registration.py +++ b/crypto/transactions/types/validator_registration.py @@ -1,6 +1,5 @@ from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.utils.abi_encoder import AbiEncoder -from crypto.utils.abi_decoder import AbiDecoder from crypto.enums.abi_function import AbiFunction class ValidatorRegistration(AbstractTransaction): diff --git a/crypto/transactions/types/vote.py b/crypto/transactions/types/vote.py index 02e056db..079200b4 100644 --- a/crypto/transactions/types/vote.py +++ b/crypto/transactions/types/vote.py @@ -1,6 +1,5 @@ from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.utils.abi_encoder import AbiEncoder -from crypto.utils.abi_decoder import AbiDecoder class Vote(AbstractTransaction): def __init__(self, data: dict = None): From d5d72e1117077b420cf3f0cd40ad4f79a543bb49 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:15:56 -0600 Subject: [PATCH 14/23] add validator resignation --- crypto/enums/abi_function.py | 8 ++++---- .../builder/validator_resignation_builder.py | 6 ++++++ .../types/validator_resignation.py | 8 ++++++++ .../test_validator_resignation_builder.py | 18 ++++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 crypto/transactions/builder/validator_resignation_builder.py create mode 100644 crypto/transactions/types/validator_resignation.py create mode 100644 tests/transactions/builder/test_validator_resignation_builder.py diff --git a/crypto/enums/abi_function.py b/crypto/enums/abi_function.py index 0262b0fb..0538184d 100644 --- a/crypto/enums/abi_function.py +++ b/crypto/enums/abi_function.py @@ -9,12 +9,12 @@ class AbiFunction(Enum): def transaction_class(self): from crypto.transactions.types.vote import Vote from crypto.transactions.types.unvote import Unvote - # from crypto.transactions.types.validator_registration import ValidatorRegistration - # from crypto.transactions.types.validator_resignation import ValidatorResignation + from crypto.transactions.types.validator_registration import ValidatorRegistration + from crypto.transactions.types.validator_resignation import ValidatorResignation return { AbiFunction.VOTE: Vote, AbiFunction.UNVOTE: Unvote, - # AbiFunction.VALIDATOR_REGISTRATION: ValidatorRegistration, - # AbiFunction.VALIDATOR_RESIGNATION: ValidatorResignation + AbiFunction.VALIDATOR_REGISTRATION: ValidatorRegistration, + AbiFunction.VALIDATOR_RESIGNATION: ValidatorResignation }[self] \ No newline at end of file diff --git a/crypto/transactions/builder/validator_resignation_builder.py b/crypto/transactions/builder/validator_resignation_builder.py new file mode 100644 index 00000000..294cd771 --- /dev/null +++ b/crypto/transactions/builder/validator_resignation_builder.py @@ -0,0 +1,6 @@ +from crypto.transactions.builder.base import AbstractTransactionBuilder +from crypto.transactions.types.validator_resignation import ValidatorResignation + +class ValidatorResignationBuilder(AbstractTransactionBuilder): + def get_transaction_instance(self, data: dict): + return ValidatorResignation(data) \ No newline at end of file diff --git a/crypto/transactions/types/validator_resignation.py b/crypto/transactions/types/validator_resignation.py new file mode 100644 index 00000000..4825e54a --- /dev/null +++ b/crypto/transactions/types/validator_resignation.py @@ -0,0 +1,8 @@ +from crypto.transactions.types.abstract_transaction import AbstractTransaction +from crypto.utils.abi_encoder import AbiEncoder +from crypto.enums.abi_function import AbiFunction + +class ValidatorResignation(AbstractTransaction): + def get_payload(self) -> str: + encoder = AbiEncoder() + return encoder.encode_function_call(AbiFunction.VALIDATOR_RESIGNATION.value) \ No newline at end of file diff --git a/tests/transactions/builder/test_validator_resignation_builder.py b/tests/transactions/builder/test_validator_resignation_builder.py new file mode 100644 index 00000000..e1adfaf1 --- /dev/null +++ b/tests/transactions/builder/test_validator_resignation_builder.py @@ -0,0 +1,18 @@ +from crypto.transactions.builder.validator_resignation_builder import ValidatorResignationBuilder + +def test_validator_resignation_transaction(passphrase, load_transaction_fixture): + fixture = load_transaction_fixture('validator-resignation') + + builder = ( + ValidatorResignationBuilder() + .gas_price(fixture['data']['gasPrice']) + .nonce(fixture['data']['nonce']) + .network(fixture['data']['network']) + .gas_limit(fixture['data']['gasLimit']) + .recipient_address(fixture['data']['recipientAddress']) + .sign(passphrase) + ) + + assert builder.transaction.serialize().hex() == fixture['serialized'] + assert builder.transaction.data['id'] == fixture['data']['id'] + assert builder.verify() \ No newline at end of file From 544f1917beb2f5807590c3d47a5744d8025d40d1 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:52:56 -0600 Subject: [PATCH 15/23] test serializer and fixes --- crypto/transactions/serializer.py | 5 +-- .../types/abstract_transaction.py | 11 ++++-- .../types/validator_registration.py | 7 +++- crypto/transactions/types/vote.py | 9 ++++- tests/transactions/test_serializer.py | 37 +++++++++++++++++++ 5 files changed, 59 insertions(+), 10 deletions(-) create mode 100644 tests/transactions/test_serializer.py diff --git a/crypto/transactions/serializer.py b/crypto/transactions/serializer.py index b5a84cbc..b10d4dbd 100644 --- a/crypto/transactions/serializer.py +++ b/crypto/transactions/serializer.py @@ -6,8 +6,6 @@ write_bit32, write_bit64, ) -# from crypto.utils.address import Address # TODO: Implement or import Address - class Serializer: def __init__(self, transaction: AbstractTransaction): @@ -57,7 +55,7 @@ def serialize_data(self) -> bytes: payload_hex = self.transaction.data.get('data', '') payload_length = len(payload_hex) // 2 bytes_data += write_bit32(payload_length) - + if payload_length > 0: bytes_data += unhexlify(payload_hex) @@ -67,4 +65,5 @@ def serialize_signatures(self) -> bytes: bytes_data = bytes() if 'signature' in self.transaction.data: bytes_data += unhexlify(self.transaction.data['signature']) + return bytes_data diff --git a/crypto/transactions/types/abstract_transaction.py b/crypto/transactions/types/abstract_transaction.py index 046cfba2..d7506152 100644 --- a/crypto/transactions/types/abstract_transaction.py +++ b/crypto/transactions/types/abstract_transaction.py @@ -6,7 +6,7 @@ from crypto.identity.private_key import PrivateKey from crypto.utils.transaction_hasher import TransactionHasher from coincurve import PublicKey - +from crypto.utils.abi_decoder import AbiDecoder class AbstractTransaction: def __init__(self, data: Optional[dict] = None): @@ -19,8 +19,13 @@ def get_payload(self) -> str: def decode_payload(self, data: dict) -> Optional[dict]: if 'data' not in data or data['data'] == '': return None - # TODO: add AbiDecoder to decode the payload - return {} + + payload = data['data'] + decoder = AbiDecoder() + + decoded_data = decoder.decode_function_data(payload) + + return decoded_data def refresh_payload_data(self): self.data['data'] = self.get_payload().lstrip('0x') diff --git a/crypto/transactions/types/validator_registration.py b/crypto/transactions/types/validator_registration.py index 9b4e12b6..a8b8bf80 100644 --- a/crypto/transactions/types/validator_registration.py +++ b/crypto/transactions/types/validator_registration.py @@ -4,11 +4,14 @@ class ValidatorRegistration(AbstractTransaction): def __init__(self, data: dict = None): + data = data or {} + payload = self.decode_payload(data) + if payload: + data['validatorPublicKey'] = payload.get('args', [None])[0].lstrip('0x') if payload.get('args') else None super().__init__(data) - self.decode_payload(data) def get_payload(self) -> str: if 'validatorPublicKey' not in self.data: return '' encoder = AbiEncoder() - return encoder.encode_function_call(AbiFunction.VALIDATOR_REGISTRATION.value, ['0x' + self.data['validatorPublicKey']]) + return encoder.encode_function_call(AbiFunction.VALIDATOR_REGISTRATION.value, ['0x' + self.data['validatorPublicKey']]) \ No newline at end of file diff --git a/crypto/transactions/types/vote.py b/crypto/transactions/types/vote.py index 079200b4..93e8428a 100644 --- a/crypto/transactions/types/vote.py +++ b/crypto/transactions/types/vote.py @@ -1,13 +1,18 @@ from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.utils.abi_encoder import AbiEncoder +from crypto.enums.abi_function import AbiFunction class Vote(AbstractTransaction): def __init__(self, data: dict = None): + data = data or {} + payload = self.decode_payload(data) + if payload: + data['vote'] = payload.get('args', [None])[0] if payload.get('args') else None + super().__init__(data) - self.decode_payload(data) def get_payload(self) -> str: if 'vote' not in self.data: return '' encoder = AbiEncoder() - return encoder.encode_function_call('vote', [self.data['vote']]) \ No newline at end of file + return encoder.encode_function_call(AbiFunction.VOTE.value, [self.data['vote']]) \ No newline at end of file diff --git a/tests/transactions/test_serializer.py b/tests/transactions/test_serializer.py new file mode 100644 index 00000000..d97ebb5c --- /dev/null +++ b/tests/transactions/test_serializer.py @@ -0,0 +1,37 @@ +from crypto.transactions.serializer import Serializer +from crypto.transactions.types.transfer import Transfer +from crypto.transactions.types.vote import Vote +from crypto.transactions.types.unvote import Unvote +from crypto.transactions.types.validator_registration import ValidatorRegistration +from crypto.transactions.types.validator_resignation import ValidatorResignation + +def test_transfer_serialization(load_transaction_fixture): + fixture = load_transaction_fixture('transfer') + transaction = Transfer(fixture['data']) + serializer = Serializer.new(transaction) + assert serializer.serialize().hex() == fixture['serialized'] + +def test_vote_serialization(load_transaction_fixture): + fixture = load_transaction_fixture('vote') + transaction = Vote(fixture['data']) + print(f"transaction: {transaction.data}") + serializer = Serializer.new(transaction) + assert serializer.serialize().hex() == fixture['serialized'] + +def test_unvote_serialization(load_transaction_fixture): + fixture = load_transaction_fixture('unvote') + transaction = Unvote(fixture['data']) + serializer = Serializer.new(transaction) + assert serializer.serialize().hex() == fixture['serialized'] + +def test_validator_registration_serialization(load_transaction_fixture): + fixture = load_transaction_fixture('validator-registration') + transaction = ValidatorRegistration(fixture['data']) + serializer = Serializer.new(transaction) + assert serializer.serialize().hex() == fixture['serialized'] + +def test_validator_resignation_serialization(load_transaction_fixture): + fixture = load_transaction_fixture('validator-resignation') + transaction = ValidatorResignation(fixture['data']) + serializer = Serializer.new(transaction) + assert serializer.serialize().hex() == fixture['serialized'] \ No newline at end of file From d6af57a2045496dc740e741f992f9b5abc26d311 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 08:53:05 -0600 Subject: [PATCH 16/23] remove deprecated tests --- tests/transactions/deserializers/__init__.py | 1 - .../deserializers/test_multi_payment.py | 23 ---- .../test_multi_signature_registration.py | 100 ------------------ .../deserializers/test_transfer.py | 21 ---- .../test_username_registration.py | 20 ---- .../test_username_resignation.py | 19 ---- .../test_validator_registration.py | 20 ---- .../test_validator_resignation.py | 19 ---- tests/transactions/deserializers/test_vote.py | 20 ---- tests/transactions/serializers/__init__.py | 1 - .../serializers/test_multi_payment.py | 6 -- .../test_multi_signature_registration.py | 6 -- .../transactions/serializers/test_transfer.py | 6 -- .../serializers/test_username_registration.py | 6 -- .../serializers/test_username_resignation.py | 6 -- .../test_validator_registration.py | 6 -- .../serializers/test_validator_resignation.py | 6 -- tests/transactions/serializers/test_vote.py | 6 -- 18 files changed, 292 deletions(-) delete mode 100644 tests/transactions/deserializers/__init__.py delete mode 100644 tests/transactions/deserializers/test_multi_payment.py delete mode 100644 tests/transactions/deserializers/test_multi_signature_registration.py delete mode 100644 tests/transactions/deserializers/test_transfer.py delete mode 100644 tests/transactions/deserializers/test_username_registration.py delete mode 100644 tests/transactions/deserializers/test_username_resignation.py delete mode 100644 tests/transactions/deserializers/test_validator_registration.py delete mode 100644 tests/transactions/deserializers/test_validator_resignation.py delete mode 100644 tests/transactions/deserializers/test_vote.py delete mode 100644 tests/transactions/serializers/__init__.py delete mode 100644 tests/transactions/serializers/test_multi_payment.py delete mode 100644 tests/transactions/serializers/test_multi_signature_registration.py delete mode 100644 tests/transactions/serializers/test_transfer.py delete mode 100644 tests/transactions/serializers/test_username_registration.py delete mode 100644 tests/transactions/serializers/test_username_resignation.py delete mode 100644 tests/transactions/serializers/test_validator_registration.py delete mode 100644 tests/transactions/serializers/test_validator_resignation.py delete mode 100644 tests/transactions/serializers/test_vote.py diff --git a/tests/transactions/deserializers/__init__.py b/tests/transactions/deserializers/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/tests/transactions/deserializers/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/transactions/deserializers/test_multi_payment.py b/tests/transactions/deserializers/test_multi_payment.py deleted file mode 100644 index 3f65c6d0..00000000 --- a/tests/transactions/deserializers/test_multi_payment.py +++ /dev/null @@ -1,23 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_multi_payment_deserializer(transaction_type_6): - deserializer = Deserializer(transaction_type_6['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_6['version'] - assert actual.network == transaction_type_6['network'] - assert actual.typeGroup == transaction_type_6['typeGroup'] - assert actual.type == transaction_type_6['type'] - assert actual.nonce == transaction_type_6['nonce'] - assert actual.senderPublicKey == transaction_type_6['senderPublicKey'] - assert actual.fee == transaction_type_6['fee'] - assert actual.amount == transaction_type_6['amount'] - assert actual.signature == transaction_type_6['signature'] - assert actual.id == transaction_type_6['id'] - - for index, payment in enumerate(transaction_type_6['asset']['payments']): - assert actual.asset['payments'][index]['amount'] == payment['amount'] - assert actual.asset['payments'][index]['recipientId'] == payment['recipientId'] - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_multi_signature_registration.py b/tests/transactions/deserializers/test_multi_signature_registration.py deleted file mode 100644 index e079cc3c..00000000 --- a/tests/transactions/deserializers/test_multi_signature_registration.py +++ /dev/null @@ -1,100 +0,0 @@ -from crypto.constants import TRANSACTION_FEES -from crypto.identity.public_key import PublicKey -from crypto.transactions.builder.multi_signature_registration import MultiSignatureRegistration -from crypto.transactions.deserializer import Deserializer -from crypto.transactions.serializer import Serializer - - -def test_multi_signature_registration_deserializer(transaction_type_4): - deserializer = Deserializer(transaction_type_4['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_4['version'] - assert actual.network == transaction_type_4['network'] - assert actual.typeGroup == transaction_type_4['typeGroup'] - assert actual.type == transaction_type_4['type'] - assert actual.nonce == transaction_type_4['nonce'] - assert actual.senderPublicKey == transaction_type_4['senderPublicKey'] - assert actual.fee == transaction_type_4['fee'] - assert actual.amount == transaction_type_4['amount'] - assert actual.signature == transaction_type_4['signature'] - assert actual.id == transaction_type_4['id'] - - assert actual.asset['multiSignature']['min'] == transaction_type_4['asset']['multiSignature']['min'] - assert actual.asset['multiSignature']['publicKeys'] == transaction_type_4['asset']['multiSignature']['publicKeys'] - assert actual.signatures == transaction_type_4['signatures'] - - actual.verify_multisig_schnorr() - - -def test_multi_signature_registration_deserializer_from_serialized(transaction_type_4): - result = Serializer(transaction_type_4).serialize(False, True, False) - - deserializer = Deserializer(result) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_4['version'] - assert actual.network == transaction_type_4['network'] - assert actual.typeGroup == transaction_type_4['typeGroup'] - assert actual.type == transaction_type_4['type'] - assert actual.nonce == transaction_type_4['nonce'] - assert actual.senderPublicKey == transaction_type_4['senderPublicKey'] - assert actual.fee == transaction_type_4['fee'] - assert actual.amount == transaction_type_4['amount'] - assert actual.signature == transaction_type_4['signature'] - assert actual.id == transaction_type_4['id'] - - assert actual.asset['multiSignature']['min'] == transaction_type_4['asset']['multiSignature']['min'] - assert actual.asset['multiSignature']['publicKeys'] == transaction_type_4['asset']['multiSignature']['publicKeys'] - assert actual.signatures == transaction_type_4['signatures'] - - actual.verify_multisig_schnorr() - -def test_with_methods(passphrase): - transaction = MultiSignatureRegistration() - - passphrase_2 = f'{passphrase} 2' - passphrase_3 = f'{passphrase} 3' - - sender_public_key = PublicKey.from_passphrase(passphrase) - participant_2 = PublicKey.from_passphrase(passphrase_2) - participant_3 = PublicKey.from_passphrase(passphrase_3) - - transaction.set_sender_public_key(sender_public_key) - transaction.set_nonce(15) - transaction.set_min(2) - transaction.add_participant(sender_public_key) - transaction.add_participant(participant_2) - transaction.add_participant(participant_3) - - transaction.multi_sign(passphrase, 0) - transaction.multi_sign(passphrase_2, 1) - transaction.multi_sign(passphrase_3, 2) - - transaction.sign(passphrase) - - result = Serializer(transaction.to_dict()).serialize(False, True, False) - - deserializer = Deserializer(result) - actual = deserializer.deserialize() - - assert actual.version == 1 - assert actual.network == 30 - assert actual.typeGroup == 1 - assert actual.type == 4 - assert actual.nonce == 15 - assert actual.senderPublicKey == sender_public_key - assert actual.fee == TRANSACTION_FEES[4] - assert actual.amount == 0 - assert actual.signature == transaction.transaction.signature - assert actual.id == transaction.transaction.id - - assert actual.asset['multiSignature']['min'] == 2 - assert actual.asset['multiSignature']['publicKeys'][0] == sender_public_key - assert actual.asset['multiSignature']['publicKeys'][1] == participant_2 - assert actual.asset['multiSignature']['publicKeys'][2] == participant_3 - - assert actual.signatures == transaction.transaction.signatures - assert len(actual.signatures) == 3 - - actual.verify_multisig_schnorr() diff --git a/tests/transactions/deserializers/test_transfer.py b/tests/transactions/deserializers/test_transfer.py deleted file mode 100644 index ec033f96..00000000 --- a/tests/transactions/deserializers/test_transfer.py +++ /dev/null @@ -1,21 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_transfer_deserializer(transaction_type_0): - deserializer = Deserializer(transaction_type_0['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_0['version'] - assert actual.network == transaction_type_0['network'] - assert actual.typeGroup == transaction_type_0['typeGroup'] - assert actual.type == transaction_type_0['type'] - assert actual.nonce == transaction_type_0['nonce'] - assert actual.senderPublicKey == transaction_type_0['senderPublicKey'] - assert actual.fee == transaction_type_0['fee'] - assert actual.amount == transaction_type_0['amount'] - assert actual.expiration == transaction_type_0['expiration'] - assert actual.recipientId == transaction_type_0['recipientId'] - assert actual.signature == transaction_type_0['signature'] - assert actual.id == transaction_type_0['id'] - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_username_registration.py b/tests/transactions/deserializers/test_username_registration.py deleted file mode 100644 index eb0709ec..00000000 --- a/tests/transactions/deserializers/test_username_registration.py +++ /dev/null @@ -1,20 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_username_registration_deserializer(transaction_type_8): - deserializer = Deserializer(transaction_type_8['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_8['version'] - assert actual.network == transaction_type_8['network'] - assert actual.typeGroup == transaction_type_8['typeGroup'] - assert actual.type == transaction_type_8['type'] - assert actual.nonce == transaction_type_8['nonce'] - assert actual.senderPublicKey == transaction_type_8['senderPublicKey'] - assert actual.fee == transaction_type_8['fee'] - assert actual.asset == transaction_type_8['asset'] - assert actual.signature == transaction_type_8['signature'] - assert actual.amount == transaction_type_8['amount'] - assert actual.id == transaction_type_8['id'] - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_username_resignation.py b/tests/transactions/deserializers/test_username_resignation.py deleted file mode 100644 index fb43f759..00000000 --- a/tests/transactions/deserializers/test_username_resignation.py +++ /dev/null @@ -1,19 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_username_resignation_deserializer(transaction_type_9): - deserializer = Deserializer(transaction_type_9['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_9['version'] - assert actual.network == transaction_type_9['network'] - assert actual.typeGroup == transaction_type_9['typeGroup'] - assert actual.type == transaction_type_9['type'] - assert actual.nonce == transaction_type_9['nonce'] - assert actual.senderPublicKey == transaction_type_9['senderPublicKey'] - assert actual.fee == transaction_type_9['fee'] - assert actual.signature == transaction_type_9['signature'] - assert actual.amount == transaction_type_9['amount'] - assert actual.id == transaction_type_9['id'] - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_validator_registration.py b/tests/transactions/deserializers/test_validator_registration.py deleted file mode 100644 index 087532bf..00000000 --- a/tests/transactions/deserializers/test_validator_registration.py +++ /dev/null @@ -1,20 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_validator_registration_deserializer(transaction_type_2): - deserializer = Deserializer(transaction_type_2['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_2['version'] - assert actual.network == transaction_type_2['network'] - assert actual.typeGroup == transaction_type_2['typeGroup'] - assert actual.type == transaction_type_2['type'] - assert actual.nonce == transaction_type_2['nonce'] - assert actual.senderPublicKey == transaction_type_2['senderPublicKey'] - assert actual.fee == transaction_type_2['fee'] - assert actual.asset == transaction_type_2['asset'] - assert actual.signature == transaction_type_2['signature'] - assert actual.amount == transaction_type_2['amount'] - assert actual.id == transaction_type_2['id'] - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_validator_resignation.py b/tests/transactions/deserializers/test_validator_resignation.py deleted file mode 100644 index b1dbd65c..00000000 --- a/tests/transactions/deserializers/test_validator_resignation.py +++ /dev/null @@ -1,19 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_validator_resignation_deserializer(transaction_type_7): - deserializer = Deserializer(transaction_type_7['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_7['version'] - assert actual.network == transaction_type_7['network'] - assert actual.typeGroup == transaction_type_7['typeGroup'] - assert actual.type == transaction_type_7['type'] - assert actual.nonce == transaction_type_7['nonce'] - assert actual.senderPublicKey == transaction_type_7['senderPublicKey'] - assert actual.fee == transaction_type_7['fee'] - assert actual.signature == transaction_type_7['signature'] - assert actual.amount == transaction_type_7['amount'] - assert actual.id == transaction_type_7['id'] - - actual.verify_schnorr() diff --git a/tests/transactions/deserializers/test_vote.py b/tests/transactions/deserializers/test_vote.py deleted file mode 100644 index 3ed39887..00000000 --- a/tests/transactions/deserializers/test_vote.py +++ /dev/null @@ -1,20 +0,0 @@ -from crypto.transactions.deserializer import Deserializer - - -def test_vote_deserializer(transaction_type_3): - deserializer = Deserializer(transaction_type_3['serialized']) - actual = deserializer.deserialize() - - assert actual.version == transaction_type_3['version'] - assert actual.network == transaction_type_3['network'] - assert actual.typeGroup == transaction_type_3['typeGroup'] - assert actual.type == transaction_type_3['type'] - assert actual.amount == transaction_type_3['amount'] - assert actual.fee == transaction_type_3['fee'] - assert actual.nonce == transaction_type_3['nonce'] - assert actual.senderPublicKey == transaction_type_3['senderPublicKey'] # noqa - assert actual.signature == transaction_type_3['signature'] - - assert actual.asset == transaction_type_3['asset'] # noqa - - actual.verify_schnorr() diff --git a/tests/transactions/serializers/__init__.py b/tests/transactions/serializers/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/tests/transactions/serializers/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/transactions/serializers/test_multi_payment.py b/tests/transactions/serializers/test_multi_payment.py deleted file mode 100644 index 08a58d14..00000000 --- a/tests/transactions/serializers/test_multi_payment.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_6): - result = Serializer(transaction_type_6).serialize(False, True) - assert result == transaction_type_6['serialized'] diff --git a/tests/transactions/serializers/test_multi_signature_registration.py b/tests/transactions/serializers/test_multi_signature_registration.py deleted file mode 100644 index 5ac1f860..00000000 --- a/tests/transactions/serializers/test_multi_signature_registration.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_4): - result = Serializer(transaction_type_4).serialize(False, True, False) - assert result == transaction_type_4['serialized'] diff --git a/tests/transactions/serializers/test_transfer.py b/tests/transactions/serializers/test_transfer.py deleted file mode 100644 index a7128532..00000000 --- a/tests/transactions/serializers/test_transfer.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_0): - result = Serializer(transaction_type_0).serialize(False, True) - assert result == transaction_type_0['serialized'] diff --git a/tests/transactions/serializers/test_username_registration.py b/tests/transactions/serializers/test_username_registration.py deleted file mode 100644 index f0e17b9b..00000000 --- a/tests/transactions/serializers/test_username_registration.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_8): - result = Serializer(transaction_type_8).serialize(False, True) - assert result == transaction_type_8['serialized'] diff --git a/tests/transactions/serializers/test_username_resignation.py b/tests/transactions/serializers/test_username_resignation.py deleted file mode 100644 index 5592096a..00000000 --- a/tests/transactions/serializers/test_username_resignation.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_9): - result = Serializer(transaction_type_9).serialize(False, True) - assert result == transaction_type_9['serialized'] diff --git a/tests/transactions/serializers/test_validator_registration.py b/tests/transactions/serializers/test_validator_registration.py deleted file mode 100644 index ba6e03e1..00000000 --- a/tests/transactions/serializers/test_validator_registration.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_2): - result = Serializer(transaction_type_2).serialize(False, True) - assert result == transaction_type_2['serialized'] diff --git a/tests/transactions/serializers/test_validator_resignation.py b/tests/transactions/serializers/test_validator_resignation.py deleted file mode 100644 index 6172982a..00000000 --- a/tests/transactions/serializers/test_validator_resignation.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_7): - result = Serializer(transaction_type_7).serialize(False, True) - assert result == transaction_type_7['serialized'] diff --git a/tests/transactions/serializers/test_vote.py b/tests/transactions/serializers/test_vote.py deleted file mode 100644 index ed83e62d..00000000 --- a/tests/transactions/serializers/test_vote.py +++ /dev/null @@ -1,6 +0,0 @@ -from crypto.transactions.serializer import Serializer - - -def test_serializer(transaction_type_3): - result = Serializer(transaction_type_3).serialize(False, True) - assert result == transaction_type_3['serialized'] From 28b21a6d09a3838bdc2eaa600c2d7747304f1dc5 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 09:05:08 -0600 Subject: [PATCH 17/23] deserializer adjustments and tests --- crypto/transactions/deserializer.py | 77 +++++++++++++------------ tests/transactions/test_deserializer.py | 48 +++++++++++++++ 2 files changed, 88 insertions(+), 37 deletions(-) create mode 100644 tests/transactions/test_deserializer.py diff --git a/crypto/transactions/deserializer.py b/crypto/transactions/deserializer.py index 091aa20f..416a5c40 100644 --- a/crypto/transactions/deserializer.py +++ b/crypto/transactions/deserializer.py @@ -1,10 +1,10 @@ from crypto.transactions.types.abstract_transaction import AbstractTransaction from crypto.transactions.types.transfer import Transfer -# from crypto.transactions.types.evm_call import EvmCall -# from crypto.transactions.types.vote import Vote -# from crypto.transactions.types.unvote import Unvote -# from crypto.transactions.types.validator_registration import ValidatorRegistration -# from crypto.transactions.types.validator_resignation import ValidatorResignation +from crypto.transactions.types.evm_call import EvmCall +from crypto.transactions.types.vote import Vote +from crypto.transactions.types.unvote import Unvote +from crypto.transactions.types.validator_registration import ValidatorRegistration +from crypto.transactions.types.validator_resignation import ValidatorResignation from binascii import unhexlify, hexlify from binary.unsigned_integer.reader import ( @@ -12,9 +12,8 @@ read_bit32, read_bit64, ) -# from crypto.enums.abi_function import AbiFunction # TODO: Implement or import AbiFunction -# from crypto.utils.abi_decoder import AbiDecoder # TODO: Implement or import AbiDecoder - +from crypto.enums.abi_function import AbiFunction +from crypto.utils.abi_decoder import AbiDecoder class Deserializer: SIGNATURE_SIZE = 64 @@ -49,18 +48,18 @@ def read_bytes(self, length: int) -> bytes: return result def deserialize_common(self, data: dict): - data['network'], _ = read_bit8(self.serialized, self.pointer) + data['network'] = read_bit8(self.serialized, self.pointer) self.pointer += 1 - nonce, _ = read_bit64(self.serialized, self.pointer) + nonce = read_bit64(self.serialized, self.pointer) data['nonce'] = str(nonce) self.pointer += 8 - gas_price, _ = read_bit32(self.serialized, self.pointer) + gas_price = read_bit32(self.serialized, self.pointer) data['gasPrice'] = gas_price self.pointer += 4 - gas_limit, _ = read_bit32(self.serialized, self.pointer) + gas_limit = read_bit32(self.serialized, self.pointer) data['gasLimit'] = gas_limit self.pointer += 4 @@ -71,9 +70,8 @@ def deserialize_data(self, data: dict): self.pointer += 32 data['value'] = str(value) - self.pointer += 32 - recipient_marker, _ = read_bit8(self.serialized, self.pointer) + recipient_marker = read_bit8(self.serialized, self.pointer) self.pointer += 1 if recipient_marker == 1: @@ -81,7 +79,7 @@ def deserialize_data(self, data: dict): recipient_address = '0x' + hexlify(recipient_address_bytes).decode() data['recipientAddress'] = recipient_address - payload_length, _ = read_bit32(self.serialized, self.pointer) + payload_length = read_bit32(self.serialized, self.pointer) self.pointer += 4 payload_hex = '' @@ -100,27 +98,32 @@ def guess_transaction_from_data(self, data: dict) -> AbstractTransaction: if data['value'] != '0': return Transfer(data) - # payload_data = self.decode_payload(data) - payload_data = None # As AbiDecoder is not implemented + payload_data = self.decode_payload(data) if payload_data is None: - return Transfer(data) # Using Transfer for now - - # if function_name == AbiFunction.VOTE.value: - # return Vote(data) - # elif function_name == AbiFunction.UNVOTE.value: - # return Unvote(data) - # elif function_name == AbiFunction.VALIDATOR_REGISTRATION.value: - # return ValidatorRegistration(data) - # elif function_name == AbiFunction.VALIDATOR_RESIGNATION.value: - # return ValidatorResignation(data) - # else: - # return Transfer(data) - - # def decode_payload(self, data: dict) -> dict: - # payload = data.get('data', '') - # - # if payload == '': - # return None - # - # return AbiDecoder().decode_function_data(payload) + return EvmCall(data) + + function_name = payload_data.get('functionName') + if function_name == AbiFunction.VOTE.value: + return Vote(data) + elif function_name == AbiFunction.UNVOTE.value: + return Unvote(data) + elif function_name == AbiFunction.VALIDATOR_REGISTRATION.value: + return ValidatorRegistration(data) + elif function_name == AbiFunction.VALIDATOR_RESIGNATION.value: + return ValidatorResignation(data) + else: + return EvmCall(data) + + def decode_payload(self, data: dict) -> dict: + payload = data.get('data', '') + + if payload == '': + return None + + decoder = AbiDecoder() + try: + return decoder.decode_function_data(payload) + except Exception as e: + print(f"Error decoding payload: {str(e)}") + return None \ No newline at end of file diff --git a/tests/transactions/test_deserializer.py b/tests/transactions/test_deserializer.py new file mode 100644 index 00000000..50357cd2 --- /dev/null +++ b/tests/transactions/test_deserializer.py @@ -0,0 +1,48 @@ +from crypto.transactions.deserializer import Deserializer +from crypto.transactions.types.transfer import Transfer +from crypto.transactions.types.vote import Vote +from crypto.transactions.types.unvote import Unvote +from crypto.transactions.types.validator_registration import ValidatorRegistration +from crypto.transactions.types.validator_resignation import ValidatorResignation + +def assert_deserialized(fixture, keys): + deserializer = Deserializer.new(fixture['serialized']) + transaction = deserializer.deserialize() + for key in keys: + assert transaction.data[key] == fixture['data'][key], f"Mismatch in {key}" + assert transaction.serialize().hex() == fixture['serialized'] + assert transaction.verify() + return transaction + +def test_deserialize_transfer(load_transaction_fixture): + fixture = load_transaction_fixture('transfer') + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'value', 'signature']) + + assert isinstance(transaction, Transfer) + assert transaction.data['value'] == '10000000000000000000' + +def test_deserialize_vote(load_transaction_fixture): + fixture = load_transaction_fixture('vote') + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) + + assert isinstance(transaction, Vote) + assert transaction.data['vote'] == '0x512F366D524157BcF734546eB29a6d687B762255' + assert transaction.data['id'] == '749744e0d689c46e37ff2993a984599eac4989a9ef0028337b335c9d43abf936' + +def test_deserialize_unvote(load_transaction_fixture): + fixture = load_transaction_fixture('unvote') + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) + + assert isinstance(transaction, Unvote) + +def test_deserialize_validator_registration(load_transaction_fixture): + fixture = load_transaction_fixture('validator-registration') + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) + + assert isinstance(transaction, ValidatorRegistration) + +def test_deserialize_validator_resignation(load_transaction_fixture): + fixture = load_transaction_fixture('validator-resignation') + transaction = assert_deserialized(fixture, ['id', 'nonce', 'gasPrice', 'gasLimit', 'signature']) + + assert isinstance(transaction, ValidatorResignation) \ No newline at end of file From 033efbacbfe1c67d6859e58da6a1f3c4014b5c4e Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 09:12:03 -0600 Subject: [PATCH 18/23] adjust test transaction --- tests/transactions/test_transaction.py | 52 ++++++++++++++++---------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/tests/transactions/test_transaction.py b/tests/transactions/test_transaction.py index 76bca5f6..1b316f35 100644 --- a/tests/transactions/test_transaction.py +++ b/tests/transactions/test_transaction.py @@ -1,24 +1,36 @@ -from crypto.transactions.transaction import Transaction +from crypto.identity.private_key import PrivateKey +from crypto.transactions.deserializer import Deserializer +from crypto.transactions.types.abstract_transaction import AbstractTransaction +def test_compute_id_of_transaction(load_transaction_fixture): + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() + assert len(transaction.get_id()) == 64 -def test_transaction_serialize(transaction_type_0): - transaction = Transaction(**transaction_type_0) - serialized = transaction.serialize(False, True, False) - assert serialized == transaction_type_0['serialized'] +def test_sign_transaction_with_passphrase(load_transaction_fixture): + private_key = PrivateKey.from_passphrase('this is a top secret passphrase') + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize() + transaction.data['signature'] = '' + + assert 'signature' not in transaction.data or transaction.data['signature'] == '' + transaction.sign(private_key) + assert transaction.data['signature'] != '' -def test_transaction_deserialize(transaction_type_0): - transaction = Transaction() - deserialized = transaction.deserialize(transaction_type_0['serialized']) - data = deserialized.to_dict() - assert data['amount'] == transaction_type_0['amount'] - assert data['senderPublicKey'] == transaction_type_0['senderPublicKey'] - assert data['recipientId'] == transaction_type_0['recipientId'] - assert data['id'] == transaction_type_0['id'] - assert data['fee'] == transaction_type_0['fee'] - assert data['signature'] == transaction_type_0['signature'] - assert data['version'] == transaction_type_0['version'] - assert data['network'] == transaction_type_0['network'] - assert data['type'] == transaction_type_0['type'] - assert data['typeGroup'] == transaction_type_0['typeGroup'] - assert data['nonce'] == transaction_type_0['nonce'] +def test_verify_transaction(load_transaction_fixture): + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + assert transaction.verify() + +def test_transaction_to_bytes(load_transaction_fixture): + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + actual = transaction.get_bytes() + assert isinstance(actual, bytes) + +def test_transaction_to_array(load_transaction_fixture): + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + actual = transaction.to_dict() + assert isinstance(actual, dict) + +def test_transaction_to_json(load_transaction_fixture): + transaction = Deserializer.new(load_transaction_fixture('transfer')['serialized']).deserialize(); + actual = transaction.to_json() + assert isinstance(actual, str) From 5b6244b8cfe5e182c75c80aedf6f12152c4c8845 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 09:14:24 -0600 Subject: [PATCH 19/23] cleanup --- crypto/configuration/fee.py | 25 --------- crypto/constants.py | 41 --------------- tests/configuration/test_fee.py | 17 ------- tests/resources/__init__.py | 1 - tests/resources/test_transaction.py | 23 --------- tests/transactions/test_signature.py | 76 ---------------------------- 6 files changed, 183 deletions(-) delete mode 100644 crypto/configuration/fee.py delete mode 100644 crypto/constants.py delete mode 100644 tests/configuration/test_fee.py delete mode 100644 tests/resources/__init__.py delete mode 100644 tests/resources/test_transaction.py delete mode 100644 tests/transactions/test_signature.py diff --git a/crypto/configuration/fee.py b/crypto/configuration/fee.py deleted file mode 100644 index d6c3e528..00000000 --- a/crypto/configuration/fee.py +++ /dev/null @@ -1,25 +0,0 @@ -from crypto.constants import TRANSACTION_FEES - -fees = TRANSACTION_FEES.copy() - -def get_fee(transaction_type: int, *, default: int = 0) -> int: - """Get a fee for a given transaction type - - Args: - transaction_type (int): transaction type for which we wish to get a fee - - Returns: - int | None: transaction fee, or None if the transaction type is not found - """ - return fees.get(transaction_type, default) - -def set_fee(transaction_type: int, value: int) -> None: - """Set a fee - - Args: - transaction_type (int): transaction_type for which we wish to set a fee - value (int): fee for a given transaction type - """ - global fees - - fees[transaction_type] = value diff --git a/crypto/constants.py b/crypto/constants.py deleted file mode 100644 index dac42194..00000000 --- a/crypto/constants.py +++ /dev/null @@ -1,41 +0,0 @@ -from enum import Enum - -TRANSACTION_TRANSFER = 0 -TRANSACTION_VALIDATOR_REGISTRATION = 2 -TRANSACTION_VOTE = 3 -TRANSACTION_MULTI_SIGNATURE_REGISTRATION = 4 -TRANSACTION_MULTI_PAYMENT = 6 -TRANSACTION_VALIDATOR_RESIGNATION = 7 -TRANSACTION_USERNAME_REGISTRATION = 8 -TRANSACTION_USERNAME_RESIGNATION = 9 - -TRANSACTION_TYPES = { - TRANSACTION_TRANSFER: 'transfer', - TRANSACTION_VALIDATOR_REGISTRATION: 'validator_registration', - TRANSACTION_VOTE: 'vote', - TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 'multi_signature_registration', - TRANSACTION_MULTI_PAYMENT: 'multi_payment', - TRANSACTION_VALIDATOR_RESIGNATION: 'validator_resignation', - TRANSACTION_USERNAME_REGISTRATION: 'username_registration', - TRANSACTION_USERNAME_RESIGNATION: 'username_resignation', -} - -TRANSACTION_FEES = { - TRANSACTION_TRANSFER: 10000000, - TRANSACTION_VALIDATOR_REGISTRATION: 2500000000, - TRANSACTION_VOTE: 100000000, - TRANSACTION_MULTI_SIGNATURE_REGISTRATION: 500000000, - TRANSACTION_MULTI_PAYMENT: 10000000, - TRANSACTION_VALIDATOR_RESIGNATION: 2500000000, - TRANSACTION_USERNAME_REGISTRATION: 2500000000, - TRANSACTION_USERNAME_RESIGNATION: 2500000000, -} - - -class TRANSACTION_TYPE_GROUP(Enum): - def __int__(self): - return int(self.value) - - TEST = 0 - CORE = 1 - RESERVED = 1000 # Everything above is available to anyone diff --git a/tests/configuration/test_fee.py b/tests/configuration/test_fee.py deleted file mode 100644 index 832d8311..00000000 --- a/tests/configuration/test_fee.py +++ /dev/null @@ -1,17 +0,0 @@ -from crypto.configuration.fee import get_fee, set_fee -from crypto.constants import TRANSACTION_FEES, TRANSACTION_TRANSFER - - -def test_get_fee(): - result = get_fee(TRANSACTION_TRANSFER) - assert result == TRANSACTION_FEES[TRANSACTION_TRANSFER] - - -def test_set_fee(): - set_fee(TRANSACTION_TRANSFER, 1) - result = get_fee(TRANSACTION_TRANSFER) - assert result == 1 - # set it back to the default fee so that other tests aren't affected - set_fee(TRANSACTION_TRANSFER, TRANSACTION_FEES[TRANSACTION_TRANSFER]) - result = get_fee(TRANSACTION_TRANSFER) - assert result == TRANSACTION_FEES[TRANSACTION_TRANSFER] diff --git a/tests/resources/__init__.py b/tests/resources/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/tests/resources/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/tests/resources/test_transaction.py b/tests/resources/test_transaction.py deleted file mode 100644 index 0ac1024d..00000000 --- a/tests/resources/test_transaction.py +++ /dev/null @@ -1,23 +0,0 @@ -from crypto.transactions.transaction import Transaction - - -def test_transaction_serialize(transaction_type_0): - transaction = Transaction(**transaction_type_0) - serialized = transaction.serialize(False, True, True) - assert serialized == transaction_type_0['serialized'] - - -def test_transaction_deserialize(transaction_type_0): - transaction = Transaction() - deserialized = transaction.deserialize(transaction_type_0['serialized']) - data = deserialized.to_dict() - assert data['amount'] == transaction_type_0['amount'] - assert data['senderPublicKey'] == transaction_type_0['senderPublicKey'] - assert data['recipientId'] == transaction_type_0['recipientId'] - assert data['id'] == transaction_type_0['id'] - assert data['fee'] == transaction_type_0['fee'] - assert data['signature'] == transaction_type_0['signature'] - assert data['version'] == transaction_type_0['version'] - assert data['network'] == transaction_type_0['network'] - assert data['type'] == transaction_type_0['type'] - assert data['nonce'] == transaction_type_0['nonce'] diff --git a/tests/transactions/test_signature.py b/tests/transactions/test_signature.py deleted file mode 100644 index c9462ab2..00000000 --- a/tests/transactions/test_signature.py +++ /dev/null @@ -1,76 +0,0 @@ -from crypto.transactions.builder.transfer import Transfer -from crypto.transactions.signature import Signature - -def test_should_sign_and_verify(): - isVerified = Signature.verify( - bytes.fromhex( - Signature.sign( - bytes.fromhex("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2"), - bytes.fromhex("814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2"), - ), - ), - bytes.fromhex('814857ce48e291893feab95df02e1dbf7ad3994ba46f247f77e4eefd5d8734a2'), - bytes.fromhex('e84093c072af70004a38dd95e34def119d2348d5261228175d032e5f2070e19f'), - ) - - assert isVerified == True - -def test_should_sign_and_verify_with_ecdsa_key_with_prefix(): - isVerified = Signature.verify( - bytes.fromhex( - Signature.sign( - bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), - bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), - ), - ), - bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), - bytes.fromhex('025f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), - ) - - assert isVerified == True - -def test_should_sign_and_verify_with_ecdsa_key_without_prefix(): - isVerified = Signature.verify( - bytes.fromhex( - Signature.sign( - bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), - bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), - ), - ), - bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), - bytes.fromhex('5f7362e1baff21b8441c20b3f54583eb2f5925afada140b5a95880a2224a9d48'), - ) - - assert isVerified == True - -def test_should_not_sign_and_verify_with_wrong_ecdsa_key(): - isVerified = Signature.verify( - bytes.fromhex( - Signature.sign( - bytes.fromhex("6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297"), - bytes.fromhex("6acdb0def03305800b75e9c020e0a9b0504a543f56253f694ff35f1dce8a193f"), - ), - ), - bytes.fromhex('6616cd071ecbfe525be817d29eb1ccd6d93af0a9207356b38dcd73fcc84ff297'), - bytes.fromhex('02d076ae01c84ad5e72eb2aae9a3c60784b08cc1f0e8624fe3cc51648a163ee120'), - ) - - assert isVerified == False - -def test_transfer_transaction(): - transaction = Transfer( - recipientId='0xb693449AdDa7EFc015D87944EAE8b7C37EB1690A', - amount=1, - fee=10000000 - ) - transaction.set_type_group(1) - transaction.set_nonce(1) - transaction.sign('my super secret passphrase') - - isVerified = Signature.verify( - bytes.fromhex('d07662b9a917f158a7ad8431aff8b31a70fe3a7af562db3ead41e7b5e00b7d9a27b3faf26699096baddbb45b2f1c3b0aa180301b001e19b8eaeb4e13055eaa0c'), - transaction.transaction.to_bytes(), - bytes.fromhex('023efc1da7f315f3c533a4080e491f32cd4219731cef008976c3876539e1f192d3'), - ) - - assert isVerified == True From 917469ad1f30d4ed0e45bf3698d4c3b43bc80878 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 09:16:26 -0600 Subject: [PATCH 20/23] fix deprecation --- crypto/utils/slot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/utils/slot.py b/crypto/utils/slot.py index 23b3d080..9828b664 100644 --- a/crypto/utils/slot.py +++ b/crypto/utils/slot.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone from crypto.configuration.network import get_network @@ -9,7 +9,7 @@ def get_time(): Returns: int: difference in seconds """ - now = datetime.utcnow() + now = datetime.now(timezone.utc) network = get_network() seconds = int((now - network['epoch']).total_seconds()) return seconds @@ -17,4 +17,4 @@ def get_time(): def get_epoch(): network = get_network() - return network['epoch'] + return network['epoch'] \ No newline at end of file From 1e584a4b99f173916efcfeca1008aa28f352c3bd Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 09:17:46 -0600 Subject: [PATCH 21/23] Update slot.py --- crypto/utils/slot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypto/utils/slot.py b/crypto/utils/slot.py index 9828b664..23b3d080 100644 --- a/crypto/utils/slot.py +++ b/crypto/utils/slot.py @@ -1,4 +1,4 @@ -from datetime import datetime, timezone +from datetime import datetime from crypto.configuration.network import get_network @@ -9,7 +9,7 @@ def get_time(): Returns: int: difference in seconds """ - now = datetime.now(timezone.utc) + now = datetime.utcnow() network = get_network() seconds = int((now - network['epoch']).total_seconds()) return seconds @@ -17,4 +17,4 @@ def get_time(): def get_epoch(): network = get_network() - return network['epoch'] \ No newline at end of file + return network['epoch'] From d5d9547f4615c2da46d3ea745c1fd16b60606241 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Mon, 2 Dec 2024 09:18:35 -0600 Subject: [PATCH 22/23] Update abi_function.py --- crypto/enums/abi_function.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/crypto/enums/abi_function.py b/crypto/enums/abi_function.py index 0538184d..ece44321 100644 --- a/crypto/enums/abi_function.py +++ b/crypto/enums/abi_function.py @@ -5,16 +5,3 @@ class AbiFunction(Enum): UNVOTE = 'unvote' VALIDATOR_REGISTRATION = 'registerValidator' VALIDATOR_RESIGNATION = 'resignValidator' - - def transaction_class(self): - from crypto.transactions.types.vote import Vote - from crypto.transactions.types.unvote import Unvote - from crypto.transactions.types.validator_registration import ValidatorRegistration - from crypto.transactions.types.validator_resignation import ValidatorResignation - - return { - AbiFunction.VOTE: Vote, - AbiFunction.UNVOTE: Unvote, - AbiFunction.VALIDATOR_REGISTRATION: ValidatorRegistration, - AbiFunction.VALIDATOR_RESIGNATION: ValidatorResignation - }[self] \ No newline at end of file From 9b47db532bbad62274688b9500e726ab21116488 Mon Sep 17 00:00:00 2001 From: Alfonso Bribiesca Date: Tue, 3 Dec 2024 12:14:25 -0600 Subject: [PATCH 23/23] remove deprecated serialzier/deserializer files --- crypto/transactions/deserializers/__init__.py | 1 - crypto/transactions/deserializers/base.py | 14 ------- .../deserializers/multi_payment.py | 32 ---------------- .../multi_signature_registration.py | 34 ----------------- crypto/transactions/deserializers/transfer.py | 24 ------------ .../deserializers/username_registration.py | 25 ------------ .../deserializers/username_resignation.py | 12 ------ .../deserializers/validator_registration.py | 18 --------- .../deserializers/validator_resignation.py | 12 ------ crypto/transactions/deserializers/vote.py | 38 ------------------- crypto/transactions/serializers/__init__.py | 1 - crypto/transactions/serializers/base.py | 10 ----- .../transactions/serializers/multi_payment.py | 22 ----------- .../multi_signature_registration.py | 17 --------- crypto/transactions/serializers/transfer.py | 21 ---------- .../serializers/username_registration.py | 17 --------- .../serializers/username_resignation.py | 8 ---- .../serializers/validator_registration.py | 14 ------- .../serializers/validator_resignation.py | 8 ---- crypto/transactions/serializers/vote.py | 26 ------------- 20 files changed, 354 deletions(-) delete mode 100644 crypto/transactions/deserializers/__init__.py delete mode 100644 crypto/transactions/deserializers/base.py delete mode 100644 crypto/transactions/deserializers/multi_payment.py delete mode 100644 crypto/transactions/deserializers/multi_signature_registration.py delete mode 100644 crypto/transactions/deserializers/transfer.py delete mode 100644 crypto/transactions/deserializers/username_registration.py delete mode 100644 crypto/transactions/deserializers/username_resignation.py delete mode 100644 crypto/transactions/deserializers/validator_registration.py delete mode 100644 crypto/transactions/deserializers/validator_resignation.py delete mode 100644 crypto/transactions/deserializers/vote.py delete mode 100644 crypto/transactions/serializers/__init__.py delete mode 100644 crypto/transactions/serializers/base.py delete mode 100644 crypto/transactions/serializers/multi_payment.py delete mode 100644 crypto/transactions/serializers/multi_signature_registration.py delete mode 100644 crypto/transactions/serializers/transfer.py delete mode 100644 crypto/transactions/serializers/username_registration.py delete mode 100644 crypto/transactions/serializers/username_resignation.py delete mode 100644 crypto/transactions/serializers/validator_registration.py delete mode 100644 crypto/transactions/serializers/validator_resignation.py delete mode 100644 crypto/transactions/serializers/vote.py diff --git a/crypto/transactions/deserializers/__init__.py b/crypto/transactions/deserializers/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/crypto/transactions/deserializers/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crypto/transactions/deserializers/base.py b/crypto/transactions/deserializers/base.py deleted file mode 100644 index 6cbfbed9..00000000 --- a/crypto/transactions/deserializers/base.py +++ /dev/null @@ -1,14 +0,0 @@ -from crypto.transactions.transaction import Transaction - -class BaseDeserializer(object): - serialized: bytes - asset_offset: int - transaction: Transaction - - def __init__(self, serialized: bytes, asset_offset: int, transaction: Transaction): - self.serialized = serialized - self.asset_offset = asset_offset - self.transaction = transaction - - def deserialize(self): - raise NotImplementedError diff --git a/crypto/transactions/deserializers/multi_payment.py b/crypto/transactions/deserializers/multi_payment.py deleted file mode 100644 index 23f9adac..00000000 --- a/crypto/transactions/deserializers/multi_payment.py +++ /dev/null @@ -1,32 +0,0 @@ -from binascii import hexlify, unhexlify -from binary.unsigned_integer.reader import read_bit16, read_bit64 - -from crypto.identity.address import get_checksum_address -from crypto.transactions.deserializers.base import BaseDeserializer - -class MultiPaymentDeserializer(BaseDeserializer): - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - payment_length = read_bit16(self.serialized, starting_position) & 0xff - - self.transaction.asset['payments'] = [] - - index = 0 - - for _ in range(payment_length): - amount = read_bit64(self.serialized, offset=starting_position + 2 + index) - - recipient_start_index = (starting_position + 10 + index) * 2 - recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 40] - - self.transaction.asset['payments'].append({'amount': amount, 'recipientId': get_checksum_address('0x'+ unhexlify(recipientId).hex())}) - - index += 20 + 8 - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + 4 + (payment_length * (20 + 8)) * 2 - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/multi_signature_registration.py b/crypto/transactions/deserializers/multi_signature_registration.py deleted file mode 100644 index c6381fcc..00000000 --- a/crypto/transactions/deserializers/multi_signature_registration.py +++ /dev/null @@ -1,34 +0,0 @@ -from binascii import hexlify -from binary.unsigned_integer.reader import read_bit8 - -from crypto.transactions.deserializers.base import BaseDeserializer - -class MultiSignatureRegistrationDeserializer(BaseDeserializer): - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - self.transaction.asset = { - 'multiSignature': { - 'min': read_bit8(self.serialized, starting_position) & 0xff, - 'publicKeys': [] - } - } - - count = read_bit8(self.serialized, starting_position + 1) & 0xff - - for index in range(count): - index_start = int(self.asset_offset) + 4 - - if (index > 0): - index_start += index * 66 - public_key = hexlify(self.serialized)[index_start:index_start + 66].decode() - self.transaction.asset['multiSignature']['publicKeys'].append(public_key) - - self.transaction.signatures = [] if self.transaction.signatures is None else self.transaction.signatures - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + 4 + (count * 66) - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/transfer.py b/crypto/transactions/deserializers/transfer.py deleted file mode 100644 index eb78c876..00000000 --- a/crypto/transactions/deserializers/transfer.py +++ /dev/null @@ -1,24 +0,0 @@ -from binascii import hexlify, unhexlify -from binary.unsigned_integer.reader import read_bit32, read_bit64 - -from crypto.identity.address import get_checksum_address -from crypto.transactions.deserializers.base import BaseDeserializer - -class TransferDeserializer(BaseDeserializer): - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - self.transaction.amount = read_bit64(self.serialized, offset=starting_position) - self.transaction.expiration = read_bit32(self.serialized, offset=starting_position + 8) - - recipient_start_index = (int(self.asset_offset / 2) + 11) * 2 - recipientId = hexlify(self.serialized)[recipient_start_index:recipient_start_index + 42] - - self.transaction.recipientId = get_checksum_address(unhexlify(recipientId).hex()) - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + (8 + 4 + 20) * 2 - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/username_registration.py b/crypto/transactions/deserializers/username_registration.py deleted file mode 100644 index 7c1c6b68..00000000 --- a/crypto/transactions/deserializers/username_registration.py +++ /dev/null @@ -1,25 +0,0 @@ -from binascii import hexlify, unhexlify - -from binary.unsigned_integer.reader import read_bit8 - -from crypto.transactions.deserializers.base import BaseDeserializer - -class UsernameRegistrationDeserializer(BaseDeserializer): - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - username_length = read_bit8(self.serialized, starting_position) & 0xff - - start_index = self.asset_offset + 2 - end_index = start_index + (username_length * 2) - username = hexlify(self.serialized)[start_index:end_index] - username = unhexlify(username) - - self.transaction.asset['username'] = username.decode() - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + (username_length + 1) * 2 - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/username_resignation.py b/crypto/transactions/deserializers/username_resignation.py deleted file mode 100644 index e278b111..00000000 --- a/crypto/transactions/deserializers/username_resignation.py +++ /dev/null @@ -1,12 +0,0 @@ -from binascii import hexlify - -from crypto.transactions.deserializers.base import BaseDeserializer - -class UsernameResignationDeserializer(BaseDeserializer): - def deserialize(self): - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/validator_registration.py b/crypto/transactions/deserializers/validator_registration.py deleted file mode 100644 index 18dbebcd..00000000 --- a/crypto/transactions/deserializers/validator_registration.py +++ /dev/null @@ -1,18 +0,0 @@ -from binascii import hexlify - -from crypto.transactions.deserializers.base import BaseDeserializer - -class ValidatorRegistrationDeserializer(BaseDeserializer): - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - validator_public_key = self.serialized[starting_position:starting_position + 48] - - self.transaction.asset['validatorPublicKey'] = validator_public_key.hex() - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + (48 * 2) - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/validator_resignation.py b/crypto/transactions/deserializers/validator_resignation.py deleted file mode 100644 index 78e36371..00000000 --- a/crypto/transactions/deserializers/validator_resignation.py +++ /dev/null @@ -1,12 +0,0 @@ -from binascii import hexlify - -from crypto.transactions.deserializers.base import BaseDeserializer - -class ValidatorResignationDeserializer(BaseDeserializer): - def deserialize(self): - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset - ) - - return self.transaction diff --git a/crypto/transactions/deserializers/vote.py b/crypto/transactions/deserializers/vote.py deleted file mode 100644 index a04c1891..00000000 --- a/crypto/transactions/deserializers/vote.py +++ /dev/null @@ -1,38 +0,0 @@ -from binascii import hexlify -from binary.unsigned_integer.reader import read_bit8 - -from crypto.transactions.deserializers.base import BaseDeserializer - -class VoteDeserializer(BaseDeserializer): - def deserialize(self): - starting_position = int(self.asset_offset / 2) - - vote_length = read_bit8(self.serialized, starting_position) & 0xff - - self.transaction.asset['votes'] = [] - self.transaction.asset['unvotes'] = [] - - vote_position = starting_position + 1 - - for index in range(vote_length): - vote = self.serialized[vote_position + (index * 33):vote_position + (index * 33) + 33].hex() - - self.transaction.asset['votes'].append(vote) - - unvote_position = vote_position + (vote_length * 33) - - unvote_length = read_bit8(self.serialized, unvote_position) & 0xff - - unvote_position += 1 - - for index in range(unvote_length): - unvote = self.serialized[unvote_position + (index * 33):unvote_position + (index * 33) + 33].hex() - - self.transaction.asset['unvotes'].append(unvote) - - self.transaction.parse_signatures( - hexlify(self.serialized).decode(), - self.asset_offset + 2 + (vote_length * 66) + 2 + (unvote_length * 66) - ) - - return self.transaction diff --git a/crypto/transactions/serializers/__init__.py b/crypto/transactions/serializers/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/crypto/transactions/serializers/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crypto/transactions/serializers/base.py b/crypto/transactions/serializers/base.py deleted file mode 100644 index 84cbb955..00000000 --- a/crypto/transactions/serializers/base.py +++ /dev/null @@ -1,10 +0,0 @@ -class BaseSerializer(object): - transaction: dict - bytes_data: bytes - - def __init__(self, transaction: dict, bytes_data: bytes = bytes()): - self.transaction = transaction - self.bytes_data = bytes_data - - def serialize(self): - raise NotImplementedError diff --git a/crypto/transactions/serializers/multi_payment.py b/crypto/transactions/serializers/multi_payment.py deleted file mode 100644 index 50817681..00000000 --- a/crypto/transactions/serializers/multi_payment.py +++ /dev/null @@ -1,22 +0,0 @@ -from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit16, write_bit64 - -from crypto.transactions.serializers.base import BaseSerializer - -class MultiPaymentSerializer(BaseSerializer): - """Serializer handling multi payment data - """ - - def serialize(self) -> bytes: - self.bytes_data += write_bit16(len(self.transaction['asset']['payments'])) - - for payment in self.transaction['asset']['payments']: - recipient = payment['recipientId'][2:] - - if type(recipient) is str: - recipient = recipient.encode() - - self.bytes_data += write_bit64(payment['amount']) - self.bytes_data += write_high(recipient) - - return self.bytes_data diff --git a/crypto/transactions/serializers/multi_signature_registration.py b/crypto/transactions/serializers/multi_signature_registration.py deleted file mode 100644 index 950dfd8c..00000000 --- a/crypto/transactions/serializers/multi_signature_registration.py +++ /dev/null @@ -1,17 +0,0 @@ -from binascii import unhexlify -from binary.unsigned_integer.writer import write_bit8 - -from crypto.transactions.serializers.base import BaseSerializer - -class MultiSignatureSerializer(BaseSerializer): - """Serializer handling multi-signature data - """ - - def serialize(self) -> bytes: - public_keys_length = len(self.transaction['asset']['multiSignature']['publicKeys']) - self.bytes_data += write_bit8(self.transaction['asset']['multiSignature']['min']) - self.bytes_data += write_bit8(public_keys_length) - for key in self.transaction['asset']['multiSignature']['publicKeys']: - self.bytes_data += unhexlify(key.encode()) - - return self.bytes_data diff --git a/crypto/transactions/serializers/transfer.py b/crypto/transactions/serializers/transfer.py deleted file mode 100644 index 5a2edbb5..00000000 --- a/crypto/transactions/serializers/transfer.py +++ /dev/null @@ -1,21 +0,0 @@ -from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit32, write_bit64 - -from crypto.transactions.serializers.base import BaseSerializer - -class TransferSerializer(BaseSerializer): - """Serializer handling transfer data - """ - - def serialize(self) -> bytes: - self.bytes_data += write_bit64(self.transaction['amount']) - self.bytes_data += write_bit32(self.transaction.get('expiration', 0)) - - recipient = self.transaction['recipientId'][2:] - - if type(recipient) is str: - recipient = recipient.encode() - - self.bytes_data += write_high(recipient) - - return self.bytes_data diff --git a/crypto/transactions/serializers/username_registration.py b/crypto/transactions/serializers/username_registration.py deleted file mode 100644 index 0b5a6a2a..00000000 --- a/crypto/transactions/serializers/username_registration.py +++ /dev/null @@ -1,17 +0,0 @@ -from binascii import hexlify, unhexlify -from binary.hex.writer import write_high -from binary.unsigned_integer.writer import write_bit8 - -from crypto.transactions.serializers.base import BaseSerializer - -class UsernameRegistrationSerializer(BaseSerializer): - """Serializer handling username registration data - """ - - def serialize(self) -> bytes: - username_bytes = hexlify(self.transaction['asset']['username'].encode()) - - self.bytes_data += write_bit8(len(username_bytes) // 2) - self.bytes_data += unhexlify(username_bytes) - - return self.bytes_data diff --git a/crypto/transactions/serializers/username_resignation.py b/crypto/transactions/serializers/username_resignation.py deleted file mode 100644 index a5543181..00000000 --- a/crypto/transactions/serializers/username_resignation.py +++ /dev/null @@ -1,8 +0,0 @@ -from crypto.transactions.serializers.base import BaseSerializer - -class UsernameResignationSerializer(BaseSerializer): - """Serializer handling username resignation data - """ - - def serialize(self) -> bytes: - return self.bytes_data diff --git a/crypto/transactions/serializers/validator_registration.py b/crypto/transactions/serializers/validator_registration.py deleted file mode 100644 index 77fbbe80..00000000 --- a/crypto/transactions/serializers/validator_registration.py +++ /dev/null @@ -1,14 +0,0 @@ -from binary.hex.writer import write_high - -from crypto.transactions.serializers.base import BaseSerializer - -class ValidatorRegistrationSerializer(BaseSerializer): - """Serializer handling validator registration data - """ - - def serialize(self) -> bytes: - validator_bytes = self.transaction['asset']['validatorPublicKey'].encode() - - self.bytes_data += write_high(validator_bytes) - - return self.bytes_data diff --git a/crypto/transactions/serializers/validator_resignation.py b/crypto/transactions/serializers/validator_resignation.py deleted file mode 100644 index 38d0e307..00000000 --- a/crypto/transactions/serializers/validator_resignation.py +++ /dev/null @@ -1,8 +0,0 @@ -from crypto.transactions.serializers.base import BaseSerializer - -class ValidatorResignationSerializer(BaseSerializer): - """Serializer handling validator resignation data - """ - - def serialize(self) -> bytes: - return self.bytes_data diff --git a/crypto/transactions/serializers/vote.py b/crypto/transactions/serializers/vote.py deleted file mode 100644 index 30c6d69d..00000000 --- a/crypto/transactions/serializers/vote.py +++ /dev/null @@ -1,26 +0,0 @@ -from binascii import unhexlify -from binary.unsigned_integer.writer import write_bit8 - -from crypto.transactions.serializers.base import BaseSerializer - -class VoteSerializer(BaseSerializer): - """Serializer handling vote data - """ - - def serialize(self) -> bytes: - vote_bytes = [] - unvote_bytes = [] - - for vote in self.transaction['asset']['votes']: - vote_bytes.append(vote) - - for unvote in self.transaction['asset']['unvotes']: - unvote_bytes.append(unvote) - - self.bytes_data += write_bit8(len(self.transaction['asset']['votes'])) - self.bytes_data += unhexlify(''.join(vote_bytes)) - - self.bytes_data += write_bit8(len(self.transaction['asset']['unvotes'])) - self.bytes_data += unhexlify(''.join(unvote_bytes)) - - return self.bytes_data