Skip to content

Commit

Permalink
feat: Allow component templating
Browse files Browse the repository at this point in the history
  • Loading branch information
igamigo committed Dec 23, 2024
1 parent a41bb4b commit c73a3d2
Show file tree
Hide file tree
Showing 5 changed files with 448 additions and 66 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Changes

- Introduced `ComponentPackage` and `ComponentConfig` with TOML serialization (#1015).
- Introduced `ComponentPackage` and `ComponentMetadata` with TOML serialization and templating (#1015, #1027).
- Removed workers list from the proxy configuration file (#1018).
- Added health check endpoints to the prover service (#1006).
- Implemented serialization for `AccountHeader` (#996).
Expand Down
164 changes: 132 additions & 32 deletions objects/src/accounts/package/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
use alloc::{string::String, vec::Vec};
use std::{collections::BTreeSet, string::ToString};
use alloc::{
collections::{BTreeMap, BTreeSet},
string::{String, ToString},
vec::Vec,
};

use assembly::Library;
use semver::Version;
use serde::{
de::{Error as DeError, Unexpected},
Deserialize, Deserializer, Serialize, Serializer,
};
use storage_entry::{TemplateKey, TemplateValue};
use thiserror::Error;
use vm_core::utils::{Deserializable, Serializable};

use super::AccountType;
use super::{AccountComponent, AccountType};
use crate::AccountError;

mod storage_entry;
pub use storage_entry::StorageEntry;
Expand All @@ -20,9 +25,13 @@ pub use storage_entry::StorageEntry;

/// Represents a package containing a component's metadata and its associated library.
///
/// The `ComponentPackage` encapsulates all necessary information to initialize and manage
/// The [ComponentPackage] encapsulates all necessary information to initialize and manage
/// a component within the system. It includes the configuration details and the compiled
/// library code required for the component's operation.
///
/// A package can be instantiated into [AccountComponent] objects by calling `instantiate()`.
/// The component metadata can be defined with generic keys that can be replaced at instantiation
/// time.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ComponentPackage {
/// The component's metadata. This describes the component and how the storage is laid out,
Expand All @@ -38,12 +47,37 @@ impl ComponentPackage {
pub fn new(
metadata: ComponentMetadata,
library: Library,
) -> Result<Self, ComponentMetadataError> {
) -> Result<Self, ComponentPackageError> {
_ = toml::to_string(&metadata)
.map_err(|err| ComponentMetadataError::MetadataDeserializationError(err.to_string()));
.map_err(|err| ComponentPackageError::MetadataDeserializationError(err.to_string()));
Ok(Self { metadata, library })
}

/// Instantiates an [AccountComponent] from the [ComponentPackage].
///
/// The package's component metadata might contain templated values, which can be input by
/// mapping key names to [template values](TemplateValue) through the `template_keys`
/// parameter.
///
/// # Errors
///
/// - If any of the component's storage entries cannot be transformed into a valid storage
/// slot. This could be because the metadata is invalid, or template values were not provided
/// (or they are not of a valid type)
pub fn instantiate_component(
&self,
template_keys: &BTreeMap<String, TemplateValue>,
) -> Result<AccountComponent, ComponentPackageError> {
let mut storage_slots = vec![];
for storage_entry in self.metadata().storage_entries() {
let entry_storage_slots =
storage_entry.clone().try_into_storage_slots(template_keys)?;
storage_slots.extend(entry_storage_slots);
}

AccountComponent::new(self.library.clone(), storage_slots)
.map_err(ComponentPackageError::AccountComponentError)
}
pub fn metadata(&self) -> &ComponentMetadata {
&self.metadata
}
Expand Down Expand Up @@ -110,41 +144,92 @@ pub struct ComponentMetadata {
}

impl ComponentMetadata {
/// Create a new `ComponentMetadata`
/// Create a new `ComponentMetadata`.
///
/// # Errors
///
/// - If the specified storage slots are not contiguous across all storage entries
/// - If the specified storage slots are not contiguous across all storage entries.
pub fn new(
name: String,
description: String,
version: Version,
targets: BTreeSet<AccountType>,
storage: Vec<StorageEntry>,
) -> Result<Self, ComponentMetadataError> {
// Ensure no gaps in slots
let mut all_slots: Vec<u8> =
storage.iter().flat_map(|entry| entry.slot_indices().iter().copied()).collect();
) -> Result<Self, ComponentPackageError> {
let component = Self {
name,
description,
version,
targets,
storage,
};
component.validate()?;
Ok(component)
}

/// Validate the `ComponentMetadata` object.
///
/// # Errors
///
/// - If the specified storage slots contain duplicates.
/// - If the first slot is not zero.
/// - If the slots are not contiguous.
pub fn validate(&self) -> Result<(), ComponentPackageError> {
let mut all_slots: Vec<u8> = self
.storage
.iter()
.flat_map(|entry| entry.slot_indices().iter().copied())
.collect();

// Check for duplicates
let mut seen = BTreeSet::new();
let duplicate =
all_slots
.iter()
.find_map(|&slot| if !seen.insert(slot) { Some(slot) } else { None });

if let Some(dup) = duplicate {
return Err(ComponentPackageError::DuplicateSlots(dup));
}

// Check that slots start at 0 and are contiguous
all_slots.sort_unstable();
if let Some(v) = all_slots.first() {
if *v != 0 {
return Err(ComponentMetadataError::NonContiguousSlots);
if let Some(&first_slot) = all_slots.first() {
if first_slot != 0 {
return Err(ComponentPackageError::IncorrectStorageFirstSlot);
}
}
for slots in all_slots.windows(2) {
if slots[1] != slots[0] + 1 {
return Err(ComponentMetadataError::NonContiguousSlots);
return Err(ComponentPackageError::NonContiguousSlots);
}
}

Ok(Self {
name,
description,
version,
targets,
storage,
})
Ok(())
}

/// Deserializes `toml_string` and validates the resulting [ComponentMetadata]
///
/// # Errors
///
/// - If deserialization or validation fails
pub fn from_toml(toml_string: &str) -> Result<Self, ComponentPackageError> {
let component: ComponentMetadata =
toml::from_str(toml_string).map_err(ComponentPackageError::DeserializationError)?;
component.validate()?;
Ok(component)
}

/// Retrieves the set of keys (identified by a string) that require a value at the moment of
/// component instantiation.
fn _get_template_keys(&self) -> BTreeSet<TemplateKey> {
let mut key_set = BTreeSet::new();
for storage_entry in &self.storage {
for key in storage_entry.template_keys() {
key_set.insert(key.clone());
}
}
key_set
}

pub fn name(&self) -> &str {
Expand All @@ -163,19 +248,33 @@ impl ComponentMetadata {
&self.targets
}

pub fn storage(&self) -> &Vec<StorageEntry> {
pub fn storage_entries(&self) -> &Vec<StorageEntry> {
&self.storage
}
}

#[derive(Debug, Error)]
pub enum ComponentMetadataError {
#[error("multi slot storage entry should contain one value per slot")]
pub enum ComponentPackageError {
#[error("error creating AccountComponent: {0}")]
AccountComponentError(AccountError),
#[error("error trying to deserialize from toml")]
DeserializationError(#[source] toml::de::Error),
#[error("slot {0} is defined multiple times")]
DuplicateSlots(u8),
#[error("component storage slots have to start at 0")]
IncorrectStorageFirstSlot,
#[error("template value was not of the expected type {0}")]
IncorrectTemplateValue(String),
#[error("multi-slot entry should contain as many values as storage slots indices")]
InvalidMultiSlotEntry,
#[error("component storage slots are not contiguous")]
NonContiguousSlots,
#[error("error deserializing component metadata: {0}")]
MetadataDeserializationError(String),
#[error("component storage slots are not contiguous")]
NonContiguousSlots,
#[error("error creating storage map: {0}")]
StorageMapError(AccountError),
#[error("template value ({0}) was not provided in the map")]
TemplateValueNotProvided(String),
}

// SERIALIZATION
Expand Down Expand Up @@ -283,7 +382,7 @@ mod tests {
BTreeSet::new(),
storage,
);
assert!(result.is_err());
assert!(matches!(result, Err(ComponentPackageError::NonContiguousSlots)));
}

#[test]
Expand Down Expand Up @@ -316,11 +415,12 @@ mod tests {
.unwrap();

let library = Assembler::default().assemble_library([CODE]).unwrap();
let component = ComponentPackage::new(component_template, library).unwrap();
let package = ComponentPackage::new(component_template, library).unwrap();
_ = package.instantiate_component(&BTreeMap::new()).unwrap();

let serialized = component.to_bytes();
let serialized = package.to_bytes();
let deserialized = ComponentPackage::read_from_bytes(&serialized).unwrap();

assert_eq!(deserialized, component)
assert_eq!(deserialized, package)
}
}
Loading

0 comments on commit c73a3d2

Please sign in to comment.