1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap as Map, HashSet as Set};
use std::{
    fs,
    path::{Path, PathBuf},
};
use tracing::debug;

use crate::{paths::ContractFQN, util::OrBug, ConfigError, ContractName, Result};

/// Manifest generated by cubist pre-compile.  Contains info about the original as well as
/// generated (shim/interface) contracts. These files are, by default, stored within the contracts
/// directory of each target build (see Path below).
///
/// Path: {build_dir}/{target}/contracts/cubist-manifest.json
///
/// Example JSON file produced by `cubist pre-compile`:
///
/// ```
/// # use cubist_config::PreCompileManifest;
/// # use serde_json::{from_str, json};
/// # let manifest_json = json!(
///   {
///      "files": [
///          {
///              "is_shim": false,
///              "rel_path": "poly.sol",
///              "contract_dependencies": {
///                 "PolyCounter": [ "EthCounter" ]
///              }
///          },
///          {
///             "is_shim": true,
///             "rel_path": "EthCounter.sol",
///             "contract_dependencies": {
///                "EthCounter": []
///             }
///          }
///      ]
///   }
/// # );
/// # let m: PreCompileManifest = from_str(&manifest_json.to_string())
/// #                               .unwrap();
/// ```
///
/// This manifest tells other cubist commands where to find contract files (`poly.sol`) for the
/// target chain and which source files have generated shim contracts (`EthCounter.sol`).
///
/// Manifests are consumed by all SDKs.
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PreCompileManifest {
    /// Produced files.
    pub files: Vec<FileArtifact>,
}

/// A file produced during the 'pre-compile' step
#[derive(Debug, Deserialize, Serialize, JsonSchema)]
#[serde(deny_unknown_fields)]
#[non_exhaustive]
pub struct FileArtifact {
    /// Whether this is an auto-generated shim contract or not
    pub is_shim: bool,
    /// Path relative to the directory of the manifest file
    pub rel_path: PathBuf,
    /// Names of contracts defined in this file, each mapped to other contracts
    /// (not necessarily defined in this file) which it may call
    pub contract_dependencies: Map<ContractName, Set<ContractName>>,
}

impl FileArtifact {
    /// Construct file artifact for a shim contract.
    ///
    /// # Arguments
    /// * `rel_path` - path relative to the directory of the manifest file
    /// * `contracts` - names of contracts defined in this file
    ///
    /// # Panics
    /// * if `rel_path` is absolute
    pub fn shim(rel_path: PathBuf, contracts: Vec<ContractName>) -> Self {
        assert!(!rel_path.is_absolute());
        FileArtifact {
            is_shim: true,
            rel_path,
            contract_dependencies: contracts.into_iter().map(|k| (k, Set::new())).collect(),
        }
    }

    /// Construct file artifact for a native contract.
    ///
    /// # Arguments
    /// * `rel_path` - path relative to the directory of the manifest file
    /// * `contracts` - names of contracts defined in this file, each mapped to other contracts it may call
    ///
    /// # Panics
    /// * if `rel_path` is absolute
    pub fn native_contract(
        rel_path: PathBuf,
        contracts: Map<ContractName, Set<ContractName>>,
    ) -> Self {
        assert!(!rel_path.is_absolute());
        FileArtifact {
            is_shim: false,
            rel_path,
            contract_dependencies: contracts,
        }
    }
}

impl PreCompileManifest {
    /// Create manifest from JSON file.
    pub fn from_file(file: &Path) -> Result<Self> {
        let json = fs::read_to_string(file).map_err(|e| {
            ConfigError::FsError("Failed to read pre-compile manifest file", file.into(), e)
        })?;
        match serde_json::from_str(&json) {
            Ok(m) => Ok(m),
            // We raise ConfigError if parsing fails; manifests are afterall configurations
            Err(e) => Err(ConfigError::MalformedConfig(file.into(), e)),
        }
    }

    /// Whether contract `cc` is allowed to call contract `dep` (i.e.,
    /// whether `dep` is a dependency of `cc`)
    pub fn is_dependency(&self, cc: &ContractFQN, dep: &ContractFQN) -> bool {
        debug!("Checking dependency {cc} -> {dep}");
        let file = match self
            .files
            .iter()
            .find(|f| !f.is_shim && f.rel_path == cc.file)
        {
            Some(file) => file,
            None => {
                debug!("File '{}' not found in cubist manifest", cc.file.display());
                return false;
            }
        };
        let deps = match file.contract_dependencies.get(&cc.name) {
            Some(deps) => deps,
            None => {
                debug!("No contract '{cc}' not found in cubist manifest");
                return false;
            }
        };
        deps.contains(&dep.name)
    }

    /// Return relative paths to all (non-shim) contract files.
    pub fn contract_files(&self) -> impl Iterator<Item = &PathBuf> + '_ {
        self.files
            .iter()
            .filter(|f| !f.is_shim)
            .map(|f| &f.rel_path)
    }

    /// Return relative paths to all shim contract files.
    pub fn shim_files(&self) -> impl Iterator<Item = &PathBuf> + '_ {
        self.files.iter().filter(|f| f.is_shim).map(|f| &f.rel_path)
    }

    /// Save manifest to JSON file.
    pub fn to_file(&self, destination: &Path) -> Result<()> {
        let json =
            serde_json::to_string_pretty(&self).or_bug("Serializing PreCompileManifest to json");
        fs::write(destination, json).map_err(|e| {
            ConfigError::FsError(
                "Failed to write pre-compile manifest file",
                destination.into(),
                e,
            )
        })?;
        Ok(())
    }
}