brillig/foreign_call.rs
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 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
use acir_field::AcirField;
use serde::{Deserialize, Serialize};
/// Single input or output of a [foreign call][crate::Opcode::ForeignCall].
///
/// This enum can represent either a single field element or an array of field elements.
///
/// The `#[serde(untagged)]` attribute uses the natural JSON representation:
/// `Single(5)` serializes as `5`, and `Array([1,2,3])` serializes as `[1,2,3]`.
/// This allows external systems to pass values directly without needing to know about
/// Rust enum variants, relying on JSON's native distinction between single values and arrays.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum ForeignCallParam<F> {
/// A single field element value.
Single(F),
/// Multiple field element values (array or vector passed by value).
Array(Vec<F>),
}
impl<F> From<F> for ForeignCallParam<F> {
fn from(value: F) -> Self {
ForeignCallParam::Single(value)
}
}
impl<F> From<Vec<F>> for ForeignCallParam<F> {
fn from(values: Vec<F>) -> Self {
ForeignCallParam::Array(values)
}
}
impl<F: AcirField> ForeignCallParam<F> {
/// Convert the parameter into a uniform vector representation.
///
/// This is useful for flattening data when processing foreign call results,
/// allowing both `Single` and `Array` variants to be handled uniformly as `Vec<F>`.
pub fn fields(&self) -> Vec<F> {
match self {
ForeignCallParam::Single(value) => vec![*value],
ForeignCallParam::Array(values) => values.to_vec(),
}
}
/// Unwrap the field in a `Single` input. Panics if it's an `Array`.
pub fn unwrap_field(&self) -> F {
match self {
ForeignCallParam::Single(value) => *value,
ForeignCallParam::Array(_) => panic!("Expected single value, found array"),
}
}
}
/// Represents the full output of a [foreign call][crate::Opcode::ForeignCall].
///
/// A foreign call can return multiple outputs, where each output can be either
/// a single field element or an array. This struct wraps a vector of [ForeignCallParam]
/// to represent all outputs from a single foreign call.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, Default)]
pub struct ForeignCallResult<F> {
/// Resolved output values of the foreign call.
///
/// Each element represents one output, which can be either a single value or an array.
pub values: Vec<ForeignCallParam<F>>,
}
/// Convenience conversion for a foreign call returning a single field element value.
///
/// Creates a `ForeignCallResult` with one output containing the single value.
impl<F> From<F> for ForeignCallResult<F> {
fn from(value: F) -> Self {
ForeignCallResult { values: vec![value.into()] }
}
}
/// Conversion for a foreign call returning a single array output.
/// Creates a `ForeignCallResult` with one output, where that output is an array.
/// This represents one array output, not multiple single-value outputs:
///
/// ```ignore
/// let result: ForeignCallResult<F> = vec![1, 2, 3].into();
/// // result.values.len() == 1
/// // result.values[0] == ForeignCallParam::Array([1, 2, 3])
/// ```
///
/// For multiple single-value outputs, use `From<Vec<ForeignCallParam<F>>>` instead:
/// ```ignore
/// let result: ForeignCallResult<F> = vec![
/// ForeignCallParam::Single(1),
/// ForeignCallParam::Single(2),
/// ForeignCallParam::Single(3)
/// ].into();
/// // result.values.len() == 3
/// ```
impl<F> From<Vec<F>> for ForeignCallResult<F> {
fn from(values: Vec<F>) -> Self {
ForeignCallResult { values: vec![values.into()] }
}
}
/// Conversion for a foreign call returning multiple outputs.
///
/// Each element in the vector represents a separate output, which can be
/// either a single value or an array.
impl<F> From<Vec<ForeignCallParam<F>>> for ForeignCallResult<F> {
fn from(values: Vec<ForeignCallParam<F>>) -> Self {
ForeignCallResult { values }
}
}
#[cfg(test)]
mod tests {
use super::*;
use acir_field::FieldElement;
#[test]
fn test_foreign_call_param_from_single() {
let value = FieldElement::from(42u128);
let param = ForeignCallParam::from(value);
assert_eq!(param, ForeignCallParam::Single(value));
assert_eq!(param.fields(), vec![value]);
assert_eq!(param.unwrap_field(), value);
}
#[test]
fn test_foreign_call_param_from_array() {
let values =
vec![FieldElement::from(1u128), FieldElement::from(2u128), FieldElement::from(3u128)];
let param = ForeignCallParam::from(values.clone());
assert_eq!(param, ForeignCallParam::Array(values.clone()));
assert_eq!(param.fields(), values);
}
#[test]
fn test_foreign_call_param_array_roundtrip() {
// Test that Array variant round trips through From<Vec<F>> and fields()
let original = vec![
FieldElement::from(10u128),
FieldElement::from(20u128),
FieldElement::from(30u128),
];
// Need explicit type annotation to disambiguate from From<F> impl
let param: ForeignCallParam<FieldElement> = original.clone().into();
let roundtrip = param.fields();
assert_eq!(roundtrip, original);
}
#[test]
fn test_foreign_call_param_single_to_array() {
// Note: Single doesn't roundtrip perfectly because fields() returns Vec
// This test documents the expected behavior
let value = FieldElement::from(42u128);
let param = ForeignCallParam::Single(value);
// fields() converts Single to a Vec with one element
assert_eq!(param.fields(), vec![value]);
}
#[test]
#[should_panic(expected = "Expected single value, found array")]
fn test_foreign_call_param_unwrap_field_panics_on_array() {
let param =
ForeignCallParam::Array(vec![FieldElement::from(1u128), FieldElement::from(2u128)]);
// This should panic
param.unwrap_field();
}
#[test]
fn test_foreign_call_result_from_single_value() {
let value = FieldElement::from(42u128);
let result = ForeignCallResult::from(value);
assert_eq!(result.values.len(), 1);
assert_eq!(result.values[0], ForeignCallParam::Single(value));
}
#[test]
fn test_foreign_call_result_from_vec_creates_single_array_output() {
let values =
vec![FieldElement::from(1u128), FieldElement::from(2u128), FieldElement::from(3u128)];
let result = ForeignCallResult::from(values.clone());
// From<Vec<F>> creates one output that is an Array
assert_eq!(result.values.len(), 1);
assert_eq!(result.values[0], ForeignCallParam::Array(values));
}
#[test]
fn test_foreign_call_result_from_params_creates_multiple_outputs() {
let params = vec![
ForeignCallParam::Single(FieldElement::from(1u128)),
ForeignCallParam::Single(FieldElement::from(2u128)),
ForeignCallParam::Single(FieldElement::from(3u128)),
];
let result = ForeignCallResult::from(params.clone());
// From<Vec<ForeignCallParam<F>>> creates multiple outputs
assert_eq!(result.values.len(), 3);
assert_eq!(result.values, params);
}
}