use runar_compiler_rust::frontend::anf_optimize::optimize_ec;
use runar_compiler_rust::ir::{ANFBinding, ANFMethod, ANFProgram, ANFValue};
const INFINITY_HEX: &str = "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
const G_HEX: &str = "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8";
fn some_point_hex() -> String {
"ab".repeat(64)
}
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 get_body(program: &ANFProgram) -> &[ANFBinding] {
&program.methods[0].body
}
fn find_binding<'a>(bindings: &'a [ANFBinding], name: &str) -> Option<&'a ANFBinding> {
bindings.iter().find(|b| b.name == name)
}
fn 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 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(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(str::to_string).collect(),
},
source_loc: None,
}
}
fn assert_binding(name: &str, val_ref: &str) -> ANFBinding {
ANFBinding {
name: name.to_string(),
value: ANFValue::Assert {
value: val_ref.to_string(),
},
source_loc: None,
}
}
#[test]
fn test_rule1_ecadd_x_infinity_becomes_alias() {
let bindings = vec![
const_hex("t0", &some_point_hex()),
const_hex("t1", INFINITY_HEX),
call("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "Rule 1: expected @ref:t0, got {name}");
}
other => panic!("Rule 1: expected LoadConst(@ref:t0), got {other:?}"),
}
}
#[test]
fn test_rule2_ecadd_infinity_x_becomes_alias() {
let bindings = vec![
const_hex("t0", INFINITY_HEX),
const_hex("t1", &"cd".repeat(64)),
call("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t1", "Rule 2: expected @ref:t1, got {name}");
}
other => panic!("Rule 2: expected LoadConst(@ref:t1), got {other:?}"),
}
}
#[test]
fn test_rule3_ecmul_x_one_becomes_alias() {
let bindings = vec![
const_hex("t0", &some_point_hex()),
const_int("t1", 1),
call("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "Rule 3: expected @ref:t0, got {name}");
}
other => panic!("Rule 3: expected LoadConst(@ref:t0), got {other:?}"),
}
}
#[test]
fn test_rule4_ecmul_x_zero_becomes_infinity() {
let bindings = vec![
const_hex("t0", &some_point_hex()),
const_int("t1", 0),
call("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(INFINITY_HEX),
"Rule 4: expected INFINITY constant, got {value}"
);
}
other => panic!("Rule 4: expected LoadConst(INFINITY), got {other:?}"),
}
}
#[test]
fn test_rule5_ecmulgen_zero_becomes_infinity() {
let bindings = vec![
const_int("t0", 0),
call("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t1 = find_binding(body, "t1").expect("binding t1 should be present");
match &t1.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(INFINITY_HEX),
"Rule 5: expected INFINITY, got {value}"
);
}
other => panic!("Rule 5: expected LoadConst(INFINITY), got {other:?}"),
}
}
#[test]
fn test_rule6_ecmulgen_one_becomes_g() {
let bindings = vec![
const_int("t0", 1),
call("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t1 = find_binding(body, "t1").expect("binding t1 should be present");
match &t1.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(G_HEX),
"Rule 6: expected G constant, got {value}"
);
}
other => panic!("Rule 6: expected LoadConst(G), got {other:?}"),
}
}
#[test]
fn test_rule7_double_negate_eliminates() {
let bindings = vec![
const_hex("t0", &some_point_hex()),
call("t1", "ecNegate", vec!["t0"]),
call("t2", "ecNegate", vec!["t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "Rule 7: expected @ref:t0, got {name}");
}
other => panic!("Rule 7: expected LoadConst(@ref:t0), got {other:?}"),
}
}
#[test]
fn test_rule8_ecadd_self_cancel_becomes_infinity() {
let bindings = vec![
const_hex("t0", &some_point_hex()),
call("t1", "ecNegate", vec!["t0"]),
call("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
assert_eq!(
value.as_str(),
Some(INFINITY_HEX),
"Rule 8: expected INFINITY, got {value}"
);
}
other => panic!("Rule 8: expected LoadConst(INFINITY), got {other:?}"),
}
}
#[test]
fn test_rule12_ecmul_g_becomes_ecmulgen() {
let bindings = vec![
const_hex("t0", G_HEX),
const_int("t1", 42),
call("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::Call { func, args } => {
assert_eq!(func, "ecMulGen", "Rule 12: expected ecMulGen, got {func}");
assert_eq!(args, &["t1"], "Rule 12: expected args [t1], got {args:?}");
}
other => panic!("Rule 12: expected Call(ecMulGen), got {other:?}"),
}
}
#[test]
fn test_non_ec_program_passes_through_unchanged() {
let bindings = vec![
const_int("t0", 42),
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_body(&result);
assert_eq!(body.len(), 4, "non-EC program should have 4 bindings unchanged");
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_dead_bindings_eliminated_after_rule1() {
let bindings = vec![
const_hex("t0", &some_point_hex()),
const_hex("t1", INFINITY_HEX),
call("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
!names.contains(&"t1"),
"dead binding t1 (INFINITY) should be eliminated after Rule 1; still present in: {names:?}"
);
assert!(names.contains(&"t0"), "t0 should be kept (referenced via load_const @ref:)");
assert!(names.contains(&"t2"), "t2 should remain");
assert!(names.contains(&"t3"), "t3 should remain");
}
#[test]
fn test_dead_bindings_eliminated_after_rule5() {
let bindings = vec![
const_int("t0", 0),
call("t1", "ecMulGen", vec!["t0"]),
assert_binding("t2", "t1"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
!names.contains(&"t0"),
"dead binding t0 (scalar 0) should be eliminated; still present in: {names:?}"
);
}
#[test]
fn test_side_effect_assert_preserved_even_if_unreferenced() {
let bindings = vec![
const_int("t0", 1),
assert_binding("t1", "t0"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
names.contains(&"t1"),
"assert binding t1 must be preserved (side effect); got: {names:?}"
);
}
#[test]
fn test_side_effect_call_preserved_even_if_unreferenced() {
let bindings = vec![
const_hex("t0", &"ab".repeat(33)),
const_hex("t1", &"cd".repeat(33)),
ANFBinding {
name: "t2".to_string(),
value: ANFValue::Call {
func: "checkSig".to_string(),
args: vec!["t0".to_string(), "t1".to_string()],
},
source_loc: None,
},
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
names.contains(&"t2"),
"call binding t2 (checkSig) must be preserved (side effect); got: {names:?}"
);
}
#[test]
fn test_hash160_call_unchanged() {
let bindings = vec![
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 result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t1 = find_binding(body, "t1").expect("hash160 binding should be preserved");
match &t1.value {
ANFValue::Call { func, args } => {
assert_eq!(func, "hash160", "expected hash160 preserved, got {func}");
assert_eq!(args, &["t0"]);
}
other => panic!("expected Call(hash160), got {other:?}"),
}
}
#[test]
fn test_contract_metadata_preserved() {
use runar_compiler_rust::ir::ANFProperty;
let program = ANFProgram {
contract_name: "P2PKH".to_string(),
properties: vec![
ANFProperty {
name: "pubKeyHash".to_string(),
prop_type: "Addr".to_string(),
readonly: true,
initial_value: None,
},
],
methods: vec![ANFMethod {
name: "unlock".to_string(),
params: vec![],
body: vec![
const_int("t0", 42),
assert_binding("t1", "t0"),
],
is_public: true,
}],
};
let result = optimize_ec(program);
assert_eq!(result.contract_name, "P2PKH", "contract name should be preserved");
assert_eq!(result.properties.len(), 1, "property count should be preserved");
assert_eq!(result.properties[0].name, "pubKeyHash", "property name should be preserved");
}
#[test]
fn test_multiple_methods_all_optimized() {
let p = some_point_hex();
let method1_body = vec![
const_hex("t0", &p),
const_hex("t1", INFINITY_HEX),
call("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let method2_body = vec![
const_hex("m0", &p),
const_hex("m1", INFINITY_HEX),
call("m2", "ecAdd", vec!["m0", "m1"]),
assert_binding("m3", "m2"),
];
let program = ANFProgram {
contract_name: "TwoMethods".to_string(),
properties: vec![],
methods: vec![
ANFMethod {
name: "method1".to_string(),
params: vec![],
body: method1_body,
is_public: true,
},
ANFMethod {
name: "method2".to_string(),
params: vec![],
body: method2_body,
is_public: true,
},
],
};
let result = optimize_ec(program);
assert_eq!(result.methods.len(), 2, "both methods should remain");
let b1 = &result.methods[0].body;
let t2 = find_binding(b1, "t2").expect("t2 in method1 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:t0", "method1: Rule 1 should alias t2 to t0");
}
other => panic!("method1: expected LoadConst(@ref:t0), got {other:?}"),
}
let b2 = &result.methods[1].body;
let m2 = find_binding(b2, "m2").expect("m2 in method2 should be present");
match &m2.value {
ANFValue::LoadConst { value } => {
let name = value.as_str().unwrap();
assert_eq!(name, "@ref:m0", "method2: Rule 1 should alias m2 to m0");
}
other => panic!("method2: expected LoadConst(@ref:m0), got {other:?}"),
}
}
#[test]
fn test_empty_method_body_unchanged() {
let program = ANFProgram {
contract_name: "Empty".to_string(),
properties: vec![],
methods: vec![ANFMethod {
name: "check".to_string(),
params: vec![],
body: vec![],
is_public: true,
}],
};
let result = optimize_ec(program);
assert_eq!(result.methods.len(), 1, "method should be present");
assert!(result.methods[0].body.is_empty(), "empty body should remain empty");
}
#[test]
fn test_side_effect_call_binding_preserved() {
let bindings = vec![
const_hex("t0", &"ab".repeat(33)),
const_hex("t1", &"cd".repeat(33)),
call("t2", "checkSig", vec!["t0", "t1"]),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let names: Vec<&str> = body.iter().map(|b| b.name.as_str()).collect();
assert!(
names.contains(&"t2"),
"checkSig call binding t2 must be preserved as it has a side effect; got: {names:?}"
);
}
#[test]
fn test_rule10_ecadd_ecmulgen_plus_ecmulgen_becomes_ecmulgen_sum() {
let bindings = vec![
const_int("k1", 3),
const_int("k2", 4),
call("t0", "ecMulGen", vec!["k1"]),
call("t1", "ecMulGen", vec!["k2"]),
call("t2", "ecAdd", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::Call { func, args } => {
assert_eq!(
func, "ecMulGen",
"Rule 10: ecAdd(ecMulGen(k1), ecMulGen(k2)) should become ecMulGen(...), got {func}"
);
assert_eq!(
args.len(),
1,
"Rule 10: ecMulGen should have exactly 1 argument, got {:?}",
args
);
}
other => panic!("Rule 10: expected Call(ecMulGen), got {other:?}"),
}
}
#[test]
fn test_chained_rules_12_then_5() {
let bindings = vec![
const_hex("t0", G_HEX),
const_int("t1", 0),
call("t2", "ecMul", vec!["t0", "t1"]),
assert_binding("t3", "t2"),
];
let result = optimize_ec(make_program(bindings));
let body = get_body(&result);
let t2 = find_binding(body, "t2").expect("binding t2 should be present");
match &t2.value {
ANFValue::LoadConst { value } => {
let s = value.as_str().unwrap_or("");
assert!(
s == INFINITY_HEX || value.as_str().is_some(),
"expected valid constant, got {value}"
);
}
ANFValue::Call { func, .. } => {
assert_eq!(
func, "ecMulGen",
"expected ecMulGen as intermediate result, got {func}"
);
}
other => panic!("unexpected binding kind: {other:?}"),
}
}