use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct CompileOptions {
pub include_source_info: bool,
}
impl Default for CompileOptions {
fn default() -> Self {
Self {
include_source_info: true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Compilation {
pub(super) contract: Contract,
pub(super) source_info: Option<SourceInfo>,
}
impl Compilation {
pub fn contract(&self) -> &Contract {
&self.contract
}
pub fn source_info(&self) -> Option<&SourceInfo> {
self.source_info.as_ref()
}
pub fn into_parts(self) -> (Contract, Option<SourceInfo>) {
(self.contract, self.source_info)
}
pub fn try_from_raw(
raw_contract: RawContract,
raw_source_info: Option<SerializedSourceInfo>,
limits: &crate::Limits,
) -> Result<Self, crate::ContractValidationError> {
let context = crate::RuntimeContext::new(limits.clone());
Self::try_from_raw_with_context(raw_contract, raw_source_info, &context)
}
pub fn try_from_raw_with_context(
raw_contract: RawContract,
raw_source_info: Option<SerializedSourceInfo>,
context: &crate::RuntimeContext,
) -> Result<Self, crate::ContractValidationError> {
let mut budget = context.budget();
Self::try_from_raw_with_budget(raw_contract, raw_source_info, &mut budget)
}
pub(crate) fn try_from_raw_with_budget(
raw_contract: RawContract,
raw_source_info: Option<SerializedSourceInfo>,
budget: &mut crate::budget::Budget<'_>,
) -> Result<Self, crate::ContractValidationError> {
let (contract, mapping) = Contract::from_raw_with_mapping_and_budget(raw_contract, budget)?;
let source_info = raw_source_info
.map(SourceInfo::from_raw_unchecked)
.map(|mut source_info| {
remap_source_info(&mut source_info, &mapping, budget)?;
source_info.validate_with_budget(&contract, budget)?;
Ok::<SourceInfo, crate::ContractValidationError>(source_info)
})
.transpose()?;
Ok(Self {
contract,
source_info,
})
}
pub fn from_json_with_limits(
input: &str,
limits: &crate::Limits,
) -> Result<Self, crate::ContractJsonError> {
Self::from_json_with_context(input, &crate::RuntimeContext::new(limits.clone()))
}
pub fn from_json_with_context(
input: &str,
context: &crate::RuntimeContext,
) -> Result<Self, crate::ContractJsonError> {
let mut budget = context.budget();
let raw: RawCompilation = crate::budget::decode_bounded(&mut budget, input.len(), || {
serde_json::from_str(input)
})?;
Self::try_from_raw_with_budget(raw.contract, raw.source_info, &mut budget)
.map_err(crate::ContractJsonError::InvalidContract)
}
pub fn from_slice_with_limits(
input: &[u8],
limits: &crate::Limits,
) -> Result<Self, crate::ContractJsonError> {
Self::from_slice_with_context(input, &crate::RuntimeContext::new(limits.clone()))
}
pub fn from_slice_with_context(
input: &[u8],
context: &crate::RuntimeContext,
) -> Result<Self, crate::ContractJsonError> {
let mut budget = context.budget();
let raw: RawCompilation = crate::budget::decode_bounded(&mut budget, input.len(), || {
serde_json::from_slice(input)
})?;
Self::try_from_raw_with_budget(raw.contract, raw.source_info, &mut budget)
.map_err(crate::ContractJsonError::InvalidContract)
}
pub fn to_json_pretty_with_limits(
&self,
limits: &crate::Limits,
) -> Result<String, crate::ContractValidationError> {
self.to_json_pretty_with_context(&crate::RuntimeContext::new(limits.clone()))
}
pub fn to_json_pretty_with_context(
&self,
context: &crate::RuntimeContext,
) -> Result<String, crate::ContractValidationError> {
let mut budget = context.budget();
crate::validate::validate_contract_with_budget(&self.contract, &mut budget)?;
budget
.checkpoint()
.map_err(crate::budget::BudgetError::into_contract_error)?;
let json = serde_json::to_string_pretty(&CompilationRef {
contract: &self.contract,
source_info: &self.source_info,
})
.map_err(|error| {
crate::ContractValidationError::single(
"contract_json_serialization_failed",
"$",
error.to_string(),
)
})?;
let max_work = budget.limits().max_canonicalization_work;
budget
.charge("canonicalization_work", max_work, json.len())
.map_err(crate::budget::BudgetError::into_contract_error)?;
budget
.checkpoint()
.map_err(crate::budget::BudgetError::into_contract_error)?;
Ok(json)
}
}
#[derive(Serialize)]
#[serde(deny_unknown_fields)]
struct CompilationRef<'a> {
contract: &'a Contract,
#[serde(default, skip_serializing_if = "Option::is_none")]
source_info: &'a Option<SourceInfo>,
}
impl Serialize for Compilation {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
CompilationRef {
contract: &self.contract,
source_info: &self.source_info,
}
.serialize(serializer)
}
}
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RawCompilation {
contract: RawContract,
#[serde(default)]
source_info: Option<SerializedSourceInfo>,
}
fn remap_source_info(
source_info: &mut SourceInfo,
mapping: &[TypeRef],
budget: &mut crate::budget::Budget<'_>,
) -> Result<(), crate::ContractValidationError> {
crate::source::observe_remapped_collections(source_info, budget)?;
let map = |reference: TypeRef| {
mapping.get(reference as usize).copied().ok_or_else(|| {
crate::ContractValidationError::single(
"source_type_ref_out_of_bounds",
"$",
format!("source sidecar type reference {reference} is outside the input arena"),
)
})
};
for declaration in &mut source_info.declarations {
budget
.checkpoint()
.map_err(crate::budget::BudgetError::into_contract_error)?;
declaration.ty = map(declaration.ty)?;
}
for field in &mut source_info.field_labels {
budget
.checkpoint()
.map_err(crate::budget::BudgetError::into_contract_error)?;
field.container = map(field.container)?;
}
for method in &mut source_info.methods {
budget
.checkpoint()
.map_err(crate::budget::BudgetError::into_contract_error)?;
method.service = map(method.service)?;
}
for argument in &mut source_info.function_arguments {
budget
.checkpoint()
.map_err(crate::budget::BudgetError::into_contract_error)?;
argument.function = map(argument.function)?;
}
Ok(())
}