Skip to content

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'' ExecuteProposal

2. 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

Canton DeFi - Multichain DeFi Technical Reference