1use std::collections::BTreeMap;
2
3use alloy_primitives::LogData;
4use serde::{Deserialize, Serialize};
5
6use crate::{digest::Digest, ByteArray};
7
8use super::{ChainName, ComponentID, ServiceID, WorkflowID};
9
10#[derive(Serialize, Deserialize, Clone, Debug)]
11#[serde(rename_all = "snake_case")]
12pub struct Service {
13 pub id: ServiceID,
15
16 pub name: String,
18
19 pub components: BTreeMap<ComponentID, Component>,
22
23 pub workflows: BTreeMap<WorkflowID, Workflow>,
26
27 pub status: ServiceStatus,
28
29 pub config: ServiceConfig,
30}
31
32impl Service {
33 pub fn new_simple(
34 id: ServiceID,
35 name: Option<String>,
36 trigger: Trigger,
37 component_digest: Digest,
38 submit: Submit,
39 config: Option<ServiceConfig>,
40 ) -> Self {
41 let component_id = ComponentID::default();
42 let workflow_id = WorkflowID::default();
43
44 let workflow = Workflow {
45 trigger,
46 component: component_id,
47 submit,
48 };
49
50 let component = Component {
51 wasm: component_digest,
52 permissions: Permissions::default(),
53 };
54
55 let components = BTreeMap::from([(workflow.component.clone(), component)]);
56
57 let workflows = BTreeMap::from([(workflow_id, workflow)]);
58
59 Self {
60 name: name.unwrap_or_else(|| id.to_string()),
61 id,
62 components,
63 workflows,
64 status: ServiceStatus::Active,
65 config: config.unwrap_or_default(),
66 }
67 }
68}
69
70#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
71#[serde(rename_all = "snake_case")]
72pub struct Component {
73 pub wasm: Digest,
74 pub permissions: Permissions,
77}
78
79#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
82#[serde(rename_all = "snake_case")]
83pub struct Workflow {
84 pub trigger: Trigger,
85 pub component: ComponentID,
87 pub submit: Submit,
89}
90
91#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
93#[serde(rename_all = "snake_case")]
94pub enum Trigger {
95 CosmosContractEvent {
97 address: layer_climb_address::Address,
98 chain_name: ChainName,
99 event_type: String,
100 },
101 EthContractEvent {
102 address: alloy_primitives::Address,
103 chain_name: ChainName,
104 event_hash: ByteArray<32>,
105 },
106 Manual,
108}
109
110#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
112pub enum TriggerData {
113 CosmosContractEvent {
114 contract_address: layer_climb_address::Address,
116 chain_name: ChainName,
118 event: cosmwasm_std::Event,
120 block_height: u64,
122 },
123 EthContractEvent {
124 contract_address: alloy_primitives::Address,
126 chain_name: ChainName,
128 log: LogData,
130 block_height: u64,
132 },
133 Raw(Vec<u8>),
134}
135
136impl TriggerData {
137 pub fn new_raw(data: impl AsRef<[u8]>) -> Self {
138 TriggerData::Raw(data.as_ref().to_vec())
139 }
140}
141
142#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
144pub struct TriggerAction {
145 pub config: TriggerConfig,
147
148 pub data: TriggerData,
150}
151
152#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
153pub struct TriggerConfig {
155 pub service_id: ServiceID,
156 pub workflow_id: WorkflowID,
157 pub trigger: Trigger,
158}
159
160#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
162#[serde(rename_all = "snake_case")]
163pub enum Submit {
164 None,
166 EthereumContract {
168 chain_name: ChainName,
169 address: alloy_primitives::Address,
170 max_gas: Option<u64>,
171 },
172}
173
174#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
175#[serde(rename_all = "snake_case")]
176pub struct ServiceConfig {
177 pub fuel_limit: u64,
179 pub host_envs: Vec<String>,
182 pub kv: Vec<(String, String)>,
187 pub max_gas: Option<u64>,
189}
190
191impl Default for ServiceConfig {
192 fn default() -> Self {
193 Self {
194 fuel_limit: 100_000_000,
195 max_gas: None,
196 host_envs: vec![],
197 kv: vec![],
198 }
199 }
200}
201
202#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
203#[serde(rename_all = "snake_case")]
204pub enum ServiceStatus {
205 Active,
206 }
208
209#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
210#[serde(default, rename_all = "snake_case")]
211#[derive(Default)]
212pub struct Permissions {
213 pub allowed_http_hosts: AllowedHostPermission,
215 pub file_system: bool,
217}
218
219#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
222#[serde(rename_all = "snake_case")]
223pub enum AllowedHostPermission {
224 All,
225 Only(Vec<String>),
226 #[default]
227 None,
228}
229
230mod test_ext {
233 use crate::{digest::Digest, id::ChainName, ByteArray, IDError, ServiceID, WorkflowID};
234
235 use super::{Component, Submit, Trigger, TriggerConfig};
236
237 impl Submit {
238 pub fn eth_contract(
239 chain_name: ChainName,
240 address: alloy_primitives::Address,
241 max_gas: Option<u64>,
242 ) -> Submit {
243 Submit::EthereumContract {
244 chain_name,
245 address,
246 max_gas,
247 }
248 }
249 }
250
251 impl Component {
252 pub fn new(digest: Digest) -> Component {
253 Self {
254 wasm: digest,
255 permissions: Default::default(),
256 }
257 }
258 }
259
260 impl Trigger {
261 pub fn cosmos_contract_event(
262 address: layer_climb_address::Address,
263 chain_name: impl Into<ChainName>,
264 event_type: impl ToString,
265 ) -> Self {
266 Trigger::CosmosContractEvent {
267 address,
268 chain_name: chain_name.into(),
269 event_type: event_type.to_string(),
270 }
271 }
272 pub fn eth_contract_event(
273 address: alloy_primitives::Address,
274 chain_name: impl Into<ChainName>,
275 event_hash: ByteArray<32>,
276 ) -> Self {
277 Trigger::EthContractEvent {
278 address,
279 chain_name: chain_name.into(),
280 event_hash,
281 }
282 }
283 }
284
285 impl TriggerConfig {
286 pub fn cosmos_contract_event(
287 service_id: impl TryInto<ServiceID, Error = IDError>,
288 workflow_id: impl TryInto<WorkflowID, Error = IDError>,
289 contract_address: layer_climb_address::Address,
290 chain_name: impl Into<ChainName>,
291 event_type: impl ToString,
292 ) -> Result<Self, IDError> {
293 Ok(Self {
294 service_id: service_id.try_into()?,
295 workflow_id: workflow_id.try_into()?,
296 trigger: Trigger::cosmos_contract_event(contract_address, chain_name, event_type),
297 })
298 }
299
300 pub fn eth_contract_event(
301 service_id: impl TryInto<ServiceID, Error = IDError>,
302 workflow_id: impl TryInto<WorkflowID, Error = IDError>,
303 contract_address: alloy_primitives::Address,
304 chain_name: impl Into<ChainName>,
305 event_hash: ByteArray<32>,
306 ) -> Result<Self, IDError> {
307 Ok(Self {
308 service_id: service_id.try_into()?,
309 workflow_id: workflow_id.try_into()?,
310 trigger: Trigger::eth_contract_event(contract_address, chain_name, event_hash),
311 })
312 }
313
314 pub fn manual(
315 service_id: impl TryInto<ServiceID, Error = IDError>,
316 workflow_id: impl TryInto<WorkflowID, Error = IDError>,
317 ) -> Result<Self, IDError> {
318 Ok(Self {
319 service_id: service_id.try_into()?,
320 workflow_id: workflow_id.try_into()?,
321 trigger: Trigger::Manual,
322 })
323 }
324 }
325}