use crate::compatibility::check_compatibility;
use abi::{
Function,
FunctionInput,
FunctionOutput,
Mapping,
Mode,
Plaintext,
Primitive,
Program,
Record,
RecordField,
RecordRef,
StorageType,
StorageVariable,
StructRef,
UInt,
};
use leo_abi_types as abi;
fn u64t() -> Plaintext {
Plaintext::Primitive(Primitive::UInt(UInt::U64))
}
fn u32t() -> Plaintext {
Plaintext::Primitive(Primitive::UInt(UInt::U32))
}
fn addr() -> Plaintext {
Plaintext::Primitive(Primitive::Address)
}
fn input(ty: Plaintext, mode: Mode) -> FunctionInput {
FunctionInput::Plaintext { ty, mode }
}
fn output(ty: Plaintext, mode: Mode) -> FunctionOutput {
FunctionOutput::Plaintext { ty, mode }
}
fn func(name: &str, inputs: Vec<FunctionInput>, outputs: Vec<FunctionOutput>) -> Function {
Function { name: name.to_string(), inputs, outputs }
}
fn program(name: &str, functions: Vec<Function>) -> Program {
Program {
program: name.to_string(),
structs: Vec::new(),
records: Vec::new(),
mappings: Vec::new(),
storage_variables: Vec::new(),
functions,
views: Vec::new(),
}
}
fn transfer() -> Function {
func(
"transfer",
vec![input(addr(), Mode::Private), input(addr(), Mode::Private), input(u64t(), Mode::Public)],
vec![],
)
}
fn total_supply() -> Function {
func("total_supply", vec![], vec![output(u64t(), Mode::Public)])
}
fn storage(name: &str, ty: Plaintext) -> StorageVariable {
StorageVariable { name: name.into(), ty: StorageType::Plaintext(ty) }
}
fn struct_ref(path: &str, program: &str) -> Plaintext {
Plaintext::Struct(StructRef { path: vec![path.into()], program: Some(program.into()) })
}
fn record_input(path: &str, program: &str) -> FunctionInput {
FunctionInput::Record(RecordRef { path: vec![path.into()], program: Some(program.into()) })
}
#[test]
fn identical_interfaces_are_compatible() {
let problems =
check_compatibility(&program("token.aleo", vec![transfer()]), &program("std.aleo", vec![transfer()]));
assert!(problems.is_empty(), "{problems:?}");
}
#[test]
fn candidate_superset_is_compatible() {
let extra = func("mint", vec![input(addr(), Mode::Private), input(u64t(), Mode::Public)], vec![]);
let candidate = program("token.aleo", vec![transfer(), extra]);
assert!(check_compatibility(&candidate, &program("std.aleo", vec![transfer()])).is_empty());
}
#[test]
fn missing_function_is_reported() {
let problems = check_compatibility(&program("token.aleo", vec![]), &program("std.aleo", vec![transfer()]));
assert_eq!(problems, vec!["missing function `transfer`".to_string()]);
}
#[test]
fn input_type_mismatch_is_reported() {
let candidate = program("token.aleo", vec![func(
"transfer",
vec![input(addr(), Mode::Private), input(addr(), Mode::Private), input(u32t(), Mode::Public)],
vec![],
)]);
let problems = check_compatibility(&candidate, &program("std.aleo", vec![transfer()]));
assert_eq!(problems, vec!["function `transfer` differs".to_string()]);
}
#[test]
fn input_mode_mismatch_is_reported() {
let candidate = program("token.aleo", vec![func(
"transfer",
vec![input(addr(), Mode::Private), input(addr(), Mode::Private), input(u64t(), Mode::Private)],
vec![],
)]);
assert!(!check_compatibility(&candidate, &program("std.aleo", vec![transfer()])).is_empty());
}
#[test]
fn output_mismatch_is_reported() {
let standard = program("std.aleo", vec![func("balance_of", vec![input(addr(), Mode::Private)], vec![output(
u64t(),
Mode::Private,
)])]);
let candidate = program("token.aleo", vec![func("balance_of", vec![input(addr(), Mode::Private)], vec![output(
u32t(),
Mode::Private,
)])]);
assert!(!check_compatibility(&candidate, &standard).is_empty());
}
#[test]
fn mapping_value_mismatch_is_reported() {
let mut standard = program("std.aleo", vec![]);
standard.mappings.push(Mapping { name: "balances".into(), key: addr(), value: u64t() });
let mut candidate = program("token.aleo", vec![]);
candidate.mappings.push(Mapping { name: "balances".into(), key: addr(), value: u32t() });
assert_eq!(check_compatibility(&candidate, &standard), vec!["mapping `balances` differs".to_string()]);
}
#[test]
fn record_field_layout_mismatch_is_reported() {
let field = |name: &str, ty: Plaintext| RecordField { name: name.into(), ty, mode: Mode::Private };
let mut standard = program("std.aleo", vec![]);
standard
.records
.push(Record { path: vec!["Token".into()], fields: vec![field("owner", addr()), field("amount", u64t())] });
let mut candidate = program("token.aleo", vec![]);
candidate
.records
.push(Record { path: vec!["Token".into()], fields: vec![field("owner", addr()), field("amount", u32t())] });
assert_eq!(check_compatibility(&candidate, &standard), vec!["record `Token` differs".to_string()]);
}
#[test]
fn missing_record_is_reported() {
let field = RecordField { name: "owner".into(), ty: addr(), mode: Mode::Private };
let mut standard = program("std.aleo", vec![]);
standard.records.push(Record { path: vec!["Token".into()], fields: vec![field] });
let problems = check_compatibility(&program("token.aleo", vec![]), &standard);
assert_eq!(problems, vec!["missing record `Token`".to_string()]);
}
#[test]
fn identical_view_is_compatible() {
let mut standard = program("std.aleo", vec![]);
standard.views.push(total_supply());
let mut candidate = program("token.aleo", vec![]);
candidate.views.push(total_supply());
assert!(check_compatibility(&candidate, &standard).is_empty());
}
#[test]
fn missing_view_is_reported() {
let mut standard = program("std.aleo", vec![]);
standard.views.push(total_supply());
let problems = check_compatibility(&program("token.aleo", vec![]), &standard);
assert_eq!(problems, vec!["missing view `total_supply`".to_string()]);
}
#[test]
fn view_mismatch_is_reported() {
let mut standard = program("std.aleo", vec![]);
standard.views.push(total_supply());
let mut candidate = program("token.aleo", vec![]);
candidate.views.push(func("total_supply", vec![], vec![output(u32t(), Mode::Public)]));
assert_eq!(check_compatibility(&candidate, &standard), vec!["view `total_supply` differs".to_string()]);
}
#[test]
fn identical_storage_variable_is_compatible() {
let mut standard = program("std.aleo", vec![]);
standard.storage_variables.push(storage("total", u64t()));
let mut candidate = program("token.aleo", vec![]);
candidate.storage_variables.push(storage("total", u64t()));
assert!(check_compatibility(&candidate, &standard).is_empty());
}
#[test]
fn missing_storage_variable_is_reported() {
let mut standard = program("std.aleo", vec![]);
standard.storage_variables.push(storage("total", u64t()));
let problems = check_compatibility(&program("token.aleo", vec![]), &standard);
assert_eq!(problems, vec!["missing storage variable `total`".to_string()]);
}
#[test]
fn storage_variable_mismatch_is_reported() {
let mut standard = program("std.aleo", vec![]);
standard.storage_variables.push(storage("total", u64t()));
let mut candidate = program("token.aleo", vec![]);
candidate.storage_variables.push(storage("total", u32t()));
assert_eq!(check_compatibility(&candidate, &standard), vec!["storage variable `total` differs".to_string()]);
}
#[test]
fn self_record_reference_is_program_relative() {
let standard = program("std.aleo", vec![func("wrap", vec![record_input("Token", "std.aleo")], vec![])]);
let candidate = program("token.aleo", vec![func("wrap", vec![record_input("Token", "token.aleo")], vec![])]);
assert!(check_compatibility(&candidate, &standard).is_empty());
}
#[test]
fn self_struct_reference_is_program_relative() {
let standard =
program("std.aleo", vec![func("echo", vec![input(struct_ref("Point", "std.aleo"), Mode::Private)], vec![])]);
let candidate = program("token.aleo", vec![func(
"echo",
vec![input(struct_ref("Point", "token.aleo"), Mode::Private)],
vec![],
)]);
assert!(check_compatibility(&candidate, &standard).is_empty());
}
#[test]
fn unqualified_and_self_qualified_refs_match() {
let standard = program("std.aleo", vec![func(
"wrap",
vec![FunctionInput::Record(RecordRef { path: vec!["Token".into()], program: None })],
vec![],
)]);
let candidate = program("token.aleo", vec![func("wrap", vec![record_input("Token", "token.aleo")], vec![])]);
assert!(check_compatibility(&candidate, &standard).is_empty());
}
#[test]
fn external_record_reference_must_match_program() {
let standard = program("std.aleo", vec![func("wrap", vec![record_input("Coin", "credits.aleo")], vec![])]);
let mismatched = program("token.aleo", vec![func("wrap", vec![record_input("Coin", "other.aleo")], vec![])]);
assert_eq!(check_compatibility(&mismatched, &standard), vec!["function `wrap` differs".to_string()]);
let matched = program("token.aleo", vec![func("wrap", vec![record_input("Coin", "credits.aleo")], vec![])]);
assert!(check_compatibility(&matched, &standard).is_empty());
}
#[cfg(feature = "aleo-bytecode")]
mod bytecode {
use super::*;
use leo_span::create_session_if_not_set_then;
const STANDARD_SRC: &str = "\
program std_iface.aleo;
function transfer:
input r0 as address.private;
input r1 as u64.public;
output r0 as address.private;
";
const CANDIDATE_SRC: &str = "\
program token.aleo;
function transfer:
input r0 as address.private;
input r1 as u64.public;
output r0 as address.private;
function mint:
input r0 as address.private;
output r0 as address.private;
";
fn abi_of(name: &str, src: &str) -> Program {
let aleo = leo_disassembler::disassemble_from_str_for_network(name, src, leo_ast::NetworkName::TestnetV0)
.expect("expected valid Aleo bytecode");
crate::aleo::generate(&aleo)
}
#[test]
fn compat_superset_is_compatible() {
create_session_if_not_set_then(|_| {
let standard = abi_of("std_iface.aleo", STANDARD_SRC);
let candidate = abi_of("token.aleo", CANDIDATE_SRC);
let problems = check_compatibility(&candidate, &standard);
assert!(problems.is_empty(), "expected compatible: {problems:?}");
});
}
#[test]
fn compat_missing_function_is_incompatible() {
create_session_if_not_set_then(|_| {
let standard_src = "\
program std_iface.aleo;
function burn:
input r0 as u64.public;
output r0 as u64.public;
";
let standard = abi_of("std_iface.aleo", standard_src);
let candidate = abi_of("token.aleo", CANDIDATE_SRC);
let problems = check_compatibility(&candidate, &standard);
assert_eq!(problems, vec!["missing function `burn`".to_string()]);
});
}
}