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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
use std::{
    collections::HashMap,
    fmt::Display,
    path::{Path, PathBuf},
};

use serde::{Deserialize, Serialize};

use crate::{Config, Target};

/// Hex representation of a byte array (as used by functions in this
/// module that construct a path from an address)
pub fn hex(bytes: &[u8]) -> String {
    hex::encode(bytes)
}

/// Fully qualified contracts name.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
#[non_exhaustive]
pub struct ContractFQN {
    /// Name of the file where the contract is defined.
    pub file: PathBuf,
    /// Contract name.
    pub name: String,
}

impl Display for ContractFQN {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}:{}", self.file.display(), self.name)
    }
}

impl ContractFQN {
    /// Constructor for [`ContractFQN`].
    ///
    /// # Arguments
    ///
    /// - `file` Relative path to constructor
    /// - `name` Constructor name
    ///
    /// # Panics
    ///
    /// This function panics if `file` is not relative.
    pub fn new(file: PathBuf, name: String) -> Self {
        assert!(file.is_relative());
        ContractFQN { file, name }
    }
}

/// Well-known paths for a Cubist project.
#[non_exhaustive]
#[derive(Clone)]
pub struct Paths {
    /// The project directory, i.e., the directory that contains the Cubist config file.
    pub project_dir: PathBuf,
    /// Build directory: where Cubist generates per-chain projects.
    pub build_dir: PathBuf,
    /// Deploy directory: where Cubist saves deployment receipts.
    pub deploy_dir: PathBuf,
    /// Per target chain well-known paths.
    pub per_target: HashMap<Target, TargetPaths>,
}

/// Well-known paths for a target chain project (generated by Cubist).
#[non_exhaustive]
#[derive(Clone)]
pub struct TargetPaths {
    /// Build root directory of this chain-specific project.
    pub build_root: PathBuf,
    /// Deploy root directory of this chain-specific project.
    pub deploy_root: PathBuf,
    /// Manifest file that Cubist generates for various bookkeeping purposes.
    pub manifest: PathBuf,
    /// Manifest file containing Axelar contract addresses (when using axelar).
    pub axelar_manifest: PathBuf,
    /// Root directory for all generated contract source files.
    pub contracts: PathBuf,
    /// Directory where the compiler generates compiled artifacts.
    pub compiler_artifacts: PathBuf,
    /// Directory where the compiler saves its build manifest.
    pub compiler_build_infos: PathBuf,
    /// Cache file used by the compiler.
    pub compiler_cache: PathBuf,
}

impl Paths {
    /// Constructor for [`Paths`].
    pub fn new(cfg: &Config) -> Self {
        let project_dir = cfg.project_dir();
        let build_dir = cfg.build_dir();
        assert!(build_dir.is_absolute());
        let deploy_dir = cfg.deploy_dir().join(&cfg.current_network_profile);
        assert!(deploy_dir.is_absolute());
        let per_target = cfg
            .targets()
            .map(|t| (t, TargetPaths::new(build_dir.join(t), deploy_dir.join(t))))
            .collect();
        Paths {
            project_dir,
            build_dir,
            deploy_dir,
            per_target,
        }
    }

    /// Try to find a [`TargetPaths`] instance associated with a given target.
    pub fn try_for_target(&self, t: Target) -> Option<&TargetPaths> {
        self.per_target.get(&t)
    }

    /// Try to find a [`TargetPaths`] instance associated with a given target and PANIC if you can't.
    pub fn for_target(&self, t: Target) -> &TargetPaths {
        self.try_for_target(t)
            .unwrap_or_else(|| panic!("Paths not found for target '{:?}'", t))
    }

    /// Directory where cubist deployment manifests are written.
    ///
    /// Path: {deploy_dir}/cubist-deploy
    pub fn deployment_manifest_dir(&self) -> PathBuf {
        self.deploy_dir.with_file_name("cubist-deploy")
    }

    /// Destination for a contract deployment manifest file generated
    /// by Cubist.  This manifest file contains contract's deployed
    /// address as well as the deployed addresses of all of its shims.
    ///
    /// Path: {deploy_dir}/cubist-deploy/{contract_name}-{address}.json
    pub fn for_deployment_manifest(&self, contract: &ContractFQN, address: &[u8; 20]) -> PathBuf {
        self.deployment_manifest_dir()
            .join(format!("{}-{}.json", contract.name, hex(address)))
    }

    /// Full path to the file indicating that a bridge has been
    /// created for a given contract (as specified by its deployment
    /// manifest file)
    ///
    /// Path: {deploy_dir}/cubist-deploy/{contract_name}-{address}.bridged
    pub fn notify_contract_bridged(&self, contract: &ContractFQN, address: &[u8; 20]) -> PathBuf {
        Self::bridged_signal_for_manifest_file(&self.for_deployment_manifest(contract, address))
    }

    /// Full path to the file indicating that a bridge has been
    /// created for a given deployment manifest file.
    ///
    /// Path: {deploy_dir}/cubist-deploy/{contract_name}-{address}.bridged
    pub fn bridged_signal_for_manifest_file(deployment_manifest: &Path) -> PathBuf {
        deployment_manifest.with_extension("bridged")
    }
}

impl TargetPaths {
    /// Constructor for [`TargetPaths`].
    ///
    /// # Panics
    ///
    /// If `build_root` or `deploy_root` is not an absolute path.
    pub fn new(build_root: PathBuf, deploy_root: PathBuf) -> Self {
        assert!(build_root.is_absolute());
        TargetPaths {
            contracts: build_root.join("contracts"),
            manifest: build_root.join("contracts").join("cubist-manifest.json"),
            compiler_artifacts: build_root.join("artifacts"),
            compiler_build_infos: build_root.join("build_infos"),
            compiler_cache: build_root.join("cache"),
            axelar_manifest: deploy_root.join("axelar.json"),
            build_root,
            deploy_root,
        }
    }

    /// Directory where deployment receipts are stored for a given contract.
    ///
    /// Path: {deploy_dir}/{network_profile}/{target}/{contract_file}/{contract_name}
    pub fn deployment_receipts_dir(&self, contract: &ContractFQN) -> PathBuf {
        self.deploy_root.join(&contract.file).join(&contract.name)
    }

    /// Destination for a contract deployment receipt file.
    ///
    /// Path: {deploy_dir}/{network_profile}/{target}/{contract_file}/{contract_name}/{address}.json
    pub fn for_deployment_receipt(&self, contract: &ContractFQN, address: &[u8; 20]) -> PathBuf {
        self.deployment_receipts_dir(contract)
            .join(hex(address))
            .with_extension("json")
    }

    /// Destination for a generated contract source file given the relative path of an original
    /// source file (relative to the configured contracts source root directory).  Note that we
    /// generate one file per source file, no matter how many contracts are defined in it.
    ///
    /// Path: {build_dir}/{target}/{contract_file}
    pub fn for_contract(&self, source_relative_path: &Path) -> PathBuf {
        assert!(source_relative_path.is_relative());
        self.contracts.join(source_relative_path)
    }

    /// Destination for a bridge file corresponding to a contract source file.  Note that we
    /// generate one bridge file per source file, no matter how many contracts are defined in it.
    ///
    /// Path: {build_dir}/{target}/{contract_file_stem}.bridge.json
    pub fn for_bridge(&self, source_relative_path: &Path) -> PathBuf {
        self.for_contract(source_relative_path)
            .with_extension("bridge.json")
    }
}