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

Fix for sending message with ESDTTransfer for transaction decoder #186

Merged
merged 4 commits into from
Jan 21, 2025
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
36 changes: 28 additions & 8 deletions multiversx_sdk/network_providers/transaction_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def __init__(self) -> None:
self.function_name: Optional[str] = None
self.function_args: Optional[list[str]] = None
self.transfers: Optional[list[TokenTransfer]] = None
self.transfer_messages: list[bytes] = []
"""
This property is set to the extra arguments passed to ESDTTransfer when transferring tokens to non-smart contract accounts.
"""

def to_dict(self) -> dict[str, Any]:
return {
Expand All @@ -24,6 +28,7 @@ def to_dict(self) -> dict[str, Any]:
"function_name": self.function_name if self.function_name else "",
"function_args": self.function_args if self.function_args else [],
"transfers": self._transfers_to_dict(),
"transfer_messages": [message.decode() for message in self.transfer_messages],
}

def _transfers_to_dict(self) -> list[dict[str, Any]]:
Expand All @@ -48,6 +53,8 @@ def _transfers_to_dict(self) -> list[dict[str, Any]]:


class TransactionDecoder:
"""Can be used for decoding custom token transfers transactions (ESDTTransfer, NFTTransfer, MultiESDTNFTTransfer)."""

def get_transaction_metadata(self, transaction: TransactionOnNetwork) -> TransactionMetadata:
metadata = self.get_normal_transaction_metadata(transaction)

Expand Down Expand Up @@ -82,6 +89,7 @@ def get_normal_transaction_metadata(self, transaction: TransactionOnNetwork) ->
if len(args) == 0 and not transaction.receiver.is_smart_contract():
metadata.function_name = "transfer"
metadata.function_args = []
metadata.transfer_messages = [bytes.fromhex(data) for data in data_components]

return metadata

Expand All @@ -106,9 +114,15 @@ def get_esdt_transaction_metadata(self, metadata: TransactionMetadata) -> Option
result.value = value
result.transfers = []

receiver = Address.new_from_bech32(result.receiver)
Copy link
Contributor

Choose a reason for hiding this comment

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

How about the same case for NFT transfers or multi transfers? In that case, I think receiver.is_smart_contract() would not be appropriate.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

did the same for nft and multi transfer.

is_receiver_sc = receiver.is_smart_contract()

if len(args) > 2:
result.function_name = self.hex_to_string(args[2])
result.function_args = args[3:]
if is_receiver_sc:
result.function_name = self.hex_to_string(args[2])
result.function_args = args[3:]
else:
result.transfer_messages = [bytes.fromhex(arg) for arg in args[2:]]

token = Token(identifier)
transfer = TokenTransfer(token, value)
Expand Down Expand Up @@ -145,8 +159,11 @@ def get_nft_transfer_metadata(self, metadata: TransactionMetadata) -> Optional[T
result.transfers = []

if len(args) > 4:
result.function_name = self.hex_to_string(args[4])
result.function_args = args[5:]
if receiver.is_smart_contract():
Copy link
Contributor

Choose a reason for hiding this comment

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

Here, receiver is the actual receiver of the tokens, not tx.receiver, correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yep, the actual receiver

result.function_name = self.hex_to_string(args[4])
result.function_args = args[5:]
else:
result.transfer_messages = [bytes.fromhex(arg) for arg in args[4:]]

token = Token(collection_identifier, self.hex_to_number(nonce))
transfer = TokenTransfer(token, value)
Expand Down Expand Up @@ -202,10 +219,13 @@ def get_multi_transfer_metadata(self, metadata: TransactionMetadata) -> Optional
result.receiver = receiver.to_bech32()

if len(args) > index:
result.function_name = self.hex_to_string(args[index])
index += 1
result.function_args = args[index:]
index += 1
if receiver.is_smart_contract():
result.function_name = self.hex_to_string(args[index])
index += 1
result.function_args = args[index:]
index += 1
else:
result.transfer_messages = [bytes.fromhex(arg) for arg in args[index:]]

return result

Expand Down
98 changes: 98 additions & 0 deletions multiversx_sdk/network_providers/transaction_decoder_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,101 @@ def test_smart_contract_call_without_args(self):
assert metadata.value == 0
assert metadata.function_name == "myEndpoint"
assert metadata.function_args == []

def test_esdt_transfer_with_receiver_in_data_field(self):
tx_to_decode = get_empty_transaction_on_network()

tx_to_decode.sender = Address.new_from_bech32("erd1pv3020g75r033shtfzkf9yaf8xx7d76qan94wkraxm4vvqankrtsam2hz7")
tx_to_decode.receiver = Address.new_from_bech32(
"erd1athw37lnw4t4d7ax9t5x9l4hvfay3qfp0k4ldxgq2wv94ln6vuesqd5mjg"
)
tx_to_decode.data = base64.b64decode(
"RVNEVFRyYW5zZmVyQDRjNDE0ZTQ0MmQzNDMwNjYzMjM2NjZAMDI2NWYwYTQ3ZGQ0NGMwMDAwQGVhZWVlOGZiZjM3NTU3NTZmYmE2MmFlODYyZmViNzYyN2E0ODgxMjE3ZGFiZjY5OTAwNTM5ODVhZmU3YTY3MzM="
)

transaction_decoder = TransactionDecoder()
metadata = transaction_decoder.get_transaction_metadata(tx_to_decode)
assert metadata.function_name is None
assert metadata.function_args is None
assert metadata.transfers
assert metadata.transfers[0].amount == 44239040000000000000
assert metadata.transfers[0].token.identifier == "LAND-40f26f"
assert metadata.transfer_messages == [
bytes.fromhex("eaeee8fbf3755756fba62ae862feb7627a4881217dabf6990053985afe7a6733")
]

def test_native_transfer(self):
tx_to_decode = get_empty_transaction_on_network()

tx_to_decode.sender = Address.new_from_bech32("erd18w6yj09l9jwlpj5cjqq9eccfgulkympv7d4rj6vq4u49j8fpwzwsvx7e85")
tx_to_decode.receiver = Address.new_from_bech32(
"erd1lkrrrn3ws9sp854kdpzer9f77eglqpeet3e3k3uxvqxw9p3eq6xqxj43r9"
)
tx_to_decode.value = 100000000
tx_to_decode.data = "abba".encode()

transaction_decoder = TransactionDecoder()
metadata = transaction_decoder.get_transaction_metadata(tx_to_decode)

assert metadata.sender == "erd18w6yj09l9jwlpj5cjqq9eccfgulkympv7d4rj6vq4u49j8fpwzwsvx7e85"
assert metadata.receiver == "erd1lkrrrn3ws9sp854kdpzer9f77eglqpeet3e3k3uxvqxw9p3eq6xqxj43r9"
assert metadata.value == 100000000
assert metadata.function_name == "transfer"
assert metadata.function_args == []
assert metadata.transfer_messages == [bytes.fromhex("abba")]

def test_esdt_transfer_separated_messages(self):
tx_to_decode = get_empty_transaction_on_network()

tx_to_decode.sender = Address.new_from_bech32("erd1pv3020g75r033shtfzkf9yaf8xx7d76qan94wkraxm4vvqankrtsam2hz7")
tx_to_decode.receiver = Address.new_from_bech32(
"erd1athw37lnw4t4d7ax9t5x9l4hvfay3qfp0k4ldxgq2wv94ln6vuesqd5mjg"
)
tx_to_decode.data = "ESDTTransfer@4c414e442d343066323666@0265f0a47dd44c0000@aaaaaa@bb".encode()

transaction_decoder = TransactionDecoder()
metadata = transaction_decoder.get_transaction_metadata(tx_to_decode)
assert metadata.function_name is None
assert metadata.function_args is None
assert metadata.transfers
assert metadata.transfers[0].amount == 44239040000000000000
assert metadata.transfers[0].token.identifier == "LAND-40f26f"
assert metadata.transfer_messages == [bytes.fromhex("aaaaaa"), bytes.fromhex("bb")]

def test_esdtnft_transfer_separated_messages(self):
tx_to_decode = get_empty_transaction_on_network()

tx_to_decode.sender = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")
tx_to_decode.receiver = Address.new_from_bech32(
"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
)
tx_to_decode.data = "ESDTNFTTransfer@4d4e592d336131636566@01@01@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@aaaaaaaaaaaaaaaaaaaaaa@aa".encode()

transaction_decoder = TransactionDecoder()
metadata = transaction_decoder.get_transaction_metadata(tx_to_decode)
assert metadata.function_name is None
assert metadata.function_args is None
assert metadata.transfers
assert metadata.transfers[0].amount == 1
assert metadata.transfers[0].token.identifier == "MNY-3a1cef"
assert metadata.transfers[0].token.nonce == 1
assert metadata.transfer_messages == [bytes.fromhex("aaaaaaaaaaaaaaaaaaaaaa"), bytes.fromhex("aa")]

def test_multi_esdtnft_transfer_separated_messages(self):
tx_to_decode = get_empty_transaction_on_network()

tx_to_decode.sender = Address.new_from_bech32("erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th")
tx_to_decode.receiver = Address.new_from_bech32(
"erd1qyu5wthldzr8wx5c9ucg8kjagg0jfs53s8nr3zpz3hypefsdd8ssycr6th"
)
tx_to_decode.data = "MultiESDTNFTTransfer@8049d639e5a6980d1cd2392abcce41029cda74a1563523a202f09641cc2618f8@01@4d4e592d336131636566@02@01@aaaaaaaa@aa".encode()

transaction_decoder = TransactionDecoder()
metadata = transaction_decoder.get_transaction_metadata(tx_to_decode)
assert metadata.function_name is None
assert metadata.function_args is None
assert metadata.transfers
assert metadata.transfers[0].amount == 1
assert metadata.transfers[0].token.identifier == "MNY-3a1cef"
assert metadata.transfers[0].token.nonce == 2
assert metadata.transfer_messages == [bytes.fromhex("aaaaaaaa"), bytes.fromhex("aa")]
Loading