Skip to main content
Version: Next

ABI Validation

This guide explains how to set up ABI validation and testing to ensure your generated ABI is correct, conformant, and compatible with your target protocols.

Overview

ABI validation ensures that:

  • Generated ABI is valid: The ABI follows the correct schema and format
  • Type compatibility: Types are correctly mapped and compatible with target protocols
  • Method signatures: Method signatures match the expected interface
  • Event definitions: Events are properly defined and accessible
  • Protocol conformance: The ABI works correctly with specific blockchain protocols

Validation Types

1.1 Schema Validation

Validate that the generated ABI follows the correct schema:

use calimero_abi_emitter::{validate_abi, AbiManifest};

#[test]
fn test_abi_schema_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Validate schema version
assert_eq!(abi.schema_version, "1.0.0");

// Validate required fields
assert!(!abi.types.is_empty());
assert!(!abi.methods.is_empty());

// Validate type definitions
for (name, type_def) in &abi.types {
assert!(!name.is_empty());
assert!(!type_def.fields.is_empty());
}

// Validate method definitions
for method in &abi.methods {
assert!(!method.name.is_empty());
assert!(method.input.is_valid());
assert!(method.output.is_valid());
}
}

1.2 Type Validation

Validate that types are correctly defined and compatible:

use calimero_abi_emitter::{validate_abi, AbiManifest, TypeDefinition};

#[test]
fn test_abi_type_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Validate specific types
let app_state = abi.types.get("AppState").expect("AppState type not found");
assert_eq!(app_state.kind, TypeKind::Record);
assert_eq!(app_state.fields.len(), 2);

// Validate field types
let data_field = app_state.fields.iter()
.find(|f| f.name == "data")
.expect("data field not found");
assert_eq!(data_field.type_ref, TypeRef::Scalar(ScalarType::String));

let count_field = app_state.fields.iter()
.find(|f| f.name == "count")
.expect("count field not found");
assert_eq!(count_field.type_ref, TypeRef::Scalar(ScalarType::U32));
}

1.3 Method Validation

Validate that methods are correctly defined:

use calimero_abi_emitter::{validate_abi, AbiManifest, MethodDefinition};

#[test]
fn test_abi_method_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Validate specific methods
let process_method = abi.methods.iter()
.find(|m| m.name == "process_data")
.expect("process_data method not found");

assert_eq!(process_method.input, TypeRef::Scalar(ScalarType::String));
assert_eq!(process_method.output, TypeRef::Result(Box::new(TypeRef::Scalar(ScalarType::String))));
assert!(!process_method.is_async);

let get_state_method = abi.methods.iter()
.find(|m| m.name == "get_state")
.expect("get_state method not found");

assert_eq!(get_state_method.input, TypeRef::Unit);
assert_eq!(get_state_method.output, TypeRef::Reference("AppState".to_string()));
}

Protocol-Specific Validation

2.1 Ethereum Validation

Validate ABI compatibility with Ethereum:

use calimero_abi_emitter::{validate_abi, AbiManifest, Protocol};

#[test]
fn test_ethereum_abi_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Validate Ethereum-specific requirements
for method in &abi.methods {
// Ethereum methods should have valid gas estimates
assert!(method.gas_estimate.is_some());

// Input and output types should be Ethereum-compatible
assert!(is_ethereum_compatible_type(&method.input));
assert!(is_ethereum_compatible_type(&method.output));
}

// Validate events
for event in &abi.events {
// Ethereum events should have indexed fields
assert!(!event.indexed_fields.is_empty());
}
}

fn is_ethereum_compatible_type(type_ref: &TypeRef) -> bool {
match type_ref {
TypeRef::Scalar(scalar) => matches!(scalar, ScalarType::U8 | ScalarType::U32 | ScalarType::U64 | ScalarType::String | ScalarType::Bool),
TypeRef::Collection(collection) => match collection {
CollectionType::List(inner) => is_ethereum_compatible_type(inner),
CollectionType::Array(inner, _) => is_ethereum_compatible_type(inner),
CollectionType::Map(key, value) => is_ethereum_compatible_type(key) && is_ethereum_compatible_type(value),
},
TypeRef::Reference(_) => true,
_ => false,
}
}

2.2 NEAR Validation

Validate ABI compatibility with NEAR:

use calimero_abi_emitter::{validate_abi, AbiManifest, Protocol};

#[test]
fn test_near_abi_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Validate NEAR-specific requirements
for method in &abi.methods {
// NEAR methods should have valid gas estimates
assert!(method.gas_estimate.is_some());

// Input and output types should be NEAR-compatible
assert!(is_near_compatible_type(&method.input));
assert!(is_near_compatible_type(&method.output));
}

// Validate that all types are serializable
for (name, type_def) in &abi.types {
assert!(is_serializable_type(type_def));
}
}

fn is_near_compatible_type(type_ref: &TypeRef) -> bool {
match type_ref {
TypeRef::Scalar(scalar) => matches!(scalar, ScalarType::U8 | ScalarType::U32 | ScalarType::U64 | ScalarType::String | ScalarType::Bool),
TypeRef::Collection(collection) => match collection {
CollectionType::List(inner) => is_near_compatible_type(inner),
CollectionType::Array(inner, _) => is_near_compatible_type(inner),
CollectionType::Map(key, value) => is_near_compatible_type(key) && is_near_compatible_type(value),
},
TypeRef::Reference(_) => true,
_ => false,
}
}

fn is_serializable_type(type_def: &TypeDefinition) -> bool {
// Check if the type can be serialized to JSON
// This is a simplified check - in practice, you'd use serde
true
}

2.3 Multi-Protocol Validation

Validate ABI compatibility across multiple protocols:

use calimero_abi_emitter::{validate_abi, AbiManifest, Protocol};

#[test]
fn test_multi_protocol_abi_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

let protocols = vec![Protocol::Ethereum, Protocol::NEAR, Protocol::ICP, Protocol::Starknet];

for protocol in protocols {
match protocol {
Protocol::Ethereum => validate_ethereum_compatibility(&abi),
Protocol::NEAR => validate_near_compatibility(&abi),
Protocol::ICP => validate_icp_compatibility(&abi),
Protocol::Starknet => validate_starknet_compatibility(&abi),
}
}
}

fn validate_ethereum_compatibility(abi: &AbiManifest) {
// Ethereum-specific validation
}

fn validate_near_compatibility(abi: &AbiManifest) {
// NEAR-specific validation
}

fn validate_icp_compatibility(abi: &AbiManifest) {
// ICP-specific validation
}

fn validate_starknet_compatibility(abi: &AbiManifest) {
// Starknet-specific validation
}

Automated Validation

3.1 CI/CD Integration

Set up automated ABI validation in your CI/CD pipeline:

# .github/workflows/abi-validation.yml
name: ABI Validation

on: [push, pull_request]

jobs:
validate-abi:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
target: wasm32-unknown-unknown

- name: Install ABI tools
run: |
cargo install calimero-abi

- name: Build application
run: |
cargo build --target wasm32-unknown-unknown --release

- name: Extract ABI
run: |
calimero-abi extract target/wasm32-unknown-unknown/release/my_app.wasm > abi.json

- name: Validate ABI schema
run: |
calimero-abi validate-schema abi.json

- name: Validate ABI types
run: |
calimero-abi validate-types abi.json

- name: Validate ABI methods
run: |
calimero-abi validate-methods abi.json

- name: Validate protocol compatibility
run: |
calimero-abi validate-protocol abi.json --protocol ethereum
calimero-abi validate-protocol abi.json --protocol near
calimero-abi validate-protocol abi.json --protocol icp
calimero-abi validate-protocol abi.json --protocol stellar

3.2 Pre-commit Hooks

Set up pre-commit hooks for ABI validation:

# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: abi-validation
name: ABI Validation
entry:
bash -c 'cargo build --target wasm32-unknown-unknown --release &&
calimero-abi validate
target/wasm32-unknown-unknown/release/my_app.wasm'
language: system
files: \.(rs|toml)$
pass_filenames: false

3.3 Makefile Integration

Add ABI validation to your Makefile:

# Makefile
.PHONY: build validate-abi test-abi

build:
cargo build --target wasm32-unknown-unknown --release

validate-abi: build
calimero-abi extract target/wasm32-unknown-unknown/release/my_app.wasm > abi.json
calimero-abi validate abi.json
calimero-abi validate-protocol abi.json --protocol ethereum
calimero-abi validate-protocol abi.json --protocol near

test-abi: validate-abi
cargo test abi_validation
cargo test abi_conformance

Validation Tools

4.1 Command Line Tools

Use the calimero-abi command line tool for validation:

# Extract ABI from WASM binary
calimero-abi extract target/wasm32-unknown-unknown/release/my_app.wasm

# Validate ABI schema
calimero-abi validate-schema abi.json

# Validate ABI types
calimero-abi validate-types abi.json

# Validate ABI methods
calimero-abi validate-methods abi.json

# Validate protocol compatibility
calimero-abi validate-protocol abi.json --protocol ethereum

# Validate all aspects
calimero-abi validate abi.json

4.2 Programmatic Validation

Use the ABI validation library in your code:

use calimero_abi_emitter::{
validate_abi,
AbiManifest,
ValidationError,
ValidationResult
};

fn validate_my_abi() -> Result<(), ValidationError> {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json)?;

// Validate schema
validate_abi_schema(&abi)?;

// Validate types
validate_abi_types(&abi)?;

// Validate methods
validate_abi_methods(&abi)?;

// Validate protocol compatibility
validate_protocol_compatibility(&abi, Protocol::Ethereum)?;

Ok(())
}

fn validate_abi_schema(abi: &AbiManifest) -> Result<(), ValidationError> {
// Schema validation logic
Ok(())
}

fn validate_abi_types(abi: &AbiManifest) -> Result<(), ValidationError> {
// Type validation logic
Ok(())
}

fn validate_abi_methods(abi: &AbiManifest) -> Result<(), ValidationError> {
// Method validation logic
Ok(())
}

fn validate_protocol_compatibility(abi: &AbiManifest, protocol: Protocol) -> Result<(), ValidationError> {
// Protocol compatibility validation logic
Ok(())
}

Testing Strategies

5.1 Unit Testing

Write unit tests for ABI validation:

#[cfg(test)]
mod tests {
use super::*;
use calimero_abi_emitter::{validate_abi, AbiManifest};

#[test]
fn test_abi_schema_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Test schema validation
assert!(validate_abi_schema(&abi).is_ok());
}

#[test]
fn test_abi_type_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Test type validation
assert!(validate_abi_types(&abi).is_ok());
}

#[test]
fn test_abi_method_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Test method validation
assert!(validate_abi_methods(&abi).is_ok());
}

#[test]
fn test_protocol_compatibility() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Test protocol compatibility
assert!(validate_protocol_compatibility(&abi, Protocol::Ethereum).is_ok());
assert!(validate_protocol_compatibility(&abi, Protocol::NEAR).is_ok());
assert!(validate_protocol_compatibility(&abi, Protocol::ICP).is_ok());
assert!(validate_protocol_compatibility(&abi, Protocol::Starknet).is_ok());
}
}

5.2 Integration Testing

Write integration tests for ABI validation:

#[cfg(test)]
mod integration_tests {
use super::*;
use calimero_abi_emitter::{validate_abi, AbiManifest};

#[test]
fn test_full_abi_validation() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Test full ABI validation
assert!(validate_abi(&abi).is_ok());
}

#[test]
fn test_abi_roundtrip() {
let abi_json = include_str!("../target/abi_conformance.abi.json");
let abi: AbiManifest = serde_json::from_str(abi_json).unwrap();

// Test roundtrip serialization
let serialized = serde_json::to_string(&abi).unwrap();
let deserialized: AbiManifest = serde_json::from_str(&serialized).unwrap();

assert_eq!(abi, deserialized);
}
}

Error Handling

6.1 Validation Errors

Handle validation errors gracefully:

use calimero_abi_emitter::{ValidationError, ValidationResult};

fn validate_abi_with_error_handling(abi_json: &str) -> ValidationResult<()> {
let abi: AbiManifest = serde_json::from_str(abi_json)
.map_err(|e| ValidationError::InvalidJson(e))?;

validate_abi_schema(&abi)
.map_err(|e| ValidationError::SchemaValidationFailed(e))?;

validate_abi_types(&abi)
.map_err(|e| ValidationError::TypeValidationFailed(e))?;

validate_abi_methods(&abi)
.map_err(|e| ValidationError::MethodValidationFailed(e))?;

Ok(())
}

#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
#[error("Invalid JSON: {0}")]
InvalidJson(serde_json::Error),

#[error("Schema validation failed: {0}")]
SchemaValidationFailed(String),

#[error("Type validation failed: {0}")]
TypeValidationFailed(String),

#[error("Method validation failed: {0}")]
MethodValidationFailed(String),
}

6.2 Error Reporting

Provide detailed error reporting:

use calimero_abi_emitter::{ValidationError, ValidationResult};

fn validate_abi_with_detailed_errors(abi_json: &str) -> ValidationResult<()> {
let mut errors = Vec::new();

// Validate schema
if let Err(e) = validate_abi_schema(&abi) {
errors.push(format!("Schema validation failed: {}", e));
}

// Validate types
if let Err(e) = validate_abi_types(&abi) {
errors.push(format!("Type validation failed: {}", e));
}

// Validate methods
if let Err(e) = validate_abi_methods(&abi) {
errors.push(format!("Method validation failed: {}", e));
}

if !errors.is_empty() {
return Err(ValidationError::MultipleErrors(errors));
}

Ok(())
}

Performance Considerations

7.1 Validation Performance

Optimize validation performance:

use calimero_abi_emitter::{validate_abi, AbiManifest};
use std::time::Instant;

fn validate_abi_with_timing(abi_json: &str) -> Result<(), ValidationError> {
let start = Instant::now();

let abi: AbiManifest = serde_json::from_str(abi_json)?;

let parse_time = start.elapsed();
println!("ABI parsing took: {:?}", parse_time);

let validation_start = Instant::now();
validate_abi(&abi)?;
let validation_time = validation_start.elapsed();

println!("ABI validation took: {:?}", validation_time);
println!("Total time: {:?}", start.elapsed());

Ok(())
}

7.2 Caching

Cache validation results for better performance:

use std::collections::HashMap;
use calimero_abi_emitter::{validate_abi, AbiManifest};

struct ValidationCache {
cache: HashMap<String, bool>,
}

impl ValidationCache {
fn new() -> Self {
Self {
cache: HashMap::new(),
}
}

fn validate_cached(&mut self, abi_json: &str) -> Result<bool, ValidationError> {
let hash = sha256::digest(abi_json);

if let Some(&is_valid) = self.cache.get(&hash) {
return Ok(is_valid);
}

let abi: AbiManifest = serde_json::from_str(abi_json)?;
let is_valid = validate_abi(&abi).is_ok();

self.cache.insert(hash, is_valid);
Ok(is_valid)
}
}

Next Steps

Now that you understand ABI validation:

Was this page helpful?
Need some help? Check Support page