use crate::{ConstraintUnsatisfied, Mode, helpers::Constraint, *};
use core::{
cell::{Cell, RefCell},
fmt,
};
type Field = <console::TestnetV0 as console::Environment>::Field;
thread_local! {
static VARIABLE_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
static CONSTRAINT_LIMIT: Cell<Option<u64>> = const { Cell::new(None) };
pub(super) static TESTNET_CIRCUIT: RefCell<R1CS<Field>> = RefCell::new(R1CS::new());
static IN_WITNESS: Cell<bool> = const { Cell::new(false) };
static ZERO: LinearCombination<Field> = LinearCombination::zero();
static ONE: LinearCombination<Field> = LinearCombination::one();
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct TestnetCircuit;
impl Environment for TestnetCircuit {
type Affine = <console::TestnetV0 as console::Environment>::Affine;
type BaseField = Field;
type Network = console::TestnetV0;
type ScalarField = <console::TestnetV0 as console::Environment>::Scalar;
fn zero() -> LinearCombination<Self::BaseField> {
ZERO.with(|zero| zero.clone())
}
fn one() -> LinearCombination<Self::BaseField> {
ONE.with(|one| one.clone())
}
fn new_variable(mode: Mode, value: Self::BaseField) -> Variable<Self::BaseField> {
IN_WITNESS.with(|in_witness| {
if !in_witness.get() {
VARIABLE_LIMIT.with(|variable_limit| {
if let Some(limit) = variable_limit.get() {
if Self::num_variables() > limit {
Self::halt(format!("Surpassed the variable limit ({limit})"))
}
}
});
TESTNET_CIRCUIT.with(|circuit| match mode {
Mode::Constant => circuit.borrow_mut().new_constant(value),
Mode::Public => circuit.borrow_mut().new_public(value),
Mode::Private => circuit.borrow_mut().new_private(value),
})
} else {
Self::halt("Tried to initialize a new variable in witness mode")
}
})
}
fn new_witness<Fn: FnOnce() -> Output::Primitive, Output: Inject>(mode: Mode, logic: Fn) -> Output {
IN_WITNESS.with(|in_witness| {
in_witness.replace(true);
let output = logic();
in_witness.replace(false);
Inject::new(mode, output)
})
}
fn scope<S: Into<String>, Fn, Output>(name: S, logic: Fn) -> Output
where
Fn: FnOnce() -> Output,
{
IN_WITNESS.with(|in_witness| {
if !in_witness.get() {
TESTNET_CIRCUIT.with(|circuit| {
let name = name.into();
if let Err(error) = circuit.borrow_mut().push_scope(&name) {
Self::halt(error)
}
let output = logic();
if let Err(error) = circuit.borrow_mut().pop_scope(name) {
Self::halt(error)
}
output
})
} else {
Self::halt("Tried to initialize a new scope in witness mode")
}
})
}
fn enforce<Fn, A, B, C>(constraint: Fn) -> Result<(), ConstraintUnsatisfied>
where
Fn: FnOnce() -> (A, B, C),
A: Into<LinearCombination<Self::BaseField>>,
B: Into<LinearCombination<Self::BaseField>>,
C: Into<LinearCombination<Self::BaseField>>,
{
IN_WITNESS.with(|in_witness| {
if !in_witness.get() {
TESTNET_CIRCUIT.with(|circuit| {
CONSTRAINT_LIMIT.with(|constraint_limit| {
if let Some(limit) = constraint_limit.get() {
if circuit.borrow().num_constraints() > limit {
Self::halt(format!("Surpassed the constraint limit ({limit})"))
}
}
});
let (a, b, c) = constraint();
let (a, b, c) = (a.into(), b.into(), c.into());
match a.is_constant() && b.is_constant() && c.is_constant() {
true => {
if a.value() * b.value() != c.value() {
return Err(ConstraintUnsatisfied {
a: a.to_string(),
b: b.to_string(),
c: c.to_string(),
});
}
}
false => {
let constraint = Constraint(circuit.borrow().scope(), a, b, c);
circuit.borrow_mut().enforce(constraint)
}
}
Ok(())
})
} else {
Self::halt("Tried to add a new constraint in witness mode")
}
})
}
fn is_satisfied() -> bool {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().is_satisfied())
}
fn is_satisfied_in_scope() -> bool {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().is_satisfied_in_scope())
}
fn num_constants() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_constants())
}
fn num_public() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_public())
}
fn num_private() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_private())
}
fn num_variables() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_variables())
}
fn num_constraints() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_constraints())
}
fn num_nonzeros() -> (u64, u64, u64) {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_nonzeros())
}
fn num_constants_in_scope() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_constants_in_scope())
}
fn num_public_in_scope() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_public_in_scope())
}
fn num_private_in_scope() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_private_in_scope())
}
fn num_constraints_in_scope() -> u64 {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_constraints_in_scope())
}
fn num_nonzeros_in_scope() -> (u64, u64, u64) {
TESTNET_CIRCUIT.with(|circuit| circuit.borrow().num_nonzeros_in_scope())
}
fn get_variable_limit() -> Option<u64> {
VARIABLE_LIMIT.with(|current_limit| current_limit.get())
}
fn set_variable_limit(limit: Option<u64>) {
VARIABLE_LIMIT.with(|current_limit| current_limit.replace(limit));
}
fn get_constraint_limit() -> Option<u64> {
CONSTRAINT_LIMIT.with(|current_limit| current_limit.get())
}
fn set_constraint_limit(limit: Option<u64>) {
CONSTRAINT_LIMIT.with(|current_limit| current_limit.replace(limit));
}
fn halt<S: Into<String>, T>(message: S) -> T {
let error = message.into();
panic!("{}", &error)
}
fn inject_r1cs(r1cs: R1CS<Self::BaseField>) {
TESTNET_CIRCUIT.with(|circuit| {
assert_eq!(0, circuit.borrow().num_constants());
assert_eq!(1, circuit.borrow().num_public());
assert_eq!(0, circuit.borrow().num_private());
assert_eq!(1, circuit.borrow().num_variables());
assert_eq!(0, circuit.borrow().num_constraints());
let r1cs = circuit.replace(r1cs);
assert_eq!(0, r1cs.num_constants());
assert_eq!(1, r1cs.num_public());
assert_eq!(0, r1cs.num_private());
assert_eq!(1, r1cs.num_variables());
assert_eq!(0, r1cs.num_constraints());
})
}
fn eject_r1cs_and_reset() -> R1CS<Self::BaseField> {
TESTNET_CIRCUIT.with(|circuit| {
IN_WITNESS.with(|in_witness| in_witness.replace(false));
Self::set_variable_limit(None);
Self::set_constraint_limit(None);
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
assert_eq!(0, circuit.borrow().num_constants());
assert_eq!(1, circuit.borrow().num_public());
assert_eq!(0, circuit.borrow().num_private());
assert_eq!(1, circuit.borrow().num_variables());
assert_eq!(0, circuit.borrow().num_constraints());
r1cs
})
}
fn eject_assignment_and_reset() -> Assignment<<Self::Network as console::Environment>::Field> {
TESTNET_CIRCUIT.with(|circuit| {
IN_WITNESS.with(|in_witness| in_witness.replace(false));
Self::set_variable_limit(None);
Self::set_constraint_limit(None);
let r1cs = circuit.replace(R1CS::<<Self as Environment>::BaseField>::new());
assert_eq!(0, circuit.borrow().num_constants());
assert_eq!(1, circuit.borrow().num_public());
assert_eq!(0, circuit.borrow().num_private());
assert_eq!(1, circuit.borrow().num_variables());
assert_eq!(0, circuit.borrow().num_constraints());
Assignment::from(r1cs)
})
}
fn reset() {
TESTNET_CIRCUIT.with(|circuit| {
IN_WITNESS.with(|in_witness| in_witness.replace(false));
Self::set_variable_limit(None);
Self::set_constraint_limit(None);
*circuit.borrow_mut() = R1CS::<<Self as Environment>::BaseField>::new();
assert_eq!(0, circuit.borrow().num_constants());
assert_eq!(1, circuit.borrow().num_public());
assert_eq!(0, circuit.borrow().num_private());
assert_eq!(1, circuit.borrow().num_variables());
assert_eq!(0, circuit.borrow().num_constraints());
});
}
}
impl fmt::Display for TestnetCircuit {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
TESTNET_CIRCUIT.with(|circuit| write!(f, "{}", circuit.borrow()))
}
}
#[cfg(test)]
mod tests {
use snarkvm_circuit::prelude::*;
fn create_example_circuit<E: Environment>() -> Field<E> {
let one = snarkvm_console_types::Field::<E::Network>::one();
let two = one + one;
const EXPONENT: u64 = 64;
let mut candidate = Field::<E>::new(Mode::Public, one);
let mut accumulator = Field::new(Mode::Private, two);
for _ in 0..EXPONENT {
candidate += &accumulator;
accumulator *= Field::new(Mode::Private, two);
}
assert_eq!((accumulator - Field::one()).eject_value(), candidate.eject_value());
assert_eq!(2, E::num_public());
assert_eq!(2 * EXPONENT + 1, E::num_private());
assert_eq!(EXPONENT, E::num_constraints());
assert!(E::is_satisfied());
candidate
}
#[test]
fn test_print_circuit() {
let _candidate = create_example_circuit::<TestnetCircuit>();
let output = format!("{TestnetCircuit}");
println!("{output}");
}
#[test]
fn test_circuit_scope() {
TestnetCircuit::scope("test_circuit_scope", || {
assert_eq!(0, TestnetCircuit::num_constants());
assert_eq!(1, TestnetCircuit::num_public());
assert_eq!(0, TestnetCircuit::num_private());
assert_eq!(0, TestnetCircuit::num_constraints());
assert_eq!(0, TestnetCircuit::num_constants_in_scope());
assert_eq!(0, TestnetCircuit::num_public_in_scope());
assert_eq!(0, TestnetCircuit::num_private_in_scope());
assert_eq!(0, TestnetCircuit::num_constraints_in_scope());
})
}
}