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(())
}
}