1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use std::fs;
use std::io::Read;
use std::path::Path;
use crate::error::{ManifestError, Result};
use super::{ContractManifest, MAX_MANIFEST_SIZE};
fn validate_manifest_strict(manifest: &ContractManifest) -> Result<()> {
if !manifest.features.is_empty() {
return Err(ManifestError::Validation {
message: format!(
"features must be an empty object in Neo N3, got keys: {}",
manifest
.features
.keys()
.map(String::as_str)
.collect::<Vec<_>>()
.join(", ")
),
}
.into());
}
for (index, permission) in manifest.permissions.iter().enumerate() {
if let super::ManifestPermissionContract::Other(value) = &permission.contract {
return Err(ManifestError::Validation {
message: format!(
"permissions[{index}].contract must be \"*\", a 0x-prefixed 20-byte \
contract hash, or a 33-byte group public key, got {value}"
),
}
.into());
}
if let super::ManifestPermissionMethods::Wildcard(value) = &permission.methods {
if value != "*" {
return Err(ManifestError::Validation {
message: format!(
"permissions[{index}].methods wildcard must be \"*\", got {value:?}"
),
}
.into());
}
}
}
if let Some(super::ManifestTrusts::Wildcard(value)) = manifest.trusts.as_ref() {
if value != "*" {
return Err(ManifestError::Validation {
message: format!("trusts wildcard must be \"*\", got {value:?}"),
}
.into());
}
}
Ok(())
}
fn ensure_manifest_size(size: u64) -> Result<()> {
if size > MAX_MANIFEST_SIZE {
return Err(ManifestError::FileTooLarge {
size,
max: MAX_MANIFEST_SIZE,
}
.into());
}
Ok(())
}
impl ContractManifest {
/// Load a manifest from a reader containing UTF-8 JSON.
///
/// # Errors
///
/// Returns an error if reading fails, the payload exceeds the size limit,
/// the bytes are not valid UTF-8, or the JSON does not match the manifest schema.
pub fn from_reader<R: Read>(reader: R) -> Result<Self> {
let mut buf = Vec::new();
let mut limited = reader.take(MAX_MANIFEST_SIZE + 1);
limited.read_to_end(&mut buf).map_err(ManifestError::from)?;
Self::from_bytes(&buf)
}
/// Load a manifest from a raw JSON string.
///
/// # Errors
///
/// Returns an error if the payload exceeds the size limit or the JSON does
/// not match the expected manifest schema.
pub fn from_json_str(input: &str) -> Result<Self> {
// Enforce the same size cap as every other string/byte entry point so
// library and wasm callers (which reach the parser through here) cannot
// bypass it.
ensure_manifest_size(input.len() as u64)?;
input.parse()
}
/// Load a manifest from a raw JSON string and enforce strict semantic validation.
///
/// # Errors
///
/// Returns an error if parsing fails or if wildcard-like fields contain
/// non-canonical values (e.g., `"all"` instead of `"*"`).
pub fn from_json_str_strict(input: &str) -> Result<Self> {
let manifest = Self::from_json_str(input)?;
validate_manifest_strict(&manifest)?;
Ok(manifest)
}
/// Load a manifest directly from bytes (UTF-8 JSON).
///
/// # Errors
///
/// Returns an error if the payload exceeds the size limit, the bytes are
/// not valid UTF-8, or the JSON does not match the manifest schema.
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
ensure_manifest_size(bytes.len() as u64)?;
let text =
std::str::from_utf8(bytes).map_err(|err| ManifestError::InvalidUtf8 { source: err })?;
Self::from_json_str(text)
}
/// Load a manifest from a file on disk.
///
/// # Errors
///
/// Returns an error if the file cannot be read, exceeds the size limit,
/// contains invalid UTF-8, or the JSON does not match the manifest schema.
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let size = fs::metadata(&path)?.len();
ensure_manifest_size(size)?;
let data = fs::read(path)?;
Self::from_bytes(&data)
}
/// Load a manifest from a file on disk and enforce strict semantic validation.
///
/// # Errors
///
/// Returns an error if parsing fails or if wildcard-like fields contain
/// non-canonical values (e.g., `"all"` instead of `"*"`).
pub fn from_file_strict<P: AsRef<Path>>(path: P) -> Result<Self> {
let size = fs::metadata(&path)?.len();
ensure_manifest_size(size)?;
let data = fs::read(path)?;
let manifest = Self::from_bytes(&data)?;
validate_manifest_strict(&manifest)?;
Ok(manifest)
}
}
impl std::str::FromStr for ContractManifest {
type Err = crate::error::Error;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
let manifest: ContractManifest = serde_json::from_str(s).map_err(ManifestError::from)?;
Ok(manifest)
}
}