Skip to content
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
6 changes: 3 additions & 3 deletions src/hiero_sdk_python/consensus/topic_create_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def __init__(
fee_exempt_keys (list[Key], optional): Optional list of fee exempt keys for the topic (PrivateKey or PublicKey).
"""
super().__init__()
self.memo: str = memo or ""
self.topic_memo: str = memo or ""
self.admin_key: Key | None = admin_key
self.submit_key: Key | None = submit_key
self.auto_renew_period: Duration = auto_renew_period or Duration(7890000)
Expand All @@ -84,7 +84,7 @@ def set_memo(self, memo: str) -> TopicCreateTransaction:
TopicCreateTransaction: The current instance for method chaining.
"""
self._require_not_frozen()
self.memo = memo
self.topic_memo = memo
return self

def set_admin_key(self, key: Key) -> TopicCreateTransaction:
Expand Down Expand Up @@ -237,7 +237,7 @@ def _build_proto_body(self) -> consensus_create_topic_pb2.ConsensusCreateTopicTr
submitKey=key_to_proto(self.submit_key),
autoRenewPeriod=(self.auto_renew_period._to_proto() if self.auto_renew_period is not None else None),
autoRenewAccount=(self.auto_renew_account._to_proto() if self.auto_renew_account is not None else None),
memo=self.memo,
memo=self.topic_memo,
custom_fees=[custom_fee._to_topic_fee_proto() for custom_fee in self.custom_fees],
fee_schedule_key=key_to_proto(self.fee_schedule_key),
fee_exempt_key_list=[key_to_proto(key) for key in self.fee_exempt_keys],
Expand Down
6 changes: 3 additions & 3 deletions src/hiero_sdk_python/consensus/topic_update_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def __init__(
"""
super().__init__()
self.topic_id: TopicId | None = topic_id
self.memo: str = memo or ""
self.topic_memo: str | None = memo
self.admin_key: Key | None = admin_key
self.submit_key: Key | None = submit_key
self.auto_renew_period: Duration | None = auto_renew_period
Expand Down Expand Up @@ -93,7 +93,7 @@ def set_memo(self, memo: str) -> TopicUpdateTransaction:
TopicUpdateTransaction: Returns the instance for method chaining.
"""
self._require_not_frozen()
self.memo = memo
self.topic_memo = memo
return self

def set_admin_key(self, key: Key) -> TopicUpdateTransaction:
Expand Down Expand Up @@ -271,7 +271,7 @@ def _build_proto_body(self) -> consensus_update_topic_pb2.ConsensusUpdateTopicTr
),
autoRenewAccount=(self.auto_renew_account._to_proto() if self.auto_renew_account else None),
expirationTime=self.expiration_time._to_protobuf() if self.expiration_time else None,
memo=_wrappers_pb2.StringValue(value=self.memo) if self.memo is not None else None,
memo=_wrappers_pb2.StringValue(value=self.topic_memo) if self.topic_memo is not None else None,
custom_fees=custom_fees,
fee_schedule_key=key_to_proto(self.fee_schedule_key) if self.fee_schedule_key else None,
fee_exempt_key_list=fee_exempt_key_list,
Expand Down
28 changes: 26 additions & 2 deletions tests/unit/topic_create_transaction_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ def test_constructor(multiple_custom_fees, key_type, use_private):
fee_exempt_keys=fee_exempt_keys,
)

assert tx.memo == "Test Topic"
assert tx.topic_memo == "Test Topic"
assert tx.admin_key == admin_key
assert tx.submit_key == submit_key
assert tx.custom_fees == multiple_custom_fees
Expand All @@ -339,7 +339,7 @@ def test_constructor(multiple_custom_fees, key_type, use_private):
def test_constructor_default_values():
"""Test constructor with default values."""
tx_default = TopicCreateTransaction()
assert tx_default.memo == ""
assert tx_default.topic_memo == ""
assert tx_default.admin_key is None
assert tx_default.submit_key is None
assert tx_default.custom_fees == []
Expand Down Expand Up @@ -627,3 +627,27 @@ def test_freeze_with_leaves_auto_renew_account_unset_without_operator(mock_clien
body = frozen_tx.build_transaction_body()

assert not body.consensusCreateTopic.HasField("autoRenewAccount")


def test_topic_memo_does_not_collide_with_transaction_memo():
"""Regression test for: topic and transaction memos remain independent."""
tx = TopicCreateTransaction(memo="my topic memo")
assert tx.topic_memo == "my topic memo"
assert tx.memo == "" # base Transaction-level memo defaults independently

tx.set_transaction_memo("some unrelated audit note")

assert tx.topic_memo == "my topic memo", "topic_memo was clobbered by set_transaction_memo()"
assert tx.memo == "some unrelated audit note"


def test_topic_memo_reflected_in_protobuf_independent_of_transaction_memo(mock_client):
"""Verify protobuf preserves separate topic and transaction memos."""
tx = TopicCreateTransaction(memo="my topic memo")
tx.set_transaction_memo("some unrelated audit note")
tx.transaction_id = TransactionId.generate(AccountId(0, 0, 1234))
frozen_tx = tx.freeze_with(mock_client)
body = frozen_tx.build_transaction_body()

assert body.consensusCreateTopic.memo == "my topic memo"
assert body.memo == "some unrelated audit note"
65 changes: 65 additions & 0 deletions tests/unit/topic_update_transaction_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,68 @@ def test_topic_update_transaction_with_all_fields(topic_id):

# Verify the receipt contains the expected values
assert receipt.status == ResponseCode.SUCCESS


def test_topic_memo_and_transaction_memo_independent_in_protobuf(
mock_account_ids,
topic_id,
):
"""Topic and transaction memos serialize independently."""

_, _, node_account_id, _, _ = mock_account_ids

tx = TopicUpdateTransaction(
topic_id=topic_id,
memo="my topic memo",
)

tx.operator_account_id = AccountId(0, 0, 2)
tx.node_account_id = node_account_id

tx.set_transaction_memo("some unrelated audit note")

body = tx.build_transaction_body()

assert body.memo == "some unrelated audit note"
assert body.consensusUpdateTopic.memo.value == "my topic memo"


def test_set_memo_updates_topic_memo_only():
"""Verify set_memo() only updates the topic memo."""
tx = TopicUpdateTransaction()
tx.set_transaction_memo("audit note")
tx.set_memo("new topic memo")

assert tx.topic_memo == "new topic memo"
assert tx.memo == "audit note"


def test_topic_memo_serialization_distinguishes_unset_and_empty(
mock_account_ids,
topic_id,
):
"""Unset and explicit empty topic memos serialize differently."""
_, _, node_account_id, _, _ = mock_account_ids

tx = TopicUpdateTransaction(topic_id=topic_id)
tx.operator_account_id = AccountId(0, 0, 2)
tx.node_account_id = node_account_id

body = tx.build_transaction_body().consensusUpdateTopic
assert not body.HasField("memo")

tx = TopicUpdateTransaction(topic_id=topic_id, memo="")
tx.operator_account_id = AccountId(0, 0, 2)
tx.node_account_id = node_account_id

body = tx.build_transaction_body().consensusUpdateTopic
assert body.HasField("memo")
assert body.memo.value == ""

tx = TopicUpdateTransaction(topic_id=topic_id, memo="hello")
tx.operator_account_id = AccountId(0, 0, 2)
tx.node_account_id = node_account_id

body = tx.build_transaction_body().consensusUpdateTopic
assert body.HasField("memo")
assert body.memo.value == "hello"
Loading