Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Smart Contract deploy transactions parser #22

Merged
merged 3 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
4 changes: 3 additions & 1 deletion multiversx_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from multiversx_sdk.core.transactions_outcome_parsers.resources import (
SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome,
find_events_by_identifier)
from multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser import \
SmartContractTransactionsOutcomeParser
from multiversx_sdk.core.transactions_outcome_parsers.token_management_transactions_outcome_parser import \
TokenManagementTransactionsOutcomeParser
from multiversx_sdk.network_providers.api_network_provider import \
Expand Down Expand Up @@ -69,5 +71,5 @@
"UserSigner", "Mnemonic", "UserSecretKey", "UserPublicKey", "ValidatorSecretKey",
"ValidatorPublicKey", "UserVerifier", "ValidatorSigner", "ValidatorVerifier", "ValidatorPEM",
"UserWallet", "UserPEM", "QueryRunnerAdapter", "TransactionsConverter", "DelegationTransactionsOutcomeParser",
"find_events_by_identifier"
"find_events_by_identifier", "SmartContractTransactionsOutcomeParser"
]
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
Comment on lines +135 to +148
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No way to do a shorter assert? Deep equality etc.?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No "native" way as far as I know. Will think about doing a custom deep_equal() method to use in some tests.



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
4 changes: 3 additions & 1 deletion multiversx_sdk/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
from multiversx_sdk.core.transactions_outcome_parsers.resources import (
SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome,
find_events_by_identifier)
from multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser import \
SmartContractTransactionsOutcomeParser
from multiversx_sdk.core.transactions_outcome_parsers.token_management_transactions_outcome_parser import \
TokenManagementTransactionsOutcomeParser

Expand All @@ -45,5 +47,5 @@
"RegisterAndSetAllRolesTokenType", "TransactionsFactoryConfig",
"SmartContractTransactionsFactory", "TransferTransactionsFactory",
"RelayedTransactionsFactory", "AccountTransactionsFactory", "DelegationTransactionsOutcomeParser",
"find_events_by_identifier"
"find_events_by_identifier", "SmartContractTransactionsOutcomeParser"
]
5 changes: 4 additions & 1 deletion multiversx_sdk/core/transactions_outcome_parsers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
from multiversx_sdk.core.transactions_outcome_parsers.resources import (
SmartContractResult, TransactionEvent, TransactionLogs, TransactionOutcome,
find_events_by_identifier)
from multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser import \
SmartContractTransactionsOutcomeParser
from multiversx_sdk.core.transactions_outcome_parsers.token_management_transactions_outcome_parser import \
TokenManagementTransactionsOutcomeParser

__all__ = [
"TokenManagementTransactionsOutcomeParser", "SmartContractResult", "TransactionEvent",
"TransactionLogs", "TransactionOutcome", "find_events_by_identifier", "DelegationTransactionsOutcomeParser"
"TransactionLogs", "TransactionOutcome", "find_events_by_identifier", "DelegationTransactionsOutcomeParser",
"SmartContractTransactionsOutcomeParser"
]
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:
Comment on lines +9 to +10
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in specs there is alo parse_execute do we plan to implement it also?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we'll implement that in the future. We need the abi for that but we currently don't have it in sdk-py.

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 ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the future, we have to parametrize such outcome parsers to receive the hrp.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, I was thinking the same.

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
Loading