Skip to content

Artifacts

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 0x00000000...768E22 (etherscan)
Network Ethereum Mainnet
Analysis Date 2026-04-13

Runtime Bytecode

The deployed contract bytecode fetched from the blockchain.

Source: Etherscan — Contract Code

Command:

export ETH_RPC_URL=https://eth.llamarpc.com
cast code 0x0000000000BB8A44A568Ff0a9ef0E7fc20768E22

Artifact: 5,327-byte runtime bytecode. First 60 bytes shown below; full bytecode available from Etherscan or via cast code.

0x6080604052600436106100e7575f3560e01c8063b6b55f251161008757...

Creation Bytecode

The full bytecode used to deploy the contract, including the constructor prologue.

Source: Etherscan — Creation TX

Command:

export ETH_RPC_URL=https://eth.llamarpc.com
cast tx 0xa49ef903cb5be90b4e1d2bdfec294ffe729c095ff400cc0980d9ede30c54d046 input

Artifact: Returned by the Etherscan getcontractcreation endpoint (the CREATE2 factory's init-code). First 60 bytes shown below.

0x608060408190526001805462010000600160b01b0319163262010000810291...

The constructor prologue stores tx.origin as owner at slot 1 (shifted left by 16 bits, packed with slipBps = 0) and emits an OwnershipTransferred(address(0), tx.origin) event via topic 0x8be0079c...6457e0.


Verified Source Code

Source code verified on Etherscan (Exact Match).

Source: Etherscan — Contract Source

Compiler: Solidity 0.8.34 (optimizer enabled, 9,999,999 runs, Cancun EVM)

Artifact:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.34;

interface IERC20 {
    function approve(address, uint256) external returns (bool);
    function balanceOf(address) external view returns (uint256);
    function transfer(address, uint256) external returns (bool);
    function transferFrom(address, address, uint256) external returns (bool);
}

/// @notice Simple Lido stETH harvesting contract to utilize ETH yield.
/// @dev Accepts raw ETH (converts to stETH) or stETH deposits -
/// which increment basis counter to track yield - which can reconvert
/// to ETH - which may then be used in withdraw() - optional condition
/// can be attached which ensures some sort of balance increase in ETH
/// or ERC20 asset occurs as result of spending such ETH on withdraw().
contract LidoHarvester {
    event OwnershipTransferred(address indexed from, address indexed to);

    address constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84;

    uint256 public staked;
    uint16 public slipBps;
    address public owner;

    address public target;
    address public asset;
    address public holder;

    error ConditionUnmet();
    error Unauthorized();

    modifier onlyOwner() {
        require(msg.sender == owner, Unauthorized());
        _;
    }

    function transferOwnership(address to) public payable onlyOwner {
        emit OwnershipTransferred(msg.sender, owner = to);
    }

    function setSlippage(uint16 _slipBps) public payable onlyOwner {
        require(_slipBps <= 10000);
        slipBps = _slipBps;
    }

    function setTarget(address _target) public payable onlyOwner {
        address old = target;
        if (old != address(0)) IERC20(STETH).approve(old, 0);
        target = _target;
        if (_target != address(0)) IERC20(STETH).approve(_target, type(uint256).max);
    }

    function setCondition(address _asset, address _holder) public payable onlyOwner {
        (asset, holder) = (_asset, _holder);
    }

    constructor() payable {
        emit OwnershipTransferred(address(0), owner = tx.origin);
    }

    receive() external payable {
        assembly ("memory-safe") { if tload(0) { return(0, 0) } }
        uint256 stethBal = IERC20(STETH).balanceOf(address(this));
        (bool ok,) = STETH.call{value: msg.value}("");
        require(ok);
        unchecked { staked += IERC20(STETH).balanceOf(address(this)) - stethBal; }
    }

    function deposit(uint256 amt) public payable {
        uint256 stethBal = IERC20(STETH).balanceOf(address(this));
        require(IERC20(STETH).transferFrom(msg.sender, address(this), amt));
        unchecked { staked += IERC20(STETH).balanceOf(address(this)) - stethBal; }
    }

    function withdraw(address to, uint256 val, bytes calldata data, uint256 minGain)
        public payable onlyOwner
    {
        address _holder = holder;
        address _asset = asset;
        uint256 balBefore;
        bool conditioned = _holder != address(0);
        bool ethCondition = _asset == address(0);

        if (conditioned) balBefore = ethCondition ? _holder.balance : IERC20(_asset).balanceOf(_holder);

        (bool ok,) = to.call{value: val}(data);
        require(ok);

        if (conditioned) {
            uint256 balAfter = ethCondition ? _holder.balance : IERC20(_asset).balanceOf(_holder);
            require(balAfter >= balBefore + minGain, ConditionUnmet());
        }
    }

    function stake(uint256 amt) public payable onlyOwner {
        if (amt == 0) amt = address(this).balance;
        uint256 stethBal = IERC20(STETH).balanceOf(address(this));
        (bool ok,) = STETH.call{value: amt}("");
        require(ok);
        unchecked { staked += IERC20(STETH).balanceOf(address(this)) - stethBal; }
    }

    function withdrawStETH(address to, uint256 amt) public payable onlyOwner {
        staked -= amt;
        require(IERC20(STETH).transfer(to, amt));
    }

    function harvest(bytes calldata data) public payable returns (uint256 yield) {
        address _target = target;
        uint256 _staked = staked;
        uint256 ethBal = address(this).balance;
        uint256 stethBal = IERC20(STETH).balanceOf(address(this));
        if (stethBal <= _staked) return 0;
        unchecked { yield = stethBal - _staked; }
        assembly ("memory-safe") { tstore(0, 1) }
        (bool ok,) = _target.call(data);
        assembly ("memory-safe") { tstore(0, 0) }
        require(ok);
        unchecked {
            require(address(this).balance >= ethBal + yield * (10000 - slipBps) / 10000);
            require(IERC20(STETH).balanceOf(address(this)) + 2 >= _staked);
        }
    }
}

Additional Artifacts

ABI (from Etherscan)

  • Source: Etherscan v2 API (action=getsourcecode)
  • Command:
    curl -s "https://api.etherscan.io/v2/api?chainid=1&module=contract&action=getsourcecode\
    &address=0x0000000000BB8A44A568Ff0a9ef0E7fc20768E22&apikey=$ETHERSCAN_API_KEY" \
      | jq -r '.result[0].ABI' > LidoHarvester.abi.json
    

OwnershipTransferred event topic

  • Topic 0: 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0
  • Signature: OwnershipTransferred(address,address)
  • Explorer: events filter

Observed operational transactions

KIND TX HASH NOTES
Deployment 0xa49ef903...54d046 (tx) CREATE2 via factory 0x5c2271fd...d21822
setSlippage(50) 0x4905519b...4b8185 (tx) Sets slippage to 0.5%
setTarget(Curve stETH/ETH) 0x774cdaa4...368be4 (tx) Grants infinite stETH approval to Curve pool
Initial funding 0x42b7b6ab...2ecdc8 (tx) Raw ETH → staked via receive()
deposit(…) 0x7ebef8fe...998704 (tx) stETH in via transferFrom
harvest(swapData) 0x9b2aa0df...52cbfb (tx) Converted ~6.3e14 wei stETH yield → ETH via Curve; gasUsed 146,327
withdraw(to=zRouter, …) 0x2bd2dd1b...d943cf (tx) Forwarded harvested ETH to zRouter with ZORG-gain condition