use std::collections::BTreeMap;
use alloy_primitives::LogData;
use serde::{Deserialize, Serialize};
use crate::{digest::Digest, ByteArray};
use super::{ChainName, ComponentID, ServiceID, WorkflowID};
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub struct Service {
pub id: ServiceID,
pub name: String,
pub components: BTreeMap<ComponentID, Component>,
pub workflows: BTreeMap<WorkflowID, Workflow>,
pub status: ServiceStatus,
pub config: ServiceConfig,
}
impl Service {
pub fn new_simple(
id: ServiceID,
name: Option<String>,
trigger: Trigger,
component_digest: Digest,
submit: Submit,
config: Option<ServiceConfig>,
) -> Self {
let component_id = ComponentID::default();
let workflow_id = WorkflowID::default();
let workflow = Workflow {
trigger,
component: component_id,
submit,
fuel_limit: None,
};
let component = Component {
wasm: component_digest,
permissions: Permissions::default(),
};
let components = BTreeMap::from([(workflow.component.clone(), component)]);
let workflows = BTreeMap::from([(workflow_id, workflow)]);
Self {
name: name.unwrap_or_else(|| id.to_string()),
id,
components,
workflows,
status: ServiceStatus::Active,
config: config.unwrap_or_default(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct Component {
pub wasm: Digest,
pub permissions: Permissions,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub struct Workflow {
pub trigger: Trigger,
pub component: ComponentID,
pub submit: Submit,
pub fuel_limit: Option<u64>,
}
impl Workflow {
pub const DEFAULT_FUEL_LIMIT: u64 = 100_000_000;
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Trigger {
CosmosContractEvent {
address: layer_climb_address::Address,
chain_name: ChainName,
event_type: String,
},
EthContractEvent {
address: alloy_primitives::Address,
chain_name: ChainName,
event_hash: ByteArray<32>,
},
Manual,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum TriggerData {
CosmosContractEvent {
contract_address: layer_climb_address::Address,
chain_name: ChainName,
event: cosmwasm_std::Event,
block_height: u64,
},
EthContractEvent {
contract_address: alloy_primitives::Address,
chain_name: ChainName,
log: LogData,
block_height: u64,
},
Raw(Vec<u8>),
}
impl TriggerData {
pub fn new_raw(data: impl AsRef<[u8]>) -> Self {
TriggerData::Raw(data.as_ref().to_vec())
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct TriggerAction {
pub config: TriggerConfig,
pub data: TriggerData,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct TriggerConfig {
pub service_id: ServiceID,
pub workflow_id: WorkflowID,
pub trigger: Trigger,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum Submit {
None,
EthereumContract {
chain_name: ChainName,
address: alloy_primitives::Address,
max_gas: Option<u64>,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub struct ServiceConfig {
pub host_envs: Vec<String>,
pub kv: Vec<(String, String)>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Copy)]
#[serde(rename_all = "snake_case")]
pub enum ServiceStatus {
Active,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[serde(default, rename_all = "snake_case")]
#[derive(Default)]
pub struct Permissions {
pub allowed_http_hosts: AllowedHostPermission,
pub file_system: bool,
}
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum AllowedHostPermission {
All,
Only(Vec<String>),
#[default]
None,
}
mod test_ext {
use crate::{digest::Digest, id::ChainName, ByteArray, IDError, ServiceID, WorkflowID};
use super::{Component, Submit, Trigger, TriggerConfig};
impl Submit {
pub fn eth_contract(
chain_name: ChainName,
address: alloy_primitives::Address,
max_gas: Option<u64>,
) -> Submit {
Submit::EthereumContract {
chain_name,
address,
max_gas,
}
}
}
impl Component {
pub fn new(digest: Digest) -> Component {
Self {
wasm: digest,
permissions: Default::default(),
}
}
}
impl Trigger {
pub fn cosmos_contract_event(
address: layer_climb_address::Address,
chain_name: impl Into<ChainName>,
event_type: impl ToString,
) -> Self {
Trigger::CosmosContractEvent {
address,
chain_name: chain_name.into(),
event_type: event_type.to_string(),
}
}
pub fn eth_contract_event(
address: alloy_primitives::Address,
chain_name: impl Into<ChainName>,
event_hash: ByteArray<32>,
) -> Self {
Trigger::EthContractEvent {
address,
chain_name: chain_name.into(),
event_hash,
}
}
}
impl TriggerConfig {
pub fn cosmos_contract_event(
service_id: impl TryInto<ServiceID, Error = IDError>,
workflow_id: impl TryInto<WorkflowID, Error = IDError>,
contract_address: layer_climb_address::Address,
chain_name: impl Into<ChainName>,
event_type: impl ToString,
) -> Result<Self, IDError> {
Ok(Self {
service_id: service_id.try_into()?,
workflow_id: workflow_id.try_into()?,
trigger: Trigger::cosmos_contract_event(contract_address, chain_name, event_type),
})
}
pub fn eth_contract_event(
service_id: impl TryInto<ServiceID, Error = IDError>,
workflow_id: impl TryInto<WorkflowID, Error = IDError>,
contract_address: alloy_primitives::Address,
chain_name: impl Into<ChainName>,
event_hash: ByteArray<32>,
) -> Result<Self, IDError> {
Ok(Self {
service_id: service_id.try_into()?,
workflow_id: workflow_id.try_into()?,
trigger: Trigger::eth_contract_event(contract_address, chain_name, event_hash),
})
}
pub fn manual(
service_id: impl TryInto<ServiceID, Error = IDError>,
workflow_id: impl TryInto<WorkflowID, Error = IDError>,
) -> Result<Self, IDError> {
Ok(Self {
service_id: service_id.try_into()?,
workflow_id: workflow_id.try_into()?,
trigger: Trigger::Manual,
})
}
}
}