diff --git a/docs/multiversx_sdk.core.transactions_outcome_parsers.rst b/docs/multiversx_sdk.core.transactions_outcome_parsers.rst index 51f14bb5..d0c49659 100644 --- a/docs/multiversx_sdk.core.transactions_outcome_parsers.rst +++ b/docs/multiversx_sdk.core.transactions_outcome_parsers.rst @@ -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 ----------------------------------------------------------------------------------------------------------- diff --git a/multiversx_sdk/__init__.py b/multiversx_sdk/__init__.py index ed79525b..e5c04b47 100644 --- a/multiversx_sdk/__init__.py +++ b/multiversx_sdk/__init__.py @@ -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 \ @@ -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" ] diff --git a/multiversx_sdk/converters/transactions_converter.py b/multiversx_sdk/converters/transactions_converter.py index c1a7ffce..8e6d2e34 100644 --- a/multiversx_sdk/converters/transactions_converter.py +++ b/multiversx_sdk/converters/transactions_converter.py @@ -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: @@ -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: diff --git a/multiversx_sdk/converters/transactions_converter_test.py b/multiversx_sdk/converters/transactions_converter_test.py index c062e232..0eeb8955 100644 --- a/multiversx_sdk/converters/transactions_converter_test.py +++ b/multiversx_sdk/converters/transactions_converter_test.py @@ -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: @@ -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 diff --git a/multiversx_sdk/core/__init__.py b/multiversx_sdk/core/__init__.py index 2fc3fc7a..88a2ecc9 100644 --- a/multiversx_sdk/core/__init__.py +++ b/multiversx_sdk/core/__init__.py @@ -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 @@ -45,5 +47,5 @@ "RegisterAndSetAllRolesTokenType", "TransactionsFactoryConfig", "SmartContractTransactionsFactory", "TransferTransactionsFactory", "RelayedTransactionsFactory", "AccountTransactionsFactory", "DelegationTransactionsOutcomeParser", - "find_events_by_identifier" + "find_events_by_identifier", "SmartContractTransactionsOutcomeParser" ] diff --git a/multiversx_sdk/core/transactions_outcome_parsers/__init__.py b/multiversx_sdk/core/transactions_outcome_parsers/__init__.py index 21da81f5..815ab2ea 100644 --- a/multiversx_sdk/core/transactions_outcome_parsers/__init__.py +++ b/multiversx_sdk/core/transactions_outcome_parsers/__init__.py @@ -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" ] diff --git a/multiversx_sdk/core/transactions_outcome_parsers/resources.py b/multiversx_sdk/core/transactions_outcome_parsers/resources.py index 204abe7a..b491fba7 100644 --- a/multiversx_sdk/core/transactions_outcome_parsers/resources.py +++ b/multiversx_sdk/core/transactions_outcome_parsers/resources.py @@ -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 = "", @@ -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) diff --git a/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser.py b/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser.py new file mode 100644 index 00000000..8291ad9d --- /dev/null +++ b/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser.py @@ -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) diff --git a/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser_test.py b/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser_test.py new file mode 100644 index 00000000..5e35ce8b --- /dev/null +++ b/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser_test.py @@ -0,0 +1,148 @@ +import base64 + +import pytest + +from multiversx_sdk.converters.transactions_converter import \ + TransactionsConverter +from multiversx_sdk.core.address import Address +from multiversx_sdk.core.transactions_outcome_parsers.resources import ( + SmartContractCallOutcome, TransactionEvent, TransactionLogs, + TransactionOutcome) +from multiversx_sdk.core.transactions_outcome_parsers.smart_contract_transactions_outcome_parser import \ + SmartContractTransactionsOutcomeParser +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.proxy_network_provider import \ + ProxyNetworkProvider +from multiversx_sdk.network_providers.transaction_events import \ + TransactionEvent as TxEventOnNetwork +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 TestSmartContractTransactionsOutcomeParser: + parser = SmartContractTransactionsOutcomeParser() + + def test_parse_minimalistic_deploy_outcome(self): + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqqacl85rd0gl2q8wggl8pwcyzcr4fflc5d8ssve45cj") + deployer = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + code_hash = b"abba" + + event = TransactionEvent( + identifier="SCDeploy", + topics=[contract.get_public_key(), deployer.get_public_key(), code_hash] + ) + + logs = TransactionLogs(events=[event]) + direct_sc_call_outcome = SmartContractCallOutcome(return_code="ok", return_message="ok") + + transaction_outcome = TransactionOutcome( + direct_smart_contract_call_outcome=direct_sc_call_outcome, + transaction_logs=logs + ) + + parsed = self.parser.parse_deploy(transaction_outcome) + assert parsed.return_code == "ok" + assert parsed.return_message == "ok" + assert len(parsed.contracts) == 1 + assert parsed.contracts[0].address == contract.to_bech32() + assert parsed.contracts[0].owner_address == deployer.to_bech32() + assert parsed.contracts[0].code_hash == code_hash + + def test_parse_deploy_outcome(self): + contract = Address.new_from_bech32("erd1qqqqqqqqqqqqqpgqqacl85rd0gl2q8wggl8pwcyzcr4fflc5d8ssve45cj") + deployer = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + code_hash = bytes.fromhex("abba") + + transaction_converter = TransactionsConverter() + + event = TxEventOnNetwork() + event.identifier = "SCDeploy" + event.topics = [ + TxEventTopicOnNetwork(base64.b64encode(contract.get_public_key()).decode()), + TxEventTopicOnNetwork(base64.b64encode(deployer.get_public_key()).decode()), + TxEventTopicOnNetwork(base64.b64encode(code_hash).decode()) + ] + + logs = TxLogsOnNetwork() + logs.events = [event] + + item = ContractResultItemOnNetwork() + item.nonce = 8 + item.data = "@6f6b" + contract_result = ContractResultOnNetwork([item]) + + tx_on_network = TransactionOnNetwork() + tx_on_network.nonce = 7 + tx_on_network.logs = logs + tx_on_network.contract_results = contract_result + + tx_outcome = transaction_converter.transaction_on_network_to_outcome(tx_on_network) + + parsed = self.parser.parse_deploy(tx_outcome) + + assert parsed.return_code == "" + assert parsed.return_message == "" + assert len(parsed.contracts) == 1 + assert parsed.contracts[0].address == contract.to_bech32() + assert parsed.contracts[0].owner_address == deployer.to_bech32() + assert parsed.contracts[0].code_hash == code_hash + + def test_parse_deploy_outcome_with_error(self): + deployer = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th") + transaction_converter = TransactionsConverter() + + event = TxEventOnNetwork() + event.identifier = "signalError" + event.topics = [ + TxEventTopicOnNetwork(base64.b64encode(deployer.get_public_key()).decode()), + TxEventTopicOnNetwork(base64.b64encode(b"wrong number of arguments").decode()), + ] + event.data = "@75736572206572726f72" + + logs = TxLogsOnNetwork() + logs.events = [event] + + tx_on_network = TransactionOnNetwork() + tx_on_network.nonce = 7 + tx_on_network.logs = logs + + tx_outcome = transaction_converter.transaction_on_network_to_outcome(tx_on_network) + + parsed = self.parser.parse_deploy(tx_outcome) + + assert parsed.return_code == "" + assert parsed.return_message == "" + assert len(parsed.contracts) == 0 + assert parsed.contracts == [] + + @pytest.mark.networkInteraction + def test_parse_successful_deploy(self): + successful_tx_hash = "30bc4f262543e235b73ae6db7bcbf3a54513fe3c1ed7a86af688a8f0e7fe8655" + proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") + tx_converter = TransactionsConverter() + + tx_on_network = proxy.get_transaction(successful_tx_hash) + tx_outcome = tx_converter.transaction_on_network_to_outcome(tx_on_network) + + parsed = self.parser.parse_deploy(tx_outcome) + assert parsed.contracts[0].address == "erd1qqqqqqqqqqqqqpgq29deu3uhcvuk7jhxd5cxrvh23xulkcewd8ssyf38ec" + assert parsed.contracts[0].owner_address == "erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th" + + @pytest.mark.networkInteraction + def test_parse_failed_deploy(self): + faied_tx_hash = "832780459c6c9589035dbbe5b8d1d86ca9674f4aab8379cbca9a94978e604ffd" + proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com") + tx_converter = TransactionsConverter() + + tx_on_network = proxy.get_transaction(faied_tx_hash) + tx_outcome = tx_converter.transaction_on_network_to_outcome(tx_on_network) + + parsed = self.parser.parse_deploy(tx_outcome) + assert len(parsed.contracts) == 0 + assert parsed.contracts == [] diff --git a/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser_types.py b/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser_types.py new file mode 100644 index 00000000..a47a581d --- /dev/null +++ b/multiversx_sdk/core/transactions_outcome_parsers/smart_contract_transactions_outcome_parser_types.py @@ -0,0 +1,16 @@ +from dataclasses import dataclass +from typing import List + + +@dataclass +class DeployedSmartContract: + address: str + owner_address: str + code_hash: bytes + + +@dataclass +class SmartContractDeployOutcome: + return_code: str + return_message: str + contracts: List[DeployedSmartContract] diff --git a/multiversx_sdk/core/transactions_outcome_parsers/token_management_transactions_outcome_parser_test.py b/multiversx_sdk/core/transactions_outcome_parsers/token_management_transactions_outcome_parser_test.py index 66c449b6..fe0668b7 100644 --- a/multiversx_sdk/core/transactions_outcome_parsers/token_management_transactions_outcome_parser_test.py +++ b/multiversx_sdk/core/transactions_outcome_parsers/token_management_transactions_outcome_parser_test.py @@ -26,7 +26,7 @@ def test_ensure_error(self): sc_result = SmartContractResult() logs = TransactionLogs("", [event]) - tx_outcome = TransactionOutcome([sc_result], logs) + tx_outcome = TransactionOutcome(transaction_results=[sc_result], transaction_logs=logs) with pytest.raises(ParseTransactionOutcomeError, match=re.escape("encountered signalError: ticker name is not valid (user error)")): self.parser.parse_issue_fungible(tx_outcome) @@ -49,7 +49,7 @@ def test_parse_issue_fungible(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_issue_fungible(tx_results_and_logs) assert len(outcome) == 1 @@ -98,7 +98,7 @@ def test_parse_issue_non_fungible(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [first_event, second_event, third_event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_issue_non_fungible(tx_results_and_logs) assert len(outcome) == 1 @@ -121,7 +121,7 @@ def test_parse_issue_semi_fungible(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_issue_semi_fungible(tx_results_and_logs) assert len(outcome) == 1 @@ -144,7 +144,7 @@ def test_parse_register_meta_esdt(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_register_meta_esdt(tx_results_and_logs) assert len(outcome) == 1 @@ -214,7 +214,7 @@ def test_parse_register_and_set_all_roles(self): logs=result_logs ) - tx_results_and_logs = TransactionOutcome([sc_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[sc_result], transaction_logs=tx_log) outcome = self.parser.parse_register_and_set_all_roles(tx_results_and_logs) assert len(outcome) == 2 @@ -243,7 +243,7 @@ def test_parse_set_special_role(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_set_special_role(tx_results_and_logs) assert len(outcome) == 1 @@ -270,7 +270,7 @@ def test_parse_nft_create(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_nft_create(tx_results_and_logs) assert len(outcome) == 1 @@ -296,7 +296,7 @@ def test_parse_local_mint(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_local_mint(tx_results_and_logs) assert len(outcome) == 1 @@ -323,7 +323,7 @@ def test_parse_local_burn(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_local_burn(tx_results_and_logs) assert len(outcome) == 1 @@ -343,7 +343,7 @@ def test_parse_pause(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_pause(tx_results_and_logs) assert len(outcome) == 1 @@ -360,7 +360,7 @@ def test_parse_unpause(self): ) empty_result = SmartContractResult() tx_log = TransactionLogs("erd18s6a06ktr2v6fgxv4ffhauxvptssnaqlds45qgsrucemlwc8rawq553rt2", [event]) - tx_results_and_logs = TransactionOutcome([empty_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[empty_result], transaction_logs=tx_log) outcome = self.parser.parse_unpause(tx_results_and_logs) assert len(outcome) == 1 @@ -391,7 +391,7 @@ def test_parse_freeze(self): data="RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==".encode(), logs=tx_log ) - tx_results_and_logs = TransactionOutcome([sc_result], TransactionLogs()) + tx_results_and_logs = TransactionOutcome(transaction_results=[sc_result]) outcome = self.parser.parse_freeze(tx_results_and_logs) assert len(outcome) == 1 @@ -425,7 +425,7 @@ def test_parse_unfreeze(self): data="RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==".encode(), logs=tx_log ) - tx_results_and_logs = TransactionOutcome([sc_result], TransactionLogs()) + tx_results_and_logs = TransactionOutcome(transaction_results=[sc_result]) outcome = self.parser.parse_unfreeze(tx_results_and_logs) assert len(outcome) == 1 @@ -459,7 +459,7 @@ def test_parse_wipe(self): data="RVNEVEZyZWV6ZUA0MTQxNDEyZDMyMzk2MzM0NjMzOQ==".encode(), logs=tx_log ) - tx_results_and_logs = TransactionOutcome([sc_result], TransactionLogs()) + tx_results_and_logs = TransactionOutcome(transaction_results=[sc_result]) outcome = self.parser.parse_wipe(tx_results_and_logs) assert len(outcome) == 1 @@ -488,7 +488,7 @@ def test_parse_update_attributes(self): ) tx_log = TransactionLogs("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", [event]) tx_result = SmartContractResult() - tx_results_and_logs = TransactionOutcome([tx_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[tx_result], transaction_logs=tx_log) outcome = self.parser.parse_update_attributes(tx_results_and_logs) assert len(outcome) == 1 @@ -514,7 +514,7 @@ def test_parse_add_quantity(self): ) tx_log = TransactionLogs("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", [event]) tx_result = SmartContractResult() - tx_results_and_logs = TransactionOutcome([tx_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[tx_result], transaction_logs=tx_log) outcome = self.parser.parse_add_quantity(tx_results_and_logs) assert len(outcome) == 1 @@ -540,7 +540,7 @@ def test_parse_burn_quantity(self): ) tx_log = TransactionLogs("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th", [event]) tx_result = SmartContractResult() - tx_results_and_logs = TransactionOutcome([tx_result], tx_log) + tx_results_and_logs = TransactionOutcome(transaction_results=[tx_result], transaction_logs=tx_log) outcome = self.parser.parse_burn_quantity(tx_results_and_logs) assert len(outcome) == 1 diff --git a/multiversx_sdk/network_providers/transaction_events.py b/multiversx_sdk/network_providers/transaction_events.py index 3da90bc5..2ba0e895 100644 --- a/multiversx_sdk/network_providers/transaction_events.py +++ b/multiversx_sdk/network_providers/transaction_events.py @@ -13,6 +13,7 @@ def __init__(self) -> None: self.topics: List[TransactionEventTopic] = [] self.data_payload: Optional[TransactionEventData] = None self.data: str = '' + self.additional_data: List[TransactionEventData] = [] @staticmethod def from_http_response(response: Dict[str, Any]) -> 'TransactionEvent': @@ -29,6 +30,11 @@ def from_http_response(response: Dict[str, Any]) -> 'TransactionEvent': result.data_payload = TransactionEventData(raw_data) result.data = raw_data.decode() + additional_data: Any = response.get("additionalData", []) + if additional_data is None: + additional_data = [] + result.additional_data = [TransactionEventData(base64.b64decode(data)) for data in additional_data] + return result def to_dictionary(self) -> Dict[str, Any]: @@ -37,7 +43,8 @@ def to_dictionary(self) -> Dict[str, Any]: "identifier": self.identifier, "topics": [item.hex() for item in self.topics], "data_payload": self.data_payload.hex() if self.data_payload else "", - "data": self.data + "data": self.data, + "additional_data": [data.hex() for data in self.additional_data] } diff --git a/multiversx_sdk/network_providers/transactions.py b/multiversx_sdk/network_providers/transactions.py index 8f5af353..a03264d9 100644 --- a/multiversx_sdk/network_providers/transactions.py +++ b/multiversx_sdk/network_providers/transactions.py @@ -47,6 +47,7 @@ def __init__(self) -> None: self.signature: str = "" self.status: TransactionStatus = TransactionStatus() self.timestamp: int = 0 + self.function: str = "" self.block_nonce: int = 0 self.hyperblock_nonce: int = 0 @@ -111,6 +112,7 @@ def from_http_response( result.gas_limit = response.get("gasLimit", 0) data = response.get("data", "") or "" + result.function = response.get("function", "") result.data = base64.b64decode(data).decode() result.status = TransactionStatus(response.get("status")) diff --git a/requirements.txt b/requirements.txt index f120fc08..21d4b75f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -pycryptodomex==3.16.0 -protobuf==3.20.1 -cryptography==36.0.2 +pycryptodomex==3.19.1 +protobuf==3.20.2 +cryptography==42.0.2 pynacl==1.5.0 mnemonic==0.20 requests==2.31.0