1use crate::dto::{
2 auth::{
3 DelegationProvisionResponse, DelegationRequest, RoleAttestationRequest,
4 SignedRoleAttestation,
5 },
6 prelude::*,
7};
8
9#[derive(CandidType, Clone, Debug, Deserialize)]
16pub enum Request {
17 CreateCanister(CreateCanisterRequest),
18 UpgradeCanister(UpgradeCanisterRequest),
19 RecycleCanister(RecycleCanisterRequest),
20 Cycles(CyclesRequest),
21 IssueDelegation(DelegationRequest),
22 IssueRoleAttestation(RoleAttestationRequest),
23}
24
25impl Request {
26 #[must_use]
30 pub const fn create_canister(request: CreateCanisterRequest) -> Self {
31 Self::CreateCanister(request)
32 }
33
34 #[must_use]
38 pub const fn upgrade_canister(request: UpgradeCanisterRequest) -> Self {
39 Self::UpgradeCanister(request)
40 }
41
42 #[must_use]
46 pub const fn recycle_canister(request: RecycleCanisterRequest) -> Self {
47 Self::RecycleCanister(request)
48 }
49
50 #[must_use]
54 pub const fn cycles(request: CyclesRequest) -> Self {
55 Self::Cycles(request)
56 }
57
58 #[must_use]
62 pub const fn issue_delegation(request: DelegationRequest) -> Self {
63 Self::IssueDelegation(request)
64 }
65
66 #[must_use]
70 pub const fn issue_role_attestation(request: RoleAttestationRequest) -> Self {
71 Self::IssueRoleAttestation(request)
72 }
73
74 #[must_use]
78 pub const fn family(&self) -> RequestFamily {
79 match self {
80 Self::CreateCanister(_) => RequestFamily::Provision,
81 Self::UpgradeCanister(_) => RequestFamily::Upgrade,
82 Self::RecycleCanister(_) => RequestFamily::RecycleCanister,
83 Self::Cycles(_) => RequestFamily::RequestCycles,
84 Self::IssueDelegation(_) => RequestFamily::IssueDelegation,
85 Self::IssueRoleAttestation(_) => RequestFamily::IssueRoleAttestation,
86 }
87 }
88
89 #[must_use]
93 pub const fn metadata(&self) -> Option<RootRequestMetadata> {
94 match self {
95 Self::CreateCanister(req) => req.metadata,
96 Self::UpgradeCanister(req) => req.metadata,
97 Self::RecycleCanister(req) => req.metadata,
98 Self::Cycles(req) => req.metadata,
99 Self::IssueDelegation(req) => req.metadata,
100 Self::IssueRoleAttestation(req) => req.metadata,
101 }
102 }
103
104 #[must_use]
108 pub const fn with_metadata(mut self, metadata: RootRequestMetadata) -> Self {
109 match &mut self {
110 Self::CreateCanister(req) => req.metadata = Some(metadata),
111 Self::UpgradeCanister(req) => req.metadata = Some(metadata),
112 Self::RecycleCanister(req) => req.metadata = Some(metadata),
113 Self::Cycles(req) => req.metadata = Some(metadata),
114 Self::IssueDelegation(req) => req.metadata = Some(metadata),
115 Self::IssueRoleAttestation(req) => req.metadata = Some(metadata),
116 }
117 self
118 }
119
120 #[must_use]
124 pub const fn without_metadata(mut self) -> Self {
125 match &mut self {
126 Self::CreateCanister(req) => req.metadata = None,
127 Self::UpgradeCanister(req) => req.metadata = None,
128 Self::RecycleCanister(req) => req.metadata = None,
129 Self::Cycles(req) => req.metadata = None,
130 Self::IssueDelegation(req) => req.metadata = None,
131 Self::IssueRoleAttestation(req) => req.metadata = None,
132 }
133 self
134 }
135
136 #[must_use]
140 pub const fn upgrade_request(&self) -> Option<&UpgradeCanisterRequest> {
141 match self {
142 Self::UpgradeCanister(request) => Some(request),
143 _ => None,
144 }
145 }
146}
147
148#[derive(Clone, Copy, Debug, Eq, PartialEq)]
155pub enum RequestFamily {
156 Provision,
157 Upgrade,
158 RecycleCanister,
159 RequestCycles,
160 IssueDelegation,
161 IssueRoleAttestation,
162}
163
164impl RequestFamily {
165 #[must_use]
169 pub const fn label(self) -> &'static str {
170 match self {
171 Self::Provision => "Provision",
172 Self::Upgrade => "Upgrade",
173 Self::RecycleCanister => "RecycleCanister",
174 Self::RequestCycles => "RequestCycles",
175 Self::IssueDelegation => "IssueDelegation",
176 Self::IssueRoleAttestation => "IssueRoleAttestation",
177 }
178 }
179}
180
181#[derive(CandidType, Clone, Debug, Deserialize)]
188pub enum RootCapabilityCommand {
189 ProvisionCanister(CreateCanisterRequest),
190 UpgradeCanister(UpgradeCanisterRequest),
191 RecycleCanister(RecycleCanisterRequest),
192 RequestCycles(CyclesRequest),
193 IssueDelegation(DelegationRequest),
194 IssueRoleAttestation(RoleAttestationRequest),
195}
196
197impl From<Request> for RootCapabilityCommand {
198 fn from(value: Request) -> Self {
199 match value {
200 Request::CreateCanister(req) => Self::ProvisionCanister(req),
201 Request::UpgradeCanister(req) => Self::UpgradeCanister(req),
202 Request::RecycleCanister(req) => Self::RecycleCanister(req),
203 Request::Cycles(req) => Self::RequestCycles(req),
204 Request::IssueDelegation(req) => Self::IssueDelegation(req),
205 Request::IssueRoleAttestation(req) => Self::IssueRoleAttestation(req),
206 }
207 }
208}
209
210#[derive(CandidType, Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
217pub struct RootRequestMetadata {
218 pub request_id: [u8; 32],
219 pub ttl_seconds: u64,
220}
221
222#[derive(CandidType, Clone, Debug, Deserialize)]
229pub struct CreateCanisterRequest {
230 pub canister_role: CanisterRole,
231 pub parent: CreateCanisterParent,
232 pub extra_arg: Option<Vec<u8>>,
233 #[serde(default)]
234 pub metadata: Option<RootRequestMetadata>,
235}
236
237#[derive(CandidType, Clone, Debug, Deserialize)]
244pub enum CreateCanisterParent {
245 Root,
246 ThisCanister,
248 Parent,
250 Canister(Principal),
251 Index(CanisterRole),
252}
253
254#[derive(CandidType, Clone, Debug, Deserialize)]
261pub struct UpgradeCanisterRequest {
262 pub canister_pid: Principal,
263 #[serde(default)]
264 pub metadata: Option<RootRequestMetadata>,
265}
266
267#[derive(CandidType, Clone, Debug, Deserialize)]
274pub struct RecycleCanisterRequest {
275 pub canister_pid: Principal,
276 #[serde(default)]
277 pub metadata: Option<RootRequestMetadata>,
278}
279
280#[derive(CandidType, Clone, Debug, Deserialize)]
287pub struct CyclesRequest {
288 pub cycles: u128,
289 #[serde(default)]
290 pub metadata: Option<RootRequestMetadata>,
291}
292
293#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
300pub enum Response {
301 CreateCanister(CreateCanisterResponse),
302 UpgradeCanister(UpgradeCanisterResponse),
303 RecycleCanister(RecycleCanisterResponse),
304 Cycles(CyclesResponse),
305 DelegationIssued(DelegationProvisionResponse),
306 RoleAttestationIssued(SignedRoleAttestation),
307}
308
309#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
315pub struct CreateCanisterResponse {
316 pub new_canister_pid: Principal,
317}
318
319#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
325pub struct UpgradeCanisterResponse {}
326
327#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
333pub struct RecycleCanisterResponse {}
334
335#[derive(CandidType, Clone, Debug, Deserialize, Serialize)]
341pub struct CyclesResponse {
342 pub cycles_transferred: u128,
343}
344
345#[cfg(test)]
350mod tests {
351 use super::*;
352
353 fn p(id: u8) -> Principal {
354 Principal::from_slice(&[id; 29])
355 }
356
357 fn metadata(id: u8) -> RootRequestMetadata {
358 RootRequestMetadata {
359 request_id: [id; 32],
360 ttl_seconds: 60,
361 }
362 }
363
364 fn requests_with_no_metadata() -> Vec<Request> {
365 vec![
366 Request::create_canister(CreateCanisterRequest {
367 canister_role: CanisterRole::new("app"),
368 parent: CreateCanisterParent::Root,
369 extra_arg: None,
370 metadata: None,
371 }),
372 Request::upgrade_canister(UpgradeCanisterRequest {
373 canister_pid: p(2),
374 metadata: None,
375 }),
376 Request::recycle_canister(RecycleCanisterRequest {
377 canister_pid: p(7),
378 metadata: None,
379 }),
380 Request::cycles(CyclesRequest {
381 cycles: 100,
382 metadata: None,
383 }),
384 Request::issue_delegation(DelegationRequest {
385 shard_pid: p(3),
386 scopes: vec!["rpc:verify".to_string()],
387 aud: vec![p(4)],
388 ttl_secs: 60,
389 verifier_targets: vec![],
390 include_root_verifier: false,
391 shard_public_key_sec1: None,
392 metadata: None,
393 }),
394 Request::issue_role_attestation(RoleAttestationRequest {
395 subject: p(5),
396 role: CanisterRole::new("test"),
397 subnet_id: None,
398 audience: Some(p(6)),
399 ttl_secs: 60,
400 epoch: 0,
401 metadata: None,
402 }),
403 ]
404 }
405
406 #[test]
407 fn request_family_matches_all_variants() {
408 let families: Vec<RequestFamily> = requests_with_no_metadata()
409 .iter()
410 .map(Request::family)
411 .collect();
412 assert_eq!(
413 families,
414 vec![
415 RequestFamily::Provision,
416 RequestFamily::Upgrade,
417 RequestFamily::RecycleCanister,
418 RequestFamily::RequestCycles,
419 RequestFamily::IssueDelegation,
420 RequestFamily::IssueRoleAttestation,
421 ]
422 );
423 }
424
425 #[test]
426 fn with_metadata_and_without_metadata_cover_all_variants() {
427 let replay_meta = metadata(7);
428
429 for request in requests_with_no_metadata() {
430 let with_meta = request.clone().with_metadata(replay_meta);
431 assert_eq!(
432 with_meta.metadata(),
433 Some(replay_meta),
434 "with_metadata must set metadata for every request variant"
435 );
436
437 let without_meta = with_meta.without_metadata();
438 assert_eq!(
439 without_meta.metadata(),
440 None,
441 "without_metadata must strip metadata for every request variant"
442 );
443 }
444 }
445
446 #[test]
447 fn upgrade_request_is_only_available_for_upgrade_variant() {
448 let upgrade = Request::upgrade_canister(UpgradeCanisterRequest {
449 canister_pid: p(9),
450 metadata: Some(metadata(9)),
451 });
452 assert!(upgrade.upgrade_request().is_some());
453
454 for request in requests_with_no_metadata() {
455 if !matches!(request, Request::UpgradeCanister(_)) {
456 assert!(request.upgrade_request().is_none());
457 }
458 }
459 }
460}