Skip to content
Solutions

Solutions for systems that move real value

Each kind of system fails in its own way. We start every project by writing that failure down, then build and test against it.

Fig. 01 — One spec per kind of system

VaultInvariants.t.solFoundry
01

DeFi protocols

Fails when

  • A price read from a pool an attacker can move
  • Shares minted before the assets arrive
  • Rounding that favours the caller

Must always hold

// Shares are never worth more than the assets behind them.
function invariant_solvency() public {
  assertGe(asset.balanceOf(address(vault)),
           vault.convertToAssets(vault.totalSupply()));
}

Illustrative tests, one per kind of system.

How every solution starts

From a sentence to a passing test

Whatever the system, the work starts the same way. Here is one rule, followed all the way through.

01

Write the failure down

Before any code, in plain words, in the specification you sign off.

“Shares can never be worth more than the assets behind them.”SPEC.md · I-1

02

Turn it into a test

The sentence becomes an invariant the fuzzer attacks with random call sequences.

function invariant_solvency() {
  assertGe(assets, owed());
}
03

Build until it holds

Every pull request runs it. A finding reaches you as a failing test, then a fix.

$ forge test
[PASS] invariant_solvency()
ok · runs on every PR

Illustrative example from a vault engagement.

Use cases

The problem, what we build, what we test

Five kinds of system we build most often, and the one failure each is designed around.

DeFi protocols

Money moves in one transaction, and any path you did not think of is someone else's profit.

Architecture
  1. User
  2. Router
  3. Pool
  4. Vaulttested hardest
  5. Oracle
What we build

AMMs, vaults, staking, lending primitives and routers.

  • AMMs
  • ERC-4626 vaults
  • Staking
  • Lending primitives
  • Routers
What we test hardest

Solvency and share-price invariants under long random call sequences.

Related service
VaultInvariants.t.solFoundry · illustrative test
// Shares are never worth more than the assets behind them.
function invariant_solvency() public {
  assertGe(asset.balanceOf(address(vault)),
           vault.convertToAssets(vault.totalSupply()));
}

Tokenized assets

The token must obey rules the law already set: who may hold it, and who may move it.

Architecture
  1. Investor
  2. Claims
  3. Compliancetested hardest
  4. Token
  5. Payouts
What we build

Permissioned tokens, claims registries, issuer consoles and distributions.

  • ERC-3643 tokens
  • Claims registries
  • Issuer consoles
  • Distributions
What we test hardest

Transfer rules, role separation and distribution rounding.

Related service
Compliance.t.solFoundry · illustrative test
// Only verified investors can ever hold the token.
function invariant_holdersVerified() public {
  for (uint256 i; i < holders.length; i++)
    assertTrue(registry.isVerified(holders[i]));
}

Custody & wallets

Keys are the product. One compromised device should never be enough.

Architecture
  1. Client
  2. API
  3. Policytested hardest
  4. Signer
  5. KMS
What we build

Threshold signing, policy engines, web and mobile clients.

  • Threshold signing
  • Policy engines
  • Web & mobile clients
  • Audit logs
What we test hardest

Approval bypass, replay and key rotation.

Related service
policy.test.tsVitest · illustrative test
// No path signs with fewer than two approvals.
test("cannot sign alone", async () => {
  const req = await policy.request(transfer);
  await approve(req, alice);
  await expect(signer.sign(req)).rejects.toThrow("2 of 3");
});

Tokens & governance

Supply, voting power and treasury access are where incentives turn into attacks.

Architecture
  1. Holder
  2. Token
  3. Governortested hardest
  4. Timelock
  5. Treasury
What we build

ERC-20 systems, vesting, governors, timelocks and treasuries.

  • ERC-20 systems
  • Vesting
  • Governors
  • Timelocks
  • Treasuries
What we test hardest

Flash-loan voting, vesting edge cases and privileged roles.

Related service
Governance.t.solFoundry · illustrative test
// Votes count at the snapshot, so a flash loan adds nothing.
function invariant_noFlashVotes() public {
  uint256 snap = gov.proposalSnapshot(proposalId);
  assertEq(gov.getVotes(attacker, snap), votesAt[snap]);
}

AI on real systems

An agent that can write to your systems needs the same care as a contract.

Architecture
  1. Question
  2. Retriever
  3. Agent
  4. Approvaltested hardest
  5. ERP
What we build

Retrieval, scoped tool-calling agents and approval queues.

  • Retrieval (RAG)
  • Scoped tool-calling agents
  • Approval queues
  • Evaluation sets
What we test hardest

Unapproved actions, prompt injection and quality regressions.

Related service
test_agent.pypytest · illustrative test
# No write reaches the ERP without a human approval.
def test_write_needs_approval(agent, erp, queue):
    agent.run("mark order 4411 as shipped")
    assert erp.writes == []
    assert queue.pending[0].action == "update_order"

Not sure which of these you need?

Describe the product and the risk you are worried about. We will tell you what we would build, and what we would not.