Skip to content

Commit 15ac433

Browse files
authored
feat(tck): Impl Tck endpoint for createAccount (hiero-ledger#1978)
Signed-off-by: Manish Dait <daitmanish88@gmail.com>
1 parent 7de4f34 commit 15ac433

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2722
-614
lines changed

.codacy.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
---
2+
exclude_paths:
3+
- "tck/**"
4+
25
engines:
36
bandit:
47
enabled: true

.github/workflows/tck-test.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: TCK Unit Test
2+
3+
on:
4+
push:
5+
branches:
6+
- "main"
7+
paths:
8+
- "tck/**"
9+
- "tests/tck/**"
10+
pull_request:
11+
paths:
12+
- "tck/**"
13+
- "tests/tck/**"
14+
workflow_dispatch: {}
15+
16+
permissions:
17+
contents: read
18+
19+
20+
jobs:
21+
tck-unit-test:
22+
name: Tck Unit Test
23+
runs-on: hl-sdk-py-lin-md
24+
25+
steps:
26+
- name: Harden the runner (Audit all outbound calls)
27+
uses: step-security/harden-runner@58077d3c7e43986b6b15fba718e8ea69e387dfcc # v2.15.1
28+
with:
29+
egress-policy: audit
30+
31+
- name: Checkout repository
32+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
33+
34+
- name: Set up Python
35+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
36+
with:
37+
python-version: "3.14"
38+
39+
- name: Install uv
40+
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
41+
with:
42+
enable-cache: true
43+
44+
- name: Install dependencies
45+
run: uv sync --all-extras --dev
46+
47+
- name: Generate Proto Files
48+
run: uv run generate_proto.py
49+
50+
- name: Run unit tests
51+
run: |
52+
uv run pytest tests/tck -v

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ This changelog is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.
99
### Src
1010
- Fix the TransactionGetReceiptQuery to raise ReceiptStatusError for the non-retryable and non success receipt status
1111
- Refactor `AccountInfo` to use the existing `StakingInfo` wrapper class instead of flattened staking fields. Access is now via `info.staking_info.staked_account_id`, `info.staking_info.staked_node_id`, and `info.staking_info.decline_reward`. The old flat accessors (`info.staked_account_id`, `info.staked_node_id`, `info.decline_staking_reward`) are still available as deprecated properties and will emit a `DeprecationWarning`. (#1366)
12-
12+
- Added abstract `Key` supper class to handle various proto Keys.
1313

1414
### Examples
1515

1616
### Tests
17-
17+
- Added TCK endpoint for the createAccount method
1818

1919
### Docs
2020

codecov.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,8 @@ coverage:
1010
target: 92%
1111
threshold: 2%
1212

13+
ignore:
14+
- "tck/**"
15+
1316
comment:
1417
layout: "reach, diff, flags"

src/hiero_sdk_python/account/account_create_transaction.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@
22
AccountCreateTransaction class.
33
"""
44

5+
import ctypes
56
from typing import Optional, Union
67
import warnings
78

89
from hiero_sdk_python.account.account_id import AccountId
910
from hiero_sdk_python.channels import _Channel
1011
from hiero_sdk_python.crypto.evm_address import EvmAddress
11-
from hiero_sdk_python.crypto.public_key import PublicKey
12+
from hiero_sdk_python.crypto.key import Key
1213
from hiero_sdk_python.crypto.private_key import PrivateKey
1314
from hiero_sdk_python.Duration import Duration
1415
from hiero_sdk_python.executable import _Method
15-
from hiero_sdk_python.hapi.services import crypto_create_pb2, duration_pb2, transaction_pb2, basic_types_pb2
16+
from hiero_sdk_python.hapi.services import crypto_create_pb2, duration_pb2, transaction_pb2
1617
from hiero_sdk_python.hapi.services.schedulable_transaction_body_pb2 import (
1718
SchedulableTransactionBody,
1819
)
1920
from hiero_sdk_python.hbar import Hbar
2021
from hiero_sdk_python.transaction.transaction import Transaction
21-
from hiero_sdk_python.utils.key_utils import Key, key_to_proto
2222

2323
AUTO_RENEW_PERIOD = Duration(7890000) # around 90 days in seconds
2424
DEFAULT_TRANSACTION_FEE = Hbar(3).to_tinybars() # 3 Hbars
@@ -258,12 +258,12 @@ def set_staked_account_id(
258258
"""
259259
self._require_not_frozen()
260260
if isinstance(account_id, str):
261-
self.staked_account_id = AccountId.from_string(account_id)
262-
elif isinstance(account_id, AccountId):
263-
self.staked_account_id = account_id
264-
else:
261+
account_id = AccountId.from_string(account_id)
262+
elif not isinstance(account_id, AccountId):
265263
raise TypeError("account_id must be of type str or AccountId")
266-
264+
265+
self.staked_account_id = account_id
266+
self.staked_node_id = None
267267
return self
268268

269269
def set_staked_node_id(self, node_id: int) -> "AccountCreateTransaction":
@@ -281,6 +281,7 @@ def set_staked_node_id(self, node_id: int) -> "AccountCreateTransaction":
281281
raise TypeError("node_id must be of type int")
282282

283283
self.staked_node_id = node_id
284+
self.staked_account_id = None
284285
return self
285286

286287
def set_decline_staking_reward(
@@ -315,21 +316,22 @@ def _build_proto_body(self) -> crypto_create_pb2.CryptoCreateTransactionBody:
315316
ValueError: If required fields are missing.
316317
TypeError: If initial_balance is an invalid type.
317318
"""
318-
if not self.key:
319-
raise ValueError("Key must be set before building the transaction.")
320319

321320
if isinstance(self.initial_balance, Hbar):
322321
initial_balance_tinybars = self.initial_balance.to_tinybars()
323322
elif isinstance(self.initial_balance, int):
324323
initial_balance_tinybars = self.initial_balance
325324
else:
326325
raise TypeError("initial_balance must be Hbar or int (tinybars).")
327-
328-
proto_key = key_to_proto(self.key)
326+
327+
# Check for overflow
328+
if initial_balance_tinybars >= (2**64):
329+
raise OverflowError(f"Value {initial_balance_tinybars} exceeds 64-bit unsigned integer limit.")
329330

330331
proto_body = crypto_create_pb2.CryptoCreateTransactionBody(
331-
key=proto_key,
332-
initialBalance=initial_balance_tinybars,
332+
key=self.key.to_proto_key() if self.key is not None else None,
333+
# triggers an INVALID_INITIAL_BALANCE pre-check error instead of a local error.
334+
initialBalance=ctypes.c_uint64(initial_balance_tinybars).value,
333335
receiverSigRequired=self.receiver_signature_required,
334336
autoRenewPeriod=duration_pb2.Duration(seconds=self.auto_renew_period.seconds),
335337
memo=self.account_memo,
@@ -338,9 +340,12 @@ def _build_proto_body(self) -> crypto_create_pb2.CryptoCreateTransactionBody:
338340
decline_reward=self.decline_staking_reward
339341
)
340342

341-
if self.staked_account_id:
343+
if self.staked_node_id is not None and self.staked_account_id is not None:
344+
raise ValueError("Specify either staked_node_id or staked_account_id, not both.")
345+
346+
if self.staked_account_id is not None:
342347
proto_body.staked_account_id.CopyFrom(self.staked_account_id._to_proto())
343-
elif self.staked_node_id:
348+
elif self.staked_node_id is not None:
344349
proto_body.staked_node_id = self.staked_node_id
345350

346351
return proto_body

src/hiero_sdk_python/contract/contract_id.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import TYPE_CHECKING, Optional
1111

1212
from hiero_sdk_python.crypto.evm_address import EvmAddress
13+
from hiero_sdk_python.crypto.key import Key
1314
from hiero_sdk_python.hapi.services import basic_types_pb2
1415
from hiero_sdk_python.utils.entity_id_helper import (
1516
parse_from_string,
@@ -26,7 +27,7 @@
2627

2728

2829
@dataclass(frozen=True)
29-
class ContractId:
30+
class ContractId(Key):
3031
"""
3132
Represents a unique contract ID on the Hedera network.
3233
@@ -90,6 +91,15 @@ def _to_proto(self):
9091
contractNum=self.contract,
9192
evm_address=self.evm_address,
9293
)
94+
95+
def to_proto_key(self) -> basic_types_pb2.Key:
96+
"""
97+
Convert the ContractId instance to a protobuf Key object.
98+
99+
Returns:
100+
basic_types_pb2.Key: The protobuf object of Key
101+
"""
102+
return basic_types_pb2.Key(contractID=self._to_proto())
93103

94104
@classmethod
95105
def from_string(cls, contract_id_str: str) -> "ContractId":
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from dataclasses import dataclass
2+
from typing import TYPE_CHECKING
3+
4+
from hiero_sdk_python.contract.contract_id import ContractId
5+
from hiero_sdk_python.hapi.services import basic_types_pb2
6+
from hiero_sdk_python.utils.entity_id_helper import format_to_string_with_checksum
7+
8+
if TYPE_CHECKING:
9+
from hiero_sdk_python.client.client import Client
10+
11+
@dataclass(frozen=True)
12+
class DelegateContractId(ContractId):
13+
"""
14+
Represents a delegatable contract identifier used as a key in the Hiero network.
15+
16+
A DelegateContractId is a permissive key type that designates a smart contract
17+
authorized to sign a transaction if it is the recipient of the active message
18+
frame. Unlike a standard ContractID, this key type does not require the code
19+
executing in the current frame to belong to the specified contract.
20+
"""
21+
22+
def to_proto_key(self) -> basic_types_pb2.Key:
23+
return basic_types_pb2.Key(delegatable_contract_id=self._to_proto())
24+
25+
26+
27+
def __str__(self) -> str:
28+
"""
29+
Returns the string representation of the DelegateContractId.
30+
31+
Format will be 'shard.realm.contract' or 'shard.realm.evm_address_hex'
32+
if evm_address is set. Does not include a checksum.
33+
34+
Returns:
35+
str: The string representation of the ContractId.
36+
"""
37+
if self.evm_address is not None:
38+
return f"{self.shard}.{self.realm}.{self.evm_address.hex()}"
39+
40+
return f"{self.shard}.{self.realm}.{self.contract}"
41+
42+
def __repr__(self) -> str:
43+
"""
44+
Returns a detailed string representation of the ContractId for debugging.
45+
46+
Returns:
47+
str: DelegateContractId(shard=X, realm=Y, contract=Z) or
48+
DelegateContractId(shard=X, realm=Y, evm_address=...) if evm_address is set.
49+
"""
50+
if self.evm_address is not None:
51+
return f"DelegateContractId(shard={self.shard}, realm={self.realm}, evm_address={self.evm_address.hex()})"
52+
53+
return f"DelegateContractId(shard={self.shard}, realm={self.realm}, contract={self.contract})"
54+
55+
56+
def to_string_with_checksum(self, client: "Client") -> str:
57+
"""
58+
Generates a string representation with a network-specific checksum.
59+
60+
Format: 'shard.realm.contract-checksum' (e.g., "0.0.123-vfmkw").
61+
62+
Args:
63+
client (Client): The client instance used to generate the
64+
network-specific checksum.
65+
66+
Returns:
67+
str: The string representation with checksum.
68+
69+
Raises:
70+
ValueError: If the DelegateContractId has an `evm_address` set,
71+
as checksums cannot be applied to EVM addresses.
72+
"""
73+
if self.evm_address is not None:
74+
raise ValueError(
75+
"to_string_with_checksum cannot be applied to DelegateContractId with evm_address"
76+
)
77+
78+
return format_to_string_with_checksum(
79+
self.shard, self.realm, self.contract, client
80+
)
81+

src/hiero_sdk_python/crypto/evm_address.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
class EvmAddress:
1+
from hiero_sdk_python.crypto.key import Key
2+
3+
4+
class EvmAddress(Key):
25
"""
36
Represents a 20-byte EVM address derived from the rightmost 20 bytes of
47
32 byte Keccak-256 hash of an ECDSA public key.
@@ -34,6 +37,10 @@ def from_string(cls, evm_address: str) -> "EvmAddress":
3437
def from_bytes(cls, address_bytes: "bytes") -> "EvmAddress":
3538
"""Create an EvmAddress from raw bytes."""
3639
return cls(address_bytes)
40+
41+
42+
def to_proto_key(self):
43+
raise RuntimeError("to_proto_key() not implemented for EvmAddress")
3744

3845
def to_string(self) -> str:
3946
"""Return the EVM address as a hex string"""

0 commit comments

Comments
 (0)