Skip to content

Commit 47b590e

Browse files
feat: hooks necessary files (#1153)
Signed-off-by: Ivaylo Nikolov <[email protected]>
1 parent f45e3cd commit 47b590e

14 files changed

+1502
-0
lines changed

src/hooks/evm_hook_call.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use hedera_proto::services;
2+
3+
use crate::{
4+
FromProtobuf,
5+
ToProtobuf,
6+
};
7+
8+
/// An EVM hook call.
9+
#[derive(Debug, Clone, PartialEq, Eq)]
10+
pub struct EvmHookCall {
11+
/// The call data for the EVM hook.
12+
pub call_data: Option<Vec<u8>>,
13+
/// The gas limit for the hook call.
14+
pub gas_limit: Option<u64>,
15+
}
16+
17+
impl EvmHookCall {
18+
/// Create a new `EvmHookCall`.
19+
pub fn new(call_data: Option<Vec<u8>>) -> Self {
20+
Self { call_data, gas_limit: None }
21+
}
22+
23+
/// Set the call data for the hook.
24+
pub fn set_call_data(&mut self, call_data: Vec<u8>) -> &mut Self {
25+
self.call_data = Some(call_data);
26+
self
27+
}
28+
29+
/// Set the gas limit for the hook call.
30+
pub fn set_gas_limit(&mut self, gas_limit: u64) -> &mut Self {
31+
self.gas_limit = Some(gas_limit);
32+
self
33+
}
34+
}
35+
36+
impl ToProtobuf for EvmHookCall {
37+
type Protobuf = services::EvmHookCall;
38+
39+
fn to_protobuf(&self) -> Self::Protobuf {
40+
services::EvmHookCall {
41+
data: self.call_data.clone().unwrap_or_default(),
42+
gas_limit: self.gas_limit.unwrap_or(0),
43+
}
44+
}
45+
}
46+
47+
impl FromProtobuf<services::EvmHookCall> for EvmHookCall {
48+
fn from_protobuf(pb: services::EvmHookCall) -> crate::Result<Self> {
49+
Ok(Self {
50+
call_data: if pb.data.is_empty() { None } else { Some(pb.data) },
51+
gas_limit: Some(pb.gas_limit),
52+
})
53+
}
54+
}
55+
56+
#[cfg(test)]
57+
mod tests {
58+
use super::*;
59+
60+
#[test]
61+
fn test_evm_hook_call_creation() {
62+
let call_data = vec![1, 2, 3, 4, 5];
63+
let hook_call = EvmHookCall::new(Some(call_data.clone()));
64+
65+
assert_eq!(hook_call.call_data, Some(call_data));
66+
}
67+
68+
#[test]
69+
fn test_evm_hook_call_setters() {
70+
let mut hook_call = EvmHookCall::new(None);
71+
let call_data = vec![6, 7, 8, 9, 10];
72+
73+
hook_call.set_call_data(call_data.clone());
74+
assert_eq!(hook_call.call_data, Some(call_data));
75+
}
76+
77+
#[test]
78+
fn test_evm_hook_call_protobuf_roundtrip() {
79+
let call_data = vec![11, 12, 13, 14, 15];
80+
let original = EvmHookCall::new(Some(call_data));
81+
82+
let protobuf = original.to_protobuf();
83+
let reconstructed = EvmHookCall::from_protobuf(protobuf).unwrap();
84+
85+
assert_eq!(original, reconstructed);
86+
}
87+
}

src/hooks/evm_hook_spec.rs

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
use hedera_proto::services;
2+
3+
use crate::contract::ContractId;
4+
use crate::{
5+
FromProtobuf,
6+
ToProtobuf,
7+
};
8+
9+
/// Shared specifications for an EVM hook. May be used for any extension point.
10+
#[derive(Debug, Clone, PartialEq, Eq)]
11+
pub struct EvmHookSpec {
12+
/// The id of a contract that implements the extension point API with EVM bytecode.
13+
pub contract_id: Option<ContractId>,
14+
}
15+
16+
impl EvmHookSpec {
17+
/// Create a new `EvmHookSpec`.
18+
pub fn new(contract_id: Option<ContractId>) -> Self {
19+
Self { contract_id }
20+
}
21+
}
22+
23+
impl ToProtobuf for EvmHookSpec {
24+
type Protobuf = services::EvmHookSpec;
25+
26+
fn to_protobuf(&self) -> Self::Protobuf {
27+
services::EvmHookSpec {
28+
bytecode_source: self
29+
.contract_id
30+
.as_ref()
31+
.map(|id| services::evm_hook_spec::BytecodeSource::ContractId(id.to_protobuf())),
32+
}
33+
}
34+
}
35+
36+
impl FromProtobuf<services::EvmHookSpec> for EvmHookSpec {
37+
#[allow(unreachable_patterns)]
38+
fn from_protobuf(pb: services::EvmHookSpec) -> crate::Result<Self> {
39+
let contract_id = match pb.bytecode_source {
40+
Some(services::evm_hook_spec::BytecodeSource::ContractId(id)) => {
41+
Some(ContractId::from_protobuf(id)?)
42+
}
43+
// For future unsupported bytecode sources.
44+
Some(_) => {
45+
return Err(crate::Error::from_protobuf("unsupported EvmHookSpec.bytecode_source"));
46+
}
47+
None => None,
48+
};
49+
Ok(Self { contract_id })
50+
}
51+
}
52+
53+
#[cfg(test)]
54+
#[cfg(test)]
55+
mod tests {
56+
use super::*;
57+
use crate::contract::ContractId;
58+
59+
#[test]
60+
fn new_with_contract_id_sets_field() {
61+
let cid = ContractId::new(0, 0, 123);
62+
let spec = EvmHookSpec::new(Some(cid));
63+
assert_eq!(spec.contract_id, Some(cid));
64+
}
65+
66+
#[test]
67+
fn new_without_contract_id_sets_none() {
68+
let spec = EvmHookSpec::new(None);
69+
assert!(spec.contract_id.is_none());
70+
}
71+
72+
#[test]
73+
fn to_protobuf_with_contract_id_sets_bytecode_source() {
74+
let cid = ContractId::new(0, 0, 321);
75+
let spec = EvmHookSpec::new(Some(cid));
76+
let pb = spec.to_protobuf();
77+
78+
let got = match pb.bytecode_source {
79+
Some(hedera_proto::services::evm_hook_spec::BytecodeSource::ContractId(id)) => {
80+
Some(ContractId::from_protobuf(id).unwrap())
81+
}
82+
None => None,
83+
};
84+
85+
assert_eq!(got, Some(cid));
86+
}
87+
88+
#[test]
89+
fn to_protobuf_without_contract_id_sets_none() {
90+
let spec = EvmHookSpec::new(None);
91+
let pb = spec.to_protobuf();
92+
assert!(pb.bytecode_source.is_none());
93+
}
94+
95+
#[test]
96+
fn from_protobuf_with_contract_id_parses() {
97+
let cid = ContractId::new(0, 0, 555);
98+
let pb = hedera_proto::services::EvmHookSpec {
99+
bytecode_source: Some(
100+
hedera_proto::services::evm_hook_spec::BytecodeSource::ContractId(
101+
cid.to_protobuf(),
102+
),
103+
),
104+
};
105+
106+
let spec = EvmHookSpec::from_protobuf(pb).unwrap();
107+
assert_eq!(spec.contract_id, Some(cid));
108+
}
109+
110+
#[test]
111+
fn from_protobuf_without_contract_id_parses_none() {
112+
let pb = hedera_proto::services::EvmHookSpec { bytecode_source: None };
113+
let spec = EvmHookSpec::from_protobuf(pb).unwrap();
114+
assert!(spec.contract_id.is_none());
115+
}
116+
117+
#[test]
118+
fn protobuf_roundtrip() {
119+
let cid = ContractId::new(0, 0, 999);
120+
let original = EvmHookSpec::new(Some(cid));
121+
let pb = original.to_protobuf();
122+
let reconstructed = EvmHookSpec::from_protobuf(pb).unwrap();
123+
assert_eq!(original, reconstructed);
124+
}
125+
}

src/hooks/fungible_hook_call.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use hedera_proto::services;
2+
3+
use crate::hooks::{
4+
EvmHookCall,
5+
FungibleHookType,
6+
HookCall,
7+
};
8+
use crate::{
9+
FromProtobuf,
10+
ToProtobuf,
11+
};
12+
13+
/// A typed hook call for fungible (HBAR and FT) transfers.
14+
#[derive(Debug, Clone, PartialEq, Eq)]
15+
pub struct FungibleHookCall {
16+
/// The underlying hook call data.
17+
pub hook_call: HookCall,
18+
/// The type of fungible hook.
19+
pub hook_type: FungibleHookType,
20+
}
21+
22+
impl FungibleHookCall {
23+
/// Create a new `FungibleHookCall`.
24+
pub fn new(hook_call: HookCall, hook_type: FungibleHookType) -> Self {
25+
Self { hook_call, hook_type }
26+
}
27+
28+
/// Internal method to create from protobuf with a known type.
29+
pub(crate) fn from_protobuf_with_type(
30+
pb: services::HookCall,
31+
hook_type: FungibleHookType,
32+
) -> crate::Result<Self> {
33+
Ok(Self { hook_call: HookCall::from_protobuf(pb)?, hook_type })
34+
}
35+
}
36+
37+
impl ToProtobuf for FungibleHookCall {
38+
type Protobuf = services::HookCall;
39+
40+
fn to_protobuf(&self) -> Self::Protobuf {
41+
self.hook_call.to_protobuf()
42+
}
43+
}
44+
45+
#[cfg(test)]
46+
mod tests {
47+
use super::*;
48+
49+
#[test]
50+
fn test_fungible_hook_call_creation() {
51+
let hook_id = 123;
52+
let hook_type = FungibleHookType::PreTxAllowanceHook;
53+
let mut hook_call_obj = HookCall::new(None, None);
54+
hook_call_obj.set_hook_id(hook_id);
55+
let hook_call = FungibleHookCall::new(hook_call_obj, hook_type);
56+
57+
assert_eq!(hook_call.hook_call.hook_id, Some(hook_id));
58+
assert_eq!(hook_call.hook_type, hook_type);
59+
}
60+
61+
#[test]
62+
fn test_fungible_hook_call_with_call() {
63+
let call_data = vec![1, 2, 3, 4, 5];
64+
let evm_call = EvmHookCall::new(Some(call_data));
65+
let hook_type = FungibleHookType::PrePostTxAllowanceHook;
66+
let mut hook_call_obj = HookCall::new(None, None);
67+
hook_call_obj.set_call(evm_call.clone());
68+
let hook_call = FungibleHookCall::new(hook_call_obj, hook_type);
69+
70+
assert_eq!(hook_call.hook_call.call, Some(evm_call));
71+
assert_eq!(hook_call.hook_type, hook_type);
72+
}
73+
}

src/hooks/fungible_hook_type.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/// Types of fungible (HBAR and FT) hooks.
2+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3+
#[repr(u8)]
4+
pub enum FungibleHookType {
5+
/// A single call made before attempting the transfer.
6+
PreTxAllowanceHook = 0,
7+
/// Two calls - first before attempting the transfer (allowPre), and second after
8+
/// attempting the transfer (allowPost).
9+
PrePostTxAllowanceHook = 1,
10+
}
11+
12+
impl FungibleHookType {
13+
/// Returns the numeric value of the hook type.
14+
pub fn value(&self) -> u8 {
15+
*self as u8
16+
}
17+
}
18+
19+
#[cfg(test)]
20+
mod tests {
21+
use super::*;
22+
23+
#[test]
24+
fn test_fungible_hook_type_values() {
25+
assert_eq!(FungibleHookType::PreTxAllowanceHook.value(), 0);
26+
assert_eq!(FungibleHookType::PrePostTxAllowanceHook.value(), 1);
27+
}
28+
}

0 commit comments

Comments
 (0)