Skip to content

Commit

Permalink
smart contract deploy transactions parser
Browse files Browse the repository at this point in the history
  • Loading branch information
popenta committed Apr 10, 2024
1 parent 4d999cc commit 24c6497
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 30 deletions.
16 changes: 16 additions & 0 deletions docs/multiversx_sdk.core.transactions_outcome_parsers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,22 @@ multiversx\_sdk.core.transactions\_outcome\_parsers.resources module
:undoc-members:
:show-inheritance:

multiversx\_sdk.core.transactions\_outcome\_parsers.smart\_contract\_transactions\_outcome\_parser module
---------------------------------------------------------------------------------------------------------

.. automodule:: multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser
:members:
:undoc-members:
:show-inheritance:

multiversx\_sdk.core.transactions\_outcome\_parsers.smart\_contract\_transactions\_outcome\_parser\_types module
----------------------------------------------------------------------------------------------------------------

.. automodule:: multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser_types
:members:
:undoc-members:
:show-inheritance:

multiversx\_sdk.core.transactions\_outcome\_parsers.token\_management\_transactions\_outcome\_parser module
-----------------------------------------------------------------------------------------------------------

Expand Down
49 changes: 49 additions & 0 deletions multiversx_sdk/converters/transactions_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@
from multiversx_sdk.converters.errors import MissingFieldError
from multiversx_sdk.core.interfaces import ITransaction
from multiversx_sdk.core.transaction import Transaction
from multiversx_sdk.core.transactions_outcome_parsers.resources import (
SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome)
from multiversx_sdk.network_providers.contract_results import \
ContractResultItem as SCResultItemOnNetwork
from multiversx_sdk.network_providers.transaction_events import \
TransactionEvent as TransactionEventOnNetwork
from multiversx_sdk.network_providers.transactions import TransactionOnNetwork


class TransactionsConverter:
Expand Down Expand Up @@ -50,6 +57,48 @@ def dictionary_to_transaction(self, dictionary: Dict[str, Any]) -> Transaction:
guardian_signature=self._bytes_from_hex(dictionary.get("guardianSignature", ""))
)

def transaction_on_network_to_outcome(self, transaction_on_network: TransactionOnNetwork) -> TransactionOutcome:
results = [self._sc_result_item_on_network_to_sc_result(item) for item in transaction_on_network.contract_results.items]
logs = TransactionLogs(
address=transaction_on_network.logs.address.to_bech32(),
events=[self._event_on_network_to_event(event) for event in transaction_on_network.logs.events]
)

return TransactionOutcome(
transaction_results=results,
transaction_logs=logs
)

def _event_on_network_to_event(self, event: TransactionEventOnNetwork) -> TransactionEvent:
address = event.address.to_bech32()
identifier = event.identifier
topics = [topic.raw for topic in event.topics]

legacy_data = event.data_payload.raw if event.data_payload else b'' or event.data.encode()
data_items = [data.raw for data in event.additional_data] if event.additional_data else []

if len(data_items) == 0:
if len(legacy_data):
data_items.append(legacy_data)

return TransactionEvent(address, identifier, topics, data_items)

def _sc_result_item_on_network_to_sc_result(self, sc_result_item: SCResultItemOnNetwork) -> SmartContractResult:
sender = sc_result_item.sender.to_bech32()
receiver = sc_result_item.receiver.to_bech32()
data = sc_result_item.data.encode()
logs = TransactionLogs(
address=sc_result_item.logs.address.to_bech32(),
events=[self._event_on_network_to_event(event) for event in sc_result_item.logs.events]
)

return SmartContractResult(
sender=sender,
receiver=receiver,
data=data,
logs=logs
)

def _ensure_mandatory_fields_for_transaction(self, dictionary: Dict[str, Any]) -> None:
sender = dictionary.get("sender", None)
if sender is None:
Expand Down
162 changes: 162 additions & 0 deletions multiversx_sdk/converters/transactions_converter_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
import base64

from multiversx_sdk.converters.transactions_converter import \
TransactionsConverter
from multiversx_sdk.core.address import Address
from multiversx_sdk.core.transaction import Transaction
from multiversx_sdk.core.transactions_outcome_parsers.resources import (
SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome)
from multiversx_sdk.network_providers.contract_results import \
ContractResultItem as ContractResultItemOnNetwork
from multiversx_sdk.network_providers.contract_results import \
ContractResults as ContractResultOnNetwork
from multiversx_sdk.network_providers.transaction_events import \
TransactionEvent as TxEventOnNetwork
from multiversx_sdk.network_providers.transaction_events import \
TransactionEventData as TxEventDataOnNetwork
from multiversx_sdk.network_providers.transaction_events import \
TransactionEventTopic as TxEventTopicOnNetwork
from multiversx_sdk.network_providers.transaction_logs import \
TransactionLogs as TxLogsOnNetwork
from multiversx_sdk.network_providers.transactions import TransactionOnNetwork


class TransactionMatcher:
Expand Down Expand Up @@ -47,3 +65,147 @@ def test_transaction_converter():
restored_tx = converter.dictionary_to_transaction(tx_as_dict)

assert TransactionMatcher(transaction) == restored_tx


def test_convert_tx_on_network_to_outcome():
converter = TransactionsConverter()

tx_on_network = TransactionOnNetwork()
tx_on_network.nonce = 7
tx_on_network.function = "hello"

event = TxEventOnNetwork()
event.identifier = "foobar"
event.data_payload = TxEventDataOnNetwork(b"foo")

logs = TxLogsOnNetwork()
logs.address = Address.new_from_bech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8")
logs.events = [event]

tx_on_network.logs = logs

# @too much gas provided for processing: gas provided = 596384500, gas used = 733010
tx_event_topic = TxEventTopicOnNetwork("QHRvbyBtdWNoIGdhcyBwcm92aWRlZCBmb3IgcHJvY2Vzc2luZzogZ2FzIHByb3ZpZGVkID0gNTk2Mzg0NTAwLCBnYXMgdXNlZCA9IDczMzAxMA==")

event = TxEventOnNetwork()
event.identifier = "writeLog"
event.topics = [tx_event_topic]
event.data_payload = TxEventDataOnNetwork(base64.b64decode("QDZmNmI="))

logs = TxLogsOnNetwork()
logs.address = Address.new_from_bech32("erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8")
logs.events = [event]

contract_result_item = ContractResultItemOnNetwork()
contract_result_item.nonce = 8
contract_result_item.data = "@6f6b@2a"
contract_result_item.logs = logs

contract_result = ContractResultOnNetwork([contract_result_item])
tx_on_network.contract_results = contract_result

actual_tx_outcome = converter.transaction_on_network_to_outcome(tx_on_network)

expected_tx_outcome = TransactionOutcome(
transaction_results=[SmartContractResult(
sender="",
receiver="",
data=b"@6f6b@2a",
logs=TransactionLogs(
address="erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8",
events=[TransactionEvent(
address="",
identifier="writeLog",
topics=[b"@too much gas provided for processing: gas provided = 596384500, gas used = 733010"],
data_items=[base64.b64decode("QDZmNmI=")]
)]
)
)],
transaction_logs=TransactionLogs(
address="erd1k2s324ww2g0yj38qn2ch2jwctdy8mnfxep94q9arncc6xecg3xaq6mjse8",
events=[
TransactionEvent(
address="",
identifier="foobar",
data_items=[b"foo"]
)]
)
)

assert actual_tx_outcome.logs.address == expected_tx_outcome.logs.address
assert actual_tx_outcome.logs.events[0].identifier == expected_tx_outcome.logs.events[0].identifier
assert actual_tx_outcome.logs.events[0].data_items == expected_tx_outcome.logs.events[0].data_items
assert actual_tx_outcome.logs.events[0].address == expected_tx_outcome.logs.events[0].address
assert actual_tx_outcome.logs.events[0].topics == expected_tx_outcome.logs.events[0].topics

assert actual_tx_outcome.transaction_results[0].sender == expected_tx_outcome.transaction_results[0].sender
assert actual_tx_outcome.transaction_results[0].receiver == expected_tx_outcome.transaction_results[0].receiver
assert actual_tx_outcome.transaction_results[0].data == expected_tx_outcome.transaction_results[0].data
assert actual_tx_outcome.transaction_results[0].logs.address == expected_tx_outcome.transaction_results[0].logs.address
assert actual_tx_outcome.transaction_results[0].logs.events[0].address == expected_tx_outcome.transaction_results[0].logs.events[0].address
assert actual_tx_outcome.transaction_results[0].logs.events[0].identifier == expected_tx_outcome.transaction_results[0].logs.events[0].identifier
assert actual_tx_outcome.transaction_results[0].logs.events[0].data_items == expected_tx_outcome.transaction_results[0].logs.events[0].data_items
assert actual_tx_outcome.transaction_results[0].logs.events[0].topics == expected_tx_outcome.transaction_results[0].logs.events[0].topics


def test_convert_tx_on_network_to_outcome_with_signal_error():
converter = TransactionsConverter()

tx_on_network = TransactionOnNetwork()
tx_on_network.nonce = 42
tx_on_network.function = "hello"

event = TxEventOnNetwork()
event.identifier = "signalError"
event.data_payload = TxEventDataOnNetwork(b"@657865637574696f6e206661696c6564")
event.additional_data = [TxEventDataOnNetwork("@657865637574696f6e206661696c6564".encode()), TxEventDataOnNetwork("foobar".encode())]
event.address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x")
first_topic = TxEventTopicOnNetwork("XmC5/yOF6ie6DD2kaJd5qPc2Ss7h2w7nvuWaxmCiiXQ=")
second_topic = TxEventTopicOnNetwork("aW5zdWZmaWNpZW50IGZ1bmRz")
event.topics = [first_topic, second_topic]

logs = TxLogsOnNetwork()
logs.address = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x")
logs.events = [event]

contract_result_item = ContractResultItemOnNetwork()
contract_result_item.nonce = 42
contract_result_item.data = "@657865637574696f6e206661696c6564"
contract_result_item.logs = logs

contract_result = ContractResultOnNetwork([contract_result_item])
tx_on_network.contract_results = contract_result

actual_tx_outcome = converter.transaction_on_network_to_outcome(tx_on_network)

expected_tx_outcome = TransactionOutcome(
transaction_results=[SmartContractResult(
sender="",
receiver="",
data="@657865637574696f6e206661696c6564".encode(),
logs=TransactionLogs(
address="erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x",
events=[TransactionEvent(
address="erd1qqqqqqqqqqqqqpgqj8k976l59n7fyth8ujl4as5uyn3twn0ha0wsge5r5x",
identifier="signalError",
topics=[
Address.new_from_bech32("erd1testnlersh4z0wsv8kjx39me4rmnvjkwu8dsaea7ukdvvc9z396qykv7z7").get_public_key(),
"insufficient funds".encode()
],
data_items=[
"@657865637574696f6e206661696c6564".encode(), "foobar".encode()
]
)]
)
)]
)

assert len(actual_tx_outcome.transaction_results) == len(expected_tx_outcome.transaction_results) == 1
assert actual_tx_outcome.transaction_results[0].sender == expected_tx_outcome.transaction_results[0].sender
assert actual_tx_outcome.transaction_results[0].receiver == expected_tx_outcome.transaction_results[0].receiver
assert actual_tx_outcome.transaction_results[0].data == expected_tx_outcome.transaction_results[0].data
assert actual_tx_outcome.transaction_results[0].logs.address == expected_tx_outcome.transaction_results[0].logs.address
assert actual_tx_outcome.transaction_results[0].logs.events[0].address == expected_tx_outcome.transaction_results[0].logs.events[0].address
assert actual_tx_outcome.transaction_results[0].logs.events[0].identifier == expected_tx_outcome.transaction_results[0].logs.events[0].identifier
assert actual_tx_outcome.transaction_results[0].logs.events[0].data_items == expected_tx_outcome.transaction_results[0].logs.events[0].data_items
assert actual_tx_outcome.transaction_results[0].logs.events[0].topics == expected_tx_outcome.transaction_results[0].logs.events[0].topics
18 changes: 10 additions & 8 deletions multiversx_sdk/core/transactions_outcome_parsers/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,6 @@ def __init__(self,
self.logs = logs


class TransactionOutcome:
def __init__(self,
transaction_results: List[SmartContractResult],
transaction_logs: TransactionLogs) -> None:
self.transaction_results = transaction_results
self.logs = transaction_logs


class SmartContractCallOutcome:
def __init__(self,
function: str = "",
Expand All @@ -53,6 +45,16 @@ def __init__(self,
self.return_code = return_code


class TransactionOutcome:
def __init__(self,
direct_smart_contract_call_outcome: SmartContractCallOutcome = SmartContractCallOutcome(),
transaction_results: List[SmartContractResult] = [],
transaction_logs: TransactionLogs = TransactionLogs()) -> None:
self.direct_smart_contract_call = direct_smart_contract_call_outcome
self.transaction_results = transaction_results
self.logs = transaction_logs


def find_events_by_identifier(transaction_outcome: TransactionOutcome, identifier: str) -> List[TransactionEvent]:
return find_events_by_predicate(transaction_outcome, lambda event: event.identifier == identifier)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from multiversx_sdk.core.address import Address
from multiversx_sdk.core.transactions_outcome_parsers.resources import (
TransactionEvent, TransactionOutcome, find_events_by_identifier)
from multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser_types import (
DeployedSmartContract, SmartContractDeployOutcome)
from multiversx_sdk.network_providers.constants import DEFAULT_ADDRESS_HRP


class SmartContractTransactionsOutcomeParser:
def __init__(self) -> None:
pass

def parse_deploy(self, transaction_outcome: TransactionOutcome) -> SmartContractDeployOutcome:
direct_call_outcome = transaction_outcome.direct_smart_contract_call
events = find_events_by_identifier(transaction_outcome, "SCDeploy")
contracts = [self._parse_sc_deploy_event(event) for event in events]

return SmartContractDeployOutcome(direct_call_outcome.return_code, direct_call_outcome.return_message, contracts)

def _parse_sc_deploy_event(self, event: TransactionEvent) -> DeployedSmartContract:
contract_address_topic = event.topics[0] if event.topics[0] else b''
owner_address_topic = event.topics[1] if event.topics[1] else b''
code_hash_topic = event.topics[2] if event.topics[2] else b''

contract_address = Address(contract_address_topic, DEFAULT_ADDRESS_HRP).to_bech32() if len(contract_address_topic) else ""
owner_address = Address(owner_address_topic, DEFAULT_ADDRESS_HRP).to_bech32() if len(owner_address_topic) else ""
code_hash = code_hash_topic

return DeployedSmartContract(contract_address, owner_address, code_hash)
Loading

0 comments on commit 24c6497

Please sign in to comment.