EtfVault

Description:

Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "hopium/etf/main/etf-vault.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "hopium/common/interface/imDirectory.sol";
import "hopium/etf/interface/imEtfRouter.sol";

interface IERC20 {
    function transfer(address to, uint256 value) external returns (bool);
    function balanceOf(address account) external view returns (uint256);
}

contract TransferHelpers {
    error ZeroAmount();
    error EthSendFailed();

    function _sendEth(address to, uint256 amount) internal {
        if (amount == 0) revert ZeroAmount();
        (bool ok, ) = payable(to).call{value: amount}("");
        if (!ok) revert EthSendFailed();
    }

    function _sendToken(address tokenAddress, address toAddress, uint256 amount) internal {
        if (amount == 0) revert ZeroAmount();
        IERC20(tokenAddress).transfer(toAddress, amount);
    }

    function _sendEthOrToken(address tokenAddress, address toAddress) internal {
        if (tokenAddress == address(0)) {
            _sendEth(toAddress, address(this).balance);
        } else {
            _sendToken(tokenAddress, toAddress, IERC20(tokenAddress).balanceOf(address(this)));
        }
    }

    function _recoverAsset(address tokenAddress, address toAddress) internal {
        _sendEthOrToken(tokenAddress, toAddress);
    }
}

/// @notice Etf Vault implementation (logic). Clones delegatecall into this.
/// @dev Assumes ImEtfRouter -> ImDirectory for access control & Directory wiring.
contract EtfVault is ImEtfRouter, TransferHelpers {
    bool private _initialized;

    /// @notice Constructor runs once for the implementation only.
    /// @dev You can pass an existing Directory here; clones still call initialize().
    constructor(address _directory) ImDirectory(_directory) {}

    error AlreadyInitialized();
    /// @notice One-time initializer for clones (constructors don't run for proxies)
    function initialize(address _directory) external {
         if (_initialized) revert AlreadyInitialized();
        _initialized = true;

        _setDirectory(_directory); // from ImDirectory (no modifier)
    }

    /// @notice Redeem underlying tokens to receiver; callable only by EtfRouter
    function redeem(address tokenAddress, uint256 amount, address receiver) external onlyEtfRouter {
        _sendToken(tokenAddress, receiver, amount);
    }

    /// @notice Owner (Directory.owner()) can recover stuck ETH/ERC20
    function recoverAsset(address tokenAddress, address toAddress) external onlyOwner {
        _recoverAsset(tokenAddress, toAddress);
    }

    receive() external payable {}
}
"
    },
    "hopium/etf/interface/imEtfRouter.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import "hopium/common/interface/imDirectory.sol";

interface IEtfRouter {
    function mintEtfTokens(uint256 etfId, address receiver, string calldata affiliateCode) external payable;
}

abstract contract ImEtfRouter is ImDirectory {

    function getEtfRouter() internal view virtual returns (IEtfRouter) {
        return IEtfRouter(fetchFromDirectory("etf-router"));
    }

    modifier onlyEtfRouter() {
        require(msg.sender == fetchFromDirectory("etf-router"), "msg.sender is not etf router");
        _;
    }
}"
    },
    "hopium/common/interface/imDirectory.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

/// @notice Interface used by the registry to talk to the external directory.
interface IDirectory {
    function owner() external view returns (address);
    function fetchFromDirectory(string memory _key) external view returns (address);
}

abstract contract ImDirectory {
    IDirectory public Directory;

    constructor(address _directory) {
        _setDirectory(_directory); // no modifier here
    }

    function changeDirectoryAddress(address _directory) external onlyOwner {
        _setDirectory(_directory);
    }

    function _setDirectory(address _directory) internal {
        require(_directory != address(0), "Directory cannot be zero address");
        require(_directory.code.length > 0, "Directory must be a contract");

        // Sanity check the interface
        try IDirectory(_directory).owner() returns (address) {
            Directory = IDirectory(_directory);
        } catch {
            revert("Directory address does not implement owner()");
        }
    }

    modifier onlyOwner() {
        require(msg.sender == Directory.owner(), "Caller is not the owner");
        _;
    }

    function owner() public view returns (address) {
        return Directory.owner();
    }

    function fetchFromDirectory(string memory _key) public view returns (address) {
        return Directory.fetchFromDirectory(_key);
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "remappings": []
  }
}}

Tags:
Proxy, Upgradeable, Factory|addr:0x6e55562d9c3b122f2a17a2179ef22b54ee625c83|verified:true|block:23668278|tx:0x3dd23aaf10d5a1576a03d07618c62cd7c599a2d3797a862dc9620894fec94d25|first_check:1761567296

Submitted on: 2025-10-27 13:14:56

Comments

Log in to comment.

No comments yet.