#![doc = include_str!("../README.md")]
mod bech32;
pub mod capabilities;
#[cfg(feature = "native")]
pub mod cli;
pub mod default_context;
pub mod default_signature;
mod dispatch;
mod encode;
mod error;
pub mod hooks;
#[cfg(feature = "macros")]
mod reexport_macros;
#[cfg(feature = "macros")]
pub use reexport_macros::*;
mod prefix;
mod response;
mod serde_address;
#[cfg(test)]
mod tests;
pub mod transaction;
#[cfg(feature = "native")]
pub mod utils;
#[cfg(feature = "macros")]
extern crate sov_modules_macros;
use core::fmt::{self, Debug, Display};
use std::collections::{HashMap, HashSet};
use std::hash::Hash;
use std::str::FromStr;
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "native")]
pub use clap;
use digest::typenum::U32;
use digest::Digest;
#[cfg(feature = "native")]
pub use dispatch::CliWallet;
pub use dispatch::{DispatchCall, EncodeCall, Genesis};
pub use error::Error;
pub use prefix::Prefix;
pub use response::CallResponse;
#[cfg(feature = "native")]
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
pub use sov_rollup_interface::da::{BlobReaderTrait, DaSpec};
pub use sov_rollup_interface::services::da::SlotData;
pub use sov_rollup_interface::stf::Event;
pub use sov_rollup_interface::zk::{
StateTransition, ValidityCondition, ValidityConditionChecker, Zkvm,
};
pub use sov_rollup_interface::{digest, BasicAddress, RollupAddress};
use sov_state::{Storage, Witness, WorkingSet};
use thiserror::Error;
pub use crate::bech32::AddressBech32;
pub mod optimistic {
pub use sov_rollup_interface::optimistic::{Attestation, ProofOfBond};
}
impl AsRef<[u8]> for Address {
fn as_ref(&self) -> &[u8] {
&self.addr
}
}
impl BasicAddress for Address {}
impl RollupAddress for Address {}
#[cfg_attr(feature = "native", derive(schemars::JsonSchema))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[derive(PartialEq, Clone, Copy, Eq, borsh::BorshDeserialize, borsh::BorshSerialize, Hash)]
pub struct Address {
addr: [u8; 32],
}
impl<'a> TryFrom<&'a [u8]> for Address {
type Error = anyhow::Error;
fn try_from(addr: &'a [u8]) -> Result<Self, Self::Error> {
if addr.len() != 32 {
anyhow::bail!("Address must be 32 bytes long");
}
let mut addr_bytes = [0u8; 32];
addr_bytes.copy_from_slice(addr);
Ok(Self { addr: addr_bytes })
}
}
impl FromStr for Address {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
AddressBech32::from_str(s)
.map_err(|e| anyhow::anyhow!(e))
.map(|addr_bech32| addr_bech32.into())
}
}
impl From<[u8; 32]> for Address {
fn from(addr: [u8; 32]) -> Self {
Self { addr }
}
}
impl Display for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", AddressBech32::from(self))
}
}
impl Debug for Address {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", AddressBech32::from(self))
}
}
impl From<AddressBech32> for Address {
fn from(addr: AddressBech32) -> Self {
Self {
addr: addr.to_byte_array(),
}
}
}
#[derive(Error, Debug)]
pub enum SigVerificationError {
#[error("Bad signature {0}")]
BadSignature(String),
}
pub trait Signature {
type PublicKey;
fn verify(&self, pub_key: &Self::PublicKey, msg: &[u8]) -> Result<(), SigVerificationError>;
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "native", derive(schemars::JsonSchema))]
pub enum NonInstantiable {}
pub trait PublicKey {
fn to_address<A: RollupAddress>(&self) -> A;
}
#[cfg(feature = "native")]
pub trait PrivateKey {
type PublicKey: PublicKey;
type Signature: Signature<PublicKey = Self::PublicKey>;
fn generate() -> Self;
fn pub_key(&self) -> Self::PublicKey;
fn sign(&self, msg: &[u8]) -> Self::Signature;
fn to_address<A: RollupAddress>(&self) -> A {
self.pub_key().to_address::<A>()
}
}
pub trait Spec {
#[cfg(feature = "native")]
type Address: RollupAddress
+ BorshSerialize
+ BorshDeserialize
+ Sync
+ ::schemars::JsonSchema
+ Into<AddressBech32>
+ From<AddressBech32>
+ FromStr<Err = anyhow::Error>;
#[cfg(not(feature = "native"))]
type Address: RollupAddress + BorshSerialize + BorshDeserialize;
type Storage: Storage + Clone + Send + Sync;
#[cfg(feature = "native")]
type PublicKey: borsh::BorshDeserialize
+ borsh::BorshSerialize
+ Eq
+ Hash
+ Clone
+ Debug
+ PublicKey
+ Serialize
+ for<'a> Deserialize<'a>
+ ::schemars::JsonSchema
+ Send
+ Sync
+ FromStr<Err = anyhow::Error>;
#[cfg(feature = "native")]
type PrivateKey: Debug
+ Send
+ Sync
+ for<'a> TryFrom<&'a [u8], Error = anyhow::Error>
+ Serialize
+ DeserializeOwned
+ PrivateKey<PublicKey = Self::PublicKey, Signature = Self::Signature>;
#[cfg(not(feature = "native"))]
type PublicKey: borsh::BorshDeserialize
+ borsh::BorshSerialize
+ Eq
+ Hash
+ Clone
+ Debug
+ Send
+ Sync
+ PublicKey;
type Hasher: Digest<OutputSize = U32>;
#[cfg(feature = "native")]
type Signature: borsh::BorshDeserialize
+ borsh::BorshSerialize
+ Serialize
+ for<'a> Deserialize<'a>
+ schemars::JsonSchema
+ Eq
+ Clone
+ Debug
+ Send
+ Sync
+ FromStr<Err = anyhow::Error>
+ Signature<PublicKey = Self::PublicKey>;
#[cfg(not(feature = "native"))]
type Signature: borsh::BorshDeserialize
+ borsh::BorshSerialize
+ Eq
+ Clone
+ Debug
+ Signature<PublicKey = Self::PublicKey>
+ Send
+ Sync;
type Witness: Witness;
}
pub trait Context: Spec + Clone + Debug + PartialEq + 'static {
fn sender(&self) -> &Self::Address;
fn new(sender: Self::Address) -> Self;
}
impl<T> Genesis for T
where
T: Module,
{
type Context = <Self as Module>::Context;
type Config = <Self as Module>::Config;
fn genesis(
&self,
config: &Self::Config,
working_set: &mut WorkingSet<<<Self as Genesis>::Context as Spec>::Storage>,
) -> Result<(), Error> {
<Self as Module>::genesis(self, config, working_set)
}
}
pub trait Module: Default {
type Context: Context;
type Config;
type CallMessage: Debug + BorshSerialize + BorshDeserialize;
fn genesis(
&self,
_config: &Self::Config,
_working_set: &mut WorkingSet<<Self::Context as Spec>::Storage>,
) -> Result<(), Error> {
Ok(())
}
fn call(
&self,
_message: Self::CallMessage,
_context: &Self::Context,
_working_set: &mut WorkingSet<<Self::Context as Spec>::Storage>,
) -> Result<CallResponse, Error> {
unreachable!()
}
}
pub trait ModuleCallJsonSchema: Module {
fn json_schema() -> String;
}
pub trait ModuleInfo {
type Context: Context;
fn address(&self) -> &<Self::Context as Spec>::Address;
fn prefix(&self) -> Prefix;
fn dependencies(&self) -> Vec<&<Self::Context as Spec>::Address>;
}
struct ModuleVisitor<'a, C: Context> {
visited: HashSet<&'a <C as Spec>::Address>,
visited_on_this_path: Vec<&'a <C as Spec>::Address>,
sorted_modules: std::vec::Vec<&'a dyn ModuleInfo<Context = C>>,
}
impl<'a, C: Context> ModuleVisitor<'a, C> {
pub fn new() -> Self {
Self {
visited: HashSet::new(),
sorted_modules: Vec::new(),
visited_on_this_path: Vec::new(),
}
}
fn visit_modules(
&mut self,
modules: Vec<&'a dyn ModuleInfo<Context = C>>,
) -> Result<(), anyhow::Error> {
let mut module_map = HashMap::new();
for module in &modules {
module_map.insert(module.address(), *module);
}
for module in modules {
self.visited_on_this_path.clear();
self.visit_module(module, &module_map)?;
}
Ok(())
}
fn visit_module(
&mut self,
module: &'a dyn ModuleInfo<Context = C>,
module_map: &HashMap<&<C as Spec>::Address, &'a (dyn ModuleInfo<Context = C>)>,
) -> Result<(), anyhow::Error> {
let address = module.address();
if let Some((index, _)) = self
.visited_on_this_path
.iter()
.enumerate()
.find(|(_, &x)| x == address)
{
let cycle = &self.visited_on_this_path[index..];
anyhow::bail!(
"Cyclic dependency of length {} detected: {:?}",
cycle.len(),
cycle
);
} else {
self.visited_on_this_path.push(address)
}
if self.visited.insert(address) {
for dependency_address in module.dependencies() {
let dependency_module = *module_map.get(dependency_address).ok_or_else(|| {
anyhow::Error::msg(format!("Module not found: {:?}", dependency_address))
})?;
self.visit_module(dependency_module, module_map)?;
}
self.sorted_modules.push(module);
}
self.visited_on_this_path.pop();
Ok(())
}
}
fn sort_modules_by_dependencies<C: Context>(
modules: Vec<&dyn ModuleInfo<Context = C>>,
) -> Result<Vec<&dyn ModuleInfo<Context = C>>, anyhow::Error> {
let mut module_visitor = ModuleVisitor::<C>::new();
module_visitor.visit_modules(modules)?;
Ok(module_visitor.sorted_modules)
}
pub fn sort_values_by_modules_dependencies<C: Context, TValue>(
module_value_tuples: Vec<(&dyn ModuleInfo<Context = C>, TValue)>,
) -> Result<Vec<TValue>, anyhow::Error>
where
TValue: Clone,
{
let sorted_modules = sort_modules_by_dependencies(
module_value_tuples
.iter()
.map(|(module, _)| *module)
.collect(),
)?;
let mut value_map = HashMap::new();
for module in module_value_tuples {
let prev_entry = value_map.insert(module.0.address(), module.1);
anyhow::ensure!(prev_entry.is_none(), "Duplicate module address! Only one instance of each module is allowed in a given runtime. Module with address {} is duplicated", module.0.address());
}
let mut sorted_values = Vec::new();
for module in sorted_modules {
sorted_values.push(value_map.get(module.address()).unwrap().clone());
}
Ok(sorted_values)
}
#[cfg(feature = "native")]
pub trait CliWalletArg: From<Self::CliStringRepr> {
type CliStringRepr;
}