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 fn to_checksum(&self) -> [console::types::U8<N>; 32] {
75 crate::to_checksum::source_code_checksum(&self.to_string())
76 }
77
78 pub const fn inputs(&self) -> &IndexSet<Input<N>> {
80 &self.inputs
81 }
82
83 pub fn input_types(&self) -> Vec<FinalizeType<N>> {
85 self.inputs.iter().map(|input| input.finalize_type()).cloned().collect()
86 }
87
88 pub fn commands(&self) -> &[Command<N>] {
90 &self.commands
91 }
92
93 pub const fn outputs(&self) -> &IndexSet<Output<N>> {
95 &self.outputs
96 }
97
98 pub fn output_types(&self) -> Vec<FinalizeType<N>> {
100 self.outputs.iter().map(|output| output.finalize_type()).cloned().collect()
101 }
102
103 pub const fn positions(&self) -> &HashMap<Identifier<N>, usize> {
105 &self.positions
106 }
107
108 pub fn exceeds_max_array_size(&self, max_array_size: u32) -> bool {
112 self.inputs.iter().any(|input| {
113 matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.exceeds_max_array_size(max_array_size))
114 }) || self.commands.iter().any(|command| command.exceeds_max_array_size(max_array_size))
115 || self.outputs.iter().any(|output| {
116 matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.exceeds_max_array_size(max_array_size))
117 })
118 }
119
120 pub fn contains_external_struct(&self) -> bool {
122 self.inputs
123 .iter()
124 .any(|input| matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.contains_external_struct()))
125 || self
126 .commands
127 .iter()
128 .any(|command| matches!(command, Command::Instruction(inst) if inst.contains_external_struct()))
129 || self.outputs.iter().any(
130 |output| matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.contains_external_struct()),
131 )
132 }
133
134 pub fn contains_string_type(&self) -> bool {
137 self.inputs
138 .iter()
139 .any(|input| matches!(input.finalize_type(), FinalizeType::Plaintext(p) if p.contains_string_type()))
140 || self.commands.iter().any(|command| command.contains_string_type())
141 || self
142 .outputs
143 .iter()
144 .any(|output| matches!(output.finalize_type(), FinalizeType::Plaintext(p) if p.contains_string_type()))
145 }
146}
147
148impl<N: Network> ViewCore<N> {
149 #[inline]
151 fn add_input(&mut self, input: Input<N>) -> Result<()> {
152 ensure!(self.commands.is_empty(), "Cannot add inputs after commands have been added");
154 ensure!(self.outputs.is_empty(), "Cannot add inputs after outputs have been added");
155
156 ensure!(self.inputs.len() < N::MAX_INPUTS, "Cannot add more than {} inputs", N::MAX_INPUTS);
158 ensure!(!self.inputs.contains(&input), "Cannot add duplicate input statement");
160
161 ensure!(
163 matches!(input.finalize_type(), FinalizeType::Plaintext(..)),
164 "View inputs must be plaintext (futures are forbidden)"
165 );
166
167 ensure!(matches!(input.register(), Register::Locator(..)), "Input register must be a locator");
169
170 self.inputs.insert(input);
171 Ok(())
172 }
173
174 #[inline]
176 pub fn add_command(&mut self, command: Command<N>) -> Result<()> {
177 ensure!(self.outputs.is_empty(), "Cannot add commands after outputs have been added");
179
180 ensure!(self.commands.len() < N::MAX_COMMANDS, "Cannot add more than {} commands", N::MAX_COMMANDS);
182
183 ensure!(!command.is_write(), "Forbidden operation: view functions cannot use 'set' or 'remove'");
185 ensure!(!command.is_async(), "Forbidden operation: view functions cannot invoke an 'async' instruction");
186 ensure!(!command.is_await(), "Forbidden operation: view functions cannot 'await' a future");
187 ensure!(!command.is_call(), "Forbidden operation: view functions cannot 'call' another function");
188 ensure!(!command.is_instruction_for_record(), "Forbidden operation: view functions cannot operate on records");
189 ensure!(!command.is_rand_chacha(), "Forbidden operation: view functions cannot use 'rand.chacha'");
191
192 for register in command.destinations() {
194 ensure!(matches!(register, Register::Locator(..)), "Destination register must be a locator");
195 }
196
197 if let Some(position) = command.branch_to() {
199 ensure!(!self.positions.contains_key(position), "Cannot branch to an earlier position '{position}'");
200 }
201
202 if let Some(position) = command.position() {
203 ensure!(!self.positions.contains_key(position), "Cannot redefine position '{position}'");
204 ensure!(self.positions.len() < N::MAX_POSITIONS, "Cannot add more than {} positions", N::MAX_POSITIONS);
205 self.positions.insert(*position, self.commands.len());
206 }
207
208 self.commands.push(command);
209 Ok(())
210 }
211
212 #[inline]
214 fn add_output(&mut self, output: Output<N>) -> Result<()> {
215 ensure!(self.outputs.len() < N::MAX_OUTPUTS, "Cannot add more than {} outputs", N::MAX_OUTPUTS);
217
218 ensure!(
220 matches!(output.finalize_type(), FinalizeType::Plaintext(..)),
221 "View outputs must be plaintext (futures are forbidden)"
222 );
223
224 self.outputs.insert(output);
225 Ok(())
226 }
227}
228
229impl<N: Network> TypeName for ViewCore<N> {
230 #[inline]
231 fn type_name() -> &'static str {
232 "view"
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 type CurrentNetwork = console::network::MainnetV0;
241
242 #[test]
243 fn test_add_input() {
244 let name = Identifier::from_str("view_core_test").unwrap();
245 let mut view = ViewCore::<CurrentNetwork>::new(name);
246
247 let input = Input::<CurrentNetwork>::from_str("input r0 as field.public;").unwrap();
248 assert!(view.add_input(input.clone()).is_ok());
249 assert!(view.add_input(input).is_err());
250 }
251
252 #[test]
253 fn test_reject_set_command() {
254 let name = Identifier::from_str("view_core_test").unwrap();
255 let mut view = ViewCore::<CurrentNetwork>::new(name);
256
257 let cmd = Command::<CurrentNetwork>::from_str("set 1u64 into balances[0u64];").unwrap();
258 let err = view.add_command(cmd).unwrap_err();
259 assert!(err.to_string().contains("'set' or 'remove'"));
260 }
261
262 #[test]
263 fn test_reject_remove_command() {
264 let name = Identifier::from_str("view_core_test").unwrap();
265 let mut view = ViewCore::<CurrentNetwork>::new(name);
266
267 let cmd = Command::<CurrentNetwork>::from_str("remove balances[0u64];").unwrap();
268 let err = view.add_command(cmd).unwrap_err();
269 assert!(err.to_string().contains("'set' or 'remove'"));
270 }
271
272 #[test]
273 fn test_reject_rand_chacha() {
274 let name = Identifier::from_str("view_core_test").unwrap();
275 let mut view = ViewCore::<CurrentNetwork>::new(name);
276
277 let cmd = Command::<CurrentNetwork>::from_str("rand.chacha into r0 as u64;").unwrap();
278 let err = view.add_command(cmd).unwrap_err();
279 assert!(err.to_string().contains("rand.chacha"));
280 }
281
282 #[test]
283 fn test_reject_await_command() {
284 let name = Identifier::from_str("view_core_test").unwrap();
285 let mut view = ViewCore::<CurrentNetwork>::new(name);
286
287 let cmd = Command::<CurrentNetwork>::from_str("await r0;").unwrap();
288 let err = view.add_command(cmd).unwrap_err();
289 assert!(err.to_string().contains("'await'"));
290 }
291
292 #[test]
293 fn test_reject_call_instruction() {
294 let name = Identifier::from_str("view_core_test").unwrap();
295 let mut view = ViewCore::<CurrentNetwork>::new(name);
296
297 let cmd = Command::<CurrentNetwork>::from_str("call foo r0 into r1;").unwrap();
298 let err = view.add_command(cmd).unwrap_err();
299 assert!(err.to_string().contains("'call'"));
300 }
301
302 #[test]
303 fn test_reject_cast_to_record() {
304 let name = Identifier::from_str("view_core_test").unwrap();
305 let mut view = ViewCore::<CurrentNetwork>::new(name);
306
307 let cmd =
308 Command::<CurrentNetwork>::from_str("cast r0.owner r0.token_amount into r1 as token.record;").unwrap();
309 let err = view.add_command(cmd).unwrap_err();
310 assert!(err.to_string().contains("operate on records"));
311 }
312
313 #[test]
314 fn test_reject_cast_to_dynamic_record() {
315 let name = Identifier::from_str("view_core_test").unwrap();
316 let mut view = ViewCore::<CurrentNetwork>::new(name);
317
318 let cmd = Command::<CurrentNetwork>::from_str("cast r0 into r1 as dynamic.record;").unwrap();
319 let err = view.add_command(cmd).unwrap_err();
320 assert!(err.to_string().contains("operate on records"));
321 }
322
323 #[test]
324 fn test_reject_get_record_dynamic() {
325 let name = Identifier::from_str("view_core_test").unwrap();
326 let mut view = ViewCore::<CurrentNetwork>::new(name);
327
328 let cmd = Command::<CurrentNetwork>::from_str("get.record.dynamic r0.x into r1 as bool;").unwrap();
329 let err = view.add_command(cmd).unwrap_err();
330 assert!(err.to_string().contains("operate on records"));
331 }
332
333 #[test]
334 fn test_reject_async_instruction() {
335 let name = Identifier::from_str("view_core_test").unwrap();
336 let mut view = ViewCore::<CurrentNetwork>::new(name);
337
338 let cmd = Command::<CurrentNetwork>::from_str("async foo r0 r1 into r3;").unwrap();
339 let err = view.add_command(cmd).unwrap_err();
340 assert!(err.to_string().contains("'async'"));
341 }
342}