5.1 Testing Matrix
The robustness of the hybrid architecture requires a multi-layered testing approach encompassing both the confidential Daml environment and the public execution environment.
Testing Layers
┌─────────────────────────────────────┐
│ 1. Unit Tests │
│ • Daml contracts │
│ • Smart contracts (Solidity) │
│ • Relayer logic │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 2. Integration Tests │
│ • Canton Devnet + Testnets │
│ • End-to-end flows │
│ • Cross-component │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 3. Adversarial Tests │
│ • Replay attacks │
│ • Signature forgery │
│ • Relayer equivocation │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ 4. Chaos Engineering │
│ • Network partitions │
│ • Node failures │
│ • Latency injection │
└─────────────────────────────────────┘1. Unit Tests (Daml)
Verify core governance logic using Daml Script:
daml
-- Test: Policy enforcement
testPolicyEnforcement : Script ()
testPolicyEnforcement = script do
controller <- allocateParty "Controller"
operator <- allocateParty "Operator"
-- Create policy with 50% max exposure
policyCid <- submit controller do
createCmd VaultPolicy with
controller
maxExposurePerc = 50.0
allowedProtocols = ["Aave", "Compound"]
-- Attempt rebalance exceeding limit (should fail)
submitMustFail operator do
exerciseCmd policyCid AutoRebalance with
allocations = [("Aave", 60.0)] -- Exceeds 50%!
-- Test: Multi-party approval quorum
testApprovalQuorum : Script ()
testApprovalQuorum = script do
proposer <- allocateParty "Proposer"
approver1 <- allocateParty "Approver1"
approver2 <- allocateParty "Approver2"
approver3 <- allocateParty "Approver3"
-- Create proposal requiring 2-of-3
proposalCid <- submit proposer do
createCmd RebalanceProposal with
proposer
approvers = [approver1, approver2, approver3]
quorumRequired = 2
proposedAllocations = [("Aave", 40.0), ("Compound", 60.0)]
-- First approval
proposalCid' <- submit approver1 do
exerciseCmd proposalCid ApproveProposal with approver = approver1
-- Try to execute (should fail - only 1 approval)
submitMustFail proposer do
exerciseCmd proposalCid' ExecuteProposal
-- Second approval
proposalCid'' <- submit approver2 do
exerciseCmd proposalCid' ApproveProposal with approver = approver2
-- Execute (should succeed - quorum met)
submit proposer do
exerciseCmd proposalCid'' ExecuteProposal2. Integration Tests
End-to-End Flow A (Automated)
typescript
describe('Flow A: Automated Rebalance', () => {
it('should execute compliant rebalance automatically', async () => {
// 1. Setup Canton devnet
const canton = await CantonDevnet.start();
const policy = await canton.createPolicy({
maxExposurePerc: 50,
allowedProtocols: ['Aave', 'Uniswap']
});
// 2. Deploy vault contract on testnet
const vault = await deployVault(ethereum.sepolia, {
cantonSigner: canton.policyIssuer.address
});
// 3. Start relayer
const relayer = new Relayer({
cantonApi: canton.apiUrl,
vaults: { ethereum: vault.address }
});
// 4. Submit signed oracle feed
await canton.submitOracleFeed({
prices: { 'USDC/ETH': 0.0003 },
signature: await oracle.sign(...)
});
// 5. Canton should auto-generate intent
const intent = await canton.waitForIntent(policy.id);
expect(intent).toBeDefined();
expect(intent.actions.length).toBeGreaterThan(0);
// 6. Relayer should process and submit
await relayer.processIntent(intent);
// 7. Verify execution on-chain
const tx = await vault.getLastExecution();
expect(tx.success).toBe(true);
expect(tx.intentNonce).toBe(intent.nonce);
});
});End-to-End Flow B (Approval Required)
typescript
describe('Flow B: Human-in-the-loop', () => {
it('should require quorum before execution', async () => {
const canton = await CantonDevnet.start();
// Create proposal
const proposal = await canton.createProposal({
allocations: [
{ protocol: 'Aave', percent: 60 },
{ protocol: 'Compound', percent: 40 }
],
quorum: 2
});
// Initially no intent
expect(await canton.queryIntent(proposal.id)).toBeNull();
// First approval
await canton.approveProposal(proposal.id, approver1);
// Still no intent (quorum not met)
expect(await canton.queryIntent(proposal.id)).toBeNull();
// Second approval
await canton.approveProposal(proposal.id, approver2);
// Now intent should be generated
const intent = await canton.waitForIntent(proposal.id);
expect(intent).toBeDefined();
});
});Flow C: Multi-Chain Coordination
typescript
describe('Flow C: Cross-Chain Atomic', () => {
it('should coordinate execution across chains', async () => {
const canton = await CantonDevnet.start();
// Create multi-chain intent group
const group = await canton.createIntentGroup({
intents: [
{ chain: 'ethereum', actions: [...] },
{ chain: 'polygon', actions: [...] }
],
groupId: 'group-001'
});
// Start relayer
const relayer = new Relayer({
cantonApi: canton.apiUrl,
chains: ['ethereum', 'polygon']
});
// Process group
const result = await relayer.processIntentGroup(group);
// Verify both succeeded
expect(result.ethereum.status).toBe(1);
expect(result.polygon.status).toBe(1);
});
it('should compensate on partial failure', async () => {
// Mock polygon failure
mockChain('polygon').revert();
const result = await relayer.processIntentGroup(group);
// Ethereum succeeded, Polygon failed
expect(result.ethereum.status).toBe(1);
expect(result.polygon.status).toBe(0);
// Verify compensation was issued
const compensation = await canton.queryCompensation(
result.ethereum.txHash
);
expect(compensation).toBeDefined();
expect(compensation.failedChain).toBe('polygon');
});
});3. Adversarial Tests
Replay Attack Prevention
typescript
describe('Security: Replay Attacks', () => {
it('should reject replayed intent', async () => {
// Execute valid intent
const intent1 = await canton.createIntent({ nonce: 1 });
await vault.executeIntent(intent1.payload, intent1.signature, '0x');
// Try to replay same intent
await expect(
vault.executeIntent(intent1.payload, intent1.signature, '0x')
).to.be.revertedWith('Invalid nonce');
});
it('should reject expired intent', async () => {
const intent = await canton.createIntent({
deadline: Date.now() - 1000 // Already expired
});
await expect(
vault.executeIntent(intent.payload, intent.signature, '0x')
).to.be.revertedWith('Intent expired');
});
});Signature Forgery
typescript
describe('Security: Signature Verification', () => {
it('should reject invalid Canton signature', async () => {
const intent = await canton.createIntent();
// Forge signature with wrong key
const fakeSignature = await attacker.sign(intent.payload);
await expect(
vault.executeIntent(intent.payload, fakeSignature, '0x')
).to.be.revertedWith('Invalid Canton signature');
});
it('should reject tampered payload', async () => {
const intent = await canton.createIntent();
// Tamper with payload
const tamperedPayload = intent.payload.replace('Aave', 'Evil');
await expect(
vault.executeIntent(tamperedPayload, intent.signature, '0x')
).to.be.revertedWith('Invalid Canton signature');
});
});4. Chaos Engineering
Network Partition Simulation
typescript
describe('Chaos: Network Failures', () => {
it('should recover from Canton disconnection', async () => {
const relayer = new Relayer(...);
// Start processing
const intentPromise = relayer.processNextIntent();
// Simulate network partition
await chaos.partition('relayer', 'canton', '5s');
// Should retry and eventually succeed
const result = await intentPromise;
expect(result.status).toBe('completed');
});
it('should handle chain RPC failure', async () => {
// Simulate Ethereum RPC outage
await chaos.kill('ethereum-rpc', '10s');
// Relayer should failover to backup RPC
const result = await relayer.submitTransaction(tx);
expect(result).toBeDefined();
});
});MPC Node Failure
typescript
describe('Chaos: MPC Failures', () => {
it('should continue with threshold available', async () => {
// Kill 1 of 5 MPC nodes (threshold = 3)
await chaos.kill('mpc-node-1');
// Should still succeed with 4 nodes
const result = await mpc.signTransaction(tx);
expect(result.signature).toBeDefined();
});
it('should fail gracefully below threshold', async () => {
// Kill 3 of 5 nodes (threshold = 3)
await chaos.kill(['mpc-node-1', 'mpc-node-2', 'mpc-node-3']);
// Should fail with clear error
await expect(
mpc.signTransaction(tx)
).rejects.toThrow('Threshold not met');
});
});Test Coverage Requirements
Component Coverage Target
─────────────────────────────────────────
Daml Contracts 95%
Solidity Contracts 90%
Relayer Logic 85%
MPC Integration 80%
End-to-End Flows 100% (critical paths)Continuous Testing
yaml
# .github/workflows/test.yml
name: Continuous Testing
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Daml tests
run: daml test
- name: Run Solidity tests
run: forge test
integration-tests:
runs-on: ubuntu-latest
steps:
- name: Start Canton devnet
run: docker-compose up -d canton
- name: Deploy to testnets
run: npm run deploy:test
- name: Run integration tests
run: npm run test:integration
security-tests:
runs-on: ubuntu-latest
steps:
- name: Run adversarial tests
run: npm run test:security
- name: Run Slither
run: slither .
- name: Run Mythril
run: mythril analyze contracts/
chaos-tests:
runs-on: ubuntu-latest
steps:
- name: Setup chaos environment
run: npm run chaos:setup
- name: Run chaos tests
run: npm run test:chaos