use leo_abi_types as abi;
pub fn check_compatibility(candidate: &abi::Program, standard: &abi::Program) -> Vec<String> {
let mut problems = Vec::new();
let (ch, sh) = (candidate.program.as_str(), standard.program.as_str());
for s in &standard.functions {
match candidate.functions.iter().find(|c| c.name == s.name) {
None => problems.push(format!("missing function `{}`", s.name)),
Some(c) if function_compatible(c, ch, s, sh) => {}
Some(_) => problems.push(format!("function `{}` differs", s.name)),
}
}
for s in &standard.views {
match candidate.views.iter().find(|c| c.name == s.name) {
None => problems.push(format!("missing view `{}`", s.name)),
Some(c) if function_compatible(c, ch, s, sh) => {}
Some(_) => problems.push(format!("view `{}` differs", s.name)),
}
}
for s in &standard.mappings {
match candidate.mappings.iter().find(|c| c.name == s.name) {
None => problems.push(format!("missing mapping `{}`", s.name)),
Some(c) if mapping_compatible(c, ch, s, sh) => {}
Some(_) => problems.push(format!("mapping `{}` differs", s.name)),
}
}
for s in &standard.storage_variables {
match candidate.storage_variables.iter().find(|c| c.name == s.name) {
None => problems.push(format!("missing storage variable `{}`", s.name)),
Some(c) if storage_type_compatible(&c.ty, ch, &s.ty, sh) => {}
Some(_) => problems.push(format!("storage variable `{}` differs", s.name)),
}
}
for s in &standard.records {
match candidate.records.iter().find(|c| c.path == s.path) {
None => problems.push(format!("missing record `{}`", s.path.join("::"))),
Some(c) if record_compatible(c, ch, s, sh) => {}
Some(_) => problems.push(format!("record `{}` differs", s.path.join("::"))),
}
}
for s in &standard.structs {
match candidate.structs.iter().find(|c| c.path == s.path) {
None => problems.push(format!("missing struct `{}`", s.path.join("::"))),
Some(c) if struct_compatible(c, ch, s, sh) => {}
Some(_) => problems.push(format!("struct `{}` differs", s.path.join("::"))),
}
}
problems
}
fn ref_owner<'a>(program: &'a Option<String>, holder: &str) -> Option<&'a str> {
match program.as_deref() {
Some(p) if p != holder => Some(p),
_ => None,
}
}
fn function_compatible(c: &abi::Function, ch: &str, s: &abi::Function, sh: &str) -> bool {
c.inputs.len() == s.inputs.len()
&& c.outputs.len() == s.outputs.len()
&& c.inputs.iter().zip(&s.inputs).all(|(a, b)| input_compatible(a, ch, b, sh))
&& c.outputs.iter().zip(&s.outputs).all(|(a, b)| output_compatible(a, ch, b, sh))
}
fn input_compatible(c: &abi::FunctionInput, ch: &str, s: &abi::FunctionInput, sh: &str) -> bool {
use abi::FunctionInput::{DynamicRecord, Plaintext, Record};
match (c, s) {
(Plaintext { ty: a, mode: ma }, Plaintext { ty: b, mode: mb }) => {
ma == mb && plaintext_compatible(a, ch, b, sh)
}
(Record(a), Record(b)) => a.path == b.path && ref_owner(&a.program, ch) == ref_owner(&b.program, sh),
(DynamicRecord, DynamicRecord) => true,
_ => false,
}
}
fn output_compatible(c: &abi::FunctionOutput, ch: &str, s: &abi::FunctionOutput, sh: &str) -> bool {
use abi::FunctionOutput::{DynamicRecord, Final, Plaintext, Record};
match (c, s) {
(Plaintext { ty: a, mode: ma }, Plaintext { ty: b, mode: mb }) => {
ma == mb && plaintext_compatible(a, ch, b, sh)
}
(Record(a), Record(b)) => a.path == b.path && ref_owner(&a.program, ch) == ref_owner(&b.program, sh),
(Final, Final) | (DynamicRecord, DynamicRecord) => true,
_ => false,
}
}
fn mapping_compatible(c: &abi::Mapping, ch: &str, s: &abi::Mapping, sh: &str) -> bool {
plaintext_compatible(&c.key, ch, &s.key, sh) && plaintext_compatible(&c.value, ch, &s.value, sh)
}
fn record_compatible(c: &abi::Record, ch: &str, s: &abi::Record, sh: &str) -> bool {
c.fields.len() == s.fields.len()
&& c.fields
.iter()
.zip(&s.fields)
.all(|(a, b)| a.name == b.name && a.mode == b.mode && plaintext_compatible(&a.ty, ch, &b.ty, sh))
}
fn struct_compatible(c: &abi::Struct, ch: &str, s: &abi::Struct, sh: &str) -> bool {
c.fields.len() == s.fields.len()
&& c.fields.iter().zip(&s.fields).all(|(a, b)| a.name == b.name && plaintext_compatible(&a.ty, ch, &b.ty, sh))
}
fn storage_type_compatible(c: &abi::StorageType, ch: &str, s: &abi::StorageType, sh: &str) -> bool {
use abi::StorageType::{Plaintext, Vector};
match (c, s) {
(Plaintext(a), Plaintext(b)) => plaintext_compatible(a, ch, b, sh),
(Vector(a), Vector(b)) => storage_type_compatible(a, ch, b, sh),
_ => false,
}
}
fn plaintext_compatible(c: &abi::Plaintext, ch: &str, s: &abi::Plaintext, sh: &str) -> bool {
use abi::Plaintext::{Array, Optional, Primitive, Struct};
match (c, s) {
(Primitive(a), Primitive(b)) => a == b,
(Array(a), Array(b)) => a.length == b.length && plaintext_compatible(&a.element, ch, &b.element, sh),
(Optional(a), Optional(b)) => plaintext_compatible(&a.0, ch, &b.0, sh),
(Struct(a), Struct(b)) => a.path == b.path && ref_owner(&a.program, ch) == ref_owner(&b.program, sh),
_ => false,
}
}