Skip to content

Commit c846216

Browse files
committed
fix: none handling set_bytecode(None) silently wipes bytecode_file_id
Signed-off-by: exploreriii <133720349+exploreriii@users.noreply.github.com>
1 parent 7609136 commit c846216

2 files changed

Lines changed: 45 additions & 4 deletions

File tree

src/hiero_sdk_python/contract/contract_create_transaction.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def set_bytecode_file_id(self, bytecode_file_id: FileId | None) -> ContractCreat
117117
"""
118118
Sets the FileID of the file containing the contract bytecode.
119119
120+
The two bytecode sources share the protobuf initcodeSource oneof, so a
121+
non-None value clears any inline bytecode.
122+
120123
Args:
121124
bytecode_file_id (FileId | None): The FileID of the
122125
bytecode file.
@@ -126,7 +129,8 @@ def set_bytecode_file_id(self, bytecode_file_id: FileId | None) -> ContractCreat
126129
"""
127130
self._require_not_frozen()
128131
self.bytecode_file_id = bytecode_file_id
129-
self.bytecode = None
132+
if bytecode_file_id is not None:
133+
self.bytecode = None
130134
return self
131135

132136
def set_bytecode(self, code: bytes | None) -> ContractCreateTransaction:
@@ -136,6 +140,9 @@ def set_bytecode(self, code: bytes | None) -> ContractCreateTransaction:
136140
If the bytecode is small enough, it may be stored directly in the
137141
transaction, otherwise it should be stored in a file.
138142
143+
The two bytecode sources share the protobuf initcodeSource oneof, so a
144+
non-None value clears any bytecode file ID.
145+
139146
Args:
140147
code (bytes | None): The contract bytecode.
141148
@@ -144,7 +151,8 @@ def set_bytecode(self, code: bytes | None) -> ContractCreateTransaction:
144151
"""
145152
self._require_not_frozen()
146153
self.bytecode = code
147-
self.bytecode_file_id = None
154+
if code is not None:
155+
self.bytecode_file_id = None
148156
return self
149157

150158
def set_proxy_account_id(self, proxy_account_id: AccountId | None) -> ContractCreateTransaction:
@@ -352,11 +360,14 @@ def _build_proto_body(self):
352360
ContractCreateTransactionBody: The protobuf body for this transaction.
353361
354362
Raises:
355-
ValueError: If both staked_account_id and staked_node_id are set;
356-
the protobuf staked_id oneof would silently drop one of them.
363+
ValueError: If both staked_account_id and staked_node_id are set,
364+
or both bytecode and bytecode_file_id are set; each pair shares
365+
a protobuf oneof that would silently drop one of them.
357366
"""
358367
if self.staked_account_id is not None and self.staked_node_id is not None:
359368
raise ValueError("Specify either staked_node_id or staked_account_id, not both.")
369+
if self.bytecode is not None and self.bytecode_file_id is not None:
370+
raise ValueError("Specify either bytecode or bytecode_file_id, not both.")
360371
return ContractCreateTransactionBody(
361372
gas=self.gas,
362373
initialBalance=self.initial_balance,

tests/unit/contract_create_transaction_test.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,36 @@ def test_bytecode_setters_clear_each_other():
292292
assert contract_tx.bytecode_file_id is None
293293

294294

295+
def test_bytecode_setters_unset_with_none_without_clearing_the_other():
296+
"""Test that unsetting one bytecode source does not wipe the other."""
297+
contract_tx = ContractCreateTransaction().set_bytecode_file_id(FileId(0, 0, 123))
298+
299+
contract_tx.set_bytecode(None)
300+
301+
assert contract_tx.bytecode_file_id == FileId(0, 0, 123)
302+
assert contract_tx.bytecode is None
303+
304+
contract_tx = ContractCreateTransaction().set_bytecode(b"test bytecode")
305+
306+
contract_tx.set_bytecode_file_id(None)
307+
308+
assert contract_tx.bytecode == b"test bytecode"
309+
assert contract_tx.bytecode_file_id is None
310+
311+
312+
def test_build_proto_body_rejects_conflicting_bytecode_sources():
313+
"""Test the constructor path, where both initcodeSource fields can remain set."""
314+
contract_tx = ContractCreateTransaction(
315+
contract_params=ContractCreateParams(
316+
bytecode=b"test bytecode",
317+
bytecode_file_id=FileId(0, 0, 123),
318+
)
319+
)
320+
321+
with pytest.raises(ValueError, match="Specify either bytecode or bytecode_file_id"):
322+
contract_tx._build_proto_body()
323+
324+
295325
@pytest.mark.parametrize(
296326
"admin_key",
297327
[

0 commit comments

Comments
 (0)