Skip to content

Commit 374d74e

Browse files
committed
refactor: use StakingInfo wrapper in ContractInfo and remove duplicate account_info changes
Signed-off-by: Mounil <mounilkankhara@gmail.com>
1 parent 2071e94 commit 374d74e

5 files changed

Lines changed: 84 additions & 167 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
2121
- Fixed duplication in GitHub bot next issue recommendations by parsing actual issue descriptions instead of blind truncation (#1658)
2222

2323
### Src
24+
- Add `staking_info` field to `ContractInfo` class to expose staking metadata using the `StakingInfo` wrapper. (#1365)
2425
- Fix `TopicInfo.__str__()` to format `expiration_time` in UTC so unit tests pass in non-UTC environments. (#1800)
2526
- Resolve CodeQL `reflected-XSS` warning in TCK JSON-RPC endpoint
2627
- Improve `keccak256` docstring formatting for better readability and consistency (#1624)
@@ -31,7 +32,6 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
3132
- Refactored contract_delete_transaction example to use Client.from_env. (#1823)
3233

3334
### Added
34-
- Add staking_info fields (`staked_account_id`, `staked_node_id`, `decline_staking_reward`) to ContractInfo class to expose staking metadata from protobuf. (#1365)
3535

3636
### Docs
3737

src/hiero_sdk_python/account/account_info.py

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
from hiero_sdk_python.account.account_id import AccountId
1010
from hiero_sdk_python.crypto.public_key import PublicKey
1111
from hiero_sdk_python.Duration import Duration
12+
from hiero_sdk_python.hapi.services.basic_types_pb2 import StakingInfo
1213
from hiero_sdk_python.hapi.services.crypto_get_info_pb2 import CryptoGetInfoResponse
1314
from hiero_sdk_python.hbar import Hbar
14-
from hiero_sdk_python.staking_info import StakingInfo
1515
from hiero_sdk_python.timestamp import Timestamp
1616
from hiero_sdk_python.tokens.token_relationship import TokenRelationship
1717

@@ -38,7 +38,9 @@ class AccountInfo:
3838
associated with this account.
3939
account_memo (Optional[str]): The memo associated with this account.
4040
owned_nfts (Optional[int]): The number of NFTs owned by this account.
41-
staking_info (Optional[StakingInfo]): The staking information for this account.
41+
staked_account_id (Optional[AccountId]): The account to which this account is staked.
42+
staked_node_id (Optional[int]): The node to which this account is staked.
43+
decline_staking_reward (bool): Whether this account declines receiving staking rewards.
4244
"""
4345

4446
account_id: Optional[AccountId] = None
@@ -54,7 +56,9 @@ class AccountInfo:
5456
account_memo: Optional[str] = None
5557
owned_nfts: Optional[int] = None
5658
max_automatic_token_associations: Optional[int] = None
57-
staking_info: Optional[StakingInfo] = None
59+
staked_account_id: Optional[AccountId] = None
60+
staked_node_id: Optional[int] = None
61+
decline_staking_reward: Optional[bool] = None
5862

5963
@classmethod
6064
def _from_proto(cls, proto: CryptoGetInfoResponse.AccountInfo) -> "AccountInfo":
@@ -96,13 +100,21 @@ def _from_proto(cls, proto: CryptoGetInfoResponse.AccountInfo) -> "AccountInfo":
96100
account_memo=proto.memo,
97101
owned_nfts=proto.ownedNfts,
98102
max_automatic_token_associations=proto.max_automatic_token_associations,
99-
staking_info=(
100-
StakingInfo._from_proto(proto.staking_info)
101-
if proto.HasField('staking_info')
102-
else None
103-
),
104103
)
105104

105+
staking_info = proto.staking_info if proto.HasField('staking_info') else None
106+
107+
if staking_info:
108+
account_info.staked_account_id = (
109+
AccountId._from_proto(staking_info.staked_account_id)
110+
if staking_info.HasField('staked_account_id') else None
111+
)
112+
account_info.staked_node_id = (
113+
staking_info.staked_node_id
114+
if staking_info.HasField('staked_node_id') else None
115+
)
116+
account_info.decline_staking_reward = staking_info.decline_reward
117+
106118
return account_info
107119

108120
def _to_proto(self) -> CryptoGetInfoResponse.AccountInfo:
@@ -135,7 +147,11 @@ def _to_proto(self) -> CryptoGetInfoResponse.AccountInfo:
135147
memo=self.account_memo,
136148
ownedNfts=self.owned_nfts,
137149
max_automatic_token_associations=self.max_automatic_token_associations,
138-
staking_info=self.staking_info._to_proto() if self.staking_info else None,
150+
staking_info=StakingInfo(
151+
staked_account_id=self.staked_account_id._to_proto() if self.staked_account_id else None,
152+
staked_node_id=self.staked_node_id if self.staked_node_id else None,
153+
decline_reward=self.decline_staking_reward
154+
),
139155
)
140156

141157
def __str__(self) -> str:
@@ -150,10 +166,11 @@ def __str__(self) -> str:
150166
(self.account_memo, "Memo"),
151167
(self.owned_nfts, "Owned NFTs"),
152168
(self.max_automatic_token_associations, "Max Automatic Token Associations"),
169+
(self.staked_account_id, "Staked Account ID"),
170+
(self.staked_node_id, "Staked Node ID"),
153171
(self.proxy_received, "Proxy Received"),
154172
(self.expiration_time, "Expiration Time"),
155173
(self.auto_renew_period, "Auto Renew Period"),
156-
(self.staking_info, "Staking Info"),
157174
]
158175

159176
# Use a list comprehension to process simple fields (reduces complexity score)
@@ -165,6 +182,9 @@ def __str__(self) -> str:
165182

166183
if self.receiver_signature_required is not None:
167184
lines.append(f"Receiver Signature Required: {self.receiver_signature_required}")
185+
186+
if self.decline_staking_reward is not None:
187+
lines.append(f"Decline Staking Reward: {self.decline_staking_reward}")
168188

169189
if self.token_relationships:
170190
lines.append(f"Token Relationships: {len(self.token_relationships)}")
@@ -182,6 +202,7 @@ def __repr__(self) -> str:
182202
f"receiver_signature_required={self.receiver_signature_required!r}, "
183203
f"owned_nfts={self.owned_nfts!r}, "
184204
f"account_memo={self.account_memo!r}, "
185-
f"staking_info={self.staking_info!r}"
205+
f"staked_node_id={self.staked_node_id!r}, "
206+
f"staked_account_id={self.staked_account_id!r}"
186207
f")"
187208
)

tests/integration/contract_info_query_e2e_test.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ def test_integration_contract_info_query_can_execute(env):
8282
assert info.auto_renew_account_id == env.operator_id, "Auto renew account ID mismatch"
8383
assert info.auto_renew_period == auto_renew_period, "Auto renew period mismatch"
8484

85+
# Verify staking_info is populated (contracts default to no staking)
86+
staking = info.staking_info
87+
if staking is None:
88+
raise AssertionError("staking_info should not be None")
89+
if staking.staked_account_id is not None:
90+
raise AssertionError("staked_account_id should be None by default")
91+
if staking.staked_node_id is not None:
92+
raise AssertionError("staked_node_id should be None by default")
93+
if staking.decline_reward is not False:
94+
raise AssertionError("decline_reward should be False by default")
95+
8596

8697
@pytest.mark.integration
8798
def test_integration_contract_info_query_get_cost(env):

tests/unit/account_info_test.py

Lines changed: 0 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99
from hiero_sdk_python.tokens.token_relationship import TokenRelationship
1010
from hiero_sdk_python.tokens.token_id import TokenId
1111
from hiero_sdk_python.hapi.services.crypto_get_info_pb2 import CryptoGetInfoResponse
12-
from hiero_sdk_python.hapi.services.basic_types_pb2 import StakingInfo as StakingInfoProto
13-
from hiero_sdk_python.staking_info import StakingInfo
1412

1513
pytestmark = pytest.mark.unit
1614

@@ -213,154 +211,3 @@ def test_str_and_repr(account_info):
213211
assert "contract_account_id='0.0.100'" in info_repr
214212
assert "account_memo='Test account memo'" in info_repr
215213

216-
217-
def test_from_proto_with_staking_info():
218-
"""Test from_proto with staking account info"""
219-
public_key = PrivateKey.generate_ed25519().public_key()
220-
proto = CryptoGetInfoResponse.AccountInfo(
221-
accountID=AccountId(0, 0, 100)._to_proto(),
222-
key=public_key._to_proto(),
223-
balance=5000000,
224-
staking_info=StakingInfoProto(
225-
staked_account_id=AccountId(0, 0, 500)._to_proto(),
226-
decline_reward=True,
227-
),
228-
)
229-
230-
account_info = AccountInfo._from_proto(proto)
231-
232-
assert account_info.staking_info is not None
233-
assert account_info.staking_info.staked_account_id == AccountId(0, 0, 500)
234-
assert account_info.staking_info.decline_reward is True
235-
236-
237-
def test_from_proto_with_staked_node_id():
238-
"""Test from_proto with staked_node_id"""
239-
public_key = PrivateKey.generate_ed25519().public_key()
240-
proto = CryptoGetInfoResponse.AccountInfo(
241-
accountID=AccountId(0, 0, 100)._to_proto(),
242-
key=public_key._to_proto(),
243-
balance=5000000,
244-
staking_info=StakingInfoProto(
245-
staked_node_id=3,
246-
decline_reward=False,
247-
),
248-
)
249-
250-
account_info = AccountInfo._from_proto(proto)
251-
252-
assert account_info.staking_info is not None
253-
assert account_info.staking_info.staked_node_id == 3
254-
assert account_info.staking_info.decline_reward is False
255-
256-
257-
def test_from_proto_with_no_staking_info():
258-
"""Test from_proto without staking info"""
259-
public_key = PrivateKey.generate_ed25519().public_key()
260-
proto = CryptoGetInfoResponse.AccountInfo(
261-
accountID=AccountId(0, 0, 100)._to_proto(),
262-
key=public_key._to_proto(),
263-
balance=5000000,
264-
)
265-
266-
account_info = AccountInfo._from_proto(proto)
267-
268-
assert account_info.staking_info is None
269-
270-
271-
def test_to_proto_with_staking_info():
272-
"""Test to_proto with staking info"""
273-
account_info = AccountInfo(
274-
account_id=AccountId(0, 0, 100),
275-
balance=Hbar.from_tinybars(5000000),
276-
staking_info=StakingInfo(
277-
staked_account_id=AccountId(0, 0, 500),
278-
decline_reward=True,
279-
),
280-
)
281-
282-
proto = account_info._to_proto()
283-
284-
assert proto.HasField('staking_info')
285-
assert proto.staking_info.HasField('staked_account_id')
286-
assert proto.staking_info.staked_account_id == AccountId(0, 0, 500)._to_proto()
287-
assert proto.staking_info.decline_reward is True
288-
289-
290-
def test_to_proto_with_staked_node_id():
291-
"""Test to_proto with staked_node_id"""
292-
account_info = AccountInfo(
293-
account_id=AccountId(0, 0, 100),
294-
balance=Hbar.from_tinybars(5000000),
295-
staking_info=StakingInfo(
296-
staked_node_id=5,
297-
decline_reward=False,
298-
),
299-
)
300-
301-
proto = account_info._to_proto()
302-
303-
assert proto.HasField('staking_info')
304-
assert proto.staking_info.staked_node_id == 5
305-
assert proto.staking_info.decline_reward is False
306-
307-
308-
def test_proto_conversion_staking_node_round_trip():
309-
"""Test proto conversion round trip with staked_node_id"""
310-
account_info = AccountInfo(
311-
account_id=AccountId(0, 0, 100),
312-
key=PrivateKey.generate_ed25519().public_key(),
313-
balance=Hbar.from_tinybars(5000000),
314-
staking_info=StakingInfo(
315-
staked_node_id=7,
316-
decline_reward=False,
317-
),
318-
)
319-
320-
converted = AccountInfo._from_proto(account_info._to_proto())
321-
322-
assert converted.account_id == account_info.account_id
323-
assert converted.balance.to_tinybars() == account_info.balance.to_tinybars()
324-
assert converted.staking_info.staked_account_id is None
325-
assert converted.staking_info.staked_node_id == 7
326-
assert converted.staking_info.decline_reward is False
327-
328-
329-
def test_proto_conversion_staking_account_round_trip():
330-
"""Test proto conversion round trip with staked_account_id"""
331-
account_info = AccountInfo(
332-
account_id=AccountId(0, 0, 100),
333-
key=PrivateKey.generate_ed25519().public_key(),
334-
balance=Hbar.from_tinybars(5000000),
335-
staking_info=StakingInfo(
336-
staked_account_id=AccountId(0, 0, 600),
337-
decline_reward=True,
338-
),
339-
)
340-
341-
converted = AccountInfo._from_proto(account_info._to_proto())
342-
343-
assert converted.account_id == account_info.account_id
344-
assert converted.balance.to_tinybars() == account_info.balance.to_tinybars()
345-
assert converted.staking_info.staked_account_id == AccountId(0, 0, 600)
346-
assert converted.staking_info.staked_node_id is None
347-
assert converted.staking_info.decline_reward is True
348-
349-
350-
def test_proto_conversion_with_staked_node_zero():
351-
"""Test proto conversion with staked_node_id set to 0"""
352-
account_info = AccountInfo(
353-
account_id=AccountId(0, 0, 100),
354-
key=PrivateKey.generate_ed25519().public_key(),
355-
balance=Hbar.from_tinybars(5000000),
356-
staking_info=StakingInfo(
357-
staked_node_id=0,
358-
decline_reward=True,
359-
),
360-
)
361-
362-
converted = AccountInfo._from_proto(account_info._to_proto())
363-
364-
assert converted.staking_info is not None
365-
assert converted.staking_info.staked_node_id == 0
366-
assert converted.staking_info.decline_reward is True

tests/unit/contract_info_test.py

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,6 @@ def test_from_proto_with_no_staking_info():
350350

351351
def test_from_proto_with_staked_node_id():
352352
"""Test from_proto with staked_node_id (staked to node)"""
353-
public_key = PrivateKey.generate_ed25519().public_key()
354353
proto = ContractGetInfoResponse.ContractInfo(
355354
contractID=ContractId(0, 0, 200)._to_proto(),
356355
accountID=AccountId(0, 0, 300)._to_proto(),
@@ -370,7 +369,7 @@ def test_from_proto_with_staked_node_id():
370369
assert contract_info.staking_info.decline_reward is True
371370

372371

373-
def test_to_proto_with_staked_account_id(token_relationship):
372+
def test_to_proto_with_staked_account_id():
374373
"""Test to_proto with staked_account_id"""
375374
contract_info = ContractInfo(
376375
contract_id=ContractId(0, 0, 200),
@@ -428,6 +427,7 @@ def test_proto_conversion_staking_node_round_trip():
428427
assert converted.balance == contract_info.balance
429428
assert converted.staking_info.staked_account_id is None
430429
assert converted.staking_info.staked_node_id == 7
430+
assert isinstance(converted.staking_info.staked_node_id, int)
431431
assert converted.staking_info.decline_reward is False
432432

433433

@@ -449,5 +449,43 @@ def test_proto_conversion_staking_account_round_trip():
449449
assert converted.account_id == contract_info.account_id
450450
assert converted.balance == contract_info.balance
451451
assert converted.staking_info.staked_account_id == AccountId(0, 0, 600)
452+
assert isinstance(converted.staking_info.staked_account_id, AccountId)
452453
assert converted.staking_info.staked_node_id is None
453454
assert converted.staking_info.decline_reward is True
455+
456+
457+
def test_proto_conversion_with_staked_node_zero():
458+
"""Test proto conversion with staked_node_id set to 0 (valid edge case)"""
459+
contract_info = ContractInfo(
460+
contract_id=ContractId(0, 0, 200),
461+
account_id=AccountId(0, 0, 300),
462+
balance=5000000,
463+
staking_info=StakingInfo(
464+
staked_node_id=0,
465+
decline_reward=True,
466+
),
467+
)
468+
469+
converted = ContractInfo._from_proto(contract_info._to_proto())
470+
471+
assert converted.staking_info is not None
472+
assert converted.staking_info.staked_node_id == 0
473+
assert isinstance(converted.staking_info.staked_node_id, int)
474+
assert converted.staking_info.decline_reward is True
475+
476+
477+
def test_proto_conversion_no_staking_info_round_trip():
478+
"""Test proto conversion round trip with no staking info (None should stay None)"""
479+
contract_info = ContractInfo(
480+
contract_id=ContractId(0, 0, 200),
481+
account_id=AccountId(0, 0, 300),
482+
balance=5000000,
483+
staking_info=None,
484+
)
485+
486+
converted = ContractInfo._from_proto(contract_info._to_proto())
487+
488+
assert converted.contract_id == contract_info.contract_id
489+
assert converted.account_id == contract_info.account_id
490+
assert converted.balance == contract_info.balance
491+
assert converted.staking_info is None

0 commit comments

Comments
 (0)