Contract Analysis
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
Analysis Date: 2026-04-13
Metadata
Primary Contract
| PROPERTY | VALUE |
|---|---|
| Contract Address | 0x00000000...768E22 (etherscan) |
| Network | Ethereum Mainnet |
| Contract Type | Standalone |
| Deployment Date | 2026-03-05 08:21:59 UTC |
| Deployment Block | 24590040 |
| Contract Creator | 0x1C0Aa8cC...855A20 (etherscan) |
| Creation TX | 0xa49ef903...54d046 (tx) |
| Compiler Version | Solidity 0.8.34 (optimizer on, 9,999,999 runs, Cancun EVM) |
| Total Functions | 12 (4 user/receive, 7 admin, 3 view — staked(), slipBps(), owner(), plus auto-generated target(), asset(), holder()) |
| External Contract Dependencies | 1 hardcoded (Lido stETH) + up to 3 configurable (swap target, condition asset, condition holder) |
| Upgrade Mechanism | ☒ None — Not Upgradable |
| Verification Status | ☑ Verified on Etherscan (Exact Match) |
| Audit Status | △ Reported "no valid findings" from a Zellic AI-assisted scan (README claim) — no traditional audit report observed |
Related Addresses
| TYPE | ADDRESS | NOTES |
|---|---|---|
| Owner | 0x1C0Aa8cC...855A20 (etherscan) |
EOA; also deployer of zRouter and zQuoter |
| Deployer Factory | 0x5c2271fd...d21822 (etherscan) |
CREATE2 factory used to mine the vanity address |
| Lido stETH | 0xae7ab965...d7fE84 (etherscan) |
Hardcoded constant — staking asset |
| Swap Target (current) | 0xDC24316b...f67022 (etherscan) |
Curve stETH/ETH pool (Vyper); approved to pull unlimited stETH |
| Condition Asset (current) | 0x00a6bA94...2dCb12 (etherscan) |
ZORG ("zOrg Shares") ERC-20 |
| Condition Holder (current) | 0xd14a07B5...5013aa (etherscan) |
Gnosis Safe (impl 0x41675C09...C7461a (etherscan)); current ZORG balance ≈ 27,811.6 |
Executive Summary
The LidoHarvester is a minimal (~100 line) standalone contract for harvesting yield from Lido liquid staking. A single owner deposits ETH or stETH; the contract stakes any ETH into Lido to receive stETH, and a running counter called staked records the principal basis at deposit time. Because stETH is a Rebasing Token, the contract's stETH balance grows over time independent of staked. The delta between balance and basis is the yield.
A permissionless harvest(bytes data) function lets anyone call out to a pre-approved swap target with arbitrary calldata, provided that (a) after the call the ETH balance has increased by at least yield * (10000 - slipBps) / 10000, and (b) the stETH balance is still within 2 wei of staked. The harvester currently points target at the Curve stETH/ETH pool, which implies the harvest is meant to execute a stETH→ETH swap of exactly the yield.
A companion withdraw(to, val, data, minGain) function — restricted to the owner — lets the owner spend harvested ETH on an arbitrary call, optionally gated by a minGain increase in a configured holder's balance of a configured asset. In the contract's current configuration, that gate reads as: "when the owner pushes ETH out, the zOrg Safe must end up with at least minGain more ZORG shares." Combined with target = Curve stETH/ETH, the observable flow is Lido yield → ETH → ZORG → zOrg Safe.
The contract has no pause, no timelock, no multisig on the harvester itself, and no upgrade path. It is immutable, but the owner retains broad discretion: the owner can change target, asset, holder, slippage, ownership, approve unlimited stETH to any address in setTarget, and call withdrawStETH to send the entire principal to any recipient. Trust in this contract is effectively trust in the single owner EOA. Given the stated design and the deployer's pattern of self-custodial tooling, this appears intentional.
Architecture
graph TB
Owner[Owner EOA<br/>0x1C0Aa8cC...855A20]
subgraph Harvester["LidoHarvester<br/>0x00000000...768E22"]
Receive[receive<br/>ETH → stake]
Stake[stake<br/>ETH → stETH]
Deposit[deposit<br/>stETH in]
Harvest[harvest<br/>yield → ETH]
Withdraw[withdraw<br/>ETH out]
WithdrawSt[withdrawStETH<br/>principal out]
end
Lido[Lido stETH<br/>0xae7ab965...d7fE84]
Target[target<br/>Curve stETH/ETH<br/>0xDC24316b...f67022]
Asset[asset<br/>ZORG token<br/>0x00a6bA94...2dCb12]
Holder[holder<br/>zOrg Safe<br/>0xd14a07B5...5013aa]
Owner -->|ETH| Receive
Owner -->|stETH| Deposit
Owner -->|admin| Stake
Owner -->|calldata| Harvest
Owner -->|calldata + val| Withdraw
Owner -->|amt, to| WithdrawSt
Receive -->|submit| Lido
Stake -->|submit| Lido
Deposit -->|transferFrom| Lido
Harvest -->|swap yield stETH→ETH| Target
Target -->|ETH back| Harvester
Withdraw -->|"to.call{value}(data)"| Asset
Withdraw -.->|measured balance| Holder
Anyone[Anyone] -->|harvest| Harvest
System Overview
The LidoHarvester is a single-owner vault that holds Lido stETH as its principal position and treats any rebasing growth as harvestable yield. Its three operational primitives are:
- Stake: accept ETH (via
receive()orstake()) or stETH (viadeposit()), record the resulting stETH balance delta intostaked. - Harvest: anyone can call
harvest(data)to invoketarget.call(data); the contract requires that ETH balance grow by at leastyield * (1 - slipBps/10000)and thatstETHbalance stay within 2 wei ofstaked. - Spend: only the owner can call
withdraw(to, val, data, minGain)to push ETH out via an arbitrary call, optionally requiring that a configuredholder's balance of a configuredassetincrease by at leastminGain.
Notable observations:
- The contract does not implement an ERC-20 vault share token. There are no depositor shares and no pro-rata redemption logic. There is one depositor: the owner.
- The contract has no pause, no timelock, no multisig.
- The stETH address is a compile-time constant — the contract is not portable to Lido L2 deployments or other liquid-staking tokens without a redeploy.
harvest()is the only function anyone can call. All other state-changing functions are owner-gated, exceptdeposit()andreceive()which permit any account to contribute principal (but attribute no claim on it).
Design Patterns Used
- Basis-counter yield accounting:
stakedrecords principal; yield is derived asbalanceOf(this) - staked. Identical pattern to ERC-4626 share-price tracking but without shares because there is only one beneficiary. - Transient storage reentrancy flag (EIP-1153):
tstore(0, 1)duringharvest()so that if the swaptargetsends ETH back into this contract, the fallbackreceive()early-returns instead of re-staking that ETH into Lido (which would inflate the staked balance mid-harvest). - Packed storage:
slipBps(uint16) andowner(address) share slot 1, saving one SSTORE on each ownership or slippage change. - CREATE2 vanity deployment: deployed via factory so that
msg.sender == factory, buttx.origin == deployer. The constructor usestx.originas the initial owner to survive the factory indirection. - Infinite approval to mutable
target:setTarget(new)revokes approval on the old target (approve(old, 0)) and grantstype(uint256).maxto the new one. The currenttargetis the Curve stETH/ETH pool and holds unlimited spend authority over the harvester's stETH.
Access Control
Roles & Permissions
| ROLE | ASSIGNED BY | REVOKABLE | CALL COUNT |
|---|---|---|---|
| Owner | tx.origin at construction; later transferOwnership |
Yes — transferOwnership |
Unlimited |
| Keeper (anyone) | N/A — permissionless | N/A | Unlimited |
| Depositor (anyone) | N/A — permissionless | N/A | Unlimited |
Permission Matrix
| FUNCTION | OWNER | KEEPER | DEPOSITOR | ANYONE |
|---|---|---|---|---|
receive() (send ETH) |
☑ | ☑ | ☑ | ☑ |
deposit(amt) |
☑ | ☑ | ☑ | ☑ |
harvest(data) |
☑ | ☑ | ☑ | ☑ |
stake(amt) |
☑ | ☒ | ☒ | ☒ |
withdraw(to, val, data, minGain) |
☑ | ☒ | ☒ | ☒ |
withdrawStETH(to, amt) |
☑ | ☒ | ☒ | ☒ |
setTarget(addr) |
☑ | ☒ | ☒ | ☒ |
setSlippage(bps) |
☑ | ☒ | ☒ | ☒ |
setCondition(asset, holder) |
☑ | ☒ | ☒ | ☒ |
transferOwnership(addr) |
☑ | ☒ | ☒ | ☒ |
Time Locks & Delays
| ACTION | TIME LOCK | CAN CANCEL | PURPOSE |
|---|---|---|---|
| Transfer ownership | ☒ None | N/A | Immediate — single-step transfer |
| Change target (re-grants infinite stETH approval) | ☒ None | N/A | Immediate |
| Change condition (asset + holder) | ☒ None | N/A | Immediate |
| Change slippage | ☒ None | N/A | Immediate; bounded to ≤10000 bps |
| Withdraw ETH | ☒ None | N/A | Immediate |
| Withdraw stETH principal | ☒ None | N/A | Immediate |
Economic Model
Funding Sources & Sinks
Inflows:
- ETH via
receive()— staked into Lido immediately;stakedcounter incremented by resulting stETH balance delta. - stETH via
deposit(amt)— pulled viatransferFrom;stakedincremented by actual balance delta. - ETH via
stake(amt)(owner-only) — stakes either a specific amount or the full contract ETH balance;stakedincremented. - ETH returned from the swap
targetduringharvest()— not tracked instaked(it is yield-equivalent ETH).
Outflows:
- stETH via
withdrawStETH(to, amt)— owner pulls principal directly;stakeddecremented. - stETH via
setTargetinfinite approval — any address that becomestargetcan pull all stETH under the harvester's balance viatransferFrom. Effectively a custodial grant. - ETH via
withdraw(to, val, data, minGain)— owner calls an arbitrary address with arbitrary calldata and any ETH in the contract. - stETH consumed during
harvest()— thetargetpulls up to the yield amount; the contract re-checks that stETH balance is still ≥staked - 2after the call.
Economic Invariants
The contract attempts to enforce the following on each harvest():
- ETH gain ≥
yield * (10000 - slipBps) / 10000— the swap must deliver at least the expected ETH for the yield, minus allowed slippage. Note this compares pre/post contract ETH balance, not a price oracle. - Remaining stETH ≥
staked - 2— the swap is not allowed to consume principal, with a 2-wei tolerance to accommodate Lido's known 1-wei rounding per transfer.
The contract does not enforce that the target.call(data) actually performs a stETH→ETH swap. The economic safety relies on those two balance deltas alone. A target that returned the right amount of ETH from any source (e.g., paid in by the caller) would satisfy the invariants.
Fee Structure
No explicit protocol fees. slipBps is a slippage allowance on the ETH return, not a fee paid to any party. Any ETH above the slippage floor is captured by the contract.
Summary of Observations
The LidoHarvester is a compact, purpose-built yield harvester. The on-chain fingerprint is consistent with the stated README: a single-depositor vault that converts Lido rebase yield to ETH and then to some destination (in its current configuration, ZORG shares delivered to the zOrg Gnosis Safe). The following stand out:
Design choices that appear deliberate and self-consistent:
- ☑
stakedis a simple scalar rather than a share ledger — the contract never pretends to support multiple LPs, so it does not need share accounting. - ☑ Transient storage reentrancy flag (
tstore(0, 1)) is the right primitive for this shape — cheaper than a storage SLOAD/SSTORE and automatically cleared at transaction end. - ☑ The post-harvest invariant
stETH + 2 ≥ stakedcaptures Lido's known per-transfer rounding loss. - ☑
harvest()is permissionless, which enables keeper bots. The invariant-gated design means a malicious keeper cannot cause loss beyond the configured slippage. - ☑ Infinite approval is revoked on the old target in
setTarget, reducing lingering spend authority when the owner rotates venues. - ☑ No upgrade path — the contract is immutable once deployed. Owner rotation is possible but logic is fixed.
Trade-offs and trust assumptions:
- △ The owner is the single trust anchor.
withdrawStETHcan drain the entire principal,withdrawcan push arbitrary ETH to any address with any calldata, andsetTargetgrants unlimited stETH approval to a new address. This is appropriate for a self-custodial tool; it is not suitable as a pooled product without further controls. - △
targetholds an unlimited stETH approval while set. A compromised or malicious target (or a bug in the target's swap logic) could drain stETH up to the full balance. The post-harvest invariant blocks this only when harvesting — it does not prevent the target from independently pulling stETH via its approval outside a harvest call. - △ The
harvest()ETH-gain check does not reference an external price oracle. It guards against self-inflicted bad trades but not against a mispriced venue. Iftargetis a thinly liquid pool, a legitimate keeper call can still satisfy the invariant while taking substantial MEV or slippage loss within the allowed bps. - △ Constructor uses
tx.originas the initial owner. This is required because the contract is deployed via a CREATE2 factory, buttx.originis unusual for authorization and warrants attention when reading the code. - ◇
setConditioncan be used to sanity-check withdrawals into a specific downstream recipient (e.g., "this push to zRouter must deliver ZORG to the Safe"). This is useful but does not guarantee correct pricing —minGainis set per call by the owner. - ◇ A
valuefield is accepted on every owner-gated setter (they are declaredpayable). This appears to be a gas-optimization pattern rather than a functional requirement.
This document is produced for educational purposes. It reflects what the code does, not whether the code is safe to trust with funds or what it will do under all possible future configurations. Treat it as a starting point for your own review, not as a security audit.
References
| RESOURCE | NOTES |
|---|---|
| Etherscan — LidoHarvester source | Verified source used as primary artifact |
| github.com/z0r0z/lido-harvester | Author's repo — README describes intent, tests, and deployment target |
| Lido stETH contract | Hardcoded dependency |
| Curve stETH/ETH pool | Current harvest target |
| EIP-1153 — Transient storage | Mechanism used for the harvest() reentrancy flag |
| zRouter Contract Analysis | Same author; used as the to for withdraw() in observed transactions |
| zFi Project Overview | DNZN research index and known infrastructure for the zFi ecosystem |
Change Log
| DATE | AUTHOR | NOTES |
|---|---|---|
| 2026-04-13 | Artificial. | Generated by robots. Gas: 80 tok |
| 2026-04-14 | Denizen. | Reviewed, edited, and curated by humans. |