Skip to main content

snarkvm_synthesizer_program/logic/command/contains/
dynamic.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use crate::{FinalizeStoreTrait, Opcode, Operand, RegistersTrait, StackTrait};
17use console::{
18    network::prelude::*,
19    program::{Identifier, Literal, Plaintext, ProgramID, Register, Value},
20    types::Boolean,
21};
22
23/// A dynamic contains command, e.g. `contains.dynamic r0 r1 r2[r3] into r4;`.
24/// Resolves the `program` and `mapping` operands, returns true if the `key` operand is present in the `mapping`, and stores the result into `destination`.
25#[derive(Clone, PartialEq, Eq, Hash)]
26pub struct ContainsDynamic<N: Network> {
27    /// The operands.
28    operands: [Operand<N>; 4],
29    /// The destination register.
30    destination: Register<N>,
31}
32
33impl<N: Network> ContainsDynamic<N> {
34    /// Returns the opcode.
35    #[inline]
36    pub const fn opcode() -> Opcode {
37        Opcode::Command("contains.dynamic")
38    }
39
40    /// Returns the operands in the operation.
41    #[inline]
42    pub fn operands(&self) -> &[Operand<N>] {
43        &self.operands
44    }
45
46    /// Returns the operand containing the program name.
47    #[inline]
48    pub const fn program_name_operand(&self) -> &Operand<N> {
49        &self.operands[0]
50    }
51
52    /// Returns the operand containing the program network.
53    #[inline]
54    pub const fn program_network_operand(&self) -> &Operand<N> {
55        &self.operands[1]
56    }
57
58    /// Returns the operand containing the mapping name.
59    #[inline]
60    pub const fn mapping_name_operand(&self) -> &Operand<N> {
61        &self.operands[2]
62    }
63
64    /// Returns the operand containing the key.
65    #[inline]
66    pub const fn key_operand(&self) -> &Operand<N> {
67        &self.operands[3]
68    }
69
70    /// Returns the destination register.
71    #[inline]
72    pub const fn destination(&self) -> &Register<N> {
73        &self.destination
74    }
75}
76
77impl<N: Network> ContainsDynamic<N> {
78    /// Finalizes the command.
79    pub fn finalize(
80        &self,
81        stack: &impl StackTrait<N>,
82        store: &impl FinalizeStoreTrait<N>,
83        registers: &mut impl RegistersTrait<N>,
84    ) -> Result<()> {
85        // Get the program name.
86        let program_name = match registers.load(stack, self.program_name_operand())? {
87            Value::Plaintext(Plaintext::Literal(Literal::Field(field), _)) => Identifier::from_field(&field)?,
88            Value::Plaintext(Plaintext::Literal(Literal::Identifier(id_lit), _)) => {
89                Identifier::from_field(&id_lit.to_field()?)?
90            }
91            _ => bail!("Expected the first operand of `contains.dynamic` to be a field or identifier literal."),
92        };
93
94        // Get the program network.
95        let program_network = match registers.load(stack, self.program_network_operand())? {
96            Value::Plaintext(Plaintext::Literal(Literal::Field(field), _)) => Identifier::from_field(&field)?,
97            Value::Plaintext(Plaintext::Literal(Literal::Identifier(id_lit), _)) => {
98                Identifier::from_field(&id_lit.to_field()?)?
99            }
100            _ => bail!("Expected the second operand of `contains.dynamic` to be a field or identifier literal."),
101        };
102
103        // Construct the program ID.
104        let program_id = ProgramID::try_from((program_name, program_network))?;
105
106        // Get the mapping name.
107        let mapping_name = match registers.load(stack, self.mapping_name_operand())? {
108            Value::Plaintext(Plaintext::Literal(Literal::Field(field), _)) => Identifier::from_field(&field)?,
109            Value::Plaintext(Plaintext::Literal(Literal::Identifier(id_lit), _)) => {
110                Identifier::from_field(&id_lit.to_field()?)?
111            }
112            _ => bail!("Expected the third operand of `contains.dynamic` to be a field or identifier literal."),
113        };
114
115        // Ensure the mapping exists.
116        if !store.contains_mapping_speculative(&program_id, &mapping_name)? {
117            bail!("Mapping '{program_id}/{mapping_name}' does not exist");
118        }
119
120        // Load the operand as a plaintext.
121        let key = registers.load_plaintext(stack, self.key_operand())?;
122
123        // Get the mapping definition.
124        let mapping = stack.get_stack_global(&program_id)?.program().get_mapping(&mapping_name)?;
125        // Get the key type.
126        let mapping_key_type = mapping.key().plaintext_type();
127        // Ensure the key operand matches the mapping key type.
128        ensure!(
129            stack.matches_plaintext(&key, mapping_key_type).is_ok(),
130            "Expected the key to be of type '{mapping_key_type}', found '{key}'."
131        );
132
133        // Determine if the key exists in the mapping.
134        let contains_key = store.contains_key_speculative(program_id, mapping_name, &key)?;
135
136        // Assign the value to the destination register.
137        registers.store(stack, &self.destination, Value::from(Literal::Boolean(Boolean::new(contains_key))))?;
138
139        Ok(())
140    }
141}
142
143impl<N: Network> Parser for ContainsDynamic<N> {
144    /// Parses a string into an operation.
145    #[inline]
146    fn parse(string: &str) -> ParserResult<Self> {
147        // Parse the whitespace and comments from the string.
148        let (string, _) = Sanitizer::parse(string)?;
149        // Parse the opcode from the string.
150        let (string, _) = tag(*Self::opcode())(string)?;
151        // Parse the whitespace from the string.
152        let (string, _) = Sanitizer::parse_whitespaces(string)?;
153
154        // Parse the program name operand from the string.
155        let (string, program_name) = Operand::parse(string)?;
156        // Parse the whitespace from the string.
157        let (string, _) = Sanitizer::parse_whitespaces(string)?;
158        // Parse the program network operand from the string.
159        let (string, program_network) = Operand::parse(string)?;
160        // Parse the whitespace from the string.
161        let (string, _) = Sanitizer::parse_whitespaces(string)?;
162        // Parse the mapping name operand from the string.
163        let (string, mapping_name) = Operand::parse(string)?;
164        // Parse the whitespace from the string.
165        let (string, _) = Sanitizer::parse_whitespaces(string)?;
166
167        // Parse the "[" from the string.
168        let (string, _) = tag("[")(string)?;
169        // Parse the whitespace from the string.
170        let (string, _) = Sanitizer::parse_whitespaces(string)?;
171        // Parse the key operand from the string.
172        let (string, key) = Operand::parse(string)?;
173        // Parse the whitespace from the string.
174        let (string, _) = Sanitizer::parse_whitespaces(string)?;
175        // Parse the "]" from the string.
176        let (string, _) = tag("]")(string)?;
177
178        // Parse the whitespace from the string.
179        let (string, _) = Sanitizer::parse_whitespaces(string)?;
180        // Parse the "into" keyword from the string.
181        let (string, _) = tag("into")(string)?;
182        // Parse the whitespace from the string.
183        let (string, _) = Sanitizer::parse_whitespaces(string)?;
184        // Parse the destination register from the string.
185        let (string, destination) = Register::parse(string)?;
186
187        // Parse the whitespace from the string.
188        let (string, _) = Sanitizer::parse_whitespaces(string)?;
189        // Parse the ";" from the string.
190        let (string, _) = tag(";")(string)?;
191
192        Ok((string, Self { operands: [program_name, program_network, mapping_name, key], destination }))
193    }
194}
195
196impl<N: Network> FromStr for ContainsDynamic<N> {
197    type Err = Error;
198
199    /// Parses a string into the command.
200    #[inline]
201    fn from_str(string: &str) -> Result<Self> {
202        match Self::parse(string) {
203            Ok((remainder, object)) => {
204                // Ensure the remainder is empty.
205                ensure!(remainder.is_empty(), "Failed to parse string. Found invalid character in: \"{remainder}\"");
206                // Return the object.
207                Ok(object)
208            }
209            Err(error) => bail!("Failed to parse string. {error}"),
210        }
211    }
212}
213
214impl<N: Network> Debug for ContainsDynamic<N> {
215    /// Prints the command as a string.
216    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
217        Display::fmt(self, f)
218    }
219}
220
221impl<N: Network> Display for ContainsDynamic<N> {
222    /// Prints the command to a string.
223    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
224        // Print the command.
225        write!(f, "{} ", Self::opcode())?;
226        // Print the program name, program network, mapping name, and key operand.
227        write!(
228            f,
229            "{} {} {}[{}] into ",
230            self.program_name_operand(),
231            self.program_network_operand(),
232            self.mapping_name_operand(),
233            self.key_operand()
234        )?;
235        // Print the destination register.
236        write!(f, "{};", self.destination)
237    }
238}
239
240impl<N: Network> FromBytes for ContainsDynamic<N> {
241    /// Reads the command from a buffer.
242    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
243        // Read the program name.
244        let program_name = Operand::read_le(&mut reader)?;
245        // Read the program network.
246        let program_network = Operand::read_le(&mut reader)?;
247        // Read the mapping name.
248        let mapping_name = Operand::read_le(&mut reader)?;
249        // Read the key operand.
250        let key = Operand::read_le(&mut reader)?;
251        // Read the destination register.
252        let destination = Register::read_le(&mut reader)?;
253        // Return the command.
254        Ok(Self { operands: [program_name, program_network, mapping_name, key], destination })
255    }
256}
257
258impl<N: Network> ToBytes for ContainsDynamic<N> {
259    /// Writes the operation to a buffer.
260    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
261        // Write the program name.
262        self.program_name_operand().write_le(&mut writer)?;
263        // Write the program network.
264        self.program_network_operand().write_le(&mut writer)?;
265        // Write the mapping name.
266        self.mapping_name_operand().write_le(&mut writer)?;
267        // Write the key operand.
268        self.key_operand().write_le(&mut writer)?;
269        // Write the destination register.
270        self.destination.write_le(&mut writer)
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277    use console::{network::MainnetV0, program::Register};
278
279    type CurrentNetwork = MainnetV0;
280
281    #[test]
282    fn test_parse() {
283        let (string, contains) =
284            ContainsDynamic::<CurrentNetwork>::parse("contains.dynamic r0 r1 r2[r3] into r4;").unwrap();
285        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
286        assert_eq!(contains.operands().len(), 4, "The number of operands is incorrect");
287        assert_eq!(
288            contains.program_name_operand(),
289            &Operand::Register(Register::Locator(0)),
290            "The first operand is incorrect"
291        );
292        assert_eq!(
293            contains.program_network_operand(),
294            &Operand::Register(Register::Locator(1)),
295            "The second operand is incorrect"
296        );
297        assert_eq!(
298            contains.mapping_name_operand(),
299            &Operand::Register(Register::Locator(2)),
300            "The third operand is incorrect"
301        );
302        assert_eq!(contains.key_operand(), &Operand::Register(Register::Locator(3)), "The fourth operand is incorrect");
303        assert_eq!(contains.destination, Register::Locator(4), "The destination register is incorrect");
304    }
305
306    #[test]
307    fn test_from_bytes() {
308        let (string, contains) =
309            ContainsDynamic::<CurrentNetwork>::parse("contains.dynamic r0 r1 r2[r3] into r4;").unwrap();
310        assert!(string.is_empty());
311        let bytes_le = contains.to_bytes_le().unwrap();
312        let result = ContainsDynamic::<CurrentNetwork>::from_bytes_le(&bytes_le[..]);
313        assert!(result.is_ok())
314    }
315
316    #[test]
317    fn test_display_parse_roundtrip() {
318        let input = "contains.dynamic r0 r1 r2[r3] into r4;";
319        let (string, original) = ContainsDynamic::<CurrentNetwork>::parse(input).unwrap();
320        assert!(string.is_empty());
321        let displayed = format!("{original}");
322        let (remainder, reparsed) = ContainsDynamic::<CurrentNetwork>::parse(&displayed).unwrap();
323        assert!(remainder.is_empty());
324        assert_eq!(original, reparsed);
325    }
326}