use crate::utils::dependency::Dependency;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::str::FromStr;
use sway_utils::constants::DEFAULT_NODE_URL;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Manifest {
pub project: Project,
pub network: Option<Network>,
pub tx_input: Option<Vec<TxInput>>,
pub dependencies: Option<BTreeMap<String, Dependency>>,
}
impl Manifest {
pub fn get_tx_inputs_and_outputs(
&self,
) -> Result<(Vec<fuel_tx::Input>, Vec<fuel_tx::Output>), String> {
let inputs = self
.tx_input
.clone()
.unwrap_or_default()
.iter()
.map(TxInput::to_input)
.collect::<Result<Vec<_>, _>>()?;
let outputs = inputs
.iter()
.enumerate()
.filter_map(construct_output_from_input)
.collect::<Vec<_>>();
Ok((inputs, outputs))
}
}
fn construct_output_from_input((idx, input): (usize, &fuel_tx::Input)) -> Option<fuel_tx::Output> {
match input {
fuel_tx::Input::Contract {
balance_root,
state_root,
..
} => Some(fuel_tx::Output::Contract {
input_index: idx as u8, balance_root: *balance_root,
state_root: *state_root,
}),
_ => None,
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone)]
#[serde(rename_all = "kebab-case")]
pub struct TxInput {
r#type: String,
utxo_id: Option<String>,
balance_root: Option<String>,
state_root: Option<String>,
contract_id: Option<String>,
owner: Option<String>,
amount: Option<String>,
color: Option<String>,
witness_index: Option<String>,
maturity: Option<String>,
predicate: Option<String>,
predicate_data: Option<String>,
}
fn try_parse_bytes32(raw: &Option<String>, name: &str) -> Result<fuel_tx::Bytes32, String> {
let mut raw = if let Some(raw) = raw {
raw.to_string()
} else {
return Err(format!("Missing value for field {}.\nhelp: a tx-input entry in your Forc.toml manifest is missing a field named {}.", name, name));
};
if raw.len() > 2 && &raw[0..2] == "0x" {
raw = (&raw[2..]).to_string();
}
Ok(TryFrom::try_from(
hex::decode(&raw[..])
.map_err(|_| format!(r#"Given value for "{}" ({}) is not valid."#, name, raw))?
.as_slice(),
)
.unwrap())
}
fn try_parse_contract_id(raw: &Option<String>) -> Result<fuel_tx::ContractId, String> {
let mut raw = if let Some(raw) = raw {
raw.to_string()
} else {
return Err("Missing contract-id in manifest.".into());
};
if raw.len() > 2 && &raw[0..2] == "0x" {
raw = (&raw[2..]).to_string();
}
Ok(TryFrom::try_from(
hex::decode(&raw[..])
.map_err(|_| {
format!(
r#"In the manifest file (Forc.toml), the given value for "contract-id" in tx-inputs ({}) is not hexadecimal."#,
raw
)
})?
.as_slice(),
)
.unwrap())
}
fn try_parse_utxo_id(raw: &Option<String>, name: &str) -> Result<fuel_tx::UtxoId, String> {
let mut raw = if let Some(raw) = raw {
raw.to_string()
} else {
return Err(format!("Missing value for field {}.\nhelp: a tx-input entry in your Forc.toml manifest is missing a field named {}.", name, name));
};
if raw.len() > 2 && &raw[0..2] == "0x" {
raw = (&raw[2..]).to_string();
}
fuel_tx::UtxoId::from_str(&raw[..])
.map_err(|_| {
format!(
r#"In the manifest file (Forc.toml), the given value for "utxo-id" in tx-inputs ({}) is a valid UTXO ID."#,
raw
)
}
)
}
impl TxInput {
pub fn to_input(&self) -> Result<fuel_tx::Input, String> {
match self.r#type.to_lowercase().as_ref() {
"contract" => Ok(fuel_tx::Input::Contract {
utxo_id: try_parse_utxo_id(&self.utxo_id, "utxo-id")?,
balance_root: try_parse_bytes32(&self.balance_root, "balance-root")?,
state_root: try_parse_bytes32(&self.state_root, "state-root")?,
contract_id: try_parse_contract_id(&self.contract_id)?,
}),
"coin" => Err("Coin transaction inputs are not currently supported.".into()),
a => Err(format!(
r#"Expected tx input type of either "Contract" or "Coin", but received "{}""#,
a
)),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Project {
pub author: String,
pub name: String,
pub organization: Option<String>,
pub license: String,
#[serde(default = "default_entry")]
pub entry: String,
}
fn default_entry() -> String {
"main.sw".into()
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "kebab-case")]
pub struct Network {
#[serde(default = "default_url")]
pub url: String,
}
fn default_url() -> String {
DEFAULT_NODE_URL.into()
}
#[test]
fn try_parse() {
println!(
"{:#?}",
toml::from_str::<Manifest>(&super::defaults::default_manifest("test_proj")).unwrap()
)
}
#[test]
fn test_print_tx_inputs() {
let mut default_manifest: Manifest =
toml::from_str::<Manifest>(&super::defaults::default_manifest("test_proj")).unwrap();
let input1 = TxInput {
contract_id: Some(
"0xeeb578f9e1ebfb5b78f8ff74352370c120bc8cacead1f5e4f9c74aafe0ca6bfd".into(),
),
utxo_id: Some("blah".into()),
..Default::default()
};
let input2 = TxInput {
contract_id: Some(
"0xe7777777777bfb5b78f8ff74352370c120bc8cacead1f5e4f9c74aafe0ca6bfd".into(),
),
utxo_id: Some("blah".into()),
..Default::default()
};
default_manifest.tx_input = Some(vec![input1, input2]);
println!("{}", toml::to_string(&default_manifest).unwrap());
}