Skip to main content

rain_metadata/solc/
mod.rs

1use serde_json::Value;
2use strum::EnumString;
3use crate::error::Error;
4
5/// Represent section of a solidity artifact to extract
6#[derive(Copy, Clone, EnumString, strum::Display)]
7#[strum(serialize_all = "kebab-case")]
8pub enum ArtifactComponent {
9    Abi,
10    Bytecode,
11    DeployedBytecode,
12}
13
14impl ArtifactComponent {
15    /// Key this component is read from in a solc artifact object.
16    pub fn artifact_key(self) -> &'static str {
17        match self {
18            ArtifactComponent::Abi => "abi",
19            ArtifactComponent::Bytecode => "bytecode",
20            ArtifactComponent::DeployedBytecode => "deployedBytecode",
21        }
22    }
23}
24
25/// extracts the given section of a solidity artifact as [Value]
26///
27/// errors if the artifact is not a json object or carries no such key. a key
28/// that is present and explicitly null is returned as [Value::Null].
29/// The given data should be utf8 encoded json string bytes
30pub fn extract_artifact_component_json(
31    component: ArtifactComponent,
32    data: &[u8],
33) -> Result<Value, Error> {
34    let json = serde_json::from_str::<Value>(std::str::from_utf8(data)?)?;
35    let key = component.artifact_key();
36    json.get(key)
37        .cloned()
38        .ok_or_else(|| Error::InvalidInput(format!("artifact has no \"{}\" component", key)))
39}
40
41#[cfg(all(test, not(target_family = "wasm")))]
42mod tests {
43    use super::*;
44
45    fn artifact_json() -> Vec<u8> {
46        serde_json::json!({
47            "abi": [{ "type": "function", "name": "foo" }],
48            "bytecode": { "object": "0x6001" },
49            "deployedBytecode": { "object": "0x6002" }
50        })
51        .to_string()
52        .into_bytes()
53    }
54
55    /// Each component arm extracts exactly its own key.
56    #[test]
57    fn test_extract_each_component() {
58        let data = artifact_json();
59        assert_eq!(
60            extract_artifact_component_json(ArtifactComponent::Abi, &data).unwrap(),
61            serde_json::json!([{ "type": "function", "name": "foo" }])
62        );
63        assert_eq!(
64            extract_artifact_component_json(ArtifactComponent::Bytecode, &data).unwrap(),
65            serde_json::json!({ "object": "0x6001" })
66        );
67        assert_eq!(
68            extract_artifact_component_json(ArtifactComponent::DeployedBytecode, &data).unwrap(),
69            serde_json::json!({ "object": "0x6002" })
70        );
71    }
72
73    /// Every component keys off its own name, and each name is the one solc
74    /// writes into the artifact.
75    #[test]
76    fn test_artifact_key_per_component() {
77        assert_eq!(ArtifactComponent::Abi.artifact_key(), "abi");
78        assert_eq!(ArtifactComponent::Bytecode.artifact_key(), "bytecode");
79        assert_eq!(
80            ArtifactComponent::DeployedBytecode.artifact_key(),
81            "deployedBytecode"
82        );
83    }
84
85    /// An absent component is an error naming the missing key, not a silent
86    /// null. Asserted for every component so no arm can regress to indexing.
87    #[test]
88    fn test_missing_component_errors() {
89        for (component, key) in [
90            (ArtifactComponent::Abi, "abi"),
91            (ArtifactComponent::Bytecode, "bytecode"),
92            (ArtifactComponent::DeployedBytecode, "deployedBytecode"),
93        ] {
94            let err = extract_artifact_component_json(component, b"{}").unwrap_err();
95            assert_eq!(
96                err.to_string(),
97                format!("invalid input: artifact has no \"{}\" component", key)
98            );
99        }
100    }
101
102    /// Only the requested component's absence is an error: the other keys
103    /// being present does not satisfy the lookup.
104    #[test]
105    fn test_missing_component_errors_beside_present_siblings() {
106        let data = br#"{"bytecode":{"object":"0x60"},"deployedBytecode":{"object":"0x60"}}"#;
107        assert!(extract_artifact_component_json(ArtifactComponent::Abi, data).is_err());
108        assert!(extract_artifact_component_json(ArtifactComponent::Bytecode, data).is_ok());
109    }
110
111    /// A component present and explicitly null is a value, not an absence:
112    /// it round trips as null while an absent key errors.
113    #[test]
114    fn test_explicit_null_component_is_returned() {
115        assert_eq!(
116            extract_artifact_component_json(ArtifactComponent::Abi, br#"{"abi":null}"#).unwrap(),
117            Value::Null
118        );
119    }
120
121    /// A json document that is not an object has no components at all.
122    #[test]
123    fn test_non_object_artifact_errors() {
124        assert!(extract_artifact_component_json(ArtifactComponent::Abi, b"[]").is_err());
125        assert!(extract_artifact_component_json(ArtifactComponent::Abi, b"null").is_err());
126        assert!(extract_artifact_component_json(ArtifactComponent::Abi, br#""abi""#).is_err());
127    }
128
129    /// Non-utf8 and non-json inputs error.
130    #[test]
131    fn test_invalid_input_errors() {
132        assert!(extract_artifact_component_json(ArtifactComponent::Abi, &[0xff, 0xfe]).is_err());
133        assert!(extract_artifact_component_json(ArtifactComponent::Abi, b"not json").is_err());
134    }
135}