Skip to content

Commit 633a745

Browse files
feat: contract and account related hooks (#1155)
Signed-off-by: Ivaylo Nikolov <[email protected]>
1 parent 47b590e commit 633a745

28 files changed

+1654
-34
lines changed

protobufs/build.rs

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,37 @@ use regex::RegexBuilder;
1414
const DERIVE_EQ_HASH: &str = "#[derive(Eq, Hash)]";
1515
const SERVICES_FOLDER: &str = "./services/hapi/hedera-protobuf-java-api/src/main/proto/services";
1616

17+
// Recursively find all .proto files, excluding state/ and auxiliary/ subdirectories
18+
fn find_proto_files(dir: &Path) -> anyhow::Result<Vec<std::path::PathBuf>> {
19+
let mut files = Vec::new();
20+
for entry in read_dir(dir)? {
21+
let entry = entry?;
22+
let path = entry.path();
23+
24+
// Skip state/ directory (internal node state, not for SDK)
25+
// Include auxiliary/tss/ but skip other auxiliary/ subdirectories
26+
if path.is_dir() {
27+
let dir_name = path.file_name().and_then(|n| n.to_str()).unwrap_or("");
28+
29+
if dir_name == "state" {
30+
continue; // Skip state directory entirely
31+
} else if dir_name == "auxiliary" {
32+
// Only include auxiliary/tss files
33+
let tss_path = path.join("tss");
34+
if tss_path.is_dir() {
35+
files.extend(find_proto_files(&tss_path)?);
36+
}
37+
continue;
38+
}
39+
40+
files.extend(find_proto_files(&path)?);
41+
} else if path.extension().and_then(|s| s.to_str()) == Some("proto") {
42+
files.push(path);
43+
}
44+
}
45+
Ok(files)
46+
}
47+
1748
fn main() -> anyhow::Result<()> {
1849
// services is the "base" module for the hedera protobufs
1950
// in the beginning, there was only services and it was named "protos"
@@ -46,14 +77,7 @@ fn main() -> anyhow::Result<()> {
4677
)?;
4778
fs::rename(out_path.join("services"), &services_tmp_path)?;
4879

49-
let services: Vec<_> = read_dir(&services_tmp_path)?
50-
.chain(read_dir(&services_tmp_path.join("auxiliary").join("tss"))?)
51-
.filter_map(|entry| {
52-
let entry = entry.ok()?;
53-
54-
entry.file_type().ok()?.is_file().then(|| entry.path())
55-
})
56-
.collect();
80+
let services = find_proto_files(&services_tmp_path)?;
5781

5882
// iterate through each file
5983
let re_package = RegexBuilder::new(r"^package (.*);$").multi_line(true).build()?;
@@ -67,6 +91,7 @@ fn main() -> anyhow::Result<()> {
6791
let contents = contents.replace("com.hedera.hapi.services.auxiliary.history.", "");
6892
let contents = contents.replace("com.hedera.hapi.services.auxiliary.tss.", "");
6993
let contents = contents.replace("com.hedera.hapi.platform.event.", "");
94+
let contents = contents.replace("com.hedera.hapi.node.hooks.", "");
7095

7196
let contents = remove_unused_types(&contents);
7297

@@ -93,7 +118,6 @@ fn main() -> anyhow::Result<()> {
93118
.type_attribute("proto.ContractID.contract", DERIVE_EQ_HASH)
94119
.type_attribute("proto.TransactionID", DERIVE_EQ_HASH)
95120
.type_attribute("proto.Timestamp", DERIVE_EQ_HASH)
96-
.type_attribute("proto.NftTransfer", DERIVE_EQ_HASH)
97121
.type_attribute("proto.Fraction", DERIVE_EQ_HASH)
98122
.type_attribute("proto.TopicID", DERIVE_EQ_HASH)
99123
.type_attribute("proto.TokenID", DERIVE_EQ_HASH)
@@ -112,7 +136,14 @@ fn main() -> anyhow::Result<()> {
112136
.type_attribute("proto.TokenAllowance", DERIVE_EQ_HASH)
113137
.type_attribute("proto.GrantedCryptoAllowance", DERIVE_EQ_HASH)
114138
.type_attribute("proto.GrantedTokenAllowance", DERIVE_EQ_HASH)
115-
.type_attribute("proto.Duration", DERIVE_EQ_HASH);
139+
.type_attribute("proto.Duration", DERIVE_EQ_HASH)
140+
.type_attribute("proto.HookCall", DERIVE_EQ_HASH)
141+
.type_attribute("proto.HookCall.call_spec", DERIVE_EQ_HASH)
142+
.type_attribute("proto.HookCall.id", DERIVE_EQ_HASH)
143+
.type_attribute("proto.HookId", DERIVE_EQ_HASH)
144+
.type_attribute("proto.HookEntityId", DERIVE_EQ_HASH)
145+
.type_attribute("proto.HookEntityId.entity_id", DERIVE_EQ_HASH)
146+
.type_attribute("proto.EvmHookCall", DERIVE_EQ_HASH);
116147

117148
// the ResponseCodeEnum should be marked as #[non_exhaustive] so
118149
// adding variants does not trigger a breaking change

protobufs/services

Submodule services updated 2458 files

src/account/account_create_transaction.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ use hedera_proto::services::crypto_service_client::CryptoServiceClient;
55
use time::Duration;
66
use tonic::transport::Channel;
77

8+
use crate::hooks::{
9+
HookCreationDetails,
10+
LambdaEvmHook,
11+
};
812
use crate::ledger_id::RefLedgerId;
913
use crate::protobuf::{
1014
FromProtobuf,
@@ -76,6 +80,9 @@ pub struct AccountCreateTransactionData {
7680

7781
/// If true, the account declines receiving a staking reward. The default value is false.
7882
decline_staking_reward: bool,
83+
84+
/// Hooks to add immediately after creating this account.
85+
hooks: Vec<HookCreationDetails>,
7986
}
8087

8188
impl Default for AccountCreateTransactionData {
@@ -91,6 +98,7 @@ impl Default for AccountCreateTransactionData {
9198
alias: None,
9299
staked_id: None,
93100
decline_staking_reward: false,
101+
hooks: Vec::new(),
94102
}
95103
}
96104
}
@@ -293,6 +301,29 @@ impl AccountCreateTransaction {
293301
self.data_mut().decline_staking_reward = decline;
294302
self
295303
}
304+
305+
pub fn add_hook(&mut self, hook: HookCreationDetails) -> &mut Self {
306+
self.data_mut().hooks.push(hook);
307+
self
308+
}
309+
310+
pub fn add_lambda_evm_hook(&mut self, hook: LambdaEvmHook) -> &mut Self {
311+
// Helper to add a Lambda EVM hook with default extension point and hook ID
312+
use crate::hooks::HookExtensionPoint;
313+
let details =
314+
HookCreationDetails::new(HookExtensionPoint::AccountAllowanceHook, 1, Some(hook));
315+
self.data_mut().hooks.push(details);
316+
self
317+
}
318+
319+
pub fn set_hooks(&mut self, hooks: Vec<HookCreationDetails>) -> &mut Self {
320+
self.data_mut().hooks = hooks;
321+
self
322+
}
323+
324+
pub fn get_hooks(&self) -> &[HookCreationDetails] {
325+
&self.data().hooks
326+
}
296327
}
297328

298329
impl TransactionData for AccountCreateTransactionData {}
@@ -353,6 +384,11 @@ impl FromProtobuf<services::CryptoCreateTransactionBody> for AccountCreateTransa
353384
alias,
354385
staked_id: Option::from_protobuf(pb.staked_id)?,
355386
decline_staking_reward: pb.decline_reward,
387+
hooks: pb
388+
.hook_creation_details
389+
.into_iter()
390+
.map(HookCreationDetails::from_protobuf)
391+
.collect::<Result<Vec<_>, _>>()?,
356392
})
357393
}
358394
}
@@ -391,6 +427,7 @@ impl ToProtobuf for AccountCreateTransactionData {
391427
alias: self.alias.map_or(vec![], |it| it.to_bytes().to_vec()),
392428
decline_reward: self.decline_staking_reward,
393429
staked_id,
430+
hook_creation_details: self.hooks.iter().map(|h| h.to_protobuf()).collect(),
394431
}
395432
}
396433
}
@@ -417,8 +454,13 @@ mod tests {
417454
AccountCreateTransaction,
418455
AccountId,
419456
AnyTransaction,
457+
ContractId,
420458
EvmAddress,
459+
EvmHookSpec,
421460
Hbar,
461+
HookCreationDetails,
462+
HookExtensionPoint,
463+
LambdaEvmHook,
422464
PublicKey,
423465
};
424466

@@ -560,6 +602,7 @@ mod tests {
560602
20,
561603
23,
562604
],
605+
hook_creation_details: [],
563606
staked_id: Some(
564607
StakedAccountId(
565608
AccountId {
@@ -683,6 +726,7 @@ mod tests {
683726
20,
684727
23,
685728
],
729+
hook_creation_details: [],
686730
staked_id: Some(
687731
StakedNodeId(
688732
4,
@@ -710,6 +754,13 @@ mod tests {
710754
#[test]
711755
fn from_proto_body() {
712756
#[allow(deprecated)]
757+
let contract_id = ContractId::new(0, 0, 1);
758+
let hooks = vec![HookCreationDetails::new(
759+
HookExtensionPoint::AccountAllowanceHook,
760+
0,
761+
Some(LambdaEvmHook::new(EvmHookSpec::new(Some(contract_id)), vec![])),
762+
)];
763+
713764
let tx = services::CryptoCreateTransactionBody {
714765
key: Some(key().to_protobuf()),
715766
initial_balance: INITIAL_BALANCE.to_tinybars() as u64,
@@ -728,6 +779,7 @@ mod tests {
728779
staked_id: Some(services::crypto_create_transaction_body::StakedId::StakedAccountId(
729780
STAKED_ACCOUNT_ID.to_protobuf(),
730781
)),
782+
hook_creation_details: hooks.iter().map(|h| h.to_protobuf()).collect(),
731783
};
732784

733785
let tx = AccountCreateTransactionData::from_protobuf(tx).unwrap();

src/account/account_update_transaction.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use time::{
88
};
99
use tonic::transport::Channel;
1010

11+
use crate::hooks::HookCreationDetails;
1112
use crate::ledger_id::RefLedgerId;
1213
use crate::protobuf::{
1314
FromProtobuf,
@@ -89,6 +90,10 @@ pub struct AccountUpdateTransactionData {
8990

9091
/// If true, the account declines receiving a staking reward. The default value is false.
9192
decline_staking_reward: Option<bool>,
93+
94+
/// Hooks to add immediately after updating this account.
95+
hooks: Vec<HookCreationDetails>,
96+
hook_ids_to_delete: Vec<i64>,
9297
}
9398

9499
impl AccountUpdateTransaction {
@@ -271,6 +276,42 @@ impl AccountUpdateTransaction {
271276
self.data_mut().decline_staking_reward = Some(decline);
272277
self
273278
}
279+
280+
/// Returns the hooks to be created.
281+
#[must_use]
282+
pub fn get_hooks_to_create(&self) -> &[HookCreationDetails] {
283+
&self.data().hooks
284+
}
285+
286+
/// Adds a hook to be created.
287+
pub fn add_hook(&mut self, hook: HookCreationDetails) -> &mut Self {
288+
self.data_mut().hooks.push(hook);
289+
self
290+
}
291+
292+
/// Sets the hooks to be created.
293+
pub fn set_hooks(&mut self, hooks: Vec<HookCreationDetails>) -> &mut Self {
294+
self.data_mut().hooks = hooks;
295+
self
296+
}
297+
298+
/// Returns the hook IDs to be deleted.
299+
#[must_use]
300+
pub fn get_hooks_to_delete(&self) -> &[i64] {
301+
&self.data().hook_ids_to_delete
302+
}
303+
304+
/// Adds a hook ID to be deleted.
305+
pub fn delete_hook(&mut self, hook_id: i64) -> &mut Self {
306+
self.data_mut().hook_ids_to_delete.push(hook_id);
307+
self
308+
}
309+
310+
/// Sets the hook IDs to be deleted.
311+
pub fn delete_hooks(&mut self, hook_ids: Vec<i64>) -> &mut Self {
312+
self.data_mut().hook_ids_to_delete = hook_ids;
313+
self
314+
}
274315
}
275316

276317
impl TransactionData for AccountUpdateTransactionData {}
@@ -339,6 +380,12 @@ impl FromProtobuf<services::CryptoUpdateTransactionBody> for AccountUpdateTransa
339380
max_automatic_token_associations: pb.max_automatic_token_associations,
340381
staked_id: Option::from_protobuf(pb.staked_id)?,
341382
decline_staking_reward: pb.decline_reward,
383+
hooks: pb
384+
.hook_creation_details
385+
.into_iter()
386+
.map(HookCreationDetails::from_protobuf)
387+
.collect::<Result<Vec<_>, _>>()?,
388+
hook_ids_to_delete: pb.hook_ids_to_delete,
342389
})
343390
}
344391
}
@@ -382,6 +429,8 @@ impl ToProtobuf for AccountUpdateTransactionData {
382429
receive_record_threshold_field: None,
383430
receiver_sig_required_field: receiver_signature_required,
384431
staked_id,
432+
hook_creation_details: self.hooks.iter().map(|hook| hook.to_protobuf()).collect(),
433+
hook_ids_to_delete: self.hook_ids_to_delete.clone(),
385434
}
386435
}
387436
}
@@ -410,6 +459,11 @@ mod tests {
410459
AccountId,
411460
AccountUpdateTransaction,
412461
AnyTransaction,
462+
ContractId,
463+
EvmHookSpec,
464+
HookCreationDetails,
465+
HookExtensionPoint,
466+
LambdaEvmHook,
413467
PublicKey,
414468
};
415469

@@ -563,6 +617,8 @@ mod tests {
563617
100,
564618
),
565619
decline_reward: None,
620+
hook_ids_to_delete: [],
621+
hook_creation_details: [],
566622
send_record_threshold_field: None,
567623
receive_record_threshold_field: None,
568624
receiver_sig_required_field: Some(
@@ -696,6 +752,8 @@ mod tests {
696752
100,
697753
),
698754
decline_reward: None,
755+
hook_ids_to_delete: [],
756+
hook_creation_details: [],
699757
send_record_threshold_field: None,
700758
receive_record_threshold_field: None,
701759
receiver_sig_required_field: Some(
@@ -730,6 +788,14 @@ mod tests {
730788
#[test]
731789
fn from_proto_body() {
732790
#[allow(deprecated)]
791+
let contract_id = ContractId::new(0, 0, 1);
792+
let hooks = vec![HookCreationDetails::new(
793+
HookExtensionPoint::AccountAllowanceHook,
794+
0,
795+
Some(LambdaEvmHook::new(EvmHookSpec::new(Some(contract_id)), vec![])),
796+
)];
797+
let hook_ids_to_delete = vec![1, 2, 3];
798+
733799
let tx = services::CryptoUpdateTransactionBody {
734800
account_id_to_update: Some(ACCOUNT_ID.to_protobuf()),
735801
key: Some(key().to_protobuf()),
@@ -746,6 +812,8 @@ mod tests {
746812
)),
747813
proxy_fraction: 0,
748814
expiration_time: Some(EXPIRATION_TIME.to_protobuf()),
815+
hook_creation_details: hooks.iter().map(|h| h.to_protobuf()).collect(),
816+
hook_ids_to_delete,
749817
};
750818

751819
let tx = AccountUpdateTransactionData::from_protobuf(tx).unwrap();

0 commit comments

Comments
 (0)