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
//! Module for reading and examining data produced by truffle.

use crate::bytecode::Bytecode;
use crate::errors::ArtifactError;
use ethabi::Contract as Abi;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use web3::types::{Address, H256};

/// Represents a truffle artifact.
#[derive(Clone, Debug, Deserialize)]
#[serde(default = "Artifact::empty")]
pub struct Artifact {
    /// The contract name
    #[serde(rename = "contractName")]
    pub contract_name: String,
    /// The contract ABI
    #[serde(with = "compat_solc_v0_6")]
    pub abi: Abi,
    /// The contract deployment bytecode.
    pub bytecode: Bytecode,
    /// The configured networks by network ID for the contract.
    pub networks: HashMap<String, Network>,
    /// The developer documentation.
    pub devdoc: Documentation,
    /// The user documentation.
    pub userdoc: Documentation,
}

impl Artifact {
    /// Creates an empty artifact instance.
    pub fn empty() -> Self {
        Artifact {
            contract_name: String::new(),
            abi: Abi {
                constructor: None,
                functions: HashMap::new(),
                events: HashMap::new(),
                fallback: false,
            },
            bytecode: Default::default(),
            networks: HashMap::new(),
            devdoc: Default::default(),
            userdoc: Default::default(),
        }
    }

    /// Parse a truffle artifact from JSON.
    pub fn from_json<S>(json: S) -> Result<Self, ArtifactError>
    where
        S: AsRef<str>,
    {
        let artifact = serde_json::from_str(json.as_ref())?;
        Ok(artifact)
    }

    /// Loads a truffle artifact from disk.
    pub fn load<P>(path: P) -> Result<Self, ArtifactError>
    where
        P: AsRef<Path>,
    {
        let json = File::open(path)?;
        let artifact = serde_json::from_reader(json)?;
        Ok(artifact)
    }
}

/// A contract's network configuration.
#[derive(Clone, Debug, Deserialize)]
pub struct Network {
    /// The address at which the contract is deployed on this network.
    pub address: Address,
    /// The hash of the transaction that deployed the contract on this network.
    #[serde(rename = "transactionHash")]
    pub transaction_hash: Option<H256>,
}

/// A contract's documentation.
#[derive(Clone, Debug, Default, Deserialize)]
pub struct Documentation {
    /// Contract documentation
    pub details: Option<String>,
    /// Contract method documentation.
    pub methods: HashMap<String, DocEntry>,
}

#[derive(Clone, Debug, Default, Deserialize)]
/// A documentation entry.
pub struct DocEntry {
    /// The documentation details for this entry.
    pub details: Option<String>,
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn parse_empty() {
        if let Err(err) = Artifact::from_json("{}") {
            panic!("error parsing empty artifact: {:?}", err);
        }
    }
}

/// Deserialization implementation for compatibility with ABIs produced with
/// solc v0.6. This is a known `ethabi` issue with an open PR for a fix:
/// https://github.com/openethereum/ethabi/issues/185
/// https://github.com/openethereum/ethabi/pull/187
mod compat_solc_v0_6 {
    use super::*;
    use serde::de::{self, Deserializer};
    use serde_json::{json, Value};

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Abi, D::Error>
    where
        D: Deserializer<'de>,
    {
        let mut raw_abi: Vec<Value> = Deserialize::deserialize(deserializer)?;
        let receive = json!("receive");
        for entry in raw_abi.iter_mut() {
            if let Value::Object(obj) = entry {
                if let Some(kind) = obj.get_mut("type") {
                    if kind == &receive {
                        *kind = json!("fallback");
                    }
                }
            }
        }

        Abi::deserialize(Value::Array(raw_abi)).map_err(de::Error::custom)
    }
}