The machine itself
The Contract
GadflyFund.sol, deployed and verified on Robinhood Chain. One file, no imports, no proxy, no upgrade path: 9,355 bytes of runtime that will do exactly this, and only this, forever. This page is the engineering record.
Design notes
The contract is deliberately primitive. There is no proxy and no upgrade mechanism, because a record book that can be upgraded is not a record book. There is no inheritance and no external dependency, so the verified source on the explorer is the entire trusted computing base: 400-odd lines, readable in one sitting. State is append-only by construction; the ABI exposes no function that deletes or rewrites a stored record, and the erased/restored counters can only increment.
Storage is packed where packing is free. Timestamps are uint64 (sufficient until the year 292 billion), money is uint128 integer cents (the fund thinks in stamps, not wei), and the share's state machine shares slot 2 with both of its counters: state, timesErased, and timesRestored occupy seventeen bytes of one word. Reverts use custom errors rather than strings, which is cheaper and gives every failure mode a stable four-byte name; BadReveal() is 0x8ff14e0d, and the tests assert it by selector.
Every mutation emits an event carrying its full payload, so the fund's complete history is reconstructible from logs alone, without reading contract storage at all; the site does exactly this for transaction references. Access is a single operator address checked by one modifier. This is the honest shape of the trust model rather than a limitation of it: multisigs and timelocks would decorate the operator's power without diminishing it, and the whitepaper's position is that decoration is the enemy.
Exactly one function enforces something an operator could not fake: revealLetter recomputes keccak256 over the submitted text and refuses anything that does not match the stored commitment. The check is three lines. The rest of the contract exists to give those three lines a place to live.
The load-bearing code
Verbatim from the verified source. The commit locks thirty-two bytes; the reveal must reproduce them from raw text, byte for byte, or revert.
/// Lock the hash of a letter's full text before it is mailed.
function commitLetter(bytes32 commitment) external onlyOperator returns (uint256 id) {
if (commitment == bytes32(0)) revert EmptyCommitment();
_letters.push();
id = _letters.length - 1;
Letter storage l = _letters[id];
l.commitment = commitment;
l.committedAt = uint64(block.timestamp);
emit LetterCommitted(id, commitment, block.timestamp);
}/// Publish the full text. The contract checks it against the commitment.
/// This check is the only statement in the system that does not require
/// trusting the operator.
function revealLetter(uint256 id, string calldata fullText, string calldata uri)
external
onlyOperator
{
if (id >= _letters.length) revert NothingCommitted();
Letter storage l = _letters[id];
if (l.revealedAt != 0) revert AlreadyRevealed();
if (keccak256(bytes(fullText)) != l.commitment) revert BadReveal();
l.revealedAt = uint64(block.timestamp);
l.uri = uri;
emit LetterRevealed(id, l.commitment, uri, block.timestamp);
}Notes for the auditor: the text arrives as calldata and is hashed directly from it (no memory copy of the string is retained); the commitment cannot be zero, so an unset slot can never pass; a letter can be revealed exactly once (AlreadyRevealed guards the overwrite); and the out-of-bounds id reverts with NothingCommitted rather than a raw panic, so every failure mode of the rite has a name. Measured cost on the reference tests: commit ≈ 92k gas, reveal ≈ 34–61k depending on text length.
Storage layout
From forge inspect storage-layout. Slot 2 packs the state machine and both Theseus counters into one word.
| Name | Type | Slot | Offset | Bytes |
|---|---|---|---|---|
| operator | address | 0 | 0 | 20 |
| target | string | 1 | 0 | 32 |
| shareState | enum ShareState | 2 | 0 | 1 |
| timesErased | uint64 | 2 | 1 | 8 |
| timesRestored | uint64 | 2 | 9 | 8 |
| totalSpentCents | uint256 | 3 | 0 | 32 |
| totalReceivedCents | uint256 | 4 | 0 | 32 |
| _letters | Letter[] | 5 | 0 | 32 |
| _expenses | Expense[] | 6 | 0 | 32 |
| _income | Income[] | 7 | 0 | 32 |
| _meetings | Meeting[] | 8 | 0 | 32 |
Custom errors
Every failure mode has a stable four-byte name. The tests assert reverts by selector.
| BadReveal() | 0x8ff14e0d | revealed text does not hash to the commitment |
| NothingCommitted() | 0x835a24b1 | letter id does not exist |
| AlreadyRevealed() | 0xa89ac151 | a letter reveals exactly once |
| EmptyCommitment() | 0xaac1fcc6 | the zero hash cannot be committed |
| NotOperator() | 0x7c214f04 | caller is not the operator |
| NotHolding() | 0x9e4c8d94 | cannot erase what is already erased |
| NotErased() | 0xcedb69cb | cannot restore what is held |
| ZeroAddress() | 0xd92e233d | guards operator transfer and withdrawals |
| EthTransferFailed() | 0x6d963f88 | treasury ETH send failed |
| TokenTransferFailed() | 0x045c4b02 | treasury token send failed |
Function selectors
The complete external surface: 29 functions, from forge inspect methodIdentifiers.
| commitLetter(bytes32) | 0x3821fd6d | write · operator |
| revealLetter(uint256,string,string) | 0x114a47cc | write · operator · trustless check |
| recordMailing(uint256,bytes32) | 0xfff84a82 | write · operator |
| recordReply(uint256,bytes32) | 0x5114a30d | write · operator |
| markErased(string) | 0x2e8acda7 | write · operator |
| markRestored(string) | 0x947c9f2a | write · operator |
| recordExpense(bytes32,uint128,string) | 0x6bec9137 | write · operator |
| recordIncome(bytes32,uint128,string) | 0x3b5d9bc4 | write · operator |
| recordMeeting(uint16,bool,string,bool,bool,bytes32) | 0x8437d31d | write · operator |
| withdrawEth(address,uint256) | 0x1b9a91a4 | write · operator |
| withdrawToken(address,address,uint256) | 0x01e33667 | write · operator |
| setOperator(address) | 0xb3ab15fb | write · operator |
| counters() | 0xf7210633 | view |
| getLetter(uint256) | 0x96e583a9 | view |
| lettersCount() | 0x798ebe2f | view |
| getExpense(uint256) | 0x735e5db6 | view |
| expensesCount() | 0x4f5b35dc | view |
| getIncome(uint256) | 0x85791510 | view |
| incomeCount() | 0xafb7afbb | view |
| getMeeting(uint256) | 0x2d703f88 | view |
| meetingsCount() | 0x0a3b2f2c | view |
| operator() | 0x570ca735 | view |
| target() | 0xd4b83992 | view |
| inceptionAt() | 0x8708b006 | view |
| shareState() | 0x4745fe4f | view |
| timesErased() | 0x997f6526 | view |
| timesRestored() | 0xe862e5be | view |
| totalSpentCents() | 0xf9138dc8 | view |
| totalReceivedCents() | 0x26a1cefd | view |
Events
One event per mutation; the fund's full history reconstructs from logs alone.
| LetterCommitted(uint256,bytes32,uint256) | 0xfafaabdc21d3c93564291d137ecea0ca9cb86f926fc41b4624d16a49ae526f18 |
| LetterRevealed(uint256,bytes32,string,uint256) | 0x6f0fa5e12a63798ff2b929f883fe9934a96e978e1f370f61d3e9731daa802b06 |
| LetterMailed(uint256,bytes32,uint256) | 0x4dc236515560be8d8e27ec1fec69584299a7320a0d86785d1bc587c0d17fe1f6 |
| ReplyRecorded(uint256,bytes32,uint256) | 0x4ee27fc2420d15032356a858cd3e9da588ba06f45bfc63d9481445c4e16b1b1f |
| ShareErased(uint256,string,uint256) | 0x8756aa1eec173086ce3b55e03689c79e7de61adb32e9c27dd1221601217a98b6 |
| ShareRestored(uint256,string,uint256) | 0x76fbdb7b27dbfbd06cca7e2ce30f3c2cc49fa1ed191e732db84fbd2ada11f5f6 |
| ExpenseRecorded(uint256,bytes32,uint128,string,uint256) | 0x07a3cf0a95a77c87ac5ea69d8fb65870568a47fb2cdb6a2154d0d93468fb2a6d |
| IncomeRecorded(uint256,bytes32,uint128,string,uint256) | 0xb251e795488b0cd921501e9628c3efe62117da6d8cb666c761518fe37cff284e |
| MeetingRecorded(uint256,uint16,bool,bool,bool,uint256) | 0xfa842dbb070ba38d1b52a05571888b265ceae375fce9d597d336e86dcf2fdc92 |
| Received(address,uint256) | 0x88a5966d370b9919b20f3e2c13ff65706f196a4e32cc2c12bf57088f88525874 |
| EthWithdrawn(address,uint256) | 0x8455ae6be5d92f1df1c3c1484388e247a36c7e60d72055ae216dbc258f257d4b |
| TokenWithdrawn(address,address,uint256) | 0x8210728e7c071f615b840ee026032693858fbcd5e5359e67e438c890f59e5620 |
| OperatorChanged(address,address) | 0xd58299b712891143e76310d5e664c4203c940a67db37cf856bdaa3c5c76a802c |
Measured gas
From forge test --gas-report over the 24-test suite (incl. 256-run fuzz campaigns). Min/avg/max in gas units.
| Function | Min | Avg | Max |
|---|---|---|---|
| deployment | 1,952,841 | ||
| commitLetter | 23,660 | 91,701 | 92,472 |
| revealLetter | 26,194 | 46,602 | 61,428 |
| recordMailing | 24,239 | 35,376 | 55,527 |
| recordReply | 24,196 | 35,333 | 55,484 |
| markErased | 24,239 | 36,617 | 50,344 |
| markRestored | 24,216 | 28,976 | 32,774 |
| recordExpense | 24,674 | 122,343 | 184,733 |
| recordIncome | 24,583 | 82,218 | 139,853 |
| recordMeeting | 25,222 | 95,336 | 162,985 |
| withdrawEth | 23,888 | 41,522 | 59,862 |
| setOperator | 23,782 | 25,358 | 28,499 |
Tests
The Foundry suite runs 24 tests against the fund: the commit–reveal equality (matching text passes; one altered character reverts BadReveal; an unknown id reverts NothingCommitted; a second reveal reverts AlreadyRevealed), a 256-run fuzz campaign asserting that for arbitrary text the exact bytes verify and any mutation reverts, the erased→restored round trip with counter monotonicity, expense and income accumulation with event assertions, treasury deposit and withdrawal, and operator gating on every mutator. The suite is in the repository at test/GadflyFund.t.sol and completes in under twenty milliseconds.
The full verified source
The complete verified source, byte-identical to what the explorer serves. Reading time: one coffee. Trusted computing base: this, and nothing else.
GadflyFund.sol · 14,171 characters
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
/*
* ────────────────────────────────────────────────────────────────────────────
* GADFLY FUND
* The smallest activist fund in the world.
* AUM: one share. Fully exercised.
*
* Deployed on Robinhood Chain (chainId 4663).
*
* The contract is a notarized diary that grew a coin pocket. The one share
* lives off-chain in the operator's custody; the treasury that pays for
* the stamps lives here, fed by the GADFLY token's trading tax, and is
* withdrawable by the operator alone, without restriction, as disclosed.
* It records six kinds of facts:
*
* 1. Letters, under commit-reveal. The keccak256 hash of a letter's full
* UTF-8 text is locked on-chain BEFORE the letter is mailed. After
* mailing, the full text is revealed and the contract checks that the
* published text hashes to the locked commitment. This equality is the
* only trustless moment in the entire system: it proves that the letter
* the public reads is the letter the CEO received. Everything else is
* testimony.
* 2. Mailing receipts and replies, as document hashes (testimony).
* 3. The state of the one share: HOLDING or ERASED, with counters for how
* many times corporate arithmetic (reverse splits) deleted the share
* and how many times the fund bought it back. The Theseus clause.
* 4. Expenses, in integer US cents, by category (testimony).
* 5. Income, likewise: token-tax sweeps and anything else that funds
* the rites (testimony).
* 6. Annual-meeting attendance and the one question asked each year
* (testimony).
*
* This file is the canonical contract: the tests, the deploy script, and
* the site's ABI are all derived from it.
* ────────────────────────────────────────────────────────────────────────────
*/
contract GadflyFund {
// ── Errors ──────────────────────────────────────────────────────────────
error NotOperator();
error ZeroAddress();
error EmptyCommitment();
error NothingCommitted();
error AlreadyRevealed();
error BadReveal();
error NotHolding();
error NotErased();
error EthTransferFailed();
error TokenTransferFailed();
// ── Types ───────────────────────────────────────────────────────────────
enum ShareState {
HOLDING,
ERASED
}
struct Letter {
bytes32 commitment; // keccak256 of the letter's full UTF-8 text
uint64 committedAt; // block timestamp of the commit
uint64 revealedAt; // 0 while the letter is still sealed
uint64 mailedAt; // 0 until a mailing receipt is recorded
uint64 repliedAt; // 0 while the company is silent
bytes32 receiptHash; // hash of the registered-mail receipt scan
bytes32 replyHash; // hash of the company's reply scan; 0 = silence
string uri; // where the revealed full text is published
}
struct Expense {
bytes32 category; // short tag, e.g. bytes32("POSTAGE")
uint128 amountCents; // US cents; the fund thinks in stamps
uint64 recordedAt;
string memo;
}
struct Income {
bytes32 category; // e.g. bytes32("TAX") for token-tax sweeps
uint128 amountCents; // USD value as recorded by the operator
uint64 recordedAt;
string memo;
}
struct Meeting {
uint16 year; // meeting year, e.g. 2025
bool attendedInPerson;
bool questionReadAloud; // was the question read / asked on the floor
bool answered; // did any answer follow, of any quality
uint64 recordedAt;
bytes32 evidenceHash; // hash of the operator's notes or exhibits
string question; // the one question, verbatim
}
// ── Events ──────────────────────────────────────────────────────────────
event LetterCommitted(uint256 indexed id, bytes32 commitment, uint256 at);
event LetterRevealed(uint256 indexed id, bytes32 commitment, string uri, uint256 at);
event LetterMailed(uint256 indexed id, bytes32 receiptHash, uint256 at);
event ReplyRecorded(uint256 indexed id, bytes32 replyHash, uint256 at);
event ShareErased(uint256 timesErased, string note, uint256 at);
event ShareRestored(uint256 timesRestored, string note, uint256 at);
event ExpenseRecorded(
uint256 indexed id, bytes32 indexed category, uint128 amountCents, string memo, uint256 at
);
event IncomeRecorded(
uint256 indexed id, bytes32 indexed category, uint128 amountCents, string memo, uint256 at
);
event Received(address indexed from, uint256 amount);
event EthWithdrawn(address indexed to, uint256 amount);
event TokenWithdrawn(address indexed token, address indexed to, uint256 amount);
event MeetingRecorded(
uint256 indexed id,
uint16 indexed year,
bool attendedInPerson,
bool questionReadAloud,
bool answered,
uint256 at
);
event OperatorChanged(address indexed previousOperator, address indexed newOperator);
// ── Storage ─────────────────────────────────────────────────────────────
address public operator;
string public target; // ticker of the company whose one share the fund holds
uint64 public immutable inceptionAt;
ShareState public shareState; // HOLDING at deployment
uint64 public timesErased;
uint64 public timesRestored;
uint256 public totalSpentCents;
uint256 public totalReceivedCents;
Letter[] private _letters;
Expense[] private _expenses;
Income[] private _income;
Meeting[] private _meetings;
// ── Modifiers ───────────────────────────────────────────────────────────
modifier onlyOperator() {
if (msg.sender != operator) revert NotOperator();
_;
}
constructor(string memory target_) {
operator = msg.sender;
target = target_;
inceptionAt = uint64(block.timestamp);
}
// ── Letters: the one trustless moment ───────────────────────────────────
/// Lock the hash of a letter's full text before it is mailed.
function commitLetter(bytes32 commitment) external onlyOperator returns (uint256 id) {
if (commitment == bytes32(0)) revert EmptyCommitment();
_letters.push();
id = _letters.length - 1;
Letter storage l = _letters[id];
l.commitment = commitment;
l.committedAt = uint64(block.timestamp);
emit LetterCommitted(id, commitment, block.timestamp);
}
/// Publish the full text. The contract checks it against the commitment.
/// This check is the only statement in the system that does not require
/// trusting the operator.
function revealLetter(uint256 id, string calldata fullText, string calldata uri)
external
onlyOperator
{
if (id >= _letters.length) revert NothingCommitted();
Letter storage l = _letters[id];
if (l.revealedAt != 0) revert AlreadyRevealed();
if (keccak256(bytes(fullText)) != l.commitment) revert BadReveal();
l.revealedAt = uint64(block.timestamp);
l.uri = uri;
emit LetterRevealed(id, l.commitment, uri, block.timestamp);
}
/// Record the hash of the registered-mail receipt. Testimony, not proof:
/// the chain cannot watch a letter enter a mailbox.
function recordMailing(uint256 id, bytes32 receiptHash) external onlyOperator {
if (id >= _letters.length) revert NothingCommitted();
Letter storage l = _letters[id];
l.mailedAt = uint64(block.timestamp);
l.receiptHash = receiptHash;
emit LetterMailed(id, receiptHash, block.timestamp);
}
/// Record the hash of a reply, should one ever arrive.
function recordReply(uint256 id, bytes32 replyHash) external onlyOperator {
if (id >= _letters.length) revert NothingCommitted();
Letter storage l = _letters[id];
l.repliedAt = uint64(block.timestamp);
l.replyHash = replyHash;
emit ReplyRecorded(id, replyHash, block.timestamp);
}
// ── The Theseus clause ──────────────────────────────────────────────────
/// A reverse split (or similar corporate act) deleted the share.
function markErased(string calldata note) external onlyOperator {
if (shareState != ShareState.HOLDING) revert NotHolding();
shareState = ShareState.ERASED;
timesErased += 1;
emit ShareErased(timesErased, note, block.timestamp);
}
/// The fund bought the share back. Same fund, new plank.
function markRestored(string calldata note) external onlyOperator {
if (shareState != ShareState.ERASED) revert NotErased();
shareState = ShareState.HOLDING;
timesRestored += 1;
emit ShareRestored(timesRestored, note, block.timestamp);
}
// ── The ledger ──────────────────────────────────────────────────────────
function recordExpense(bytes32 category, uint128 amountCents, string calldata memo)
external
onlyOperator
returns (uint256 id)
{
_expenses.push(
Expense({
category: category,
amountCents: amountCents,
recordedAt: uint64(block.timestamp),
memo: memo
})
);
id = _expenses.length - 1;
totalSpentCents += amountCents;
emit ExpenseRecorded(id, category, amountCents, memo, block.timestamp);
}
function recordIncome(bytes32 category, uint128 amountCents, string calldata memo)
external
onlyOperator
returns (uint256 id)
{
_income.push(
Income({
category: category,
amountCents: amountCents,
recordedAt: uint64(block.timestamp),
memo: memo
})
);
id = _income.length - 1;
totalReceivedCents += amountCents;
emit IncomeRecorded(id, category, amountCents, memo, block.timestamp);
}
// ── The treasury ────────────────────────────────────────────────────────
// The token's trading tax lands here, in GADFLY and eventually in ETH.
// The operator withdraws it to buy stamps and seats, and records what
// everything was worth via recordIncome / recordExpense. The withdrawals
// are unrestricted by design and disclosed as such: this fund's deepest
// dependency has always been the operator's honesty.
receive() external payable {
emit Received(msg.sender, msg.value);
}
function withdrawEth(address payable to, uint256 amount) external onlyOperator {
if (to == address(0)) revert ZeroAddress();
(bool ok,) = to.call{value: amount}("");
if (!ok) revert EthTransferFailed();
emit EthWithdrawn(to, amount);
}
function withdrawToken(address token, address to, uint256 amount) external onlyOperator {
if (to == address(0)) revert ZeroAddress();
// transfer(address,uint256); tolerate ERC-20s that return nothing.
(bool ok, bytes memory data) =
token.call(abi.encodeWithSelector(0xa9059cbb, to, amount));
if (!ok || (data.length != 0 && !abi.decode(data, (bool)))) {
revert TokenTransferFailed();
}
emit TokenWithdrawn(token, to, amount);
}
// ── The question ────────────────────────────────────────────────────────
function recordMeeting(
uint16 year,
bool attendedInPerson,
string calldata question,
bool questionReadAloud,
bool answered,
bytes32 evidenceHash
) external onlyOperator returns (uint256 id) {
_meetings.push(
Meeting({
year: year,
attendedInPerson: attendedInPerson,
questionReadAloud: questionReadAloud,
answered: answered,
recordedAt: uint64(block.timestamp),
evidenceHash: evidenceHash,
question: question
})
);
id = _meetings.length - 1;
emit MeetingRecorded(id, year, attendedInPerson, questionReadAloud, answered, block.timestamp);
}
// ── Operator ────────────────────────────────────────────────────────────
function setOperator(address newOperator) external onlyOperator {
if (newOperator == address(0)) revert ZeroAddress();
emit OperatorChanged(operator, newOperator);
operator = newOperator;
}
// ── Views ───────────────────────────────────────────────────────────────
function lettersCount() external view returns (uint256) {
return _letters.length;
}
function getLetter(uint256 id) external view returns (Letter memory) {
if (id >= _letters.length) revert NothingCommitted();
return _letters[id];
}
function expensesCount() external view returns (uint256) {
return _expenses.length;
}
function getExpense(uint256 id) external view returns (Expense memory) {
return _expenses[id];
}
function incomeCount() external view returns (uint256) {
return _income.length;
}
function getIncome(uint256 id) external view returns (Income memory) {
return _income[id];
}
function meetingsCount() external view returns (uint256) {
return _meetings.length;
}
function getMeeting(uint256 id) external view returns (Meeting memory) {
return _meetings[id];
}
/// One call for the site's front page.
function counters()
external
view
returns (
uint256 letters_,
uint256 expenses_,
uint256 meetings_,
uint64 erased_,
uint64 restored_,
ShareState state_,
uint256 totalSpentCents_
)
{
return (
_letters.length,
_expenses.length,
_meetings.length,
timesErased,
timesRestored,
shareState,
totalSpentCents
);
}
}
GADFLY