use std::collections::{HashMap, HashSet};
use crate::ir::{ANFBinding, ANFMethod, ANFProgram, ANFValue};
const INFINITY_HEX: &str = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
const G_HEX: &str = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
type ValueMap = HashMap<String, ANFValue>;
fn is_call_to<'a>(value: &'a ANFValue, func_name: &str) -> Option<&'a Vec<String>> {
match value {
ANFValue::Call { func, args } if func == func_name => Some(args),
_ => None,
}
}
fn is_const_int(value: &ANFValue, n: i128) -> bool {
match value {
ANFValue::LoadConst { value: v } => {
if let Some(i) = v.as_i64() {
return i as i128 == n;
}
if let Some(f) = v.as_f64() {
return f as i128 == n;
}
false
}
_ => false,
}
}
fn get_const_int(value: &ANFValue) -> Option<i128> {
match value {
ANFValue::LoadConst { value: v } => {
if let Some(i) = v.as_i64() {
return Some(i as i128);
}
if let Some(f) = v.as_f64() {
let i = f as i128;
if (i as f64) == f {
return Some(i);
}
}
None
}
_ => None,
}
}
fn is_const_hex(value: &ANFValue, hex: &str) -> bool {
match value {
ANFValue::LoadConst { value: v } => v.as_str() == Some(hex),
_ => false,
}
}
fn is_infinity(value: &ANFValue) -> bool {
is_const_hex(value, INFINITY_HEX)
}
fn is_generator_point(value: &ANFValue) -> bool {
is_const_hex(value, G_HEX)
}
fn arg_is_infinity(arg_name: &str, value_map: &ValueMap) -> bool {
let v = match value_map.get(arg_name) {
Some(v) => v,
None => return false,
};
if is_infinity(v) {
return true;
}
if let Some(args) = is_call_to(v, "ecMulGen") {
if args.len() == 1 {
if let Some(scalar_val) = value_map.get(args[0].as_str()) {
if is_const_int(scalar_val, 0) {
return true;
}
}
}
}
false
}
fn arg_is_g(arg_name: &str, value_map: &ValueMap) -> bool {
let v = match value_map.get(arg_name) {
Some(v) => v,
None => return false,
};
if is_generator_point(v) {
return true;
}
if let Some(args) = is_call_to(v, "ecMulGen") {
if args.len() == 1 {
if let Some(scalar_val) = value_map.get(args[0].as_str()) {
if is_const_int(scalar_val, 1) {
return true;
}
}
}
}
false
}
fn make_load_const_hex(hex: &str) -> ANFValue {
ANFValue::LoadConst {
value: serde_json::Value::String(hex.to_string()),
}
}
fn make_load_const_int(n: i128) -> ANFValue {
ANFValue::LoadConst {
value: serde_json::json!(n as i64),
}
}
fn make_alias(target: &str) -> ANFValue {
ANFValue::LoadConst {
value: serde_json::Value::String(format!("@ref:{}", target)),
}
}
fn try_rewrite(
binding: &ANFBinding,
value_map: &ValueMap,
extra_bindings: &mut Vec<ANFBinding>,
) -> Option<ANFValue> {
let value = &binding.value;
let (func, args) = match value {
ANFValue::Call { func, args } => (func.as_str(), args),
_ => return None,
};
match func {
"ecMulGen" => {
if args.len() != 1 {
return None;
}
let scalar_val = value_map.get(args[0].as_str())?;
if is_const_int(scalar_val, 0) {
return Some(make_load_const_hex(INFINITY_HEX));
}
if is_const_int(scalar_val, 1) {
return Some(make_load_const_hex(G_HEX));
}
None
}
"ecMul" => {
if args.len() != 2 {
return None;
}
let point_arg = &args[0];
let scalar_arg = &args[1];
let scalar_val = value_map.get(scalar_arg.as_str())?;
if is_const_int(scalar_val, 0) {
return Some(make_load_const_hex(INFINITY_HEX));
}
if is_const_int(scalar_val, 1) {
return Some(make_alias(point_arg));
}
if arg_is_g(point_arg, value_map) {
return Some(ANFValue::Call {
func: "ecMulGen".to_string(),
args: vec![scalar_arg.clone()],
});
}
if let Some(point_val) = value_map.get(point_arg.as_str()) {
if let Some(inner_args) = is_call_to(point_val, "ecMul") {
if inner_args.len() == 2 {
let inner_point = inner_args[0].clone();
let inner_scalar = inner_args[1].clone();
if let Some(inner_scalar_val) = value_map.get(inner_scalar.as_str()) {
let k1 = get_const_int(inner_scalar_val);
let k2 = get_const_int(scalar_val);
if let (Some(k1), Some(k2)) = (k1, k2) {
if let Some(product) = k1.checked_mul(k2) {
let new_scalar_name = format!("{}_k", binding.name);
extra_bindings.push(ANFBinding {
name: new_scalar_name.clone(),
value: make_load_const_int(product),
source_loc: None,
});
return Some(ANFValue::Call {
func: "ecMul".to_string(),
args: vec![inner_point, new_scalar_name],
});
}
}
}
}
}
}
None
}
"ecAdd" => {
if args.len() != 2 {
return None;
}
let left_arg = &args[0];
let right_arg = &args[1];
if arg_is_infinity(right_arg, value_map) {
return Some(make_alias(left_arg));
}
if arg_is_infinity(left_arg, value_map) {
return Some(make_alias(right_arg));
}
if let Some(right_val) = value_map.get(right_arg.as_str()) {
if let Some(negate_args) = is_call_to(right_val, "ecNegate") {
if negate_args.len() == 1 && negate_args[0] == *left_arg {
return Some(make_load_const_hex(INFINITY_HEX));
}
}
}
let left_val = value_map.get(left_arg.as_str()).cloned();
let right_val = value_map.get(right_arg.as_str()).cloned();
if let (Some(ref lv), Some(ref rv)) = (&left_val, &right_val) {
if let (Some(left_args), Some(right_args)) =
(is_call_to(lv, "ecMulGen"), is_call_to(rv, "ecMulGen"))
{
if left_args.len() == 1 && right_args.len() == 1 {
let k1_name = left_args[0].clone();
let k2_name = right_args[0].clone();
if let (Some(k1_val), Some(k2_val)) = (
value_map.get(k1_name.as_str()),
value_map.get(k2_name.as_str()),
) {
let k1 = get_const_int(k1_val);
let k2 = get_const_int(k2_val);
if let (Some(k1), Some(k2)) = (k1, k2) {
if let Some(sum) = k1.checked_add(k2) {
let new_scalar_name = format!("{}_k", binding.name);
extra_bindings.push(ANFBinding {
name: new_scalar_name.clone(),
value: make_load_const_int(sum),
source_loc: None,
});
return Some(ANFValue::Call {
func: "ecMulGen".to_string(),
args: vec![new_scalar_name],
});
}
}
}
}
}
if let (Some(left_mul_args), Some(right_mul_args)) =
(is_call_to(lv, "ecMul"), is_call_to(rv, "ecMul"))
{
if left_mul_args.len() == 2
&& right_mul_args.len() == 2
&& left_mul_args[0] == right_mul_args[0]
{
let point_name = left_mul_args[0].clone();
let k1_name = left_mul_args[1].clone();
let k2_name = right_mul_args[1].clone();
if let (Some(k1_val), Some(k2_val)) = (
value_map.get(k1_name.as_str()),
value_map.get(k2_name.as_str()),
) {
let k1 = get_const_int(k1_val);
let k2 = get_const_int(k2_val);
if let (Some(k1), Some(k2)) = (k1, k2) {
if let Some(sum) = k1.checked_add(k2) {
let new_scalar_name = format!("{}_k", binding.name);
extra_bindings.push(ANFBinding {
name: new_scalar_name.clone(),
value: make_load_const_int(sum),
source_loc: None,
});
return Some(ANFValue::Call {
func: "ecMul".to_string(),
args: vec![point_name, new_scalar_name],
});
}
}
}
}
}
}
None
}
"ecNegate" => {
if args.len() != 1 {
return None;
}
let inner_val = value_map.get(args[0].as_str())?;
if let Some(negate_args) = is_call_to(inner_val, "ecNegate") {
if negate_args.len() == 1 {
return Some(make_alias(&negate_args[0]));
}
}
None
}
_ => None,
}
}
fn collect_refs_from_value(value: &ANFValue, refs: &mut HashSet<String>) {
match value {
ANFValue::LoadParam { .. } => {
}
ANFValue::LoadProp { .. } | ANFValue::GetStateScript {} => {}
ANFValue::LoadConst { value } => {
if let serde_json::Value::String(s) = value {
if let Some(target) = s.strip_prefix("@ref:") {
refs.insert(target.to_string());
}
}
}
ANFValue::BinOp { left, right, .. } => {
refs.insert(left.clone());
refs.insert(right.clone());
}
ANFValue::UnaryOp { operand, .. } => {
refs.insert(operand.clone());
}
ANFValue::Call { args, .. } => {
for arg in args {
refs.insert(arg.clone());
}
}
ANFValue::MethodCall { object, args, .. } => {
refs.insert(object.clone());
for arg in args {
refs.insert(arg.clone());
}
}
ANFValue::If {
cond,
then: then_branch,
else_branch,
} => {
refs.insert(cond.clone());
for b in then_branch {
collect_refs_from_value(&b.value, refs);
}
for b in else_branch {
collect_refs_from_value(&b.value, refs);
}
}
ANFValue::Loop { body, .. } => {
for b in body {
collect_refs_from_value(&b.value, refs);
}
}
ANFValue::Assert { value } => {
refs.insert(value.clone());
}
ANFValue::UpdateProp { value, .. } => {
refs.insert(value.clone());
}
ANFValue::CheckPreimage { preimage } => {
refs.insert(preimage.clone());
}
ANFValue::DeserializeState { preimage } => {
refs.insert(preimage.clone());
}
ANFValue::AddOutput {
satoshis,
state_values,
preimage,
} => {
refs.insert(satoshis.clone());
for sv in state_values {
refs.insert(sv.clone());
}
if !preimage.is_empty() {
refs.insert(preimage.clone());
}
}
ANFValue::AddRawOutput { satoshis, script_bytes } => {
refs.insert(satoshis.clone());
refs.insert(script_bytes.clone());
}
ANFValue::ArrayLiteral { elements } => {
for elem in elements {
refs.insert(elem.clone());
}
}
}
}
fn has_side_effect(value: &ANFValue) -> bool {
matches!(
value,
ANFValue::Assert { .. }
| ANFValue::UpdateProp { .. }
| ANFValue::CheckPreimage { .. }
| ANFValue::DeserializeState { .. }
| ANFValue::AddOutput { .. }
| ANFValue::AddRawOutput { .. }
| ANFValue::MethodCall { .. }
| ANFValue::Call { .. }
)
}
fn eliminate_dead_bindings_method(method: &ANFMethod) -> ANFMethod {
let mut body = method.body.clone();
loop {
let mut refs = HashSet::new();
for binding in &body {
collect_refs_from_value(&binding.value, &mut refs);
}
let before_len = body.len();
body.retain(|b| refs.contains(&b.name) || has_side_effect(&b.value));
if body.len() == before_len {
break;
}
}
ANFMethod {
name: method.name.clone(),
params: method.params.clone(),
body,
is_public: method.is_public,
}
}
fn optimize_method_ec(method: &ANFMethod) -> (ANFMethod, bool) {
let mut value_map: ValueMap = HashMap::new();
let mut result: Vec<ANFBinding> = Vec::new();
let mut changed = false;
for binding in &method.body {
value_map.insert(binding.name.clone(), binding.value.clone());
let mut extra_bindings = Vec::new();
let rewritten = try_rewrite(binding, &value_map, &mut extra_bindings);
if let Some(new_value) = rewritten {
for extra in &extra_bindings {
value_map.insert(extra.name.clone(), extra.value.clone());
result.push(extra.clone());
}
value_map.insert(binding.name.clone(), new_value.clone());
result.push(ANFBinding {
name: binding.name.clone(),
value: new_value,
source_loc: binding.source_loc.clone(),
});
changed = true;
} else {
result.push(binding.clone());
}
}
if !changed {
return (method.clone(), false);
}
(ANFMethod {
name: method.name.clone(),
params: method.params.clone(),
body: result,
is_public: method.is_public,
}, true)
}
pub fn optimize_ec(program: ANFProgram) -> ANFProgram {
let mut any_changed = false;
let optimized_methods: Vec<ANFMethod> = program
.methods
.iter()
.map(|m| {
let (opt, changed) = optimize_method_ec(m);
if changed {
any_changed = true;
}
opt
})
.collect();
if !any_changed {
return program;
}
let cleaned_methods: Vec<ANFMethod> = optimized_methods
.iter()
.map(eliminate_dead_bindings_method)
.collect();
ANFProgram {
contract_name: program.contract_name,
properties: program.properties,
methods: cleaned_methods,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{ANFBinding, ANFMethod, ANFParam, ANFProgram, ANFProperty, ANFValue};
fn make_program(bindings: Vec<ANFBinding>) -> ANFProgram {
ANFProgram {
contract_name: "Test".to_string(),
properties: vec![],
methods: vec![ANFMethod {
name: "test".to_string(),
params: vec![],
body: bindings,
is_public: true,
}],
}
}
fn load_const_hex(name: &str, hex: &str) -> ANFBinding {
ANFBinding {
name: name.to_string(),
value: ANFValue::LoadConst {
value: serde_json::Value::String(hex.to_string()),
},
source_loc: None,
}
}
fn load_const_int(name: &str, n: i64) -> ANFBinding {
ANFBinding {
name: name.to_string(),
value: ANFValue::LoadConst {
value: serde_json::json!(n),
},
source_loc: None,
}
}
fn call_binding(name: &str, func: &str, args: Vec<&str>) -> ANFBinding {
ANFBinding {
name: name.to_string(),
value: ANFValue::Call {
func: func.to_string(),
args: args.into_iter().map(|s| s.to_string()).collect(),
},
source_loc: None,
}
}
fn assert_binding(name: &str, value_ref: &str) -> ANFBinding {
ANFBinding {
name: name.to_string(),
value: ANFValue::Assert {
value: value_ref.to_string(),
},
source_loc: None,
}
}
fn find_binding<'a>(bindings: &'a [ANFBinding], name: &str) -> Option<&'a ANFBinding> {
bindings.iter().find(|b| b.name == name)
}
fn get_method_body(program: &ANFProgram) -> &[ANFBinding] {
&program.methods[0].body
}
fn infinity_hex() -> String {
INFINITY_HEX.to_string()
}
fn g_hex() -> String {
G_HEX.to_string()
}
fn some_point() -> String {
"ab".repeat(64)
}
#[test]
fn test_pass_through_no_ec_ops() {
let bindings = vec![
load_const_int("t0", 42),
load_const_int("t1", 10),
ANFBinding {
name: "t2".to_string(),
value: ANFValue::BinOp {
op: "+".to_string(),
left: "t0".to_string(),
right: "t1".to_string(),
result_type: None,
},
source_loc: None,
},
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
assert_eq!(body.len(), 4, "expected 4 bindings, got {}", body.len());
assert_eq!(body[0].name, "t0");
assert_eq!(body[1].name, "t1");
assert_eq!(body[2].name, "t2");
assert_eq!(body[3].name, "t3");
}
#[test]
fn test_pass_through_empty_method() {
let program = make_program(vec![]);
let result = optimize_ec(program);
assert_eq!(get_method_body(&result).len(), 0);
}
#[test]
fn test_rule1_ec_add_x_infinity() {
let bindings = vec![
load_const_hex("t0", &some_point()),
load_const_hex("t1", &infinity_hex()),
call_binding("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "expected @ref:t0, got {name}");
}
other => panic!("expected LoadConst, got {other:?}"),
}
}
#[test]
fn test_rule2_ec_add_infinity_x() {
let bindings = vec![
load_const_hex("t0", &infinity_hex()),
load_const_hex("t1", &"cd".repeat(64)),
call_binding("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t1", "expected @ref:t1, got {name}");
}
other => panic!("expected LoadConst, got {other:?}"),
}
}
#[test]
fn test_rule3_ec_mul_by_one() {
let bindings = vec![
load_const_hex("t0", &some_point()),
load_const_int("t1", 1),
call_binding("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "expected @ref:t0, got {name}");
}
other => panic!("expected LoadConst, got {other:?}"),
}
}
#[test]
fn test_rule4_ec_mul_by_zero() {
let bindings = vec![
load_const_hex("t0", &some_point()),
load_const_int("t1", 0),
call_binding("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(INFINITY_HEX),
"expected INFINITY, got {value}"
);
}
other => panic!("expected LoadConst(INFINITY), got {other:?}"),
}
}
#[test]
fn test_rule5_ec_mulgen_zero() {
let bindings = vec![
load_const_int("t0", 0),
call_binding("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t1 = find_binding(body, "t1").expect("expected binding t1");
match &t1.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(INFINITY_HEX),
"expected INFINITY, got {value}"
);
}
other => panic!("expected LoadConst(INFINITY), got {other:?}"),
}
}
#[test]
fn test_rule6_ec_mulgen_one() {
let bindings = vec![
load_const_int("t0", 1),
call_binding("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t1 = find_binding(body, "t1").expect("expected binding t1");
match &t1.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(G_HEX),
"expected G, got {value}"
);
}
other => panic!("expected LoadConst(G), got {other:?}"),
}
}
#[test]
fn test_rule7_double_negate() {
let bindings = vec![
load_const_hex("t0", &some_point()),
call_binding("t1", "ecNegate", vec!["t0"]),
call_binding("t2", "ecNegate", vec!["t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "expected @ref:t0, got {name}");
}
other => panic!("expected LoadConst, got {other:?}"),
}
}
#[test]
fn test_rule8_add_negate() {
let bindings = vec![
load_const_hex("t0", &some_point()),
call_binding("t1", "ecNegate", vec!["t0"]),
call_binding("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(INFINITY_HEX),
"expected INFINITY, got {value}"
);
}
other => panic!("expected LoadConst(INFINITY), got {other:?}"),
}
}
#[test]
fn test_rule12_mul_g_to_mulgen() {
let bindings = vec![
load_const_hex("t0", &g_hex()),
load_const_int("t1", 42),
call_binding("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t2 = find_binding(body, "t2").expect("expected binding t2");
match &t2.value {
ANFValue::Call { func, args } => {
assert_eq!(func, "ecMulGen", "expected ecMulGen, got {func}");
assert_eq!(args, &["t1"], "expected args [t1], got {args:?}");
}
other => panic!("expected Call(ecMulGen), got {other:?}"),
}
}
#[test]
fn test_dead_binding_removed() {
let bindings = vec![
load_const_hex("t0", &some_point()),
load_const_hex("t1", &infinity_hex()),
call_binding("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
!names.contains(&"t1"),
"expected dead binding t1 (INFINITY) to be eliminated, but it is still present"
);
}
#[test]
fn test_non_ec_call_unchanged() {
let bindings = vec![
load_const_hex("t0", &"ab".repeat(33)),
ANFBinding {
name: "t1".to_string(),
value: ANFValue::Call {
func: "hash160".to_string(),
args: vec!["t0".to_string()],
},
source_loc: None,
},
assert_binding("t2", "t1"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let t1 = find_binding(body, "t1").expect("expected hash160 binding t1 to be present");
match &t1.value {
ANFValue::Call { func, args } => {
assert_eq!(func, "hash160", "expected hash160 call preserved, got {func}");
assert_eq!(args, &["t0"], "expected args [t0], got {args:?}");
}
other => panic!("expected Call(hash160), got {other:?}"),
}
}
#[test]
fn test_side_effect_bindings_preserved() {
let bindings = vec![
load_const_int("t0", 1),
assert_binding("t1", "t0"),
];
let program = make_program(bindings);
let result = optimize_ec(program);
let body = get_method_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
names.contains(&"t1"),
"expected assert binding t1 to be preserved as side effect, but it was eliminated. bindings: {names:?}"
);
}
#[test]
fn test_contract_name_preserved() {
let program = ANFProgram {
contract_name: "MyContract".to_string(),
properties: vec![ANFProperty {
name: "x".to_string(),
prop_type: "bigint".to_string(),
readonly: true,
initial_value: None,
}],
methods: vec![ANFMethod {
name: "doStuff".to_string(),
params: vec![ANFParam {
name: "y".to_string(),
param_type: "bigint".to_string(),
}],
body: vec![
load_const_int("t0", 1),
assert_binding("t1", "t0"),
],
is_public: true,
}],
};
let result = optimize_ec(program);
assert_eq!(result.contract_name, "MyContract");
assert_eq!(result.properties.len(), 1);
assert_eq!(result.properties[0].name, "x");
assert_eq!(result.methods.len(), 1);
assert_eq!(result.methods[0].name, "doStuff");
}
#[test]
fn test_multiple_methods_all_optimized() {
let program = ANFProgram {
contract_name: "Test".to_string(),
properties: vec![],
methods: vec![
ANFMethod {
name: "method1".to_string(),
params: vec![],
body: vec![
load_const_int("t0", 0),
call_binding("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
],
is_public: true,
},
ANFMethod {
name: "method2".to_string(),
params: vec![],
body: vec![
load_const_int("t0", 1),
call_binding("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
],
is_public: true,
},
],
};
let result = optimize_ec(program);
assert_eq!(result.methods.len(), 2);
let body1 = &result.methods[0].body;
let t1m1 = find_binding(body1, "t1").expect("expected t1 in method1");
match &t1m1.value {
ANFValue::LoadConst { value } => {
assert_eq!(value.as_str(), Some(INFINITY_HEX), "method1 t1: expected INFINITY");
}
other => panic!("method1 t1: expected LoadConst(INFINITY), got {other:?}"),
}
let body2 = &result.methods[1].body;
let t1m2 = find_binding(body2, "t1").expect("expected t1 in method2");
match &t1m2.value {
ANFValue::LoadConst { value } => {
assert_eq!(value.as_str(), Some(G_HEX), "method2 t1: expected G");
}
other => panic!("method2 t1: expected LoadConst(G), got {other:?}"),
}
}
}