-
Notifications
You must be signed in to change notification settings - Fork 3
Whitelist cap table creators via Merkle allowlist (KYB-ready) #234
Copy link
Copy link
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Summary
Any wallet can call CapTableFactory.createCapTable(), which lets it deploy a cap table. We want to restrict creation to a vetted set of wallets (start with 0x3601a913fD3466f30f5ABb978E484d1B37Ce995D) and leave room to enforce KYB in the future.
Current State (verified)
- Factory is Ownable and deploys BeaconProxy instances; CapTable.initialize sets admin = msg.sender. Owner can upgrade the beacon implementation (onlyOwner). There is no access control on createCapTable.
- Tests assert permissionless creation and that the caller becomes ADMIN of their own cap table (e.g., testNonOwnerCanCreateCapTable, testNonOwnerIsCapTableAdmin, testNonOwnerCannotManageOthersCapTable).
ABI Impact
- The change is ABI-affecting (adds a proof parameter to createCapTable) and will require a factory re-deploy and env updates.
Proposal (Phase 1: Merkle allowlist, owner-managed, KYB‑ready)
- Gate createCapTable behind Merkle proof verification against a stored root.
- Keep an owner bypass (break-glass).
- Emit events for observability.
- Use domain-separated leaves to prevent cross-contract/chain proof reuse.
Contract Changes (CapTableFactory.sol)
- Imports: OpenZeppelin MerkleProof (v5).
- Storage:
- bytes32 public creatorAllowlistRoot; bool public whitelistEnabled = true;
Events: - event CreatorAllowlistRootUpdated(bytes32 indexed root, address indexed by);
- event WhitelistEnabledSet(bool enabled, address indexed by);
Admin (onlyOwner): - setCreatorAllowlistRoot(bytes32 root)
- setWhitelistEnabled(bool enabled)
View: - currentAllowlistRoot() -> bytes32
Gate (createCapTable): - Append proof param
bytes16 id,
string memory name,
uint256 initial_shares_authorized,
address operator,
bytes32[] calldata proof
) external returns (address)
- If whitelistEnabled: allow owner OR require valid proof:
if (msg.sender != owner()) {
bool ok = MerkleProof.verify(proof, creatorAllowlistRoot, _leaf(msg.sender));
if (!ok) revert CreatorNotAllowed(msg.sender);
}
}
Deployment / Migration
- Re-deploy the factory with the updated ABI.
- Owner sets the initial root (setCreatorAllowlistRoot) computed from an allowlist that includes: 0x3601a913fD3466f30f5ABb978E484d1B37Ce995D
- Update NEXT_PUBLIC_FACTORY_ADDRESS (frontend) and corresponding server config.
Frontend / Server Integration
- Frontend:
- Fetch Merkle proof for the connected wallet before calling createCapTable; pass proof to the contract call.
- Handle CreatorNotAllowed revert with a clear “not allowed to create” message.
- Owner-bypass remains for internal testing.
- Server:
- Provide GET /allowlist/proof?address=0x... returning { root, proof } from a maintained JSON/CSV allowlist.
- Root rotation flow: publish new root + call setCreatorAllowlistRoot.
- Build tooling:
- Script to compute the Merkle root and per-address proofs from CSV/JSON.
- Foundry script to set the root on-chain.
Tests (add/modify)
- Revert when: whitelistEnabled AND (no proof | bad proof | wrong root).
- Success when: valid proof; owner without proof; whitelistEnabled == false.
- Root rotation: old proofs fail after root update; new proofs succeed.
- Update existing factory tests to supply a valid proof (or prank as owner).
Security / Ops Notes
- Gas: Merkle adds O(log N) hashing per call (paid by caller); on-chain state stays O(1) (single root). A mapping is cheaper per-call but requires 20k gas per address to add and grows state; Merkle avoids that and scales.
- Domain separation prevents cross-chain/contract proof reuse.
- Revocation requires rotating the root; clients must re-fetch proofs. In the future, consider supporting a temporary secondary root to smooth rotations.
Acceptance Criteria
- Only wallets with a valid Merkle proof (or the owner) can create a cap table.
- Tests cover deny/allow, owner bypass, bad proof, and root rotation.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request