Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Security

321 tests. 4 audit passes. 0 open findings. Slither clean.

Test coverage

SuiteTestsMethod
InkdRegistry77Unit + edge cases
InkdRegistryV252Unit + upgrade path
InkdTreasury29Unit + split math
InkdBuyback23Unit + slippage tests
InkdToken19Unit
InkdTimelock10Unit
Fuzz suite29Fuzz — 256 runs per function
Invariant suite10Invariant — 256 sequences
Other72Unit
Total: 321 tests, 0 failures.

Fuzz tests randomize inputs across 256 iterations per function, targeting arithmetic overflow, access control bypass, and fee calculation edge cases. Invariant tests verify protocol-level properties (token supply, project count monotonicity, name uniqueness, owner existence) hold across 256 random operation sequences.


Static analysis

All contracts run Slither on every commit via CI.

SeverityOpen findings
High0
Medium0
Low0 (false positives suppressed with inline comments)

Audit passes

PassScopeFindingsStatus
Pass 1 — Initial reviewAll contracts3 Medium, 5 Low✅ Fixed
Pass 2 — Slither deep scanAll contracts2 Medium, 6 Low✅ Fixed
Pass 3 — Storage & slippageBuyback, RegistryV21 Medium✅ Fixed
Pass 4 — Professional criteriaAll contracts1 Medium, 3 Low✅ Fixed

Key findings (all resolved)

Buyback deposit() — permissionless event spoofing

Anyone could call deposit(uint256.max) and emit a misleading Deposited event, creating false accounting signals for off-chain monitors.

Fix: require(msg.sender == treasury)

Slippage protection missing on Uniswap swaps

amountOutMinimum: 0 — susceptible to sandwich attacks draining the buyback balance with minimal $INKD output.

Fix: amountOutMinimum = usdcIn × (10000 − maxSlippageBps) / 10000. Default 1%, configurable up to 10%.

Registry reentrancy in pushVersion / transferProject

State updates occurred after external Treasury calls, violating the Checks-Effects-Interactions pattern.

Fix: All state mutations moved before external calls.

Empty arweaveHash / versionTag accepted

pushVersion accepted empty strings, storing permanently invalid versions on-chain.

Fix: revert EmptyArweaveHash() / revert EmptyVersionTag()

Unbounded collaborators — O(n) gas DoS

No collaborator limit. removeCollaborator is O(n), enabling gas exhaustion at scale.

Fix: Hard cap of 50 collaborators. revert CollaboratorLimitReached()

Storage gap missing in RegistryV2

UUPS upgrade without a storage gap between V1 and V2 state variables would cause slot collisions.

Fix: uint256[42] __gap added after V2 state variables.


Security architecture

  • UUPS upgradeable proxies — logic can be upgraded, but only through the respective owner Safe multisig (2-of-2 on Base Mainnet).
  • Three separate Safes — Registry, Treasury, and Buyback are controlled independently. No single key controls the protocol.
  • EIP-3009 payments — gasless USDC transfers with unique nonces and expiry windows. Replay-safe by design.
  • CEI pattern enforced — all state mutations before external calls across all write functions.
  • Arweave immutability — once a version is pushed, the content hash is permanent on-chain and the file is permanent on Arweave.

Limitations

  • Internal audits only. No third-party security firm has reviewed the contracts. External audit is planned before significant TVL.
  • Arweave content is public. All uploaded files are permanently accessible. Encrypt sensitive data with AgentVault before uploading.
  • Upgradeability is a trust assumption. The protocol team can upgrade contract logic via multisig. This is intentional for bug fixes but requires trusting the multisig signers.

Responsible disclosure

Email security@inkdprotocol.com with details. We respond within 48 hours and coordinate disclosure after a fix is deployed.

Do not open public GitHub issues for security vulnerabilities.