From 1e2087997999d7ee30e696051f18be4211c9df28 Mon Sep 17 00:00:00 2001 From: Leigh McCulloch <351529+leighmcculloch@users.noreply.github.com> Date: Wed, 28 Feb 2024 06:04:31 +1000 Subject: [PATCH] Remove soroban-spec-tools (#80) --- .github/workflows/rust.yml | 2 +- Cargo.lock | 20 - Cargo.toml | 7 +- cmd/crates/soroban-rpc/Cargo.toml | 1 - cmd/crates/soroban-rpc/src/lib.rs | 12 +- cmd/crates/soroban-spec-tools/Cargo.toml | 39 - cmd/crates/soroban-spec-tools/README.md | 3 - cmd/crates/soroban-spec-tools/src/contract.rs | 276 ---- cmd/crates/soroban-spec-tools/src/lib.rs | 1469 ----------------- cmd/crates/soroban-spec-tools/src/utils.rs | 294 ---- 10 files changed, 5 insertions(+), 2118 deletions(-) delete mode 100644 cmd/crates/soroban-spec-tools/Cargo.toml delete mode 100644 cmd/crates/soroban-spec-tools/README.md delete mode 100644 cmd/crates/soroban-spec-tools/src/contract.rs delete mode 100644 cmd/crates/soroban-spec-tools/src/lib.rs delete mode 100644 cmd/crates/soroban-spec-tools/src/utils.rs diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 5235d480..695bbbd4 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -118,7 +118,7 @@ jobs: cargo-hack-feature-options: --features opt --ignore-unknown-features uses: stellar/actions/.github/workflows/rust-publish-dry-run-v2.yml@main with: - crates: soroban-spec-tools soroban-rpc + crates: soroban-rpc runs-on: ${{ matrix.os }} target: ${{ matrix.target }} cargo-hack-feature-options: ${{ matrix.cargo-hack-feature-options }} diff --git a/Cargo.lock b/Cargo.lock index d28c8f90..a730a8d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1661,7 +1661,6 @@ dependencies = [ "soroban-env-host", "soroban-sdk", "soroban-spec", - "soroban-spec-tools", "stellar-strkey 0.0.7", "stellar-xdr", "termcolor", @@ -1751,25 +1750,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "soroban-spec-tools" -version = "20.3.3" -dependencies = [ - "base64 0.21.7", - "ethnum", - "hex", - "itertools 0.10.5", - "serde_json", - "soroban-env-host", - "soroban-spec", - "stellar-strkey 0.0.7", - "stellar-xdr", - "thiserror", - "tokio", - "wasmparser 0.90.0", - "which", -] - [[package]] name = "soroban-wasmi" version = "0.31.1-soroban.20.0.1" diff --git a/Cargo.toml b/Cargo.toml index a0c6ea67..389794b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,10 +2,9 @@ resolver = "2" members = [ "cmd/crates/soroban-rpc", - "cmd/crates/soroban-spec-tools", "cmd/soroban-rpc/lib/preflight", ] -default-members = ["cmd/crates/soroban-rpc", "cmd/crates/soroban-spec-tools"] +default-members = ["cmd/crates/soroban-rpc"] #exclude = ["cmd/crates/soroban-test/tests/fixtures/hello"] [workspace.package] @@ -46,10 +45,6 @@ version = "20.3.0" git = "https://github.com/stellar/soroban-tools" rev = "a59f5f421a27bab71472041fc619dd8b0d1cf902" -[workspace.dependencies.soroban-spec-tools] -version = "20.3.3" -path = "./cmd/crates/soroban-spec-tools" - [workspace.dependencies.soroban-sdk] version = "=20.3.1" # git = "https://github.com/stellar/rs-soroban-sdk" diff --git a/cmd/crates/soroban-rpc/Cargo.toml b/cmd/crates/soroban-rpc/Cargo.toml index a1ff2482..2ad0970c 100644 --- a/cmd/crates/soroban-rpc/Cargo.toml +++ b/cmd/crates/soroban-rpc/Cargo.toml @@ -18,7 +18,6 @@ crate-type = ["rlib"] [dependencies] soroban-sdk = { workspace = true } -soroban-spec-tools = { workspace = true } soroban-env-host = { workspace = true } stellar-strkey = "0.0.7" stellar-xdr = { workspace = true, features = ["curr", "std", "serde"] } diff --git a/cmd/crates/soroban-rpc/src/lib.rs b/cmd/crates/soroban-rpc/src/lib.rs index 95c3692e..bc2dc920 100644 --- a/cmd/crates/soroban-rpc/src/lib.rs +++ b/cmd/crates/soroban-rpc/src/lib.rs @@ -31,8 +31,6 @@ mod txn; pub use txn::Assembled; -use soroban_spec_tools::contract; - const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); pub(crate) const DEFAULT_TRANSACTION_FEES: u32 = 100; @@ -92,8 +90,6 @@ pub enum Error { UnsupportedOperationType, #[error("unexpected contract code data type: {0:?}")] UnexpectedContractCodeDataType(LedgerEntryData), - #[error(transparent)] - CouldNotParseContractSpec(#[from] contract::Error), #[error("unexpected contract code got token")] UnexpectedToken(ContractDataEntry), #[error(transparent)] @@ -1010,11 +1006,9 @@ soroban config identity fund {address} --helper-url "# xdr::ScVal::ContractInstance(xdr::ScContractInstance { executable: xdr::ContractExecutable::Wasm(hash), .. - }) => Ok( - contract::Spec::new(&self.get_remote_wasm_from_hash(hash).await?) - .map_err(Error::CouldNotParseContractSpec)? - .spec, - ), + }) => Ok(soroban_spec::read::from_wasm( + &self.get_remote_wasm_from_hash(hash).await?, + )?), xdr::ScVal::ContractInstance(xdr::ScContractInstance { executable: xdr::ContractExecutable::StellarAsset, .. diff --git a/cmd/crates/soroban-spec-tools/Cargo.toml b/cmd/crates/soroban-spec-tools/Cargo.toml deleted file mode 100644 index 514b8292..00000000 --- a/cmd/crates/soroban-spec-tools/Cargo.toml +++ /dev/null @@ -1,39 +0,0 @@ -[package] -name = "soroban-spec-tools" -description = "Tools for using a contract's XDR spec" -homepage = "https://github.com/stellar/soroban-tools" -repository = "https://github.com/stellar/soroban-tools" -authors = ["Stellar Development Foundation "] -license = "Apache-2.0" -readme = "README.md" -version.workspace = true -edition = "2021" -rust-version.workspace = true -autobins = false - - -[lib] -crate-type = ["rlib"] - - -[dependencies] -soroban-spec = { workspace = true } -stellar-strkey = { workspace = true } -stellar-xdr = { workspace = true, features = ["curr", "std", "serde"] } -soroban-env-host = { workspace = true } - -serde_json = { workspace = true } -itertools = { workspace = true } -ethnum = { workspace = true } -hex = { workspace = true } -wasmparser = { workspace = true } -base64 = { workspace = true } -thiserror = "1.0.31" -# soroban-ledger-snapshot = { workspace = true } -# soroban-sdk = { workspace = true } -# sep5 = { workspace = true } - - -[dev-dependencies] -which = { workspace = true } -tokio = "1.28.1" diff --git a/cmd/crates/soroban-spec-tools/README.md b/cmd/crates/soroban-spec-tools/README.md deleted file mode 100644 index d2b00654..00000000 --- a/cmd/crates/soroban-spec-tools/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# soroban-spec-tools - -Tools and utilities for soroban specification / interface. diff --git a/cmd/crates/soroban-spec-tools/src/contract.rs b/cmd/crates/soroban-spec-tools/src/contract.rs deleted file mode 100644 index 2d0d857c..00000000 --- a/cmd/crates/soroban-spec-tools/src/contract.rs +++ /dev/null @@ -1,276 +0,0 @@ -use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; -use std::{ - fmt::Display, - io::{self, Cursor}, -}; - -use soroban_env_host::xdr::{ - self, Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, - ScSpecFunctionV0, ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, - StringM, WriteXdr, -}; - -pub struct Spec { - pub env_meta_base64: Option, - pub env_meta: Vec, - pub meta_base64: Option, - pub meta: Vec, - pub spec_base64: Option, - pub spec: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] xdr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), -} - -impl Spec { - pub fn new(bytes: &[u8]) -> Result { - let mut env_meta: Option<&[u8]> = None; - let mut meta: Option<&[u8]> = None; - let mut spec: Option<&[u8]> = None; - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - if let wasmparser::Payload::CustomSection(section) = payload { - let out = match section.name() { - "contractenvmetav0" => &mut env_meta, - "contractmetav0" => &mut meta, - "contractspecv0" => &mut spec, - _ => continue, - }; - *out = Some(section.data()); - }; - } - - let mut env_meta_base64 = None; - let env_meta = if let Some(env_meta) = env_meta { - env_meta_base64 = Some(base64.encode(env_meta)); - let cursor = Cursor::new(env_meta); - let mut read = Limited::new(cursor, Limits::none()); - ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? - } else { - vec![] - }; - - let mut meta_base64 = None; - let meta = if let Some(meta) = meta { - meta_base64 = Some(base64.encode(meta)); - let cursor = Cursor::new(meta); - let mut depth_limit_read = Limited::new(cursor, Limits::none()); - ScMetaEntry::read_xdr_iter(&mut depth_limit_read) - .collect::, xdr::Error>>()? - } else { - vec![] - }; - - let mut spec_base64 = None; - let spec = if let Some(spec) = spec { - spec_base64 = Some(base64.encode(spec)); - let cursor = Cursor::new(spec); - let mut read = Limited::new(cursor, Limits::none()); - ScSpecEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()? - } else { - vec![] - }; - - Ok(Spec { - env_meta_base64, - env_meta, - meta_base64, - meta, - spec_base64, - spec, - }) - } - - pub fn spec_as_json_array(&self) -> Result { - let spec = self - .spec - .iter() - .map(|e| Ok(format!("\"{}\"", e.to_xdr_base64(Limits::none())?))) - .collect::, Error>>()? - .join(",\n"); - Ok(format!("[{spec}]")) - } -} - -impl Display for Spec { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(env_meta) = &self.env_meta_base64 { - writeln!(f, "Env Meta: {env_meta}")?; - for env_meta_entry in &self.env_meta { - match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - writeln!(f, " • Interface Version: {v}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Env Meta: None\n")?; - } - - if let Some(_meta) = &self.meta_base64 { - writeln!(f, "Contract Meta:")?; - for meta_entry in &self.meta { - match meta_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => { - writeln!(f, " • {key}: {val}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Contract Meta: None\n")?; - } - - if let Some(_spec_base64) = &self.spec_base64 { - writeln!(f, "Contract Spec:")?; - for spec_entry in &self.spec { - match spec_entry { - ScSpecEntry::FunctionV0(func) => write_func(f, func)?, - ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?, - ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?, - ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, - ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, - } - } - } else { - writeln!(f, "Contract Spec: None")?; - } - Ok(()) - } -} - -fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result { - writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?; - if func.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - &indent(&func.doc.to_utf8_string_lossy(), 11).trim() - )?; - } - writeln!( - f, - " Inputs: {}", - indent(&format!("{:#?}", func.inputs), 5).trim() - )?; - writeln!( - f, - " Output: {}", - indent(&format!("{:#?}", func.outputs), 5).trim() - )?; - writeln!(f)?; - Ok(()) -} - -fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result { - writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result { - writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Fields:")?; - for field in udt.fields.iter() { - writeln!( - f, - " • {}: {}", - field.name.to_utf8_string_lossy(), - indent(&format!("{:#?}", field.type_), 8).trim() - )?; - if field.doc.len() > 0 { - writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?; - } - } - writeln!(f)?; - Ok(()) -} - -fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result { - writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result { - writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn indent(s: &str, n: usize) -> String { - let pad = " ".repeat(n); - s.lines() - .map(|line| format!("{pad}{line}")) - .collect::>() - .join("\n") -} - -fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { - if lib.len() > 0 { - format!( - "{}::{}", - lib.to_utf8_string_lossy(), - name.to_utf8_string_lossy() - ) - } else { - name.to_utf8_string_lossy() - } -} diff --git a/cmd/crates/soroban-spec-tools/src/lib.rs b/cmd/crates/soroban-spec-tools/src/lib.rs deleted file mode 100644 index bc3c2a38..00000000 --- a/cmd/crates/soroban-spec-tools/src/lib.rs +++ /dev/null @@ -1,1469 +0,0 @@ -#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)] -use std::str::FromStr; - -use itertools::Itertools; -use serde_json::{json, Value}; -use stellar_xdr::curr::{ - AccountId, BytesM, ContractExecutable, Error as XdrError, Hash, Int128Parts, Int256Parts, - PublicKey, ScAddress, ScBytes, ScContractInstance, ScMap, ScMapEntry, ScNonceKey, ScSpecEntry, - ScSpecFunctionV0, ScSpecTypeDef as ScType, ScSpecTypeMap, ScSpecTypeOption, ScSpecTypeResult, - ScSpecTypeTuple, ScSpecTypeUdt, ScSpecTypeVec, ScSpecUdtEnumV0, ScSpecUdtErrorEnumCaseV0, - ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionCaseTupleV0, ScSpecUdtUnionCaseV0, - ScSpecUdtUnionCaseVoidV0, ScSpecUdtUnionV0, ScString, ScSymbol, ScVal, ScVec, StringM, - UInt128Parts, UInt256Parts, Uint256, VecM, -}; - -pub mod contract; -pub mod utils; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("an unknown error occurred")] - Unknown, - #[error("Invalid pair {0:#?} {1:#?}")] - InvalidPair(ScVal, ScType), - #[error("value is not parseable to {0:#?}")] - InvalidValue(Option), - #[error("Unknown case {0} for {1}")] - EnumCase(String, String), - #[error("Enum {0} missing value for type {1}")] - EnumMissingSecondValue(String, String), - #[error("Enum {0} is illformed")] - IllFormedEnum(String), - #[error("Unknown const case {0}")] - EnumConst(u32), - #[error("Enum const value must be a u32 or smaller")] - EnumConstTooLarge(u64), - #[error("Missing Entry {0}")] - MissingEntry(String), - #[error("Missing Spec")] - MissingSpec, - #[error(transparent)] - Xdr(XdrError), - #[error(transparent)] - Serde(#[from] serde_json::Error), - #[error(transparent)] - Ethnum(#[from] core::num::ParseIntError), - - #[error("Missing key {0} in map")] - MissingKey(String), - #[error("Failed to convert {0} to number")] - FailedNumConversion(serde_json::Number), - #[error("First argument in an enum must be a sybmol")] - EnumFirstValueNotSymbol, - #[error("Failed to find enum case {0}")] - FailedToFindEnumCase(String), - #[error(transparent)] - FailedSilceToByte(#[from] std::array::TryFromSliceError), - #[error(transparent)] - Infallible(#[from] std::convert::Infallible), - #[error("Missing Error case {0}")] - MissingErrorCase(u32), - #[error(transparent)] - Spec(#[from] soroban_spec::read::FromWasmError), - #[error(transparent)] - Base64Spec(#[from] soroban_spec::read::ParseSpecBase64Error), -} - -#[derive(Default, Clone)] -pub struct Spec(pub Option>); - -impl TryInto for &[u8] { - type Error = soroban_spec::read::FromWasmError; - - fn try_into(self) -> Result { - let spec = soroban_spec::read::from_wasm(self)?; - Ok(Spec::new(spec)) - } -} - -impl Spec { - pub fn new(entries: Vec) -> Self { - Self(Some(entries)) - } - - pub fn from_wasm(wasm: &[u8]) -> Result { - let spec = soroban_spec::read::from_wasm(wasm)?; - Ok(Spec::new(spec)) - } - - pub fn parse_base64(base64: &str) -> Result { - let spec = soroban_spec::read::parse_base64(base64.as_bytes())?; - Ok(Spec::new(spec)) - } -} - -impl Spec { - /// # Errors - /// Could fail to find User Defined Type - pub fn doc(&self, name: &str, type_: &ScType) -> Result, Error> { - let mut str = match type_ { - ScType::Val - | ScType::U64 - | ScType::I64 - | ScType::U128 - | ScType::I128 - | ScType::U32 - | ScType::I32 - | ScType::Result(_) - | ScType::Vec(_) - | ScType::Map(_) - | ScType::Tuple(_) - | ScType::BytesN(_) - | ScType::Symbol - | ScType::Error - | ScType::Bytes - | ScType::Void - | ScType::Timepoint - | ScType::Duration - | ScType::U256 - | ScType::I256 - | ScType::String - | ScType::Bool => String::new(), - ScType::Address => String::from( - "Can be public key (G13..), a contract hash (6c45307) or an identity (alice), ", - ), - ScType::Option(type_) => return self.doc(name, &type_.value_type), - ScType::Udt(ScSpecTypeUdt { name }) => { - let spec_type = self.find(&name.to_utf8_string_lossy())?; - match spec_type { - ScSpecEntry::FunctionV0(ScSpecFunctionV0 { doc, .. }) - | ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { doc, .. }) - | ScSpecEntry::UdtUnionV0(ScSpecUdtUnionV0 { doc, .. }) - | ScSpecEntry::UdtEnumV0(ScSpecUdtEnumV0 { doc, .. }) - | ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { doc, .. }) => doc, - } - .to_utf8_string_lossy() - } - }; - if let Some(mut ex) = self.example(type_) { - if ex.contains(' ') { - ex = format!("'{ex}'"); - } else if ex.contains('"') { - ex = ex.replace('"', ""); - } - if matches!(type_, ScType::Bool) { - ex = String::new(); - } - let sep = if str.is_empty() { "" } else { "\n" }; - str = format!("{str}{sep}Example:\n --{name} {ex}"); - if ex.contains('"') {} - } - if str.is_empty() { - Ok(None) - } else { - Ok(Some(Box::leak(str.into_boxed_str()))) - } - } - - /// # Errors - /// - /// Might return errors - pub fn find(&self, name: &str) -> Result<&ScSpecEntry, Error> { - self.0 - .as_ref() - .and_then(|specs| { - specs.iter().find(|e| { - let entry_name = match e { - ScSpecEntry::FunctionV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtStructV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtUnionV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtEnumV0(x) => x.name.to_utf8_string_lossy(), - ScSpecEntry::UdtErrorEnumV0(x) => x.name.to_utf8_string_lossy(), - }; - name == entry_name - }) - }) - .ok_or_else(|| Error::MissingEntry(name.to_owned())) - } - - /// # Errors - /// - /// Might return errors - pub fn find_function(&self, name: &str) -> Result<&ScSpecFunctionV0, Error> { - match self.find(name)? { - ScSpecEntry::FunctionV0(f) => Ok(f), - _ => Err(Error::MissingEntry(name.to_owned())), - } - } - // - /// # Errors - /// - pub fn find_functions(&self) -> Result, Error> { - Ok(self - .0 - .as_ref() - .ok_or(Error::MissingSpec)? - .iter() - .filter_map(|e| match e { - ScSpecEntry::FunctionV0(x) => Some(x), - _ => None, - })) - } - - /// # Errors - /// - pub fn find_error_type(&self, value: u32) -> Result<&ScSpecUdtErrorEnumCaseV0, Error> { - if let ScSpecEntry::UdtErrorEnumV0(ScSpecUdtErrorEnumV0 { cases, .. }) = - self.find("Error")? - { - if let Some(case) = cases.iter().find(|case| value == case.value) { - return Ok(case); - } - } - Err(Error::MissingErrorCase(value)) - } - - /// # Errors - /// - /// Might return errors - pub fn from_string_primitive(s: &str, t: &ScType) -> Result { - Self::default().from_string(s, t) - } - - /// # Errors - /// - /// Might return errors - #[allow(clippy::wrong_self_convention)] - pub fn from_string(&self, s: &str, t: &ScType) -> Result { - if let ScType::Option(b) = t { - if s == "null" { - return Ok(ScVal::Void); - } - let ScSpecTypeOption { value_type } = b.as_ref().clone(); - let v = value_type.as_ref().clone(); - return self.from_string(s, &v); - } - // Parse as string and for special types assume Value::String - serde_json::from_str(s) - .map_or_else( - |e| match t { - ScType::Symbol - | ScType::String - | ScType::Bytes - | ScType::BytesN(_) - | ScType::U256 - | ScType::I256 - | ScType::U128 - | ScType::I128 - | ScType::Address => Ok(Value::String(s.to_owned())), - ScType::Udt(ScSpecTypeUdt { name }) - if matches!( - self.find(&name.to_utf8_string_lossy())?, - ScSpecEntry::UdtUnionV0(_) | ScSpecEntry::UdtStructV0(_) - ) => - { - Ok(Value::String(s.to_owned())) - } - _ => Err(Error::Serde(e)), - }, - |val| match t { - ScType::U128 | ScType::I128 | ScType::U256 | ScType::I256 => { - Ok(Value::String(s.to_owned())) - } - _ => Ok(val), - }, - ) - .and_then(|raw| self.from_json(&raw, t)) - } - - /// # Errors - /// - /// Might return errors - #[allow(clippy::wrong_self_convention)] - pub fn from_json(&self, v: &Value, t: &ScType) -> Result { - let val: ScVal = match (t, v) { - ( - ScType::Bool - | ScType::U128 - | ScType::I128 - | ScType::U256 - | ScType::I256 - | ScType::I32 - | ScType::I64 - | ScType::U32 - | ScType::U64 - | ScType::String - | ScType::Symbol - | ScType::Address - | ScType::Bytes - | ScType::BytesN(_), - _, - ) => from_json_primitives(v, t)?, - - // Vec parsing - (ScType::Vec(elem), Value::Array(raw)) => { - let converted: ScVec = raw - .iter() - .map(|item| self.from_json(item, &elem.element_type)) - .collect::, Error>>()? - .try_into() - .map_err(Error::Xdr)?; - ScVal::Vec(Some(converted)) - } - - // Map parsing - (ScType::Map(map), Value::Object(raw)) => self.parse_map(map, raw)?, - - // Option parsing - (ScType::Option(_), Value::Null) => ScVal::Void, - (ScType::Option(elem), v) => self.from_json(v, &elem.value_type)?, - - // Tuple parsing - (ScType::Tuple(elem), Value::Array(raw)) => self.parse_tuple(t, elem, raw)?, - - // User defined types parsing - (ScType::Udt(ScSpecTypeUdt { name }), _) => self.parse_udt(name, v)?, - - // TODO: Implement the rest of these - (_, raw) => serde_json::from_value(raw.clone()).map_err(Error::Serde)?, - }; - Ok(val) - } - - fn parse_udt(&self, name: &StringM<60>, value: &Value) -> Result { - let name = &name.to_utf8_string_lossy(); - match (self.find(name)?, value) { - (ScSpecEntry::UdtStructV0(strukt), Value::Object(map)) => { - if strukt - .fields - .iter() - .any(|f| f.name.to_utf8_string_lossy() == "0") - { - self.parse_tuple_strukt( - strukt, - &(0..map.len()) - .map(|i| map.get(&i.to_string()).unwrap().clone()) - .collect::>(), - ) - } else { - self.parse_strukt(strukt, map) - } - } - (ScSpecEntry::UdtStructV0(strukt), Value::Array(arr)) => { - self.parse_tuple_strukt(strukt, arr) - } - ( - ScSpecEntry::UdtUnionV0(union), - val @ (Value::Array(_) | Value::String(_) | Value::Object(_)), - ) => self.parse_union(union, val), - (ScSpecEntry::UdtEnumV0(enum_), Value::Number(num)) => parse_const_enum(num, enum_), - (s, v) => todo!("Not implemented for {s:#?} {v:#?}"), - } - } - - fn parse_tuple_strukt( - &self, - strukt: &ScSpecUdtStructV0, - array: &[Value], - ) -> Result { - let items = strukt - .fields - .to_vec() - .iter() - .zip(array.iter()) - .map(|(f, v)| { - let val = self.from_json(v, &f.type_)?; - Ok(val) - }) - .collect::, Error>>()?; - Ok(ScVal::Vec(Some(items.try_into().map_err(Error::Xdr)?))) - } - - fn parse_strukt( - &self, - strukt: &ScSpecUdtStructV0, - map: &serde_json::Map, - ) -> Result { - let items = strukt - .fields - .to_vec() - .iter() - .map(|f| { - let name = &f.name.to_utf8_string_lossy(); - let v = map - .get(name) - .ok_or_else(|| Error::MissingKey(name.clone()))?; - let val = self.from_json(v, &f.type_)?; - let key = StringM::from_str(name).unwrap(); - Ok(ScMapEntry { - key: ScVal::Symbol(key.into()), - val, - }) - }) - .collect::, Error>>()?; - let map = ScMap::sorted_from(items).map_err(Error::Xdr)?; - Ok(ScVal::Map(Some(map))) - } - - fn parse_union(&self, union: &ScSpecUdtUnionV0, value: &Value) -> Result { - let (enum_case, rest) = match value { - Value::String(s) => (s, None), - Value::Object(o) if o.len() == 1 => { - let res = o.values().next().map(|v| match v { - Value::Object(obj) if obj.contains_key("0") => { - let len = obj.len(); - Value::Array( - (0..len) - .map(|i| obj.get(&i.to_string()).unwrap().clone()) - .collect::>(), - ) - } - _ => v.clone(), - }); - (o.keys().next().unwrap(), res) - } - _ => todo!(), - }; - let case = union - .cases - .iter() - .find(|c| { - let name = match c { - ScSpecUdtUnionCaseV0::VoidV0(v) => &v.name, - ScSpecUdtUnionCaseV0::TupleV0(v) => &v.name, - }; - enum_case == &name.to_utf8_string_lossy() - }) - .ok_or_else(|| { - Error::EnumCase(enum_case.to_string(), union.name.to_utf8_string_lossy()) - })?; - - let mut res = vec![ScVal::Symbol(ScSymbol( - enum_case.try_into().map_err(Error::Xdr)?, - ))]; - - match (case, rest) { - (ScSpecUdtUnionCaseV0::VoidV0(_), _) | (ScSpecUdtUnionCaseV0::TupleV0(_), None) => (), - (ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { type_, .. }), Some(arr)) - if type_.len() == 1 => - { - res.push(self.from_json(&arr, &type_[0])?); - } - ( - ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { type_, .. }), - Some(Value::Array(arr)), - ) => { - res.extend( - arr.iter() - .zip(type_.iter()) - .map(|(elem, ty)| self.from_json(elem, ty)) - .collect::, _>>()?, - ); - } - (ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { .. }), Some(_)) => {} - }; - Ok(ScVal::Vec(Some(res.try_into().map_err(Error::Xdr)?))) - } - - fn parse_tuple( - &self, - t: &ScType, - tuple: &ScSpecTypeTuple, - items: &[Value], - ) -> Result { - let ScSpecTypeTuple { value_types } = tuple; - if items.len() != value_types.len() { - return Err(Error::InvalidValue(Some(t.clone()))); - }; - let parsed: Result, Error> = items - .iter() - .zip(value_types.iter()) - .map(|(item, t)| self.from_json(item, t)) - .collect(); - let converted: ScVec = parsed?.try_into().map_err(Error::Xdr)?; - Ok(ScVal::Vec(Some(converted))) - } - - fn parse_map( - &self, - map: &ScSpecTypeMap, - value_map: &serde_json::Map, - ) -> Result { - let ScSpecTypeMap { - key_type, - value_type, - } = map; - // TODO: What do we do if the expected key_type is not a string or symbol? - let parsed: Result, Error> = value_map - .iter() - .map(|(k, v)| -> Result { - let key = self.from_string(k, key_type)?; - let val = self.from_json(v, value_type)?; - Ok(ScMapEntry { key, val }) - }) - .collect(); - Ok(ScVal::Map(Some( - ScMap::sorted_from(parsed?).map_err(Error::Xdr)?, - ))) - } -} - -impl Spec { - /// # Errors - /// - /// Might return `Error::InvalidValue` - /// - /// # Panics - /// - /// May panic - pub fn xdr_to_json(&self, val: &ScVal, output: &ScType) -> Result { - Ok(match (val, output) { - (ScVal::Void, ScType::Val | ScType::Option(_) | ScType::Tuple(_)) - | (ScVal::Map(None) | ScVal::Vec(None), ScType::Option(_)) => Value::Null, - (ScVal::Bool(_), ScType::Bool) - | (ScVal::Void, ScType::Void) - | (ScVal::String(_), ScType::String) - | (ScVal::Symbol(_), ScType::Symbol) - | (ScVal::U64(_), ScType::U64) - | (ScVal::I64(_), ScType::I64) - | (ScVal::U32(_), ScType::U32) - | (ScVal::I32(_), ScType::I32) - | (ScVal::U128(_), ScType::U128) - | (ScVal::I128(_), ScType::I128) - | (ScVal::U256(_), ScType::U256) - | (ScVal::I256(_), ScType::I256) - | (ScVal::Duration(_), ScType::Duration) - | (ScVal::Timepoint(_), ScType::Timepoint) - | ( - ScVal::ContractInstance(_) - | ScVal::LedgerKeyContractInstance - | ScVal::LedgerKeyNonce(_), - _, - ) - | (ScVal::Address(_), ScType::Address) - | (ScVal::Bytes(_), ScType::Bytes | ScType::BytesN(_)) => to_json(val)?, - - (val, ScType::Result(inner)) => self.xdr_to_json(val, &inner.ok_type)?, - - (val, ScType::Option(inner)) => self.xdr_to_json(val, &inner.value_type)?, - (ScVal::Map(Some(_)) | ScVal::Vec(Some(_)) | ScVal::U32(_), type_) => { - self.sc_object_to_json(val, type_)? - } - - (ScVal::Error(_), ScType::Error) => todo!(), - (v, typed) => todo!("{v:#?} doesn't have a matching {typed:#?}"), - }) - } - - /// # Errors - /// - /// Might return an error - pub fn vec_m_to_json( - &self, - vec_m: &VecM, - type_: &ScType, - ) -> Result { - Ok(Value::Array( - vec_m - .to_vec() - .iter() - .map(|sc_val| self.xdr_to_json(sc_val, type_)) - .collect::, Error>>()?, - )) - } - - /// # Errors - /// - /// Might return an error - pub fn sc_map_to_json(&self, sc_map: &ScMap, type_: &ScSpecTypeMap) -> Result { - let v = sc_map - .iter() - .map(|ScMapEntry { key, val }| { - let key_s = self.xdr_to_json(key, &type_.key_type)?.to_string(); - let val_value = self.xdr_to_json(val, &type_.value_type)?; - Ok((key_s, val_value)) - }) - .collect::, Error>>()?; - Ok(Value::Object(v)) - } - - /// # Errors - /// - /// Might return an error - /// - /// # Panics - /// - /// May panic - pub fn udt_to_json(&self, name: &StringM<60>, sc_obj: &ScVal) -> Result { - let name = &name.to_utf8_string_lossy(); - let udt = self.find(name)?; - Ok(match (sc_obj, udt) { - (ScVal::Map(Some(map)), ScSpecEntry::UdtStructV0(strukt)) => serde_json::Value::Object( - strukt - .fields - .iter() - .zip(map.iter()) - .map(|(field, entry)| { - let val = self.xdr_to_json(&entry.val, &field.type_)?; - Ok((field.name.to_utf8_string_lossy(), val)) - }) - .collect::, Error>>()?, - ), - (ScVal::Vec(Some(vec_)), ScSpecEntry::UdtStructV0(strukt)) => Value::Array( - strukt - .fields - .iter() - .zip(vec_.iter()) - .map(|(field, entry)| self.xdr_to_json(entry, &field.type_)) - .collect::, Error>>()?, - ), - (ScVal::Vec(Some(vec_)), ScSpecEntry::UdtUnionV0(union)) => { - let v = vec_.to_vec(); - // let val = &v[0]; - let (first, rest) = match v.split_at(1) { - ([first], []) => (first, None), - ([first], rest) => (first, Some(rest)), - _ => return Err(Error::IllFormedEnum(union.name.to_utf8_string_lossy())), - }; - - let ScVal::Symbol(case_name) = first else { - return Err(Error::EnumFirstValueNotSymbol); - }; - let case = union - .cases - .iter() - .find(|case| { - let name = match case { - ScSpecUdtUnionCaseV0::VoidV0(v) => &v.name, - ScSpecUdtUnionCaseV0::TupleV0(v) => &v.name, - }; - name.as_vec() == case_name.as_vec() - }) - .ok_or_else(|| Error::FailedToFindEnumCase(case_name.to_utf8_string_lossy()))?; - - let case_name = case_name.to_utf8_string_lossy(); - match case { - ScSpecUdtUnionCaseV0::TupleV0(v) => { - let rest = rest.ok_or_else(|| { - Error::EnumMissingSecondValue( - union.name.to_utf8_string_lossy(), - case_name.clone(), - ) - })?; - let val = if v.type_.len() == 1 { - self.xdr_to_json(&rest[0], &v.type_[0])? - } else { - Value::Array( - v.type_ - .iter() - .zip(rest.iter()) - .map(|(type_, val)| self.xdr_to_json(val, type_)) - .collect::, Error>>()?, - ) - }; - - Value::Object([(case_name, val)].into_iter().collect()) - } - ScSpecUdtUnionCaseV0::VoidV0(_) => Value::String(case_name), - } - } - (ScVal::U32(v), ScSpecEntry::UdtEnumV0(_enum_)) => { - Value::Number(serde_json::Number::from(*v)) - } - (s, v) => todo!("Not implemented for {s:#?} {v:#?}"), - }) - } - - /// # Errors - /// - /// Might return an error - /// - /// # Panics - /// - /// Some types are not yet supported and will cause a panic if supplied - pub fn sc_object_to_json(&self, val: &ScVal, spec_type: &ScType) -> Result { - Ok(match (val, spec_type) { - (ScVal::Vec(Some(ScVec(vec_m))), ScType::Vec(type_)) => { - self.vec_m_to_json(vec_m, &type_.element_type)? - } - (ScVal::Vec(Some(ScVec(vec_m))), ScType::Tuple(tuple_type)) => Value::Array( - vec_m - .iter() - .zip(tuple_type.value_types.iter()) - .map(|(v, t)| self.xdr_to_json(v, t)) - .collect::, _>>()?, - ), - ( - sc_obj @ (ScVal::Vec(_) | ScVal::Map(_) | ScVal::U32(_)), - ScType::Udt(ScSpecTypeUdt { name }), - ) => self.udt_to_json(name, sc_obj)?, - - (ScVal::Map(Some(map)), ScType::Map(map_type)) => self.sc_map_to_json(map, map_type)?, - - (ScVal::U64(u64_), ScType::U64) => Value::Number(serde_json::Number::from(*u64_)), - - (ScVal::I64(i64_), ScType::I64) => Value::Number(serde_json::Number::from(*i64_)), - (int @ ScVal::U128(_), ScType::U128) => { - // Always output u128s as strings - let v: u128 = int - .clone() - .try_into() - .map_err(|()| Error::InvalidValue(Some(ScType::U128)))?; - Value::String(v.to_string()) - } - - (int @ ScVal::I128(_), ScType::I128) => { - // Always output u128s as strings - let v: i128 = int - .clone() - .try_into() - .map_err(|()| Error::InvalidValue(Some(ScType::I128)))?; - Value::String(v.to_string()) - } - - (ScVal::Bytes(v), ScType::Bytes | ScType::BytesN(_)) => { - Value::String(to_lower_hex(v.as_slice())) - } - - (ScVal::Bytes(_), ScType::Udt(_)) => todo!(), - - (ScVal::ContractInstance(_), _) => todo!(), - - (ScVal::Address(v), ScType::Address) => sc_address_to_json(v), - - (ok_val, ScType::Result(result_type)) => { - let ScSpecTypeResult { ok_type, .. } = result_type.as_ref(); - self.xdr_to_json(ok_val, ok_type)? - } - - (x, y) => return Err(Error::InvalidPair(x.clone(), y.clone())), - }) - } -} - -/// # Errors -/// -/// Might return an error -pub fn from_string_primitive(s: &str, t: &ScType) -> Result { - Spec::from_string_primitive(s, t) -} - -fn parse_const_enum(num: &serde_json::Number, enum_: &ScSpecUdtEnumV0) -> Result { - let num = num - .as_u64() - .ok_or_else(|| Error::FailedNumConversion(num.clone()))?; - let num = u32::try_from(num).map_err(|_| Error::EnumConstTooLarge(num))?; - enum_ - .cases - .iter() - .find(|c| c.value == num) - .ok_or(Error::EnumConst(num)) - .map(|c| ScVal::U32(c.value)) -} - -/// # Errors -/// -/// Might return an error -#[allow(clippy::too_many_lines)] -pub fn from_json_primitives(v: &Value, t: &ScType) -> Result { - let val: ScVal = match (t, v) { - // Boolean parsing - (ScType::Bool, Value::Bool(true)) => ScVal::Bool(true), - (ScType::Bool, Value::Bool(false)) => ScVal::Bool(false), - - // Number parsing - (ScType::U128, Value::String(s)) => { - let val: u128 = u128::from_str(s) - .map(Into::into) - .map_err(|_| Error::InvalidValue(Some(t.clone())))?; - let bytes = val.to_be_bytes(); - let (hi, lo) = bytes.split_at(8); - ScVal::U128(UInt128Parts { - hi: u64::from_be_bytes(hi.try_into()?), - lo: u64::from_be_bytes(lo.try_into()?), - }) - } - - (ScType::I128, Value::String(s)) => { - let val: i128 = i128::from_str(s) - .map(Into::into) - .map_err(|_| Error::InvalidValue(Some(t.clone())))?; - let bytes = val.to_be_bytes(); - let (hi, lo) = bytes.split_at(8); - ScVal::I128(Int128Parts { - hi: i64::from_be_bytes(hi.try_into()?), - lo: u64::from_be_bytes(lo.try_into()?), - }) - } - - // Number parsing - (ScType::U256, Value::String(s)) => { - let (hi, lo) = ethnum::U256::from_str_prefixed(s)?.into_words(); - let hi_bytes = hi.to_be_bytes(); - let (hi_hi, hi_lo) = hi_bytes.split_at(8); - let lo_bytes = lo.to_be_bytes(); - let (lo_hi, lo_lo) = lo_bytes.split_at(8); - ScVal::U256(UInt256Parts { - hi_hi: u64::from_be_bytes(hi_hi.try_into()?), - hi_lo: u64::from_be_bytes(hi_lo.try_into()?), - lo_hi: u64::from_be_bytes(lo_hi.try_into()?), - lo_lo: u64::from_be_bytes(lo_lo.try_into()?), - }) - } - (ScType::I256, Value::String(s)) => { - let (hi, lo) = ethnum::I256::from_str_prefixed(s)?.into_words(); - let hi_bytes = hi.to_be_bytes(); - let (hi_hi, hi_lo) = hi_bytes.split_at(8); - let lo_bytes = lo.to_be_bytes(); - let (lo_hi, lo_lo) = lo_bytes.split_at(8); - ScVal::I256(Int256Parts { - hi_hi: i64::from_be_bytes(hi_hi.try_into()?), - hi_lo: u64::from_be_bytes(hi_lo.try_into()?), - lo_hi: u64::from_be_bytes(lo_hi.try_into()?), - lo_lo: u64::from_be_bytes(lo_lo.try_into()?), - }) - } - - (ScType::I32, Value::Number(n)) => ScVal::I32( - n.as_i64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::U32, Value::Number(n)) => ScVal::U32( - n.as_u64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::I64, Value::Number(n)) => ScVal::I64( - n.as_i64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::U64 | ScType::Timepoint | ScType::Duration, Value::Number(n)) => ScVal::U64( - n.as_u64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))?, - ), - - // Symbol parsing - (ScType::Symbol, Value::String(s)) => ScVal::Symbol(ScSymbol( - s.as_bytes() - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - )), - - (ScType::Address, Value::String(s)) => sc_address_from_json(s)?, - - // Bytes parsing - (bytes @ ScType::BytesN(_), Value::Number(n)) => { - from_json_primitives(&Value::String(format!("{n}")), bytes)? - } - (ScType::BytesN(bytes), Value::String(s)) => ScVal::Bytes(ScBytes({ - if bytes.n == 32 { - // Bytes might be a strkey, try parsing it as one. Contract devs should use the new - // proper Address type, but for backwards compatibility some contracts might use a - // BytesN<32> to represent an Address. - if let Ok(key) = sc_address_from_json(s) { - return Ok(key); - } - } - // Bytes are not an address, just parse as a hex string - utils::padded_hex_from_str(s, bytes.n as usize) - .map_err(|_| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))? - })), - (ScType::Bytes, Value::Number(n)) => { - from_json_primitives(&Value::String(format!("{n}")), &ScType::Bytes)? - } - (ScType::Bytes, Value::String(s)) => ScVal::Bytes( - hex::decode(s) - .map_err(|_| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - ), - (ScType::Bytes | ScType::BytesN(_), Value::Array(raw)) => { - let b: Result, Error> = raw - .iter() - .map(|item| { - item.as_u64() - .ok_or_else(|| Error::InvalidValue(Some(t.clone())))? - .try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone()))) - }) - .collect(); - let converted: BytesM<{ u32::MAX }> = b?.try_into().map_err(Error::Xdr)?; - ScVal::Bytes(ScBytes(converted)) - } - - (ScType::String, Value::String(s)) => ScVal::String(ScString( - s.try_into() - .map_err(|_| Error::InvalidValue(Some(t.clone())))?, - )), - // Todo make proper error Which shouldn't exist - (_, raw) => serde_json::from_value(raw.clone())?, - }; - Ok(val) -} - -/// # Errors -/// -/// Might return an error -pub fn to_string(v: &ScVal) -> Result { - #[allow(clippy::match_same_arms)] - Ok(match v { - // If symbols are a top-level thing we omit the wrapping quotes - // TODO: Decide if this is a good idea or not. - ScVal::Symbol(v) => std::str::from_utf8(v.as_slice()) - .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? - .to_string(), - ScVal::LedgerKeyContractInstance => "LedgerKeyContractInstance".to_string(), - _ => serde_json::to_string(&to_json(v)?)?, - }) -} - -/// # Errors -/// -/// Might return an error -#[allow(clippy::too_many_lines)] -pub fn to_json(v: &ScVal) -> Result { - #[allow(clippy::match_same_arms)] - let val: Value = match v { - ScVal::Bool(b) => Value::Bool(*b), - ScVal::Void => Value::Null, - ScVal::LedgerKeyContractInstance => Value::String("LedgerKeyContractInstance".to_string()), - ScVal::U64(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::Timepoint(tp) => Value::Number(serde_json::Number::from(tp.0)), - ScVal::Duration(d) => Value::Number(serde_json::Number::from(d.0)), - ScVal::I64(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::U32(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::I32(v) => Value::Number(serde_json::Number::from(*v)), - ScVal::Symbol(v) => Value::String( - std::str::from_utf8(v.as_slice()) - .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? - .to_string(), - ), - ScVal::String(v) => Value::String( - std::str::from_utf8(v.as_slice()) - .map_err(|_| Error::InvalidValue(Some(ScType::Symbol)))? - .to_string(), - ), - ScVal::Vec(v) => { - let values: Result, Error> = v.as_ref().map_or_else( - || Ok(vec![]), - |v| { - v.iter() - .map(|item| -> Result { to_json(item) }) - .collect() - }, - ); - Value::Array(values?) - } - ScVal::Map(None) => Value::Object(serde_json::Map::with_capacity(0)), - ScVal::Map(Some(v)) => { - // TODO: What do we do if the key is not a string? - let mut m = serde_json::Map::::with_capacity(v.len()); - for ScMapEntry { key, val } in v.iter() { - let k: String = to_string(key)?; - let v: Value = to_json(val).map_err(|_| Error::InvalidValue(None))?; - m.insert(k, v); - } - Value::Object(m) - } - ScVal::Bytes(v) => Value::String(to_lower_hex(v.as_slice())), - ScVal::Address(v) => sc_address_to_json(v), - ScVal::U128(n) => { - let hi: [u8; 8] = n.hi.to_be_bytes(); - let lo: [u8; 8] = n.lo.to_be_bytes(); - let bytes = [hi, lo].concat(); - // Always output u128s as strings - let v = u128::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::I128)))?, - ) - .to_string(); - Value::String(v) - } - ScVal::I128(n) => { - let hi: [u8; 8] = n.hi.to_be_bytes(); - let lo: [u8; 8] = n.lo.to_be_bytes(); - let bytes = [hi, lo].concat(); - // Always output u128s as strings - let v = i128::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::I128)))?, - ) - .to_string(); - Value::String(v) - } - ScVal::U256(u256parts) => { - let bytes = [ - u256parts.hi_hi.to_be_bytes(), - u256parts.hi_lo.to_be_bytes(), - u256parts.lo_hi.to_be_bytes(), - u256parts.lo_lo.to_be_bytes(), - ] - .concat(); - let u256 = ethnum::U256::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::U256)))?, - ); - Value::String(u256.to_string()) - } - ScVal::I256(i256parts) => { - let bytes = [ - i256parts.hi_hi.to_be_bytes(), - i256parts.hi_lo.to_be_bytes(), - i256parts.lo_hi.to_be_bytes(), - i256parts.lo_lo.to_be_bytes(), - ] - .concat(); - let i256 = ethnum::I256::from_be_bytes( - bytes - .as_slice() - .try_into() - .map_err(|_| Error::InvalidValue(Some(ScType::I256)))?, - ); - Value::String(i256.to_string()) - } - ScVal::ContractInstance(ScContractInstance { - executable: ContractExecutable::Wasm(hash), - .. - }) => json!({ "hash": hash }), - ScVal::ContractInstance(ScContractInstance { - executable: ContractExecutable::StellarAsset, - .. - }) => json!({"SAC": true}), - ScVal::LedgerKeyNonce(ScNonceKey { nonce }) => { - Value::Number(serde_json::Number::from(*nonce)) - } - ScVal::Error(e) => serde_json::to_value(e)?, - }; - Ok(val) -} - -fn sc_address_to_json(v: &ScAddress) -> Value { - match v { - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(k)))) => { - Value::String(stellar_strkey::ed25519::PublicKey(*k).to_string()) - } - ScAddress::Contract(Hash(h)) => Value::String(stellar_strkey::Contract(*h).to_string()), - } -} - -fn sc_address_from_json(s: &str) -> Result { - stellar_strkey::Strkey::from_string(s) - .map_err(|_| Error::InvalidValue(Some(ScType::Address))) - .map(|parsed| match parsed { - stellar_strkey::Strkey::PublicKeyEd25519(p) => Some(ScVal::Address( - ScAddress::Account(AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(p.0)))), - )), - stellar_strkey::Strkey::Contract(c) => { - Some(ScVal::Address(ScAddress::Contract(Hash(c.0)))) - } - _ => None, - })? - .ok_or(Error::InvalidValue(Some(ScType::Address))) -} - -fn to_lower_hex(bytes: &[u8]) -> String { - let mut res = String::with_capacity(bytes.len()); - for b in bytes { - res.push_str(&format!("{b:02x}")); - } - res -} - -impl Spec { - #[must_use] - pub fn arg_value_name(&self, type_: &ScType, depth: usize) -> Option { - match type_ { - ScType::U64 => Some("u64".to_string()), - ScType::I64 => Some("i64".to_string()), - ScType::U128 => Some("u128".to_string()), - ScType::I128 => Some("i128".to_string()), - ScType::U32 => Some("u32".to_string()), - ScType::I32 => Some("i32".to_string()), - ScType::Bool => Some("bool".to_string()), - ScType::Symbol => Some("Symbol".to_string()), - ScType::Error => Some("Error".to_string()), - ScType::Bytes => Some("hex_bytes".to_string()), - ScType::Address => Some("Address".to_string()), - ScType::Void => Some("Null".to_string()), - ScType::Timepoint => Some("Timepoint".to_string()), - ScType::Duration => Some("Duration".to_string()), - ScType::U256 => Some("u256".to_string()), - ScType::I256 => Some("i256".to_string()), - ScType::String => Some("String".to_string()), - ScType::Option(val) => { - let ScSpecTypeOption { value_type } = val.as_ref(); - let inner = self.arg_value_name(value_type.as_ref(), depth + 1)?; - Some(format!("Option<{inner}>")) - } - ScType::Vec(val) => { - let ScSpecTypeVec { element_type } = val.as_ref(); - let inner = self.arg_value_name(element_type.as_ref(), depth + 1)?; - Some(format!("Array<{inner}>")) - } - ScType::Result(val) => { - let ScSpecTypeResult { - ok_type, - error_type, - } = val.as_ref(); - let ok = self.arg_value_name(ok_type.as_ref(), depth + 1)?; - let error = self.arg_value_name(error_type.as_ref(), depth + 1)?; - Some(format!("Result<{ok}, {error}>")) - } - ScType::Tuple(val) => { - let ScSpecTypeTuple { value_types } = val.as_ref(); - let names = value_types - .iter() - .map(|t| self.arg_value_name(t, depth + 1)) - .collect::>>()? - .join(", "); - Some(format!("Tuple<{names}>")) - } - ScType::Map(val) => { - let ScSpecTypeMap { - key_type, - value_type, - } = val.as_ref(); - let (key, val) = ( - self.arg_value_name(key_type.as_ref(), depth + 1)?, - self.arg_value_name(value_type.as_ref(), depth + 1)?, - ); - Some(format!("Map<{key}, {val}>")) - } - ScType::BytesN(t) => Some(format!("{}_hex_bytes", t.n)), - ScType::Udt(ScSpecTypeUdt { name }) => { - match self.find(&name.to_utf8_string_lossy()).ok()? { - ScSpecEntry::UdtStructV0(ScSpecUdtStructV0 { fields, .. }) - if fields - .first() - .map(|f| f.name.to_utf8_string_lossy() == "0") - .unwrap_or_default() => - { - let fields = fields - .iter() - .map(|t| self.arg_value_name(&t.type_, depth + 1)) - .collect::>>()? - .join(", "); - Some(format!("[{fields}]")) - } - ScSpecEntry::UdtStructV0(strukt) => self.arg_value_udt(strukt, depth), - ScSpecEntry::UdtUnionV0(union) => self.arg_value_union(union, depth), - ScSpecEntry::UdtEnumV0(enum_) => Some(arg_value_enum(enum_)), - ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_) => None, - } - } - // No specific value name for these yet. - ScType::Val => None, - } - } - - fn arg_value_udt(&self, strukt: &ScSpecUdtStructV0, depth: usize) -> Option { - let inner = strukt - .fields - .iter() - .map(|f| (f.name.to_utf8_string_lossy(), &f.type_)) - .map(|(name, type_)| { - let type_ = self.arg_value_name(type_, depth + 1)?; - Some(format!("{name}: {type_}")) - }) - .collect::>>()? - .join(", "); - Some(format!("{{ {inner} }}")) - } - - fn arg_value_union(&self, union: &ScSpecUdtUnionV0, depth: usize) -> Option { - union - .cases - .iter() - .map(|f| { - Some(match f { - ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { name, .. }) => { - name.to_utf8_string_lossy() - } - ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { - name, - type_, - .. - }) => format!( - "{}({})", - name.to_utf8_string_lossy(), - type_ - .iter() - .map(|type_| self.arg_value_name(type_, depth + 1)) - .collect::>>()? - .join(",") - ), - }) - }) - .collect::>>() - .map(|v| v.join(" | ")) - } -} - -fn arg_value_enum(enum_: &ScSpecUdtEnumV0) -> String { - enum_ - .cases - .iter() - .map(|case| case.value.to_string()) - .join(" | ") -} - -// Example implementation -impl Spec { - #[must_use] - pub fn example(&self, type_: &ScType) -> Option { - match type_ { - ScType::U64 => Some("42".to_string()), - ScType::I64 => Some("-42".to_string()), - ScType::U128 => Some("\"1000\"".to_string()), - ScType::I128 => Some("\"-100\"".to_string()), - ScType::U32 => Some("1".to_string()), - ScType::I32 => Some("-1".to_string()), - ScType::Bool => Some("true".to_string()), - ScType::Symbol => Some("\"hello\"".to_string()), - ScType::Error => Some("Error".to_string()), - ScType::Bytes => Some("\"beefface123\"".to_string()), - ScType::Address => { - Some("\"GDIY6AQQ75WMD4W46EYB7O6UYMHOCGQHLAQGQTKHDX4J2DYQCHVCR4W4\"".to_string()) - } - ScType::Void => Some("null".to_string()), - ScType::Timepoint => Some("1234".to_string()), - ScType::Duration => Some("9999".to_string()), - ScType::U256 => Some("\"2000\"".to_string()), - ScType::I256 => Some("\"-20000\"".to_string()), - ScType::String => Some("\"hello world\"".to_string()), - ScType::Option(val) => { - let ScSpecTypeOption { value_type } = val.as_ref(); - self.example(value_type.as_ref()) - } - ScType::Vec(val) => { - let ScSpecTypeVec { element_type } = val.as_ref(); - let inner = self.example(element_type.as_ref())?; - Some(format!("[ {inner} ]")) - } - ScType::Result(val) => { - let ScSpecTypeResult { - ok_type, - error_type, - } = val.as_ref(); - let ok = self.example(ok_type.as_ref())?; - let error = self.example(error_type.as_ref())?; - Some(format!("Result<{ok}, {error}>")) - } - ScType::Tuple(val) => { - let ScSpecTypeTuple { value_types } = val.as_ref(); - let names = value_types - .iter() - .map(|t| self.example(t)) - .collect::>>()? - .join(", "); - Some(format!("[{names}]")) - } - ScType::Map(map) => { - let ScSpecTypeMap { - key_type, - value_type, - } = map.as_ref(); - let (mut key, val) = ( - self.example(key_type.as_ref())?, - self.example(value_type.as_ref())?, - ); - if !matches!(key_type.as_ref(), ScType::Symbol) { - key = format!("\"{key}\""); - } - Some(format!("{{ {key}: {val} }}")) - } - ScType::BytesN(n) => { - let n = n.n as usize; - let res = if n % 2 == 0 { - "ef".repeat(n) - } else { - let mut s = "ef".repeat(n - 1); - s.push('e'); - s - }; - Some(format!("\"{res}\"")) - } - ScType::Udt(ScSpecTypeUdt { name }) => { - self.example_udts(name.to_utf8_string_lossy().as_ref()) - } - // No specific value name for these yet. - ScType::Val => None, - } - } - - fn example_udts(&self, name: &str) -> Option { - match self.find(name).ok() { - Some(ScSpecEntry::UdtStructV0(strukt)) => { - // Check if a tuple strukt - if !strukt.fields.is_empty() && strukt.fields[0].name.to_utf8_string_lossy() == "0" - { - let value_types = strukt - .fields - .iter() - .map(|f| f.type_.clone()) - .collect::>() - .try_into() - .ok()?; - return self.example(&ScType::Tuple(Box::new(ScSpecTypeTuple { value_types }))); - } - let inner = strukt - .fields - .iter() - .map(|f| (f.name.to_utf8_string_lossy(), &f.type_)) - .map(|(name, type_)| { - let type_ = self.example(type_)?; - let name = format!(r#""{name}""#); - Some(format!("{name}: {type_}")) - }) - .collect::>>()? - .join(", "); - Some(format!(r#"{{ {inner} }}"#)) - } - Some(ScSpecEntry::UdtUnionV0(union)) => self.example_union(union), - Some(ScSpecEntry::UdtEnumV0(enum_)) => { - enum_.cases.iter().next().map(|c| c.value.to_string()) - } - Some(ScSpecEntry::FunctionV0(_) | ScSpecEntry::UdtErrorEnumV0(_)) | None => None, - } - } - - fn example_union(&self, union: &ScSpecUdtUnionV0) -> Option { - let res = union - .cases - .iter() - .map(|case| match case { - ScSpecUdtUnionCaseV0::VoidV0(ScSpecUdtUnionCaseVoidV0 { name, .. }) => { - Some(format!("\"{}\"", name.to_utf8_string_lossy())) - } - ScSpecUdtUnionCaseV0::TupleV0(ScSpecUdtUnionCaseTupleV0 { - name, type_, .. - }) => { - if type_.len() == 1 { - let single = self.example(&type_[0])?; - Some(format!("{{\"{}\":{single}}}", name.to_utf8_string_lossy())) - } else { - let names = type_ - .iter() - .map(|t| self.example(t)) - .collect::>>()? - .join(", "); - Some(format!("{{\"{}\":[{names}]}}", name.to_utf8_string_lossy())) - } - } - }) - .collect::>>()?; - Some(res.join("|")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use stellar_xdr::curr::ScSpecTypeBytesN; - - #[test] - fn from_json_primitives_bytesn() { - // TODO: Add test for parsing addresses - - // Check it parses hex-encoded bytes - let b = from_json_primitives( - &Value::String("beefface".to_string()), - &ScType::BytesN(ScSpecTypeBytesN { n: 4 }), - ) - .unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0xbe, 0xef, 0xfa, 0xce].try_into().unwrap())) - ); - - // Check it parses hex-encoded bytes when they are all numbers. Normally the json would - // interpret the CLI arg as a number, so we need a special case there. - let b = from_json_primitives( - &Value::Number(4554.into()), - &ScType::BytesN(ScSpecTypeBytesN { n: 2 }), - ) - .unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0x45, 0x54].try_into().unwrap())) - ); - } - - #[test] - fn from_json_primitives_bytes() { - // Check it parses hex-encoded bytes - let b = - from_json_primitives(&Value::String("beefface".to_string()), &ScType::Bytes).unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0xbe, 0xef, 0xfa, 0xce].try_into().unwrap())) - ); - - // Check it parses hex-encoded bytes when they are all numbers. Normally the json would - // interpret the CLI arg as a number, so we need a special case there. - let b = from_json_primitives(&Value::Number(4554.into()), &ScType::Bytes).unwrap(); - assert_eq!( - b, - ScVal::Bytes(ScBytes(vec![0x45, 0x54].try_into().unwrap())) - ); - } - - #[test] - fn test_sc_address_from_json_strkey() { - // All zero contract address - match sc_address_from_json("CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABSC4") { - Ok(addr) => assert_eq!(addr, ScVal::Address(ScAddress::Contract(Hash([0; 32])))), - Err(e) => panic!("Unexpected error: {e}"), - } - - // Real contract address - match sc_address_from_json("CA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQGAXE") { - Ok(addr) => assert_eq!( - addr, - ScVal::Address(ScAddress::Contract( - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, - 0xc7, 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, - 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - .try_into() - .unwrap() - )) - ), - Err(e) => panic!("Unexpected error: {e}"), - } - - // All zero user account address - match sc_address_from_json("GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWHF") { - Ok(addr) => assert_eq!( - addr, - ScVal::Address(ScAddress::Account(AccountId( - PublicKey::PublicKeyTypeEd25519([0; 32].try_into().unwrap()) - ))) - ), - Err(e) => panic!("Unexpected error: {e}"), - } - - // Real user account address - match sc_address_from_json("GA3D5KRYM6CB7OWQ6TWYRR3Z4T7GNZLKERYNZGGA5SOAOPIFY6YQHES5") { - Ok(addr) => assert_eq!( - addr, - ScVal::Address(ScAddress::Account(AccountId( - PublicKey::PublicKeyTypeEd25519( - [ - 0x36, 0x3e, 0xaa, 0x38, 0x67, 0x84, 0x1f, 0xba, 0xd0, 0xf4, 0xed, 0x88, - 0xc7, 0x79, 0xe4, 0xfe, 0x66, 0xe5, 0x6a, 0x24, 0x70, 0xdc, 0x98, 0xc0, - 0xec, 0x9c, 0x07, 0x3d, 0x05, 0xc7, 0xb1, 0x03, - ] - .try_into() - .unwrap() - ) - ))) - ), - Err(e) => panic!("Unexpected error: {e}"), - } - } -} diff --git a/cmd/crates/soroban-spec-tools/src/utils.rs b/cmd/crates/soroban-spec-tools/src/utils.rs deleted file mode 100644 index 66b153a1..00000000 --- a/cmd/crates/soroban-spec-tools/src/utils.rs +++ /dev/null @@ -1,294 +0,0 @@ -use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; -use hex::FromHexError; -use std::{ - fmt::Display, - io::{self, Cursor}, -}; - -use stellar_xdr::curr::{ - Limited, Limits, ReadXdr, ScEnvMetaEntry, ScMetaEntry, ScMetaV0, ScSpecEntry, ScSpecFunctionV0, - ScSpecUdtEnumV0, ScSpecUdtErrorEnumV0, ScSpecUdtStructV0, ScSpecUdtUnionV0, StringM, -}; - -pub struct ContractSpec { - pub env_meta_base64: Option, - pub env_meta: Vec, - pub meta_base64: Option, - pub meta: Vec, - pub spec_base64: Option, - pub spec: Vec, -} - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("reading file {filepath}: {error}")] - CannotReadContractFile { - filepath: std::path::PathBuf, - error: io::Error, - }, - #[error("cannot parse wasm file {file}: {error}")] - CannotParseWasm { - file: std::path::PathBuf, - error: wasmparser::BinaryReaderError, - }, - #[error("xdr processing error: {0}")] - Xdr(#[from] stellar_xdr::curr::Error), - - #[error(transparent)] - Parser(#[from] wasmparser::BinaryReaderError), -} - -impl ContractSpec { - pub fn new(bytes: &[u8]) -> Result { - let mut env_meta: Option<&[u8]> = None; - let mut meta: Option<&[u8]> = None; - let mut spec: Option<&[u8]> = None; - for payload in wasmparser::Parser::new(0).parse_all(bytes) { - let payload = payload?; - if let wasmparser::Payload::CustomSection(section) = payload { - let out = match section.name() { - "contractenvmetav0" => &mut env_meta, - "contractmetav0" => &mut meta, - "contractspecv0" => &mut spec, - _ => continue, - }; - *out = Some(section.data()); - }; - } - - let mut env_meta_base64 = None; - let env_meta = if let Some(env_meta) = env_meta { - env_meta_base64 = Some(base64.encode(env_meta)); - let cursor = Cursor::new(env_meta); - let mut read = Limited::new(cursor, Limits::none()); - ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, _>>()? - } else { - vec![] - }; - - let mut meta_base64 = None; - let meta = if let Some(meta) = meta { - meta_base64 = Some(base64.encode(meta)); - let cursor = Cursor::new(meta); - let mut read = Limited::new(cursor, Limits::none()); - ScMetaEntry::read_xdr_iter(&mut read).collect::, _>>()? - } else { - vec![] - }; - - let mut spec_base64 = None; - let spec = if let Some(spec) = spec { - spec_base64 = Some(base64.encode(spec)); - let cursor = Cursor::new(spec); - let mut read = Limited::new(cursor, Limits::none()); - ScSpecEntry::read_xdr_iter(&mut read).collect::, _>>()? - } else { - vec![] - }; - - Ok(ContractSpec { - env_meta_base64, - env_meta, - meta_base64, - meta, - spec_base64, - spec, - }) - } -} - -impl Display for ContractSpec { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if let Some(env_meta) = &self.env_meta_base64 { - writeln!(f, "Env Meta: {env_meta}")?; - for env_meta_entry in &self.env_meta { - match env_meta_entry { - ScEnvMetaEntry::ScEnvMetaKindInterfaceVersion(v) => { - writeln!(f, " • Interface Version: {v}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Env Meta: None\n")?; - } - - if let Some(_meta) = &self.meta_base64 { - writeln!(f, "Contract Meta:")?; - for meta_entry in &self.meta { - match meta_entry { - ScMetaEntry::ScMetaV0(ScMetaV0 { key, val }) => { - writeln!(f, " • {key}: {val}")?; - } - } - } - writeln!(f)?; - } else { - writeln!(f, "Contract Meta: None\n")?; - } - - if let Some(_spec_base64) = &self.spec_base64 { - writeln!(f, "Contract Spec:")?; - for spec_entry in &self.spec { - match spec_entry { - ScSpecEntry::FunctionV0(func) => write_func(f, func)?, - ScSpecEntry::UdtUnionV0(udt) => write_union(f, udt)?, - ScSpecEntry::UdtStructV0(udt) => write_struct(f, udt)?, - ScSpecEntry::UdtEnumV0(udt) => write_enum(f, udt)?, - ScSpecEntry::UdtErrorEnumV0(udt) => write_error(f, udt)?, - } - } - } else { - writeln!(f, "Contract Spec: None")?; - } - Ok(()) - } -} - -fn write_func(f: &mut std::fmt::Formatter<'_>, func: &ScSpecFunctionV0) -> std::fmt::Result { - writeln!(f, " • Function: {}", func.name.to_utf8_string_lossy())?; - if func.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - &indent(&func.doc.to_utf8_string_lossy(), 11).trim() - )?; - } - writeln!( - f, - " Inputs: {}", - indent(&format!("{:#?}", func.inputs), 5).trim() - )?; - writeln!( - f, - " Output: {}", - indent(&format!("{:#?}", func.outputs), 5).trim() - )?; - writeln!(f)?; - Ok(()) -} - -fn write_union(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtUnionV0) -> std::fmt::Result { - writeln!(f, " • Union: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_struct(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtStructV0) -> std::fmt::Result { - writeln!(f, " • Struct: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Fields:")?; - for field in udt.fields.iter() { - writeln!( - f, - " • {}: {}", - field.name.to_utf8_string_lossy(), - indent(&format!("{:#?}", field.type_), 8).trim() - )?; - if field.doc.len() > 0 { - writeln!(f, "{}", indent(&format!("{:#?}", field.doc), 8))?; - } - } - writeln!(f)?; - Ok(()) -} - -fn write_enum(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtEnumV0) -> std::fmt::Result { - writeln!(f, " • Enum: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn write_error(f: &mut std::fmt::Formatter<'_>, udt: &ScSpecUdtErrorEnumV0) -> std::fmt::Result { - writeln!(f, " • Error: {}", format_name(&udt.lib, &udt.name))?; - if udt.doc.len() > 0 { - writeln!( - f, - " Docs: {}", - indent(&udt.doc.to_utf8_string_lossy(), 10).trim() - )?; - } - writeln!(f, " Cases:")?; - for case in udt.cases.iter() { - writeln!(f, " • {}", indent(&format!("{case:#?}"), 8).trim())?; - } - writeln!(f)?; - Ok(()) -} - -fn indent(s: &str, n: usize) -> String { - let pad = " ".repeat(n); - s.lines() - .map(|line| format!("{pad}{line}")) - .collect::>() - .join("\n") -} - -fn format_name(lib: &StringM<80>, name: &StringM<60>) -> String { - if lib.len() > 0 { - format!( - "{}::{}", - lib.to_utf8_string_lossy(), - name.to_utf8_string_lossy() - ) - } else { - name.to_utf8_string_lossy() - } -} - -/// # Errors -/// -/// Might return an error -pub fn padded_hex_from_str(s: &str, n: usize) -> Result, FromHexError> { - if s.len() > n * 2 { - return Err(FromHexError::InvalidStringLength); - } - let mut decoded = vec![0u8; n]; - let padded = format!("{s:0>width$}", width = n * 2); - hex::decode_to_slice(padded, &mut decoded)?; - Ok(decoded) -} - -/// # Errors -/// -/// Might return an error -pub fn contract_id_from_str(contract_id: &str) -> Result<[u8; 32], stellar_strkey::DecodeError> { - stellar_strkey::Contract::from_string(contract_id) - .map(|strkey| strkey.0) - .or_else(|_| { - // strkey failed, try to parse it as a hex string, for backwards compatibility. - padded_hex_from_str(contract_id, 32) - .map_err(|_| stellar_strkey::DecodeError::Invalid)? - .try_into() - .map_err(|_| stellar_strkey::DecodeError::Invalid) - }) - .map_err(|_| stellar_strkey::DecodeError::Invalid) -}