Skip to main content

akash_deploy_rs/
types.rs

1//! Minimal domain types for Akash deployment workflow.
2//!
3//! These are the types the workflow engine needs. Nothing more.
4//! If you're adding types here, ask yourself if the workflow
5//! actually needs them or if you're just being clever.
6
7use serde::{Deserialize, Serialize};
8
9/// A bid from a provider.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Bid {
12    pub provider: String,
13    pub price_uakt: u64,
14    pub resources: Resources,
15}
16
17/// Bid identifier — everything needed to create a lease from a bid.
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct BidId {
20    pub owner: String,
21    pub dseq: u64,
22    pub gseq: u32,
23    pub oseq: u32,
24    pub provider: String,
25    pub bseq: u32,
26}
27
28impl BidId {
29    pub fn from_bid(owner: &str, dseq: u64, gseq: u32, oseq: u32, bid: &Bid) -> Self {
30        Self {
31            owner: owner.to_string(),
32            dseq,
33            gseq,
34            oseq,
35            provider: bid.provider.clone(),
36            bseq: 0, // Default to 0 for backwards compatibility
37        }
38    }
39}
40
41/// Lease identifier.
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct LeaseId {
44    pub owner: String,
45    pub dseq: u64,
46    pub gseq: u32,
47    pub oseq: u32,
48    pub provider: String,
49}
50
51impl From<BidId> for LeaseId {
52    fn from(bid: BidId) -> Self {
53        Self {
54            owner: bid.owner,
55            dseq: bid.dseq,
56            gseq: bid.gseq,
57            oseq: bid.oseq,
58            provider: bid.provider,
59        }
60    }
61}
62
63/// Lease state on chain.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
65pub enum LeaseState {
66    Active,
67    InsufficientFunds,
68    Closed,
69}
70
71/// Lease info from chain query.
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct LeaseInfo {
74    pub state: LeaseState,
75    pub price_uakt: u64,
76}
77
78/// Certificate info from chain.
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct CertificateInfo {
81    pub owner: String,
82    pub cert_pem: Vec<u8>,
83    pub serial: String,
84}
85
86/// Provider info for display and caching.
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ProviderInfo {
89    pub address: String,
90    pub host_uri: String,
91    pub email: String,
92    pub website: String,
93    pub attributes: Vec<(String, String)>,
94    pub cached_at: u64,
95}
96
97/// Escrow account info.
98#[derive(Debug, Clone, Serialize, Deserialize)]
99pub struct EscrowInfo {
100    pub balance_uakt: u64,
101    pub deposited_uakt: u64,
102}
103
104/// Transaction result from broadcast.
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct TxResult {
107    pub hash: String,
108    pub code: u32,
109    pub raw_log: String,
110    pub height: u64,
111}
112
113impl TxResult {
114    pub fn is_success(&self) -> bool {
115        self.code == 0
116    }
117}
118
119/// Service endpoint from provider status.
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct ServiceEndpoint {
122    pub service: String,
123    pub uri: String,
124    pub port: u16,
125}
126
127/// Provider's status for a lease.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct ProviderLeaseStatus {
130    pub ready: bool,
131    pub endpoints: Vec<ServiceEndpoint>,
132}
133
134/// Resource allocation.
135#[derive(Debug, Clone, Default, Serialize, Deserialize)]
136pub struct Resources {
137    pub cpu_millicores: u32,
138    pub memory_bytes: u64,
139    pub storage_bytes: u64,
140    pub gpu_count: u32,
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    #[test]
148    fn test_bid_id_from_bid() {
149        let bid = Bid {
150            provider: "akash1provider".to_string(),
151            price_uakt: 1000,
152            resources: Resources::default(),
153        };
154
155        let bid_id = BidId::from_bid("akash1owner", 123, 1, 1, &bid);
156        assert_eq!(bid_id.owner, "akash1owner");
157        assert_eq!(bid_id.dseq, 123);
158        assert_eq!(bid_id.gseq, 1);
159        assert_eq!(bid_id.oseq, 1);
160        assert_eq!(bid_id.provider, "akash1provider");
161    }
162
163    #[test]
164    fn test_lease_id_from_bid_id() {
165        let bid_id = BidId {
166            owner: "akash1owner".to_string(),
167            dseq: 456,
168            gseq: 2,
169            oseq: 3,
170            provider: "akash1provider".to_string(),
171            bseq: 1,
172        };
173
174        let lease_id: LeaseId = bid_id.into();
175        assert_eq!(lease_id.owner, "akash1owner");
176        assert_eq!(lease_id.dseq, 456);
177        assert_eq!(lease_id.gseq, 2);
178        assert_eq!(lease_id.oseq, 3);
179        assert_eq!(lease_id.provider, "akash1provider");
180    }
181
182    #[test]
183    fn test_tx_result_is_success() {
184        let success_tx = TxResult {
185            hash: "ABC123".to_string(),
186            code: 0,
187            raw_log: "success".to_string(),
188            height: 1000,
189        };
190        assert!(success_tx.is_success());
191
192        let failed_tx = TxResult {
193            hash: "DEF456".to_string(),
194            code: 5,
195            raw_log: "insufficient funds".to_string(),
196            height: 1001,
197        };
198        assert!(!failed_tx.is_success());
199    }
200
201    #[test]
202    fn test_serialization_golden() {
203        let bid = Bid {
204            provider: "akash1test".to_string(),
205            price_uakt: 5000,
206            resources: Resources {
207                cpu_millicores: 1000,
208                memory_bytes: 1073741824,   // 1 GiB
209                storage_bytes: 10737418240, // 10 GiB
210                gpu_count: 1,
211            },
212        };
213
214        let json = serde_json::to_string(&bid).unwrap();
215
216        // Golden test: verify exact JSON structure
217        let expected = r#"{"provider":"akash1test","price_uakt":5000,"resources":{"cpu_millicores":1000,"memory_bytes":1073741824,"storage_bytes":10737418240,"gpu_count":1}}"#;
218        assert_eq!(
219            json, expected,
220            "JSON structure changed - wire format compatibility broken"
221        );
222
223        // Verify roundtrip
224        let deserialized: Bid = serde_json::from_str(&json).unwrap();
225        assert_eq!(deserialized.provider, bid.provider);
226        assert_eq!(deserialized.price_uakt, bid.price_uakt);
227    }
228
229    #[test]
230    fn test_boundary_conditions() {
231        // Test with extreme values
232        let bid = Bid {
233            provider: "akash1provider".to_string(),
234            price_uakt: u64::MAX,
235            resources: Resources {
236                cpu_millicores: u32::MAX,
237                memory_bytes: u64::MAX,
238                storage_bytes: u64::MAX,
239                gpu_count: u32::MAX,
240            },
241        };
242
243        let json = serde_json::to_string(&bid).unwrap();
244        let deserialized: Bid = serde_json::from_str(&json).unwrap();
245        assert_eq!(deserialized.price_uakt, u64::MAX);
246        assert_eq!(deserialized.resources.cpu_millicores, u32::MAX);
247
248        // Test with zeros
249        let min_resources = Resources {
250            cpu_millicores: 0,
251            memory_bytes: 0,
252            storage_bytes: 0,
253            gpu_count: 0,
254        };
255        let json = serde_json::to_string(&min_resources).unwrap();
256        let deserialized: Resources = serde_json::from_str(&json).unwrap();
257        assert_eq!(deserialized.cpu_millicores, 0);
258    }
259}