Skip to main content

rain_metadata/meta/
magic.rs

1/// All known Rain magic numbers
2#[derive(
3    serde::Serialize,
4    Clone,
5    Copy,
6    strum::EnumIter,
7    strum::EnumString,
8    strum::Display,
9    Debug,
10    PartialEq,
11    serde::Deserialize,
12)]
13#[strum(serialize_all = "kebab_case")]
14#[serde(rename_all = "kebab-case")]
15#[repr(u64)]
16pub enum KnownMagic {
17    /// Prefixes every rain meta document. This is the only place it appears:
18    /// an item claiming it as its own magic is malformed, not a nested
19    /// document, and does not decode.
20    RainMetaDocumentV1 = 0xff0a89c674ee7874,
21
22    /// Ops meta v1
23    OpMetaV1 = 0xffe5282f43e495b4,
24    /// Dotrain meta v1
25    DotrainV1 = 0xffdac2f2f37be894,
26    /// Rainlang meta v1
27    RainlangV1 = 0xff1c198cec3b48a7,
28    /// Solidity ABI meta v2
29    SolidityAbiV2 = 0xffe5ffb4a3ff2cde,
30    /// Authoring meta v1
31    AuthoringMetaV1 = 0xffe9e3a02ca8e235,
32    // Authoring meta v2
33    AuthoringMetaV2 = 0xff52fe42f1a05093,
34    /// InterpreterCaller meta v1
35    InterpreterCallerMetaV1 = 0xffc21bbf86cc199b,
36    /// ExpressionDeployer deployed bytecode meta v1
37    ExpressionDeployerV2BytecodeV1 = 0xffdb988a8cd04d32,
38    /// Rainlang source code meta v1
39    RainlangSourceV1 = 0xff13109e41336ff2,
40    //Address list meta
41    AddressList = 0xffb2637608c09e38,
42    /// Dotrain source code meta v1
43    DotrainSourceV1 = 0xffa15ef0fc437099,
44    /// Order builder state meta v1
45    OrderBuilderStateV1 = 0xffda7b2fb167c286,
46    /// Signed context oracle endpoint v1
47    /// Payload is raw UTF-8 bytes containing the oracle endpoint URL.
48    /// Used in order metadata to tell takers where to GET signed context data.
49    RaindexSignedContextOracleV1 = 0xff7a1507ba4419ca,
50    /// Web data v1
51    WebDataV1 = 0xff5dcce9b571ba42,
52    /// OffchainAsset schema reference for offchain asset data
53    OaSchema = 0xffa8e8a9b9cf4a31,
54    /// OffchainAsset IPFS hash list for offchain assets
55    OaHashList = 0xff9fae3cc645f463,
56    /// OffchainAsset structured data (e.g. receipt information)
57    OaStructure = 0xffc47a6299e8a911,
58    /// OffchainAsset token image metadata
59    OaTokenImage = 0xff8cd2927c8c86cb,
60    /// OffchainAsset token credential links
61    OaTokenCredentialLinks = 0xffbc38eb14ad2209,
62}
63
64impl KnownMagic {
65    pub fn to_prefix_bytes(&self) -> [u8; 8] {
66        // Use big endian here as the magic numbers are for binary data prefixes.
67        (*self as u64).to_be_bytes()
68    }
69}
70
71impl TryFrom<u64> for KnownMagic {
72    type Error = crate::error::Error;
73    fn try_from(value: u64) -> Result<Self, Self::Error> {
74        match value {
75            v if v == KnownMagic::OpMetaV1 as u64 => Ok(KnownMagic::OpMetaV1),
76            v if v == KnownMagic::DotrainV1 as u64 => Ok(KnownMagic::DotrainV1),
77            v if v == KnownMagic::RainlangV1 as u64 => Ok(KnownMagic::RainlangV1),
78            v if v == KnownMagic::SolidityAbiV2 as u64 => Ok(KnownMagic::SolidityAbiV2),
79            v if v == KnownMagic::AuthoringMetaV1 as u64 => Ok(KnownMagic::AuthoringMetaV1),
80            v if v == KnownMagic::AuthoringMetaV2 as u64 => Ok(KnownMagic::AuthoringMetaV2),
81            v if v == KnownMagic::AddressList as u64 => Ok(KnownMagic::AddressList),
82            // NOT RainMetaDocumentV1. That number is the document prefix, and
83            // this conversion is only ever reached for an item's own magic
84            // (cbor key 1). An item claiming to be a document is structurally
85            // invalid rather than a type nothing supports, so it is corrupt
86            // here and the meta carrying it does not decode. Nothing in this
87            // workspace emits one. rainlanguage/rain.metadata#204.
88            v if v == KnownMagic::InterpreterCallerMetaV1 as u64 => {
89                Ok(KnownMagic::InterpreterCallerMetaV1)
90            }
91            v if v == KnownMagic::ExpressionDeployerV2BytecodeV1 as u64 => {
92                Ok(KnownMagic::ExpressionDeployerV2BytecodeV1)
93            }
94            v if v == KnownMagic::RainlangSourceV1 as u64 => Ok(KnownMagic::RainlangSourceV1),
95            v if v == KnownMagic::DotrainSourceV1 as u64 => Ok(KnownMagic::DotrainSourceV1),
96            v if v == KnownMagic::OrderBuilderStateV1 as u64 => Ok(KnownMagic::OrderBuilderStateV1),
97            v if v == KnownMagic::RaindexSignedContextOracleV1 as u64 => {
98                Ok(KnownMagic::RaindexSignedContextOracleV1)
99            }
100            v if v == KnownMagic::WebDataV1 as u64 => Ok(KnownMagic::WebDataV1),
101            v if v == KnownMagic::OaSchema as u64 => Ok(KnownMagic::OaSchema),
102            v if v == KnownMagic::OaHashList as u64 => Ok(KnownMagic::OaHashList),
103            v if v == KnownMagic::OaStructure as u64 => Ok(KnownMagic::OaStructure),
104            v if v == KnownMagic::OaTokenImage as u64 => Ok(KnownMagic::OaTokenImage),
105            v if v == KnownMagic::OaTokenCredentialLinks as u64 => {
106                Ok(KnownMagic::OaTokenCredentialLinks)
107            }
108            _ => Err(crate::error::Error::UnknownMagic),
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115    use super::KnownMagic;
116    use alloy::primitives::hex;
117
118    #[test]
119    fn test_rain_meta_document_v1() {
120        let magic_number = KnownMagic::RainMetaDocumentV1;
121        let magic_number_after_prefix = magic_number.to_prefix_bytes();
122
123        assert_eq!(hex::encode(magic_number_after_prefix), "ff0a89c674ee7874");
124    }
125
126    #[test]
127    fn test_solidity_abi_v2() {
128        let magic_number = KnownMagic::SolidityAbiV2;
129        let magic_number_after_prefix = magic_number.to_prefix_bytes();
130
131        assert_eq!(hex::encode(magic_number_after_prefix), "ffe5ffb4a3ff2cde");
132    }
133
134    #[test]
135    fn test_op_meta_v1() {
136        let magic_number = KnownMagic::OpMetaV1;
137        let magic_number_after_prefix = magic_number.to_prefix_bytes();
138
139        assert_eq!(hex::encode(magic_number_after_prefix), "ffe5282f43e495b4");
140    }
141
142    #[test]
143    fn test_interpreter_caller_meta_v1() {
144        let magic_number = KnownMagic::InterpreterCallerMetaV1;
145        let magic_number_after_prefix = magic_number.to_prefix_bytes();
146
147        assert_eq!(hex::encode(magic_number_after_prefix), "ffc21bbf86cc199b");
148    }
149
150    #[test]
151    fn test_authoring_meta_v1() {
152        let magic_number = KnownMagic::AuthoringMetaV1;
153        let magic_number_after_prefix = magic_number.to_prefix_bytes();
154
155        assert_eq!(hex::encode(magic_number_after_prefix), "ffe9e3a02ca8e235");
156    }
157
158    #[test]
159    fn test_authoring_meta_v2() {
160        let magic_number = KnownMagic::AuthoringMetaV2;
161        let magic_number_after_prefix = magic_number.to_prefix_bytes();
162
163        assert_eq!(hex::encode(magic_number_after_prefix), "ff52fe42f1a05093");
164    }
165
166    #[test]
167    fn test_dotrain_meta_v1() {
168        let magic_number = KnownMagic::DotrainV1;
169        let magic_number_after_prefix = magic_number.to_prefix_bytes();
170
171        assert_eq!(hex::encode(magic_number_after_prefix), "ffdac2f2f37be894");
172    }
173
174    #[test]
175    fn test_rainlang_meta_v1() {
176        let magic_number = KnownMagic::RainlangV1;
177        let magic_number_after_prefix = magic_number.to_prefix_bytes();
178
179        assert_eq!(hex::encode(magic_number_after_prefix), "ff1c198cec3b48a7");
180    }
181
182    #[test]
183    fn test_expression_deployer_v2_bytecode_meta_v1() {
184        let magic_number = KnownMagic::ExpressionDeployerV2BytecodeV1;
185        let magic_number_after_prefix = magic_number.to_prefix_bytes();
186
187        assert_eq!(hex::encode(magic_number_after_prefix), "ffdb988a8cd04d32");
188    }
189
190    #[test]
191    fn test_rainlang_source_meta_v1() {
192        let magic_number = KnownMagic::RainlangSourceV1;
193        let magic_number_after_prefix = magic_number.to_prefix_bytes();
194
195        assert_eq!(hex::encode(magic_number_after_prefix), "ff13109e41336ff2");
196    }
197
198    #[test]
199    fn test_dotrain_source_meta_v1() {
200        let magic_number = KnownMagic::DotrainSourceV1;
201        let magic_number_after_prefix = magic_number.to_prefix_bytes();
202
203        assert_eq!(hex::encode(magic_number_after_prefix), "ffa15ef0fc437099");
204    }
205
206    #[test]
207    fn test_dotrain_instance_meta_v1() {
208        let magic_number = KnownMagic::OrderBuilderStateV1;
209        let magic_number_after_prefix = magic_number.to_prefix_bytes();
210
211        assert_eq!(hex::encode(magic_number_after_prefix), "ffda7b2fb167c286");
212    }
213
214    #[test]
215    fn test_signed_context_oracle_v1() {
216        let magic_number = KnownMagic::RaindexSignedContextOracleV1;
217        let magic_number_after_prefix = magic_number.to_prefix_bytes();
218
219        assert_eq!(hex::encode(magic_number_after_prefix), "ff7a1507ba4419ca");
220    }
221
222    #[test]
223    fn test_signed_context_oracle_v1_roundtrip() {
224        let magic_number = KnownMagic::RaindexSignedContextOracleV1;
225        let from_u64 = KnownMagic::try_from(magic_number as u64).unwrap();
226        assert_eq!(magic_number, from_u64);
227    }
228
229    #[test]
230    fn test_web_data_v1() {
231        let magic_number = KnownMagic::WebDataV1;
232        let magic_number_after_prefix = magic_number.to_prefix_bytes();
233
234        assert_eq!(hex::encode(magic_number_after_prefix), "ff5dcce9b571ba42");
235    }
236
237    #[test]
238    fn test_oa_schema() {
239        let magic_number = KnownMagic::OaSchema;
240        let magic_number_after_prefix = magic_number.to_prefix_bytes();
241
242        assert_eq!(hex::encode(magic_number_after_prefix), "ffa8e8a9b9cf4a31");
243    }
244
245    #[test]
246    fn test_oa_hash_list() {
247        let magic_number = KnownMagic::OaHashList;
248        let magic_number_after_prefix = magic_number.to_prefix_bytes();
249
250        assert_eq!(hex::encode(magic_number_after_prefix), "ff9fae3cc645f463");
251    }
252
253    #[test]
254    fn test_oa_structure() {
255        let magic_number = KnownMagic::OaStructure;
256        let magic_number_after_prefix = magic_number.to_prefix_bytes();
257
258        assert_eq!(hex::encode(magic_number_after_prefix), "ffc47a6299e8a911");
259    }
260
261    #[test]
262    fn test_oa_token_image() {
263        let magic_number = KnownMagic::OaTokenImage;
264        let magic_number_after_prefix = magic_number.to_prefix_bytes();
265
266        assert_eq!(hex::encode(magic_number_after_prefix), "ff8cd2927c8c86cb");
267    }
268
269    #[test]
270    fn test_oa_token_credential_links() {
271        let magic_number = KnownMagic::OaTokenCredentialLinks;
272        let magic_number_after_prefix = magic_number.to_prefix_bytes();
273
274        assert_eq!(hex::encode(magic_number_after_prefix), "ffbc38eb14ad2209");
275    }
276
277    #[test]
278    fn test_oa_magics_roundtrip() {
279        for magic_number in [
280            KnownMagic::OaSchema,
281            KnownMagic::OaHashList,
282            KnownMagic::OaStructure,
283            KnownMagic::OaTokenImage,
284            KnownMagic::OaTokenCredentialLinks,
285        ] {
286            let from_u64 = KnownMagic::try_from(magic_number as u64).unwrap();
287            assert_eq!(magic_number, from_u64);
288        }
289    }
290
291    #[test]
292    fn test_address_list() {
293        let magic_number = KnownMagic::AddressList;
294        let magic_number_after_prefix = magic_number.to_prefix_bytes();
295
296        assert_eq!(hex::encode(magic_number_after_prefix), "ffb2637608c09e38");
297    }
298
299    /// Pins every discriminant to its published magic number so no variant can
300    /// silently change value. Values for the documented types come from the
301    /// metadata-v1 spec magic number table.
302    #[test]
303    fn test_all_discriminants_pinned() {
304        use strum::IntoEnumIterator;
305        let expected: [(KnownMagic, &str); 20] = [
306            (KnownMagic::RainMetaDocumentV1, "ff0a89c674ee7874"),
307            (KnownMagic::OpMetaV1, "ffe5282f43e495b4"),
308            (KnownMagic::DotrainV1, "ffdac2f2f37be894"),
309            (KnownMagic::RainlangV1, "ff1c198cec3b48a7"),
310            (KnownMagic::SolidityAbiV2, "ffe5ffb4a3ff2cde"),
311            (KnownMagic::AuthoringMetaV1, "ffe9e3a02ca8e235"),
312            (KnownMagic::AuthoringMetaV2, "ff52fe42f1a05093"),
313            (KnownMagic::InterpreterCallerMetaV1, "ffc21bbf86cc199b"),
314            (
315                KnownMagic::ExpressionDeployerV2BytecodeV1,
316                "ffdb988a8cd04d32",
317            ),
318            (KnownMagic::RainlangSourceV1, "ff13109e41336ff2"),
319            (KnownMagic::AddressList, "ffb2637608c09e38"),
320            (KnownMagic::DotrainSourceV1, "ffa15ef0fc437099"),
321            (KnownMagic::OrderBuilderStateV1, "ffda7b2fb167c286"),
322            (KnownMagic::RaindexSignedContextOracleV1, "ff7a1507ba4419ca"),
323            (KnownMagic::WebDataV1, "ff5dcce9b571ba42"),
324            (KnownMagic::OaSchema, "ffa8e8a9b9cf4a31"),
325            (KnownMagic::OaHashList, "ff9fae3cc645f463"),
326            (KnownMagic::OaStructure, "ffc47a6299e8a911"),
327            (KnownMagic::OaTokenImage, "ff8cd2927c8c86cb"),
328            (KnownMagic::OaTokenCredentialLinks, "ffbc38eb14ad2209"),
329        ];
330        // every variant is pinned exactly once
331        assert_eq!(expected.len(), KnownMagic::iter().count());
332        for (magic, hex_str) in expected {
333            assert_eq!(hex::encode(magic.to_prefix_bytes()), hex_str, "{:?}", magic);
334        }
335    }
336
337    /// Every magic number begins with 0xff so a prefix can never be a valid
338    /// utf-8 sequence, per the metadata-v1 spec.
339    #[test]
340    fn test_all_prefixes_start_with_0xff() {
341        use strum::IntoEnumIterator;
342        for magic in KnownMagic::iter() {
343            assert_eq!(magic.to_prefix_bytes()[0], 0xff, "{:?}", magic);
344        }
345    }
346
347    /// TryFrom<u64> roundtrips every variant back to itself.
348    #[test]
349    fn test_try_from_u64_roundtrip_all() {
350        use strum::IntoEnumIterator;
351        for magic in KnownMagic::iter() {
352            // The document magic is the exception: this conversion reads an
353            // item's own magic, and an item cannot be a document.
354            if magic == KnownMagic::RainMetaDocumentV1 {
355                continue;
356            }
357            let from_u64 = KnownMagic::try_from(magic as u64).unwrap();
358            assert_eq!(magic, from_u64);
359        }
360    }
361
362    /// The document magic is a prefix, never an item's own magic, so it does
363    /// not convert back. rainlanguage/rain.metadata#204.
364    #[test]
365    fn test_try_from_u64_rejects_the_document_magic() {
366        assert!(matches!(
367            KnownMagic::try_from(KnownMagic::RainMetaDocumentV1 as u64),
368            Err(crate::error::Error::UnknownMagic)
369        ));
370    }
371
372    /// Values that are not known magic numbers map to Error::UnknownMagic.
373    #[test]
374    fn test_try_from_u64_unknown_magic() {
375        for unknown in [0u64, 1, 0xdeadbeef, 0xff0a89c674ee7875, u64::MAX] {
376            assert!(matches!(
377                KnownMagic::try_from(unknown),
378                Err(crate::error::Error::UnknownMagic)
379            ));
380        }
381    }
382
383    /// Strum parse/display uses the kebab-case names that the CLI documents,
384    /// e.g. the build command's default global magic "rain-meta-document-v1".
385    #[test]
386    fn test_strum_kebab_case_parse_display() {
387        use std::str::FromStr;
388        let cases: [(KnownMagic, &str); 20] = [
389            (KnownMagic::RainMetaDocumentV1, "rain-meta-document-v1"),
390            (KnownMagic::OpMetaV1, "op-meta-v1"),
391            (KnownMagic::DotrainV1, "dotrain-v1"),
392            (KnownMagic::RainlangV1, "rainlang-v1"),
393            (KnownMagic::SolidityAbiV2, "solidity-abi-v2"),
394            (KnownMagic::AuthoringMetaV1, "authoring-meta-v1"),
395            (KnownMagic::AuthoringMetaV2, "authoring-meta-v2"),
396            (
397                KnownMagic::InterpreterCallerMetaV1,
398                "interpreter-caller-meta-v1",
399            ),
400            (
401                KnownMagic::ExpressionDeployerV2BytecodeV1,
402                "expression-deployer-v2-bytecode-v1",
403            ),
404            (KnownMagic::RainlangSourceV1, "rainlang-source-v1"),
405            (KnownMagic::AddressList, "address-list"),
406            (KnownMagic::DotrainSourceV1, "dotrain-source-v1"),
407            (KnownMagic::OrderBuilderStateV1, "order-builder-state-v1"),
408            (
409                KnownMagic::RaindexSignedContextOracleV1,
410                "raindex-signed-context-oracle-v1",
411            ),
412            (KnownMagic::WebDataV1, "web-data-v1"),
413            (KnownMagic::OaSchema, "oa-schema"),
414            (KnownMagic::OaHashList, "oa-hash-list"),
415            (KnownMagic::OaStructure, "oa-structure"),
416            (KnownMagic::OaTokenImage, "oa-token-image"),
417            (
418                KnownMagic::OaTokenCredentialLinks,
419                "oa-token-credential-links",
420            ),
421        ];
422        for (magic, name) in cases {
423            assert_eq!(magic.to_string(), name, "{:?}", magic);
424            assert_eq!(KnownMagic::from_str(name).unwrap(), magic, "{}", name);
425        }
426    }
427
428    /// Serde serializes to the same kebab-case names and deserializes them
429    /// back.
430    #[test]
431    fn test_serde_kebab_case_roundtrip() {
432        use strum::IntoEnumIterator;
433        for magic in KnownMagic::iter() {
434            let json = serde_json::to_string(&magic).unwrap();
435            assert_eq!(json, format!("\"{}\"", magic), "{:?}", magic);
436            let back: KnownMagic = serde_json::from_str(&json).unwrap();
437            assert_eq!(back, magic);
438        }
439    }
440}