snarkvm_synthesizer_program/view/
mod.rs1use crate::Command;
17
18mod input;
19pub use input::*;
20
21mod output;
22pub use output::*;
23
24mod bytes;
25mod parse;
26
27use console::{
28 network::{error, prelude::*},
29 program::{FinalizeType, Identifier, Register},
30};
31
32use indexmap::IndexSet;
33use std::collections::HashMap;
34
35#[derive(Clone, PartialEq, Eq)]
40pub struct ViewCore<N: Network> {
41 name: Identifier<N>,
43 inputs: IndexSet<Input<N>>,
45 commands: Vec<Command<N>>,
47 outputs: IndexSet<Output<N>>,
49 positions: HashMap<Identifier<N>, usize>,
51}
52
53impl<N: Network> ViewCore<N> {
54 pub fn new(name: Identifier<N>) -> Self {
56 Self {
57 name,
58 inputs: IndexSet::new(),
59 commands: Vec::new(),
60 outputs: IndexSet::new(),
61 positions: HashMap::new(),
62 }
63 }
64
65 pub const fn name(&self) -> &Identifier<N> {
67 &self.name
68 }
69
70 pub const fn inputs(&self) -> &IndexSet<Input<N>> {
72 &self.inputs
73 }
74
75 pub fn input_types(&self) -> Vec<FinalizeType<N>> {
77 self.inputs.iter().map(|input| input.finalize_type()).cloned().collect()
78 }
79
80 pub fn commands(&self) -> &[Command<N>] {
82 &self.commands
83 }
84
85 pub const fn outputs(&self) -> &IndexSet<Output<N>> {
87 &self.outputs
88 }
89
90 pub fn output_types(&self) -> Vec<FinalizeType<N>> {
92 self.outputs.iter().map(|output| output.finalize_type()).cloned().collect()
93 }
94
95 pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> {
97 &self.positions
98 }
99
100 pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
104 self.inputs.iter().any(|input| {
105 matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.exceeds_max_array_size(max_array_size))
106 }) || self.commands.iter().any(|command| command.exceeds_max_array_size(max_array_size))
107 || self.outputs.iter().any(|output| {
108 matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.exceeds_max_array_size(max_array_size))
109 })
110 }
111
112 pub fn contains_external_struct(&self) -> bool {
114 self.inputs
115 .iter()
116 .any(|input| matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.contains_external_struct()))
117 || self
118 .commands
119 .iter()
120 .any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct()))
121 || self.outputs.iter().any(
122 |output| matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.contains_external_struct()),
123 )
124 }
125
126 pub fn contains_string_type(&self) -> bool {
129 self.inputs
130 .iter()
131 .any(|input| matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.contains_string_type()))
132 || self.commands.iter().any(|command| command.contains_string_type())
133 || self
134 .outputs
135 .iter()
136 .any(|output| matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.contains_string_type()))
137 }
138}
139
140impl<N: Network> ViewCore<N> {
141 #[inline]
143 fn add_input(&mut self, input: Input<N>) -> Result<()> {
144 ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
146 ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
147
148 ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
150 ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
152
153 ensure!(
155 matches!(input.finalize_type(), FinalizeType::Plaintext(..)),
156 "View inputs must be plaintext (futures are forbidden)"
157 );
158
159 ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
161
162 self.inputs.insert(input);
163 Ok(())
164 }
165
166 #[inline]
168 pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
169 ensure!(self.outputs.is_empty(), "Cannot add commands after outputs have been added");
171
172 ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
174
175 ensure!(!command.is_write(), "Forbidden operation: view functions cannot use 'set' or 'remove'");
177 ensure!(!command.is_async(), "Forbidden operation: view functions cannot invoke an 'async' instruction");
178 ensure!(!command.is_await(), "Forbidden operation: view functions cannot 'await' a future");
179 ensure!(!command.is_call(), "Forbidden operation: view functions cannot 'call' another function");
180 ensure!(!command.is_instruction_for_record(), "Forbidden operation: view functions cannot operate on records");
181 ensure!(!command.is_rand_chacha(), "Forbidden operation: view functions cannot use 'rand.chacha'");
183
184 for register in command.destinations() {
186 ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
187 }
188
189 if let Some(position) = command.branch_to() {
191 ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
192 }
193
194 if let Some(position) = command.position() {
195 ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
196 ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
197 self.positions.insert(*position, self.commands.len());
198 }
199
200 self.commands.push(command);
201 Ok(())
202 }
203
204 #[inline]
206 fn add_output(&mut self, output: Output<N>) -> Result<()> {
207 ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
209
210 ensure!(
212 matches!(output.finalize_type(), FinalizeType::Plaintext(..)),
213 "View outputs must be plaintext (futures are forbidden)"
214 );
215
216 self.outputs.insert(output);
217 Ok(())
218 }
219}
220
221impl<N: Network> TypeName for ViewCore<N> {
222 #[inline]
223 fn type_name() -> &'static str {
224 "view"
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 type CurrentNetwork = console::network::MainnetV0;
233
234 #[test]
235 fn test_add_input() {
236 let name = Identifier::from_str("view_core_test").unwrap();
237 let mut view = ViewCore::<CurrentNetwork>::new(name);
238
239 let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
240 assert!(view.add_input(input.clone()).is_ok());
241 assert!(view.add_input(input).is_err());
242 }
243
244 #[test]
245 fn test_reject_set_command() {
246 let name = Identifier::from_str("view_core_test").unwrap();
247 let mut view = ViewCore::<CurrentNetwork>::new(name);
248
249 let cmd = Command::<CurrentNetwork>::from_str("set 1u64 into balances[0u64];").unwrap();
250 let err = view.add_command(cmd).unwrap_err();
251 assert!(err.to_string().contains("'set' or 'remove'"));
252 }
253
254 #[test]
255 fn test_reject_remove_command() {
256 let name = Identifier::from_str("view_core_test").unwrap();
257 let mut view = ViewCore::<CurrentNetwork>::new(name);
258
259 let cmd = Command::<CurrentNetwork>::from_str("remove balances[0u64];").unwrap();
260 let err = view.add_command(cmd).unwrap_err();
261 assert!(err.to_string().contains("'set' or 'remove'"));
262 }
263
264 #[test]
265 fn test_reject_rand_chacha() {
266 let name = Identifier::from_str("view_core_test").unwrap();
267 let mut view = ViewCore::<CurrentNetwork>::new(name);
268
269 let cmd = Command::<CurrentNetwork>::from_str("rand.chacha into r0 as u64;").unwrap();
270 let err = view.add_command(cmd).unwrap_err();
271 assert!(err.to_string().contains("rand.chacha"));
272 }
273
274 #[test]
275 fn test_reject_await_command() {
276 let name = Identifier::from_str("view_core_test").unwrap();
277 let mut view = ViewCore::<CurrentNetwork>::new(name);
278
279 let cmd = Command::<CurrentNetwork>::from_str("await r0;").unwrap();
280 let err = view.add_command(cmd).unwrap_err();
281 assert!(err.to_string().contains("'await'"));
282 }
283
284 #[test]
285 fn test_reject_call_instruction() {
286 let name = Identifier::from_str("view_core_test").unwrap();
287 let mut view = ViewCore::<CurrentNetwork>::new(name);
288
289 let cmd = Command::<CurrentNetwork>::from_str("call foo r0 into r1;").unwrap();
290 let err = view.add_command(cmd).unwrap_err();
291 assert!(err.to_string().contains("'call'"));
292 }
293
294 #[test]
295 fn test_reject_cast_to_record() {
296 let name = Identifier::from_str("view_core_test").unwrap();
297 let mut view = ViewCore::<CurrentNetwork>::new(name);
298
299 let cmd =
300 Command::<CurrentNetwork>::from_str("cast r0.owner r0.token_amount into r1 as token.record;").unwrap();
301 let err = view.add_command(cmd).unwrap_err();
302 assert!(err.to_string().contains("operate on records"));
303 }
304
305 #[test]
306 fn test_reject_cast_to_dynamic_record() {
307 let name = Identifier::from_str("view_core_test").unwrap();
308 let mut view = ViewCore::<CurrentNetwork>::new(name);
309
310 let cmd = Command::<CurrentNetwork>::from_str("cast r0 into r1 as dynamic.record;").unwrap();
311 let err = view.add_command(cmd).unwrap_err();
312 assert!(err.to_string().contains("operate on records"));
313 }
314
315 #[test]
316 fn test_reject_get_record_dynamic() {
317 let name = Identifier::from_str("view_core_test").unwrap();
318 let mut view = ViewCore::<CurrentNetwork>::new(name);
319
320 let cmd = Command::<CurrentNetwork>::from_str("get.record.dynamic r0.x into r1 as bool;").unwrap();
321 let err = view.add_command(cmd).unwrap_err();
322 assert!(err.to_string().contains("operate on records"));
323 }
324
325 #[test]
326 fn test_reject_async_instruction() {
327 let name = Identifier::from_str("view_core_test").unwrap();
328 let mut view = ViewCore::<CurrentNetwork>::new(name);
329
330 let cmd = Command::<CurrentNetwork>::from_str("async foo r0 r1 into r3;").unwrap();
331 let err = view.add_command(cmd).unwrap_err();
332 assert!(err.to_string().contains("'async'"));
333 }
334}