This analysis is based on observations of the contract behavior. We are not smart contract security experts. This document aims to explain what the contract appears to do based on the code. It should not be considered a comprehensive security audit or financial advice. Always verify critical information independently and consult with blockchain security professionals for important decisions.
Majeur ships as five contracts in one source file, and a DAO is all four of the first four at once: a Moloch clone holding governance state, plus Shares, Loot and Badges clones bound to it permanently at summon time. Summoner is the factory that mints the set. Functions below are grouped by role across all five, with the owning contract named in every selector row, because a member interacting with a DAO moves between them constantly — voting reads Shares, exiting burns both Shares and Loot, and posting to chat checks Badges.
Access in this framework has an unusual shape. onlyDAO on Moloch means msg.sender == address(this): the only way to reach those functions is a proposal that the DAO executes against itself, or a multicall the DAO makes to itself. onlyDAO is the whole access model; there is no role registry. On the three satellites, onlyDAO means msg.sender == DAO, the Moloch clone that created them. The practical consequence is that everything labelled Admin below requires a passing vote, a spent permit, or a spent allowance — there is no privileged address anywhere in the system.
Selector counts are per contract, so the same signature appears more than once across the set — transferFrom, balanceOf, name and DAO are each implemented separately by several contracts with different semantics. The transferFrom on Badges always reverts.
Computes the identifier for an intent. This is a pure derivation with no state change, and it is the entry point to everything else — a proposal has no existence in this system beyond the number this function returns.
ATTRIBUTE
VALUE
Selector
0x997506ba
Parameters
op execution mode, to target address, value wei to send, data calldata, nonce caller-chosen salt
Access
Public view
FLAG
OBSERVATION
△
The chain never stores the preimage. openProposal and castVote accept only the resulting uint256, so target, value and calldata first appear on-chain in the execution transaction — after voting has closed
◇
address(this) is mixed in, so the same intent yields different ids on different DAOs and a proposal cannot be replayed across instances
◇
config is mixed in, which is how bumpConfig invalidates every pending proposal at once
◇
nonce is caller-chosen and lets the same action be proposed more than once, since a re-proposal of an identical intent would otherwise collide with the executed latch
Registers an intent hash as a live proposal and fixes the snapshot block that determines voting weight for its entire life. Calling it is optional — castVote invokes it automatically on the first vote — but calling it explicitly is what lets a proposer control when the electorate is measured.
ATTRIBUTE
VALUE
Selector
0x31288f40
Parameters
id the intent hash from proposalId
Access
Public; gated by proposalThreshold when non-zero
FLAG
OBSERVATION
△
The snapshot is block.number - 1 at the moment of the call, so whoever opens the proposal chooses the block at which the electorate is measured
△
The threshold is checked against getVotes(msg.sender) — current votes, not snapshot votes — so it is satisfiable by acquiring shares immediately before calling
☑
Re-entry is a no-op rather than a revert: the function returns early if snapshotBlock[id] is already set, so a redundant call cannot reset a live proposal's snapshot
◇
When autoFutarchyParam is non-zero, opening also earmarks a futarchy pool. The earmark is an accounting entry, not a transfer — no funds move at this point
CONDITION
REQUIREMENT
Not already opened
if (snapshotBlock[id] != 0) return;
Caller meets threshold
require(_shares.getVotes(msg.sender) >= threshold) when proposalThreshold != 0
Share supply exists at snapshot
if (supply == 0) revert TooEarly();
VARIABLE
CHANGE
snapshotBlock[id]
Set to block.number - 1
createdAt[id]
Set to block.timestamp when previously zero, starting the TTL clock
supplySnapshot[id]
Set to getPastTotalSupply(snap), the quorum denominator
proposalIds
Appended, making the proposal enumerable
proposerOf[id]
Set to msg.sender, the only address that may later cancel
futarchy[id]
Earmarked when auto-futarchy is configured
CONDITION
REVERT MESSAGE
Caller below proposalThreshold
Unauthorized()
No share supply at the snapshot block
TooEarly()
functionopenProposal(uint256id)public{if(snapshotBlock[id]!=0)return;Shares_shares=shares;uint96threshold=proposalThreshold;if(threshold!=0){require(_shares.getVotes(msg.sender)>=threshold,Unauthorized());}uint256supply;unchecked{uint48snap=toUint48(block.number-1);snapshotBlock[id]=snap;if(createdAt[id]==0)createdAt[id]=uint64(block.timestamp);supply=_shares.getPastTotalSupply(snap);if(supply==0)revertTooEarly();supplySnapshot[id]=supply;proposalIds.push(id);proposerOf[id]=msg.sender;emitOpened(id,snap,supply);}// auto-futarchy earmark omitted for brevity — see artifacts.md for full source}
Function: castVote(uint256 id, uint8 support)
Records a vote and mints a transferable ERC-6909 receipt for the weight cast. Support is 0 for AGAINST, 1 for FOR, 2 for ABSTAIN. Integrators should confirm this ordering against the contract rather than assume it matches whatever convention their tooling uses.
ATTRIBUTE
VALUE
Selector
0x56781388
Parameters
id proposal hash, support 0 = AGAINST, 1 = FOR, 2 = ABSTAIN
Access
Public; requires non-zero weight at the snapshot block
FLAG
OBSERVATION
☑
Weight is read only from getPastVotes at the snapshot block, with no fallback to current balance, so shares acquired after the snapshot carry no weight
△
Auto-opens the proposal when unopened, which means the first voter also sets the snapshot block for everyone else
△
The receipt minted here is transferable. A voter who sends it away can no longer call cancelVote, because that function burns the receipt from the caller
◇
Weight is stored per voter in voteWeight, which is what allows cancelVote to subtract precisely what was added rather than recomputing
◇
The cast to uint96 is unchecked, unlike the equivalent conversion in Badges.onSharesChanged which is guarded
CONDITION
REQUIREMENT
Not executed
if (executed[id]) revert AlreadyExecuted();
Valid support value
if (support > 2) revert NotOk();
Within TTL
if (block.timestamp >= t0 + ttl) revert Expired(); when proposalTTL != 0
Has not voted
if (hasVoted[id][msg.sender] != 0) revert AlreadyVoted();
Derives a proposal's current state from stored tallies and timestamps. Nothing writes a state value to storage — every state is computed on read, which means a proposal transitions between states purely with the passage of time and without any transaction marking the change.
ATTRIBUTE
VALUE
Selector
0x3e4f49e6
Parameters
id proposal hash
Access
Public view
FLAG
OBSERVATION
☒
The queue branch and the expiry branch are alternatives. Once queuedAt is non-zero the TTL check becomes unreachable for that proposal, so a queued proposal never expires and stays executable indefinitely, subject to the live tally until it runs
△
The TTL check precedes gate evaluation, so a proposal that was voted down reports Expired rather than Defeated if its clock ran out first
△
executed is checked first and is also set by cancelProposal as a tombstone, so a cancelled proposal reports Executed
◇
Failing quorum returns Active, not Defeated — a proposal with insufficient turnout stays open until the TTL ends it
◇
Turnout for quorum includes abstentions, so abstaining helps a proposal clear its quorum gate
CONDITION
REQUIREMENT
Pass condition, dynamic quorum
totalCast >= mulDiv(quorumBps, supplySnapshot, 10000) when quorumBps != 0
Starts the timelock countdown on a proposal that has passed. On a DAO with no timelock configured this is a no-op, and offers no protection from TTL expiry; the proposal must be executed inside the TTL. On one with a timelock, a proposal must be queued — by this call, or implicitly by a first executeByVotes — before the delay starts, and must be queued inside the TTL.
ATTRIBUTE
VALUE
Selector
0xddf0b009
Parameters
id proposal hash
Access
Public — any address may queue a passing proposal
FLAG
OBSERVATION
☒
Queueing permanently removes the proposal from TTL expiry. The authorisation becomes indefinite rather than merely delayed, though state() still re-evaluates the live tally against current quorum settings
△
On a timelocked DAO, failing to queue inside the TTL window destroys a proposal that met every pass condition
◇
Idempotent — a second call while already queued does not restart the clock
◇
executeByVotes will queue implicitly on its first call, so the separate queue transaction is a convenience; queueing itself is not optional on a timelocked DAO
Reveals the preimage of a passed proposal and performs the call. This is the only function in the framework that takes the full intent rather than its hash, and it recomputes the id from what it was given — so presenting a payload that does not hash to an approved proposal simply fails to match anything.
ATTRIBUTE
VALUE
Selector
0xee5b2895
Parameters
The full intent: op, to, value, data, nonce
Access
Public payable, nonReentrant — no caller restriction whatsoever
FLAG
OBSERVATION
☒
op != 0 performs a delegatecall, running the target's code against the DAO's own storage. A passing proposal can therefore rewrite any slot directly rather than being limited to the contract's own functions
△
Anyone may execute. Combined with queued proposals never expiring, a stale authorisation can be fired by any party at a moment of their choosing
△
The first call on a timelocked DAO queues and returns (true, "") without executing. The returned boolean does not distinguish queueing from execution
☑
The executed latch is set before the external call, and the whole function is nonReentrant via transient storage, so a re-entrant execution of the same proposal is blocked twice over
◇
A failed inner call reverts the whole transaction through _execute, so a proposal cannot be consumed by a failing execution
CONDITION
REQUIREMENT
Not already executed
if (executed[id]) revert AlreadyExecuted();
State is Succeeded or Queued
if (st != Succeeded && st != Queued) revert NotOk();
Timelock elapsed
if (block.timestamp < untilWhen) revert Timelocked(untilWhen);
STEP
ACTION
1
Recompute the intent hash from the supplied parameters
2
Reject if already executed or not in an executable state
3
On a timelocked DAO, queue and return early if not yet queued; otherwise require the delay to have elapsed
4
Set the executed latch
5
Perform the call or delegatecall, reverting the transaction if it fails
6
Resolve any futarchy pool in favour of the YES side
CONDITION
REVERT MESSAGE
Already executed, or preimage does not match an approved id
flowchart TD
A["executeByVotes(op, to, value, data, nonce)"] --> B["id = keccak(this, op, to, value, keccak(data), nonce, config)"]
B --> C{"executed[id]?"}
C -->|yes| R1["revert AlreadyExecuted"]
C -->|no| D{"state(id)"}
D -->|"Defeated / Expired / Active / Unopened"| R2["revert NotOk"]
D -->|"Succeeded or Queued"| E{"timelockDelay != 0?"}
E -->|no| H["set executed latch"]
E -->|yes| F{"queuedAt == 0?"}
F -->|yes| G["queue it, return (true, '') — NOT executed"]
F -->|no| I{"delay elapsed?"}
I -->|no| R3["revert Timelocked"]
I -->|yes| H
H --> J{"op == 0?"}
J -->|yes| K["to.call{value}(data)"]
J -->|no| L["to.delegatecall(data) — DAO storage context"]
K --> M["resolve futarchy YES, emit Executed"]
L --> M
style L fill:#ffe1e1
style G fill:#fff4e1
Two distinct retractions. cancelVote withdraws the caller's own vote while a proposal is still active, burning their receipt and subtracting their weight. cancelProposal withdraws the whole proposal, and is available only to the proposer and only before anyone has voted.
ATTRIBUTE
VALUE
Selector
0xbacbe2da / 0xe0a8f6f5
Parameters
id proposal hash
Access
cancelVote — any prior voter. cancelProposal — proposerOf[id] only
FLAG
OBSERVATION
☑
cancelVote subtracts the stored voteWeight rather than re-reading the snapshot, so the tally cannot drift even if checkpoint history changes shape
△
cancelVote burns the receipt from the caller, so a voter who transferred their receipt away cannot retract their vote
☑
cancelProposal requires a completely untouched proposal — zero votes across all three tallies and no futarchy pool — so it cannot be used to kill a contested proposal
◇
cancelProposal tombstones by setting executed[id] = true, which permanently burns that intent hash under the current config and makes state report Executed
CONDITION
REVERT MESSAGE
Proposal not Active
NotOk()
Caller has not voted (cancelVote)
NotOk()
Caller is not the proposer (cancelProposal)
Unauthorized()
Any vote already cast, or a futarchy pool exists (cancelProposal)
Burns the caller's shares and loot and pays out a proportional slice of each token in the supplied list. This is the exit right the framework is built around — a member who loses a vote can leave with their share of the treasury rather than be bound by the outcome.
ATTRIBUTE
VALUE
Selector
0x29f64d1a
Parameters
tokens ascending list of assets to claim, sharesToBurn, lootToBurn
The token list is caller-supplied and unvalidated against the treasury. Omitting an asset forfeits the claim on it permanently, because the burn has already happened by the time the loop runs
△
Only ETH and ERC-20 assets can be claimed. Both live DAOs implement onERC721Received and onERC1155Received, so a treasury can hold NFTs that this function cannot distribute
☑
The denominator is read before the burns, so the payout ratio is the caller's share of the pre-burn supply and cannot be inflated by ordering
☑
Strictly ascending order is enforced, which prevents claiming the same token twice in one call
☑
Shares, loot, the DAO itself and the 1007 loot sentinel are all rejected as claim targets, so the exit cannot recursively drain the governance tokens
◇
Shares and loot are equivalent for this purpose — both burn against the same combined denominator, which is why loot dilutes treasury claims without diluting votes
CONDITION
REQUIREMENT
Ragequit enabled
if (!ragequittable) revert NotOk();
Non-empty token list
require(tokens.length != 0, LengthMismatch());
Something to burn
if (sharesToBurn == 0 && lootToBurn == 0) revert NotOk();
A prediction market attached to a proposal. Anyone may fund a reward pool; voters hold receipts for whichever side they voted; when the proposal resolves, receipts on the winning side are burned for a pro-rata slice of the pool and receipts on the losing side are worth nothing. The effect is that voters on the winning side share the pool and voters on the losing side receive nothing.
ATTRIBUTE
VALUE
Selector
0x12708efd / 0xe89e80c2 / 0x849609a9
Parameters
id proposal hash; token reward asset; amount funding or receipts to burn
Access
Public; cashOutFutarchy is nonReentrant
FLAG
OBSERVATION
◇
Resolution is asymmetric. The YES side wins implicitly inside executeByVotes; the NO side must be resolved by an explicit resolveFutarchyNo call that anyone can make once the proposal is Defeated or Expired
△
Because a queued proposal never expires and cannot be defeated after its TTL, a funded pool on such a proposal can be left unresolvable in practice until someone executes it
△
The reward token may be address(this) or address(1007), sentinels meaning freshly minted shares or loot. A pool denominated that way pays winners by dilution rather than from the treasury
☑
The reward token is pinned on first funding and every later contribution must match, so a pool cannot be split across assets
☑
_finalizeFutarchy computes payoutPerUnit once at resolution against the winning side's total supply, so later receipt transfers cannot change anyone else's payout
◇
Payout is floor-rounded via mulDiv, so dust remains in the contract rather than over-paying the last claimant
CONDITION
REQUIREMENT
Reward token is permitted
Must be ETH, address(this), address(1007), shares or loot
Not already resolved
if (F.resolved) revert NotOk();
ETH funding matches value
if (msg.value != amount) revert NotOk();
Minted-token funding is self-called
if (msg.sender != address(this)) revert Unauthorized();
Resolution state (resolveFutarchyNo)
if (st != Defeated && st != Expired) revert NotOk();
functioncashOutFutarchy(uint256id,uint256amount)publicnonReentrant
returns(uint256payout){FutarchyConfigstorageF=futarchy[id];if(!F.enabled||!F.resolved)revertNotOk();uint8winner=F.winner;// 1 or 0uint256rid=_receiptId(id,winner);_burn6909(msg.sender,rid,amount);payout=mulDiv(amount,F.payoutPerUnit,1e18);if(payout==0){emitFutarchyClaimed(id,msg.sender,amount,0);return0;}_payout(F.rewardToken,msg.sender,payout);emitFutarchyClaimed(id,msg.sender,amount,payout);}
Executes a pre-authorised intent by burning one permit token, bypassing the vote entirely. Where executeByVotes asks "did this proposal pass", spendPermit asks only "does the caller hold a permit for exactly this action".
ATTRIBUTE
VALUE
Selector
0xa8841366
Parameters
The full intent, identical in shape to executeByVotes
Access
Public payable, nonReentrant; requires a permit balance for the derived id
FLAG
OBSERVATION
☒
No quorum, no timelock and no expiry apply. A permit granted once is a standing right until its count is exhausted or a later proposal reduces it
☒
The same op semantics apply, so a permit can authorise repeated delegatecall into DAO storage
△
An unspent permit is opaque by default — the intent preimage first appears only when it is spent, so unless the DAO republishes the parameters elsewhere, a granted-but-unused permit cannot be characterised by an outside observer
☑
Permit receipts are soulbound. transfer and transferFrom revert with SBT() for any id flagged in isPermitReceipt, so a permit cannot move through those transfer paths
◇
The permit must have been installed by setPermit, which is onlyDAO and therefore required a passing proposal or a summon-time init call
Two ways value leaves or enters the DAO without a per-transaction vote. spendAllowance lets a party the DAO has funded pull against a standing balance. buyShares lets anyone purchase newly minted or treasury-held shares or loot at a governance-set price.
ATTRIBUTE
VALUE
Selector
0xd74fc428 / 0x17f69222
Parameters
token/amount; or payToken, shareAmount, maxPay
Access
Public, both nonReentrant; buyShares is payable
FLAG
OBSERVATION
△
An allowance has no deadline and no rate limit. It is a standing claim the holder draws on whenever they choose
△
An allowance denominated in address(this) or address(1007) mints fresh shares or loot through _payout rather than transferring an existing balance, making it an open-ended dilution right
△
buyShares treats maxPay == 0 as "no maximum", which is also the natural default for an unset field. A buyer passing zero accepts whatever price is in storage when the transaction lands
☑
buyShares follows checks-effects-interactions — the sale cap is decremented before funds are pulled or tokens issued
☑
Excess ETH above cost is refunded on the ETH path; the ERC-20 path rejects any attached ETH outright
◇
cost = shareAmount * price performs no decimal scaling, so pricePerShare is denominated per whole share unit and callers must account for the 18 decimals themselves
chat appends a message to an on-chain log, gated on holding a badge. multicall batches several calls to the DAO itself in one transaction, and is the standard way a single proposal performs several configuration changes.
ATTRIBUTE
VALUE
Selector
0xde8caa1e / 0xac9650d8
Parameters
message text; or data array of encoded calls
Access
chat — badge holders only. multicall — public
FLAG
OBSERVATION
◇
Chat eligibility is badge possession, and badges track the seat board rather than share balance directly, so who may speak is decided by the seat allocation mechanics rather than by stake
◇
multicall uses delegatecall to address(this), which preserves msg.sender. It therefore grants no privilege by itself — onlyDAO still requires the caller to be the DAO
☑
The combination is what makes a single proposal able to change many settings: the DAO proposes a call to itself invoking multicall, and each inner call then sees msg.sender == address(this)
◇
Message text is stored in contract storage rather than emitted only as an event, so the log is readable by messages(uint256) without an archive node
Voting-power routing on the Shares contract. A holder may point their whole balance at one delegate, or divide it across as many as four in basis points. Delegation is non-custodial throughout — shares never move, only the checkpointed vote totals attributed to each delegate.
ATTRIBUTE
VALUE
Selector
0x5c19a95c / 0xb0fbd2c0 / 0x3649b58c
Parameters
delegatee; or parallel arrays of delegates and basis points
Access
Public — each holder controls only their own routing
FLAG
OBSERVATION
☑
Splits are validated to sum to exactly 10,000 basis points, contain no zero addresses, and contain no duplicates, with a hard maximum of four entries
☑
Reallocation is path-independent: _repointVotesForHolder computes old and new target allocations from the current balance and moves only the difference, so repeated delegation changes cannot accumulate rounding drift
☑
_targetAlloc assigns the remainder to the final delegate rather than flooring every entry, so the allocated total always equals the holder's balance exactly
◇
Delegation defaults to self. _delegates storing zero is interpreted as self-delegation, and _autoSelfDelegate materialises that on first balance change, so a holder need take no action to have voting power
◇
Setting a single delegate collapses any existing split, and the split is deleted rather than merged
△
Changing delegation after a proposal's snapshot block has no effect on that proposal, since castVote reads only getPastVotes at the snapshot
Deploys a complete DAO — a Moloch clone plus its Shares, Loot and Badges clones — and runs an arbitrary list of setup calls. This is the only path to an initialised Majeur DAO — init requires msg.sender == SUMMONER — and the factory had produced 56 of them at analysis, 59 by 2026-09-26.
ATTRIBUTE
VALUE
Selector
0xfec53795
Parameters
Name, symbol, URI, quorum bps, ragequittable flag, renderer, salt, initial holders and shares, and an initCalls array
Access
Public payable — permissionless
FLAG
OBSERVATION
△
initCalls execute with the new DAO as msg.sender, which satisfies onlyDAO. The summoner can therefore set every governance parameter, install permits and allowances, and mint tokens before any electorate exists to object
☑
Summoning is permissionless and the resulting DAO has no owner, so the factory operator retains no authority over DAOs it did not summon
◇
The clone address is deterministic — create2 over keccak256(abi.encode(initHolders, initShares, salt)) — so a DAO's address can be computed before deployment
◇
quorumBps, ragequittable and renderer are applied only when non-zero, so passing zero leaves the framework default rather than setting zero
Everything in this section is reachable only through onlyDAO. On Moloch that means the caller must be the DAO itself — a passing proposal executing against its own address, a spent permit, or a summon-time init call. On Shares, Loot and Badges it means the caller must be the owning Moloch clone.
The governance parameter surface. Each is a single-value setter with no timelock of its own beyond the one the proposal that calls it already passed through, and each takes effect immediately on the next state evaluation.
Changes apply retroactively to proposals already in flight, because state() reads current configuration rather than values captured at snapshot time. Lowering quorum can pass a proposal that was failing; raising it can strand one that was passing
△
setRagequittable(false) removes the exit right the framework is designed around. It is a single passing proposal away on any DAO
☑
setQuorumBps is bounded to 10,000, so quorum cannot be set above total supply
◇
Only supplySnapshot and snapshotBlock are captured per proposal. Every other gate is evaluated live
The three ways governance delegates future authority rather than exercising it directly. setPermit grants repeatable execution of one fixed intent, setAllowance grants a standing draw on a token, and setSale opens a share or loot sale at a fixed price.
ATTRIBUTE
VALUE
Selector
0x12374b04 / 0xda46098c / 0x48b7fef2
Parameters
Intent plus spender and count; spender, token and amount; or sale terms
Access
onlyDAO
FLAG
OBSERVATION
☒
None of the three carries an expiry. All persist until a later proposal changes them
△
setPermit sets the count absolutely rather than incrementally, minting or burning the difference against the spender's current balance, so revocation is setPermit(..., 0)
△
A sale with minting: true issues new shares, diluting existing holders at whatever price governance chose
◇
setSale requires a non-zero price, which prevents an accidental free mint through this path
◇
A permit's intent is revealed on-chain only when it is spent. An unspent permit is a stored hash whose scope can be established only by recomputing it from parameters published elsewhere
bumpConfig invalidates every pending proposal at once by changing the salt every id is derived from. It is the only revocation mechanism for approved-but-unexecuted proposals, and it is indiscriminate
△
setTransfersLocked can freeze shares and loot for everyone except transfers to and from the DAO, which converts a liquid position into a locked one by vote
△
setRenderer accepts any address without validation, including one with no code, which would make tokenURI and contractURI revert
◇
batchCalls performs external calls and requires each to succeed, so one failure reverts the batch
◇
Both live DAOs read config == 0, so neither has ever bumped
The satellite mutation surface. Shares and Loot expose mint and burn to their owning DAO only. Badges exposes seat management to the DAO, and onSharesChanged is the hook that keeps the seat board synchronised with share balances.
The DAO binding is written once in init behind a zero-address check and has no setter, so a satellite cannot be re-pointed at a different governor
☑
Shares.mintFromMoloch calls _autoSelfDelegate before applying the voting delta, so newly minted shares carry voting power without the recipient taking any action
△
Badges.onSharesChanged grants any free seat without comparing balances, and only applies the cutline once all 256 are taken. The board is therefore not the top 256 holders
◇
Badge eviction is sticky — a seated holder keeps their seat through balance decreases until a newcomer strictly exceeds the current minimum, or until their share balance reaches zero, which releases the seat
◇
Badges.onSharesChanged guards its uint96 conversion with an explicit Overflow() check, which the equivalent conversion in Moloch.castVote does not
functionmintFromMoloch(addressto,uint256amount)publicpayableonlyDAO{_mint(to,amount);_autoSelfDelegate(to);_afterVotingBalanceChange(to,int256(amount));}functiononSharesChanged(addressa)publicpayableonlyDAO{unchecked{Shares_shares=Moloch(DAO).shares();uint256bal256=_shares.balanceOf(a);require(bal256<=type(uint96).max,Overflow());uint96bal=uint96(bal256);// seat insertion, update, or eviction — full body in the verified source on Etherscan}}
One-time initialisation on each clone. Moloch.init is callable only by the SUMMONER immutable; the satellites are callable only while their DAO field is still zero.
ATTRIBUTE
VALUE
Selector
0x03dc0dc5 (Moloch), 0x371aa158 (Shares), 0xe1c7392a (Loot and Badges)
Parameters
Varies by contract
Access
SUMMONER for Moloch; first caller for the satellites, which in practice is always the Moloch clone within the same transaction
FLAG
OBSERVATION
☑
Each satellite guards with require(DAO == address(0)), so the require(DAO == address(0)) guard blocks re-initialisation after the summon transaction
☑
Moloch.init requires msg.sender == SUMMONER, and that immutable is baked into the implementation bytecode, so no address other than the factory can initialise any clone
△
The initCalls loop runs arbitrary calls with the DAO as sender before any electorate exists
◇
Satellite clone addresses are derived with bytes32(bytes20(address(this))) as salt, making them deterministic from the DAO address
functioninit(stringcalldataorgName,stringcalldataorgSymbol,stringcalldataorgURI,uint16_quorumBps,bool_ragequittable,address_renderer,address[]calldatainitHolders,uint256[]calldatainitShares,Call[]calldatainitCalls
)publicpayable{require(msg.sender==SUMMONER,Unauthorized());require(initHolders.length==initShares.length,LengthMismatch());// metadata and flags applied when non-zerobytes32_salt=bytes32(bytes20(address(this)));badges=Badges(_badges=_init(badgesImpl,_salt));Badges(_badges).init();shares=Shares(_shares=_init(sharesImpl,_salt));Shares(_shares).init(initHolders,initShares);loot=Loot(_loot=_init(lootImpl,_salt));Loot(_loot).init();for(uint256i;i!=initCalls.length;++i){(boolok,)=initCalls[i].target.call{value:initCalls[i].value}(initCalls[i].data);require(ok,NotOk());}}
The checkpoint reads that voting depends on. getVotes returns current delegated weight; the two past-lookups binary-search the checkpoint arrays for a historical block.
ATTRIBUTE
VALUE
Selector
0x9ab24eb0 / 0x9556de73 / 0xedb8ee5a
Parameters
Account and block number
Access
Public view on Shares
FLAG
OBSERVATION
☑
Both past-lookups reject blockNumber >= block.number with BadBlock(), which prevents querying a block whose checkpoints are not yet final
☑
_writeCheckpoint overwrites rather than appends when a checkpoint already exists for the current block, so within-block changes collapse to a single final value and cannot be read at an intermediate state
◇
getVotes reads the last checkpoint directly rather than searching, so the proposal threshold check in openProposal is a current-state read
◇
Loot has no equivalent — it carries no checkpoints and contributes nothing to voting weight or to getPastTotalSupply
Badge board reads. getSeats walks the occupied bitmap and returns every filled seat with its cached holder and balance.
ATTRIBUTE
VALUE
Selector
0xa997c66a / 0x6352211e / 0xb9cd92c3 / 0x01ffc9a7
Parameters
Seat id or holder address
Access
Public view on Badges
FLAG
OBSERVATION
☑
getSeats sizes its output by popcount before filling it, so it returns exactly the occupied seats with no empty padding
△
supportsInterface advertises ERC-721 and ERC-721 Metadata but the collection is soulbound — transferFrom always reverts with SBT(). It does not advertise ERC-5192, so an indexer classifying it from interface ids alone would render transfer controls that cannot work
◇
The seat balance stored in seats is a cache written on onSharesChanged, not a live read, so it can lag the true share balance if the hook is not triggered
The balance of the view surface is auto-generated accessors over public state, plus metadata passthroughs. None performs computation beyond a storage read or a delegation to the renderer.
FUNCTION
CONTRACT
RETURNS
getProposalCount() / proposalIds(uint256)
Moloch
Length of the per-DAO proposal registry, and the id at each index
getMessageCount() / messages(uint256)
Moloch
Length of the per-DAO chat log, and the message at each index