Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions acvm-repo/brillig/src/black_box.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};

/// These opcodes provide an equivalent of ACIR blackbox functions.
/// They are implemented as native functions in the VM.
/// For more information, see the ACIR blackbox functions in acir::circuit::opcodes::BlackBoxFuncCall
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[cfg_attr(feature = "arb", derive(proptest_derive::Arbitrary))]
pub enum BlackBoxOp {
Expand Down
146 changes: 142 additions & 4 deletions acvm-repo/brillig/src/foreign_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,19 @@ 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>),
}

Expand All @@ -22,7 +31,10 @@ impl<F> From<Vec<F>> for ForeignCallParam<F> {
}

impl<F: AcirField> ForeignCallParam<F> {
/// Convert the fields in the parameter into a vector, used to flatten data.
/// 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],
Expand All @@ -40,29 +52,155 @@ impl<F: AcirField> ForeignCallParam<F> {
}

/// 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>>,
}

/// Result of a call returning a one output value.
/// 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()] }
}
}

/// Result of a call returning a one output array.
/// 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()] }
}
}

/// Result of a call returning multiple outputs.
/// 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);
}
}
Loading
Loading