leo_ast/interpreter_value/
intrinsic.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use std::collections::HashMap;
18
19use rand::Rng as _;
20use rand_chacha::ChaCha20Rng;
21use snarkvm::{
22    prelude::{ToBits, ToBitsRaw},
23    synthesizer::program::{DeserializeVariant, SerializeVariant},
24};
25
26use crate::{
27    ArrayType,
28    Expression,
29    Intrinsic,
30    Type,
31    interpreter_value::{ExpectTc, Value},
32    tc_fail2,
33};
34use leo_errors::{InterpreterHalt, Result};
35use leo_span::{Span, Symbol};
36
37use super::*;
38
39/// A context in which we can evaluate intrinsics.
40///
41/// This is intended to be implemented by `Cursor`, which will be used during
42/// execution of the interpreter, and by `Vec<Value>`, which will be used
43/// during compile time evaluation for constant folding.
44///
45/// The default implementations for `rng`, `set_block_height`, and mapping lookup
46/// do nothing, as those features will not be available during compile time
47/// evaluation.
48pub trait IntrinsicHelper {
49    fn pop_value_impl(&mut self) -> Option<Value>;
50
51    fn pop_value(&mut self) -> Result<Value> {
52        match self.pop_value_impl() {
53            Some(v) => Ok(v),
54            None => {
55                Err(InterpreterHalt::new("value expected - this may be a bug in the Leo interpreter".to_string())
56                    .into())
57            }
58        }
59    }
60
61    fn set_block_height(&mut self, _height: u32) {}
62
63    fn set_block_timestamp(&mut self, _timestamp: i64) {}
64
65    fn set_signer(&mut self, _private_key: String) -> Result<()> {
66        Ok(())
67    }
68
69    fn lookup_mapping(&self, _program: Option<Symbol>, _name: Symbol) -> Option<&HashMap<Value, Value>> {
70        None
71    }
72
73    fn lookup_mapping_mut(&mut self, _program: Option<Symbol>, _name: Symbol) -> Option<&mut HashMap<Value, Value>> {
74        None
75    }
76
77    fn mapping_get(&self, program: Option<Symbol>, name: Symbol, key: &Value) -> Option<Value> {
78        self.lookup_mapping(program, name).and_then(|map| map.get(key).cloned())
79    }
80
81    fn mapping_set(&mut self, program: Option<Symbol>, name: Symbol, key: Value, value: Value) -> Option<()> {
82        self.lookup_mapping_mut(program, name).map(|map| {
83            map.insert(key, value);
84        })
85    }
86
87    fn mapping_remove(&mut self, program: Option<Symbol>, name: Symbol, key: &Value) -> Option<()> {
88        self.lookup_mapping_mut(program, name).map(|map| {
89            map.remove(key);
90        })
91    }
92
93    fn rng(&mut self) -> Option<&mut ChaCha20Rng> {
94        None
95    }
96}
97
98impl IntrinsicHelper for Vec<Value> {
99    fn pop_value_impl(&mut self) -> Option<Value> {
100        self.pop()
101    }
102}
103
104pub fn evaluate_intrinsic(
105    helper: &mut dyn IntrinsicHelper,
106    intrinsic: Intrinsic,
107    arguments: &[Expression],
108    span: Span,
109) -> Result<Option<Value>> {
110    use snarkvm::{
111        prelude::LiteralType,
112        synthesizer::program::{CommitVariant, ECDSAVerifyVariant, HashVariant},
113    };
114
115    let dohash = |helper: &mut dyn IntrinsicHelper, variant: HashVariant, typ: Type| -> Result<Value> {
116        let input = helper.pop_value()?.try_into().expect_tc(span)?;
117        let value = snarkvm::synthesizer::program::evaluate_hash(variant, &input, &typ.to_snarkvm()?)?;
118        Ok(value.into())
119    };
120
121    let docommit = |helper: &mut dyn IntrinsicHelper, variant: CommitVariant, typ: LiteralType| -> Result<Value> {
122        let randomizer: Scalar = helper.pop_value()?.try_into().expect_tc(span)?;
123        let input: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
124        let value = snarkvm::synthesizer::program::evaluate_commit(variant, &input, &randomizer, typ)?;
125        Ok(value.into())
126    };
127
128    let doschnorr = |helper: &mut dyn IntrinsicHelper| -> Result<Value> {
129        let signature: Signature = helper.pop_value()?.try_into().expect_tc(span)?;
130        let address: Address = helper.pop_value()?.try_into().expect_tc(span)?;
131        let message: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
132        let is_valid = snarkvm::synthesizer::program::evaluate_schnorr_verification(&signature, &address, &message)?;
133        Ok(Boolean::new(is_valid).into())
134    };
135
136    let doecdsa = |helper: &mut dyn IntrinsicHelper, variant: ECDSAVerifyVariant| -> Result<Value> {
137        let signature: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
138        let public_key: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
139        let message: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
140        let is_valid =
141            snarkvm::synthesizer::program::evaluate_ecdsa_verification(variant, &signature, &public_key, &message)?;
142        Ok(Boolean::new(is_valid).into())
143    };
144
145    let doserialize = |helper: &mut dyn IntrinsicHelper, variant: SerializeVariant| -> Result<Value> {
146        let input: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
147        let num_bits = match variant {
148            SerializeVariant::ToBits => input.to_bits_le().len(),
149            SerializeVariant::ToBitsRaw => input.to_bits_raw_le().len(),
150        };
151        let Ok(num_bits) = u32::try_from(num_bits) else {
152            crate::halt_no_span2!("cannot serialize value with more than 2^32 bits");
153        };
154        let array_type = ArrayType::bit_array(num_bits).to_snarkvm()?;
155        let value = snarkvm::synthesizer::program::evaluate_serialize(variant, &input, &array_type)?;
156        Ok(value.into())
157    };
158
159    let dodeserialize = |helper: &mut dyn IntrinsicHelper, variant: DeserializeVariant, type_: Type| -> Result<Value> {
160        let value: SvmValue = helper.pop_value()?.try_into().expect_tc(span)?;
161        let bits = match value {
162            SvmValue::Plaintext(plaintext) => plaintext.as_bit_array()?,
163            _ => crate::halt_no_span2!("expected array for deserialization"),
164        };
165        let get_struct_fail = |_: &SvmIdentifier| anyhow::bail!("structs are not supported");
166        let value = snarkvm::synthesizer::program::evaluate_deserialize(
167            variant,
168            &bits,
169            &type_.to_snarkvm()?,
170            &get_struct_fail,
171        )?;
172        Ok(value.into())
173    };
174
175    macro_rules! random {
176        ($ty: ident) => {{
177            let Some(rng) = helper.rng() else {
178                return Ok(None);
179            };
180            let value: $ty = rng.r#gen();
181            value.into()
182        }};
183    }
184
185    let value = match intrinsic {
186        Intrinsic::ChaChaRand(type_) => match type_ {
187            LiteralType::Address => random!(Address),
188            LiteralType::Boolean => random!(bool),
189            LiteralType::Field => random!(Field),
190            LiteralType::Group => random!(Group),
191            LiteralType::I8 => random!(i8),
192            LiteralType::I16 => random!(i16),
193            LiteralType::I32 => random!(i32),
194            LiteralType::I64 => random!(i64),
195            LiteralType::I128 => random!(i128),
196            LiteralType::U8 => random!(u8),
197            LiteralType::U16 => random!(u16),
198            LiteralType::U32 => random!(u32),
199            LiteralType::U64 => random!(u64),
200            LiteralType::U128 => random!(u128),
201            LiteralType::Scalar => random!(Scalar),
202            LiteralType::String | LiteralType::Signature => {
203                crate::halt_no_span2!("cannot generate random value of type `{type_}`")
204            }
205        },
206        Intrinsic::Commit(commit_variant, type_) => docommit(helper, commit_variant, type_)?,
207        Intrinsic::Hash(hash_variant, type_) => dohash(helper, hash_variant, type_)?,
208        Intrinsic::ECDSAVerify(ecdsa_variant) => doecdsa(helper, ecdsa_variant)?,
209        Intrinsic::SignatureVerify => doschnorr(helper)?,
210        Intrinsic::Serialize(variant) => doserialize(helper, variant)?,
211        Intrinsic::Deserialize(variant, type_) => dodeserialize(helper, variant, type_)?,
212        Intrinsic::GroupToXCoordinate => {
213            let g: Group = helper.pop_value()?.try_into().expect_tc(span)?;
214            g.to_x_coordinate().into()
215        }
216        Intrinsic::GroupToYCoordinate => {
217            let g: Group = helper.pop_value()?.try_into().expect_tc(span)?;
218            g.to_y_coordinate().into()
219        }
220        Intrinsic::CheatCodePrintMapping => {
221            let (program, name) = match &arguments[0] {
222                Expression::Path(id) => (None, id.identifier().name),
223                Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
224                _ => tc_fail2!(),
225            };
226            if let Some(mapping) = helper.lookup_mapping(program, name) {
227                // TODO: What is the appropriate way to print this to the console.
228                // Print the name of the mapping.
229                println!(
230                    "Mapping: {}",
231                    if let Some(program) = program { format!("{program}/{name}") } else { name.to_string() }
232                );
233                // Print the contents of the mapping.
234                for (key, value) in mapping {
235                    println!("  {key} -> {value}");
236                }
237            } else {
238                tc_fail2!();
239            }
240            Value::make_unit()
241        }
242        Intrinsic::CheatCodeSetBlockHeight => {
243            let height: u32 = helper.pop_value()?.try_into().expect_tc(span)?;
244            helper.set_block_height(height);
245            Value::make_unit()
246        }
247        Intrinsic::CheatCodeSetBlockTimestamp => {
248            let timestamp: i64 = helper.pop_value()?.try_into().expect_tc(span)?;
249            helper.set_block_timestamp(timestamp);
250            Value::make_unit()
251        }
252        Intrinsic::CheatCodeSetSigner => {
253            let private_key: String = helper.pop_value()?.try_into().expect_tc(span)?;
254            helper.set_signer(private_key)?;
255            Value::make_unit()
256        }
257        Intrinsic::Get => {
258            // TODO handle vector get
259            let key = helper.pop_value().expect_tc(span)?;
260            let (program, name) = match &arguments[0] {
261                Expression::Path(path) => (None, path.identifier().name),
262                Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
263                _ => tc_fail2!(),
264            };
265            helper.mapping_get(program, name, &key).expect_tc(span)?.clone()
266        }
267        Intrinsic::Set => {
268            // TODO handle vector set
269            let value = helper.pop_value().expect_tc(span)?;
270            let key = helper.pop_value().expect_tc(span)?;
271            let (program, name) = match &arguments[0] {
272                Expression::Path(path) => (None, path.identifier().name),
273                Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
274                _ => tc_fail2!(),
275            };
276            helper.mapping_set(program, name, key, value).expect_tc(span)?;
277            Value::make_unit()
278        }
279        Intrinsic::MappingGetOrUse => {
280            let use_value = helper.pop_value().expect_tc(span)?;
281            let key = helper.pop_value().expect_tc(span)?;
282            let (program, name) = match &arguments[0] {
283                Expression::Path(path) => (None, path.identifier().name),
284                Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
285                _ => tc_fail2!(),
286            };
287            helper.mapping_get(program, name, &key).unwrap_or(use_value)
288        }
289        Intrinsic::MappingRemove => {
290            let key = helper.pop_value().expect_tc(span)?;
291            let (program, name) = match &arguments[0] {
292                Expression::Path(path) => (None, path.identifier().name),
293                Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
294                _ => tc_fail2!(),
295            };
296            helper.mapping_remove(program, name, &key).expect_tc(span)?;
297            Value::make_unit()
298        }
299        Intrinsic::MappingContains => {
300            let key = helper.pop_value().expect_tc(span)?;
301            let (program, name) = match &arguments[0] {
302                Expression::Path(path) => (None, path.identifier().name),
303                Expression::Locator(locator) => (Some(locator.program.name.name), locator.name),
304                _ => tc_fail2!(),
305            };
306            helper.mapping_get(program, name, &key).is_some().into()
307        }
308        Intrinsic::GroupGen => Value::generator(),
309        Intrinsic::OptionalUnwrap => {
310            // TODO
311            return Ok(None);
312        }
313        Intrinsic::OptionalUnwrapOr => {
314            // TODO
315            return Ok(None);
316        }
317        Intrinsic::VectorPush
318        | Intrinsic::VectorLen
319        | Intrinsic::VectorClear
320        | Intrinsic::VectorPop
321        | Intrinsic::VectorSwapRemove
322        | Intrinsic::SelfAddress
323        | Intrinsic::SelfCaller
324        | Intrinsic::SelfChecksum
325        | Intrinsic::SelfEdition
326        | Intrinsic::SelfId
327        | Intrinsic::SelfProgramOwner
328        | Intrinsic::SelfSigner
329        | Intrinsic::BlockHeight
330        | Intrinsic::BlockTimestamp
331        | Intrinsic::NetworkId => {
332            // TODO
333            return Ok(None);
334        }
335        Intrinsic::FutureAwait => panic!("await must be handled elsewhere"),
336        Intrinsic::ProgramChecksum => {
337            // TODO: This is a placeholder. The actual implementation should look up the program in the global context and get its checksum.
338            return Ok(None);
339        }
340        Intrinsic::ProgramEdition => {
341            // TODO: This is a placeholder. The actual implementation should look up the program in the global context and get its edition.
342            return Ok(None);
343        }
344        Intrinsic::ProgramOwner => {
345            // TODO: This is a placeholder. The actual implementation should look up the program in the global context and get its owner.
346            return Ok(None);
347        }
348    };
349
350    Ok(Some(value))
351}