Integration Testing
Integration testing verifies that your smart contract correctly interfaces with the World ID Bridge and processes proofs as expected. This guide provides practical testing patterns and implementation examples.
This is part of the Verification Strategy for World ID Bridge integration.
Basic Integration Test
Start with this foundational test to verify your contract can successfully communicate with the World ID Bridge:
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[starknet::interface]
pub trait IGroth16VerifierBN254<TContractState> {
fn verify_groth16_proof_bn254(
self: @TContractState,
full_proof_with_hints: Span<felt252>
) -> Option<Span<u256>>;
}
#[starknet::interface]
pub trait IVerificationTest<TContractState> {
fn test_bridge_connection(self: @TContractState) -> bool;
fn verify_proof_and_count(ref self: TContractState, full_proof_with_hints: Span<felt252>) -> bool;
fn get_verification_count(self: @TContractState) -> u32;
}
#[starknet::contract]
pub mod VerificationTest {
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use super::{IGroth16VerifierBN254Dispatcher, IGroth16VerifierBN254DispatcherTrait, IVerificationTest};
#[storage]
pub struct Storage {
world_id_bridge: ContractAddress, // Bridge contract address
verification_count: u32, // Track successful verifications
bridge_accessible: bool, // Connection status flag
}
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
BridgeConnectionTested: BridgeConnectionTested,
ProofVerified: ProofVerified,
}
#[derive(Drop, starknet::Event)]
pub struct BridgeConnectionTested {
pub bridge_address: ContractAddress,
pub accessible: bool,
}
#[derive(Drop, starknet::Event)]
pub struct ProofVerified {
pub verification_count: u32,
pub nullifier_hash: u256,
}
#[constructor]
fn constructor(ref self: ContractState, world_id_bridge_address: ContractAddress) {
// Store the World ID Bridge contract address
self.world_id_bridge.write(world_id_bridge_address);
self.verification_count.write(0);
self.bridge_accessible.write(false);
}
#[abi(embed_v0)]
impl VerificationTestImpl of IVerificationTest<ContractState> {
/// Test basic connectivity to the World ID Bridge
/// Returns true if bridge is accessible and responding
fn test_bridge_connection(self: @ContractState) -> bool {
let bridge_address = self.world_id_bridge.read();
// Create dispatcher to test bridge accessibility
let world_id = IGroth16VerifierBN254Dispatcher {
contract_address: bridge_address
};
// Test with malformed proof to verify bridge responds (will fail verification but confirms connectivity)
let test_proof: Array<felt252> = array![999, 888, 777];
let result = world_id.verify_groth16_proof_bn254(test_proof.span());
// Bridge is accessible if it returns None (expected for invalid proof)
let accessible = result.is_none();
// Emit connection test event
self.emit(Event::BridgeConnectionTested(
BridgeConnectionTested {
bridge_address,
accessible
}
));
accessible
}
/// Verify a World ID proof and increment counter on success
/// Demonstrates full integration workflow
fn verify_proof_and_count(
ref self: ContractState,
full_proof_with_hints: Span<felt252>
) -> bool {
// Create bridge dispatcher
let world_id = IGroth16VerifierBN254Dispatcher {
contract_address: self.world_id_bridge.read()
};
// Attempt proof verification
match world_id.verify_groth16_proof_bn254(full_proof_with_hints) {
Option::Some(public_inputs) => {
// Extract public inputs for validation
let _root = *public_inputs.at(0); // Merkle tree root
let nullifier_hash = *public_inputs.at(1); // Unique nullifier
let _signal_hash = *public_inputs.at(2); // Application signal
let _external_nullifier_hash = *public_inputs.at(3); // Action identifier
// Increment successful verification counter
let new_count = self.verification_count.read() + 1;
self.verification_count.write(new_count);
// Emit success event with verification details
self.emit(Event::ProofVerified(
ProofVerified {
verification_count: new_count,
nullifier_hash
}
));
true
},
Option::None => {
// Proof verification failed
false
}
}
}
/// Get total number of successful verifications
/// Useful for testing and monitoring
fn get_verification_count(self: @ContractState) -> u32 {
self.verification_count.read()
}
}
}Starknet Foundry Test Suite
Create comprehensive tests using Starknet Foundry to validate your integration:
use verification_test::VerificationTest;
use verification_test::{IVerificationTestDispatcher, IVerificationTestDispatcherTrait};
use snforge_std::{declare, DeclareResultTrait, ContractClassTrait};
use snforge_std::{EventSpyAssertionsTrait, spy_events};
use starknet::ContractAddress;
// Test configuration constants
const WORLD_ID_BRIDGE_TESTNET: felt252 = 0x01167d6979330fcc6633111d72416322eb0e3b78ad147a9338abea3c04edfc8a;
/// Deploy test contract with World ID Bridge address
fn deploy_test_contract() -> IVerificationTestDispatcher {
let contract = declare("VerificationTest");
let bridge_address: ContractAddress = WORLD_ID_BRIDGE_TESTNET.try_into().unwrap();
let mut constructor_args = array![];
Serde::serialize(@bridge_address, ref constructor_args);
let (contract_address, _) = contract
.unwrap()
.contract_class()
.deploy(@constructor_args)
.unwrap();
IVerificationTestDispatcher { contract_address }
}
#[test]
fn test_bridge_connection() {
let dispatcher = deploy_test_contract();
let mut spy = spy_events();
// Test bridge connectivity
let connection_result = dispatcher.test_bridge_connection();
// Bridge should be accessible (returns true even for failed verification)
assert(connection_result == true, 'Bridge connection failed');
// Verify connection test event was emitted
let expected_event = VerificationTest::Event::BridgeConnectionTested(
VerificationTest::BridgeConnectionTested {
bridge_address: WORLD_ID_BRIDGE_TESTNET.try_into().unwrap(),
accessible: true
}
);
let expected_events = array![(dispatcher.contract_address, expected_event)];
spy.assert_emitted(@expected_events);
}
#[test]
fn test_invalid_proof_handling() {
let dispatcher = deploy_test_contract();
// Test with invalid proof data
let invalid_proof: Array<felt252> = array![123, 456, 789];
let result = dispatcher.verify_proof_and_count(invalid_proof.span());
// Should return false for invalid proof
assert(result == false, 'Invalid proof should fail');
// Verification count should remain zero
let count = dispatcher.get_verification_count();
assert(count == 0, 'Count should be zero');
}
#[test]
fn test_verification_counter() {
let dispatcher = deploy_test_contract();
// Initial count should be zero
let initial_count = dispatcher.get_verification_count();
assert(initial_count == 0, 'Initial count should be zero');
// Multiple invalid proof attempts should not increment counter
let invalid_proof: Array<felt252> = array![1, 2, 3];
dispatcher.verify_proof_and_count(invalid_proof.span());
dispatcher.verify_proof_and_count(invalid_proof.span());
let count_after_failures = dispatcher.get_verification_count();
assert(count_after_failures == 0, 'Count should remain zero');
}
#[test]
fn test_empty_proof_handling() {
let dispatcher = deploy_test_contract();
// Test with completely empty proof
let empty_proof: Array<felt252> = array![];
let result = dispatcher.verify_proof_and_count(empty_proof.span());
// Should gracefully handle empty proof
assert(result == false, 'Empty proof should fail');
}Validation Framework
Your integration testing should cover four critical areas to ensure production readiness. Bridge connectivity testing verifies that your contract can establish a connection to the World ID Bridge, properly store the bridge address, and receive responses from verification requests regardless of proof validity. This establishes the foundational communication layer.
Proof processing validation ensures your contract correctly invokes the verify_groth16_proof_bn254 function, handles the expected Option::None responses for invalid proofs, properly extracts public inputs when verification succeeds, and gracefully manages malformed or empty proof data without causing contract failures.
State management testing confirms that successful verifications update contract state as designed, failed verifications leave state unchanged, storage operations function correctly, and events emit with accurate verification data. This ensures your contract maintains data integrity across all scenarios.
Error handling verification demonstrates that your contract handles invalid proof data without panicking, manages bridge connection failures appropriately, respects gas limits during verification operations, and provides meaningful debugging information for troubleshooting production issues.
For more advanced verification patterns, see Nullifier Tracking and Error Handling.
Troubleshooting
-
Bridge Connection Issues: Verify the bridge contract address matches the testnet deployment:
0x01167d6979330fcc6633111d72416322eb0e3b78ad147a9338abea3c04edfc8a -
Test Failures: Check proof format and ensure you're using properly formatted Garaga proofs with hints.
-
Event Assertion Errors: Ensure event structures match exactly between contract and test code.