Skip to content

Methodology

DISCLAIMER // NFA // DYOR

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.

⊙ generated by robots | curated by humans

METADATA
Contract Address 0x0000006d...32a9a8 (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-08-09

Overview

This analysis began from a discrepancy rather than from a contract address. The zFi interface describes bonding zOrgz NFTs to add "conviction" to token listings and sorting those listings by conviction strength. The TokenList registry — deployed three days before this contract and analysed the day before this analysis — contains no such mechanism; its only ordering field, rank, is an onlyOwner setter under a 2-of-3 multisig. Neither the ZORG token nor the zOrgz collection contains the word "bond" or "conviction" anywhere in its verified source.

Locating the real implementation meant working backwards from behaviour to bytecode. Scanning recent transactions from z0r0z's canonical Externally Owned Account (EOA) surfaced repeated calls to allocate(uint256,uint256,uint256) against an address that had not appeared in any prior DNZN analysis. That address resolved to a verified contract named ZorgConviction, and its ITokenListView interface — one method, isListed — settled the relationship to the registry immediately.

From there the work was ordinary source review with on-chain verification at every step. The source is verified as an exact match, so no decompilation was needed, but verified source establishes only what the code says, not what state it holds. Every structural claim in these documents was checked against a live read: the constructor arguments against the seven immutable getters, the storage layout against a raw cast storage dump rather than a compiler-derived guess, the accounting invariants against the contract's actual ZORG and ETH balances, and the participant tables against a full replay of the event log.

The source carries developer comments explaining why particular defences exist — the ragequit reasoning behind the escrow invariant, the front-running scenario behind the maturity gate on decreaseBond, the time-homogeneity argument behind exact halving. Per the analysis rules, purpose was derived from code, not from comments; the comments were used to decide where to look harder, and each claimed defence was then read directly in the code that implements it.

One finding was not visible in the contract at all. Diffing the original renderer against its replacement — both verified, one day apart — revealed a warning added about eternalizing an unlocked bond. Tracing that warning back into the source showed a one-way interaction between eternalize and selectLockTier that no single function reveals on its own, and confirmed on-chain that one receipt had already fallen into it.

Thought Process

mindmap
  root((ZorgConviction))
    Discovery
      Interface claims conviction ranking
      TokenList has no such mechanism
        rank is onlyOwner, multisig
      Grep ZORG and zOrgz sources
        zero hits for bond or conviction
      Trace deployer EOA transactions
        allocate calls to unknown address
      Resolve to verified ZorgConviction
    Phase 0 Obtain
      cast code, 20324 bytes
      Etherscan getsourcecode
        exact match, solc 0.8.36
        6 files, 873-line core
      Creation tx is a factory call
        CREATE2 vanity, salt extracted
      Decode 8 constructor arguments
    Phase 1 Discovery
      Standalone Solady ERC721
        no proxy, no upgrade path
      Map 7 external dependencies
        TokenList read-only, isListed
        zorgz and shares escrowed
        weiNames holds the role name
        renderer and receiptArt mutable
      Catalog 81 ABI entries
        35 declared in own source
      Derive storage from declaration order
        verify with raw cast storage
    Phase 2 Deep Dive
      Bond lifecycle
        bondZorgz to allocate to unbond
        receipt id equals escrowed zOrgz id
      Conviction math
        exponential half-life
        exact composition defeats poking
        asymptotic bound at 2x weight
      Loyalty accounting
        reward index scaled 1e27
        never executed on mainnet
      Access paths
        onlyDAO immutable
        exec role is a transferable name
    Phase 3 Risk
      Centralization
        arbitrary execute, two invariants
        zorgz denylist vs shares assertion
      Economic
        eternalize is forfeiture
        tier-0 eternal caps rate forever
        unbounded minimumBond
      External dependency
        conviction has no on-chain effect
        share lock would trap escrow
      Verify solvency independently
        ETH balance equals liabilities
        ZORG balance equals bonded weight
    Phase 4 Documentation
      Six standard documents
      Diff both renderers
      Reconstruct positions from logs
      Cross-link TokenList analysis

Verification Guide

All on-chain reads were performed against Ethereum mainnet at block 25,714,806 using Foundry cast and the Etherscan v2 API. Every figure comes from a public RPC endpoint or the Etherscan API rather than from an aggregator's interpretation. Log-derived figures — the participant tables and the event counts — depend on the completeness of the Etherscan log index, and were cross-checked against the per-id getters.

External Resources

RESOURCE KNOWLEDGE PROVIDED
Etherscan — ZorgConviction Verified source, ABI, compiler settings, constructor arguments
Etherscan v2 API Verified source, the Application Binary Interface (ABI), creation record, event logs, and account transaction lists — via getsourcecode, getabi, getcontractcreation, getLogs, txlist
Foundry Book — cast call, storage, code, codesize, tx, sig, sig-event, balance
Solady ERC721 Why ownership records do not occupy sequential storage slots
Solady FixedPointMathLib Saturation behaviour of powWad, overflow bounds on divWad
EIP-1167 Identifying the DAO and ZORG token as 45-byte minimal proxies
EIP-4906 The metadata-update events this contract does not emit
EIP-170 Code-size ceiling; context for the rendering split
DNZN — TokenList Contract Analysis Listing id derivation, rank semantics, the registry's multisig owner
DNZN — z0r0z Entity Profile Deployer attribution and prior deployment patterns

Commandline Tools

Tip

Commands below use cast from the Foundry Toolkit. To run the commands below, you must set the RPC URL environment variable:

export ETH_RPC_URL=https://eth.llamarpc.com

Locate the Contract from Observed Behaviour

The starting point was a claim in an interface with no matching mechanism in the contract it describes. Confirming the absence came first, then finding where the mechanism actually lives.

# CONFIRM THE REGISTRY'S ORDERING FIELD IS ADMIN-ONLY, NOT STAKE-DRIVEN

cast call 0x0000006013dF75A31678B786061C2B54bf531524 "owner()(address)"

# CONFIRM NEITHER THE TOKEN NOR THE NFT IMPLEMENTS BONDING

curl -s "https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode\
&address=0x00a6bA94BBb5474725515De88fE04F854f2dCb12&apikey=$ETHERSCAN_API_KEY" \
  | jq -r '.result[0].SourceCode' | grep -ci "conviction\|bond"

# TRACE RECENT ACTIVITY FROM THE STACK'S CANONICAL DEPLOYER EOA

curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist\
&address=0x1C0Aa8cCD568d90d61659F060D1bFb1e6f855A20&startblock=25690000&sort=desc\
&apikey=$ETHERSCAN_API_KEY" | jq -r '.result[] | "\(.to) \(.functionName)"' | sort -u

Establish Contract Identity and Provenance

# RUNTIME SIZE AGAINST THE EIP-170 CEILING

cast codesize 0x0000006d936BA3653b8854490E16e782cd32a9a8

# CREATION RECORD — CREATOR, TX, BLOCK, TIMESTAMP

curl -s "https://api.etherscan.io/v2/api?chainid=1&module=contract\
&action=getcontractcreation&contractaddresses=0x0000006d936BA3653b8854490E16e782cd32a9a8\
&apikey=$ETHERSCAN_API_KEY" | jq -r '.result[0]'

# FIRST TRANSACTION THE DEPLOYER EOA EVER SAW — THE FUNDING SOURCE

curl -s "https://api.etherscan.io/v2/api?chainid=1&module=account&action=txlist\
&address=0xacfba7ce872c6ead99d535586f84b0d68ade4082&sort=asc\
&apikey=$ETHERSCAN_API_KEY" | jq -r '.result[0] | "\(.from) -> \(.to) \(.value)"'

# CONFIRM THE SAME EOA DEPLOYED THE REGISTRY THIS CONTRACT READS

curl -s "https://api.etherscan.io/v2/api?chainid=1&module=contract\
&action=getcontractcreation&contractaddresses=0x0000006013dF75A31678B786061C2B54bf531524\
&apikey=$ETHERSCAN_API_KEY" | jq -r '.result[0].contractCreator'

Verify Immutables Against Constructor Arguments

The constructor takes eight arguments and every one has a public getter. Comparing the two catches any value that has been changed since deployment — which is how the renderer swap was found.

# EACH GETTER SHOULD MATCH ITS CONSTRUCTOR ARGUMENT — EXCEPT renderer, WHICH DOES NOT

ZC=0x0000006d936BA3653b8854490E16e782cd32a9a8
for f in "dao()(address)" "shares()(address)" "zorgz()(address)" "weiNames()(address)" \
         "tokenList()(address)" "renderer()(address)" "receiptArt()(address)" \
         "halfLife()(uint64)"; do
  echo "$f -> $(cast call $ZC "$f")"
done

Verify the Storage Layout Rather Than Deriving It

Declaration order gives a predicted layout. Reading the slots confirms it, and catches packing that is easy to get wrong by hand — here, four of five booleans sharing slot 1 while the fifth spills into slot 2 alone.

# RAW SLOT DUMP FOR THE DECLARED RANGE

for i in $(seq 0 20); do
  echo "slot $i: $(cast storage 0x0000006d936BA3653b8854490E16e782cd32a9a8 $i)"
done

# CROSS-CHECK PACKED SLOT 1 AGAINST THE GENERATED GETTERS

cast call 0x0000006d936BA3653b8854490E16e782cd32a9a8 "receiptArt()(address)"
cast call 0x0000006d936BA3653b8854490E16e782cd32a9a8 "halfLife()(uint64)"
cast call 0x0000006d936BA3653b8854490E16e782cd32a9a8 "domainClaimed()(bool)"
cast call 0x0000006d936BA3653b8854490E16e782cd32a9a8 "paused()(bool)"

Verify the Accounting Invariants Independently

The contract asserts two invariants inside _execute. Both can be checked from outside, which is a stronger test than reading the assertion in the source.

# ZORG SIDE: BONDED WEIGHT SHOULD EQUAL THE CONTRACT'S ACTUAL TOKEN BALANCE

ZC=0x0000006d936BA3653b8854490E16e782cd32a9a8
cast call $ZC "totalLoyaltyWeight()(uint256)"
cast call 0x00a6bA94BBb5474725515De88fE04F854f2dCb12 "balanceOf(address)(uint256)" $ZC

# ETH SIDE: HELD BALANCE SHOULD COVER THE SUM OF ALL FOUR RECORDED LIABILITIES

cast balance $ZC
cast call $ZC "totalEthPrincipal()(uint256)"
cast call $ZC "loyaltyRewardReserve()(uint256)"
cast call $ZC "treasuryEth()(uint256)"
cast call $ZC "totalEthCredits()(uint256)"

Reconstruct Participants and Positions from Logs

The contract exposes no enumeration, so the set of receipts and the set of supported listings both have to be replayed from events.

# FULL EVENT LOG FROM DEPLOYMENT

curl -s "https://api.etherscan.io/v2/api?chainid=1&module=logs&action=getLogs\
&address=0x0000006d936BA3653b8854490E16e782cd32a9a8&fromBlock=25694754&toBlock=latest\
&apikey=$ETHERSCAN_API_KEY" > logs.json

# RESOLVE TOPIC HASHES TO EVENT NAMES

cast sig-event "Allocated(uint256,uint256,address,uint256,uint256)"
cast sig-event "Eternalized(address,uint256,uint256,uint256)"

# CONFIRM EACH RECONSTRUCTED POSITION AGAINST THE PER-ID GETTERS

for r in 9953 4205 339 9241 4816 77 156; do
  cast call $ZC "bondedWeight(uint256)(uint256)" $r
  cast call $ZC "allocatedByBond(uint256)(uint256)" $r
  cast call $ZC "eternalBonds(uint256)(bool)" $r
done

Confirm the Relationship to the Registry

The structural claim the rest of the analysis rests on is that conviction cannot move TokenList's ordering. It is checked from both sides.

# THE ONLY CALL THIS CONTRACT MAKES INTO THE REGISTRY

cast call 0x0000006013dF75A31678B786061C2B54bf531524 "isListed(uint256)(bool)" 0

# THE REGISTRY'S OWN ORDERING FIELD IS OWNED BY A DIFFERENT ADDRESS ENTIRELY

cast call 0x0000006013dF75A31678B786061C2B54bf531524 "owner()(address)"
# -> 0x006CD14F36F65eCbB29b2519cCBe63A0DC8549F2  (2-of-3 multisig, not the Moloch DAO)

cast call 0x0000006d936BA3653b8854490E16e782cd32a9a8 "dao()(address)"
# -> 0x5E58BA0e06ED0F5558f83bE732a4b899a674053E  (Moloch clone, no role at the registry)

Identify the Governance Chain

# THE ADMIN IS A 45-BYTE MINIMAL PROXY, NOT AN EOA OR A SAFE

cast code 0x5E58BA0e06ED0F5558f83bE732a4b899a674053E
cast code 0x00a6bA94BBb5474725515De88fE04F854f2dCb12

# THE CONSTRUCTOR'S SHARE-LOCK ASSUMPTION, CHECKED TODAY

cast call 0x00a6bA94BBb5474725515De88fE04F854f2dCb12 "transfersLocked()(bool)"

# STAKE CONCENTRATION IN THE TOKEN THAT GOVERNS BOTH

cast call 0x00a6bA94BBb5474725515De88fE04F854f2dCb12 "totalSupply()(uint256)"
cast call 0x00a6bA94BBb5474725515De88fE04F854f2dCb12 "balanceOf(address)(uint256)" \
  0x1C0Aa8cCD568d90d61659F060D1bFb1e6f855A20

Diff the Replaced Renderer

Both renderers are verified, so the swap can be examined directly instead of being taken on trust.

# FETCH BOTH SOURCES AND EXTRACT THE RENDERER FILE FROM EACH STANDARD-JSON BUNDLE

for a in 0x000000115b1b95b9e04128a2bcd9ed9d24ab141c \
         0x0000006b980ae5e796B3eF484e767993d0E29979; do
  curl -s "https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode\
&address=$a&apikey=$ETHERSCAN_API_KEY" | jq -r '.result[0].SourceCode' > "$a.json"
done

# THEN DIFF src/dao/ZorgConvictionRenderer.sol FROM EACH BUNDLE

Limitations

Verified source proves compilation, not absence of defects

An exact-match verification proves the deployed bytecode was compiled from the published source. It proves nothing about whether that source is free of defects. We read the code and reasoned about it; we did not fuzz it, formally verify it, or run it against adversarial inputs.

The loyalty distribution path has no production history

_distributeEarlyExitTax, the reward index, the dust-reconciliation arithmetic, and _sweepEmptyLoyaltyReserve have never executed on mainnet, and loyaltyRewardPerWeight is still zero. No early exit has occurred, so our reading of the contract's densest arithmetic rests on the source alone, with no observed behaviour behind it.

Upstream governance was not analysed

The dao immutable points at a Moloch clone. We confirmed it is an ERC-1167 proxy, identified its implementation, and counted its proposals. We did not analyse the Moloch implementation itself, so statements about what the DAO can do at this contract are grounded in this contract's code, while statements about how hard it is for someone to make the DAO do it are not.

Three days of history

Seven bonds across four addresses, with no exits. Nothing here has been tested by a contested vote, a market move, a delisting, or an adversary.

The zOrgz denylist finding has no demonstrated exploit path

We did not identify a working path for a DAO execute to move escrowed zOrgz. What we record is a structural asymmetry between how the two escrowed assets are protected, not a demonstrated exploit.


Assumptions

Listing id semantics come from the registry, not from this contract

allocate treats listingId as an opaque number and checks only isListed. Our mapping of ids to tokens — uint256(uint160(token)) for Ethereum tokens, a wider hashed id for foreign listings — comes from the TokenList analysis and was confirmed by calling isListed and json on the registry for each id observed in the logs.

Effective weight was reconstructed, not read

The contract stores support.weight already boosted and does not expose the per-receipt contribution to a listing. We derived each contribution by combining allocationOf with the receipt's receiptLocks.boostBps, then confirmed the sums reproduce the stored listingState weight exactly for all eight supported listings.

The renderer is treated as the reference interface

html() is described in the source as serving the canonical TokenList view, and it is what the public interface presents. We treat it as the reference client on that basis; we did not measure which client holders actually use. Any other client reading the contract directly would behave differently, and nothing on-chain constrains it.


Independent Verification

Highest-value checks to run without relying on these documents, in rough order of how much of the analysis each one confirms.

The registry relationship

Call owner() on TokenList and dao() on ZorgConviction. They are different addresses with no overlapping role. Then search the verified ZorgConviction source for tokenList — six occurrences: the declaration, three in the constructor, one that passes the address to the renderer, and exactly one call into the registry, which is isListed.

The solvency invariants

Compare totalLoyaltyWeight against the contract's ZORG balance, and cast balance against the sum of the four ETH liability counters. Both matched exactly at our snapshot block. If they ever diverge, something has happened that this analysis did not anticipate.

The tier-0 eternalization trap

Read eternalBonds(9953) and receiptLocks(9953). The first returns true, the second returns a boostBps of 10,000. Then read selectLockTier in the source and confirm its first guard. The position cannot be changed.

The emergency role

Read rolesInstalled(). While it returns false, emergencyPause and emergencyExecute are unreachable. If it ever returns true, call weiNames.ownerOf(execZorgWeiId) to find out who holds the credential, and check whether it has moved since.

The share-lock assumption

Read transfersLocked() on the ZORG token. It is false. If it ever becomes true, existing bonds lose their exit path, and no function on this contract can restore it.


Document Verification

The drafted documents were re-checked before publication in five independent passes, each with a narrow scope and each reading from primary sources rather than from the draft. Running them separately matters: a claim can be correctly formatted, non-redundant, neutral in tone, free of filler, and still be factually wrong, and a single reviewer holding all five concerns at once reliably drops the last one.

DIMENSION WHAT IT CHECKS CHECKED AGAINST
Facts Addresses, block heights, receipt and listing figures, dates and elapsed intervals, compiler version, selector values, source line counts, every quantitative claim Live chain state via cast at a pinned block, Etherscan API v2, the cached verified source and event log, and the two verified renderer sources
Standards Address display format, explorer links on every on-chain reference, table casing, section separators, icon semantics, glossary term wrapping, acronym expansion, nav registration CLAUDE.md, docs/glossary.md, mkdocs.yml, and the templates under .claude/skills/research-ethereum-contract/
Redundancy Duplicated tables and paragraphs across the six documents, and the same claim stated with different values in two places The document set itself
Voice Superlatives asserted without a verification clause, opinion adjectives, endorsements of the design, speculation about intent or users, absolute security assertions CLAUDE.md writing standards and the epistemic-humility rules in the analysis skill
Filler Rhetorical padding — meta-narration, telegraphed structure, formulaic emphasis, repeated antithesis CLAUDE.md anti-patterns

Every disagreement the fact pass raised was re-derived from chain or source before any edit was made, on the assumption that the checker is as capable of being wrong as the draft. Where two passes reached different conclusions about the same figure, the underlying event log was decoded directly to settle it. A claim was only changed where the contradicting value could be reproduced from a command in the Verification Guide above.


Token Cost Breakdown

PHASE DESCRIPTION TOKENS
Discovery Tracing the conviction mechanism from interface claim to contract address 25 tok
Phase 0 Obtain the Contract 15 tok
Phase 1 Discovery & Understanding 30 tok
Phase 2 Deep Dive Analysis 45 tok
Phase 3 Risk & Trust Analysis 30 tok
Phase 4 Documentation Generation 65 tok
Phase 5 Document Verification 175 tok
TOTAL Complete Contract Analysis 385 tok

Note: Token costs are estimates based on typical conversation lengths and complexity. Actual consumption may vary by ±10-15% depending on API responses, iterative refinement, and verification steps.