Description:
Decentralized Finance (DeFi) protocol contract providing Liquidity, Factory, Oracle functionality.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"hopium/etf/main/etf-oracle.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import "hopium/common/interface/imDirectory.sol";
import "hopium/etf/types/etf.sol";
import "hopium/etf/interface/imEtfFactory.sol";
import "hopium/uniswap/interface/imUniswapOracle.sol";
import "hopium/common/types/bips.sol";
import "hopium/common/lib/full-math.sol";
import "hopium/common/interface-ext/iErc20Metadata.sol";
import "hopium/etf/interface/iEtfOracle.sol";
abstract contract Storage {
uint256 internal constant WAD = 1e18;
}
abstract contract Utils is ImUniswapOracle, Storage {
/// @dev 10**exp with safety guard. 10**78 overflows uint256.
function _pow10(uint8 exp) internal pure returns (uint256) {
require(exp <= 77, "pow10 overflow");
// unchecked is fine due to the guard
unchecked { return 10 ** uint256(exp); }
}
/// @dev Scale arbitrary decimals to 1e18 without overflow using mulDiv.
function _scaleTo1e18(uint256 amount, uint8 decimals) internal pure returns (uint256) {
if (decimals == 18) return amount;
if (decimals < 18) {
// amount * 1e18 / 10^dec
return FullMath.mulDiv(amount, WAD, _pow10(decimals));
} else {
// amount * 1e18 / 10^dec == amount / 10^(dec-18)
return amount / _pow10(decimals - 18);
}
}
/// @dev Convert a WETH-denominated 1e18 amount to USD 1e18 using the oracle.
/// Returns 0 if either the amount or the oracle price is 0.
function _convertWethToUsd(uint256 wethAmount18) internal view returns (uint256 usdAmount18) {
if (wethAmount18 == 0) return 0;
uint256 wethUsdPrice18 = getUniswapOracle().getWethUsdPrice(); // USD per 1 WETH, 1e18
if (wethUsdPrice18 == 0) return 0;
// USD = WETH * (USD/WETH)
return FullMath.mulDiv(wethAmount18, wethUsdPrice18, WAD);
}
}
abstract contract SnapshotHelpers is Utils, ImEtfFactory {
function _snapshotVault(Etf memory etf, address etfVault)
internal
view
returns (Snapshot[] memory s, uint256 etfTvlWeth)
{
uint256 n = etf.assets.length;
s = new Snapshot[](n);
IUniswapOracle oracle = getUniswapOracle();
// 1) Fill token info and values, and accumulate TVL
unchecked {
for (uint256 i = 0; i < n; ++i) {
address t = etf.assets[i].tokenAddress;
uint8 d = IERC20Metadata(t).decimals();
uint256 bal = IERC20(t).balanceOf(etfVault);
uint256 p = oracle.getTokenWethPrice(t); // WETH per 1 token, 1e18
// tokenValueWeth18 = bal * price / 10^dec
uint256 v = (bal == 0 || p == 0) ? 0 : FullMath.mulDiv(bal, p, 10 ** d);
s[i] = Snapshot({
tokenAddress: t,
tokenDecimals: d,
currentWeight: 0, // set in pass #2
tokenRawBalance: bal,
tokenPriceWeth18: p,
tokenValueWeth18: v
});
etfTvlWeth += v;
}
}
// 2) Compute live weights as value share of TVL (in bips)
if (etfTvlWeth > 0) {
unchecked {
for (uint256 i = 0; i < n; ++i) {
s[i].currentWeight = uint16(
(s[i].tokenValueWeth18 * HUNDRED_PERCENT_BIPS) / etfTvlWeth
);
}
}
}
}
/// @dev USD-enhanced snapshot: and adds USD prices/values using the WETH->USD oracle conversion.
function _snapshotVaultWithUsd(Etf memory etf, address etfVault)
internal
view
returns (SnapshotWithUsd[] memory s, uint256 etfTvlWeth, uint256 etfTvlUsd)
{
(Snapshot[] memory snap, uint256 tvlWeth) = _snapshotVault(etf, etfVault);
etfTvlWeth = tvlWeth;
etfTvlUsd = _convertWethToUsd(tvlWeth);
uint256 n = snap.length;
s = new SnapshotWithUsd[](n);
unchecked {
for (uint256 i = 0; i < n; ++i) {
s[i] = SnapshotWithUsd({
tokenAddress: snap[i].tokenAddress,
tokenDecimals: snap[i].tokenDecimals,
currentWeight: snap[i].currentWeight,
tokenRawBalance: snap[i].tokenRawBalance,
tokenPriceWeth18: snap[i].tokenPriceWeth18,
tokenPriceUsd18: _convertWethToUsd(snap[i].tokenPriceWeth18),
tokenValueWeth18: snap[i].tokenValueWeth18,
tokenValueUsd18: _convertWethToUsd(snap[i].tokenValueWeth18)
});
}
}
}
}
abstract contract PriceHelpers is SnapshotHelpers {
/// @dev Target-weighted (index) price in WETH (1e18).
function _getEtfInitialPrice(Etf memory etf) internal view returns (uint256 priceWeth18) {
uint256 sum; // (bips * 1e18)
IUniswapOracle oracle = getUniswapOracle();
unchecked {
for (uint256 i = 0; i < etf.assets.length; ++i) {
address token = etf.assets[i].tokenAddress;
uint16 wBips = etf.assets[i].weightBips;
// 1e18-scaled: WETH per 1 whole TOKEN
uint256 p = oracle.getTokenWethPrice(token);
if (p == 0 || wBips == 0) continue;
sum += uint256(wBips) * p;
}
}
// Divide by 100% in bips → 1e18 WETH per "index unit"
return sum / HUNDRED_PERCENT_BIPS;
}
/// @dev Live NAV per whole ETF token in WETH (1e18):
/// ( Σ_i [ vaultBalance_i (raw) * price_i (WETH per whole token, 1e18) / 10^dec_i ] )
/// / ( ETF totalSupply (whole tokens) )
function _getEtfWethPrice(uint256 etfId, Etf memory etf, address etfVault) internal view returns (uint256) {
// Resolve addresses
address etfToken = getEtfFactory().getEtfTokenAddress(etfId);
// Supply in whole-token terms
uint256 rawSupply = IERC20(etfToken).totalSupply();
uint8 etfDec = IERC20Metadata(etfToken).decimals();
if (rawSupply == 0) {
// No supply yet → use index price
return _getEtfInitialPrice(etf);
}
uint256 supplyWhole18 = _scaleTo1e18(rawSupply, etfDec); // #whole ETF tokens, 1e18-scaled
if (supplyWhole18 == 0) return 0;
// Sum vault holdings valued in WETH (1e18)
uint256 totalWethValue18 = _getEtfTvlWeth(etf, etfVault);
// NAV per whole ETF token (1e18) = totalWethValue18 / (#whole ETF tokens)
return FullMath.mulDiv(totalWethValue18, WAD, supplyWhole18);
}
/// @dev Total vault value in WETH (1e18) across all ETF assets.
function _getEtfTvlWeth(Etf memory etf, address etfVault) internal view returns (uint256 totalWethValue18) {
IUniswapOracle oracle = getUniswapOracle();
unchecked {
// Sum vault holdings valued in WETH (1e18)
for (uint256 i = 0; i < etf.assets.length; ++i) {
address token = etf.assets[i].tokenAddress;
uint256 bal = IERC20(token).balanceOf(etfVault); // raw units
if (bal == 0) continue;
// WETH per 1 whole token (1e18)
uint256 wethPerToken18 = oracle.getTokenWethPrice(token);
if (wethPerToken18 == 0) continue;
// value = bal(raw) * price(1e18) / 10^dec
uint8 dec = IERC20Metadata(token).decimals();
uint256 denom = _pow10(dec);
totalWethValue18 += FullMath.mulDiv(bal, wethPerToken18, denom);
}
}
}
}
abstract contract EtfStats is PriceHelpers {
struct EthUsd {
uint256 eth18; // value in WETH (1e18)
uint256 usd18; // value in USD (1e18)
}
struct Stats {
uint256 assetsLiquidityUsd; // sum of TOKEN–WETH pool liquidity (USD, 1e18)
uint256 assetsMcapUsd; // sum of token market caps (USD, 1e18)
}
/// @dev Generic helper to sum a per-token USD metric across all ETF assets.
/// Accepts a function pointer of type (address) → (uint256).
/// Wrapped in try/catch so missing pools won’t revert.
function _sumPerAsset(Etf memory etf, function (address) external view returns (uint256) fn) internal view returns (uint256 sum) {
unchecked {
for (uint256 i = 0; i < etf.assets.length; ++i) {
address token = etf.assets[i].tokenAddress;
try fn(token) returns (uint256 value) {
sum += value;
} catch { /* ignore if oracle call fails */ }
}
}
}
/// @notice Returns full ETF stats.
function _getStats(Etf memory etf) internal view returns (Stats memory s) {
IUniswapOracle uniO = getUniswapOracle();
s.assetsLiquidityUsd = _sumPerAsset(etf, uniO.getTokenLiquidityUsd);
s.assetsMcapUsd = _sumPerAsset(etf, uniO.getTokenMarketCapUsd);
}
}
contract EtfOracle is ImDirectory, PriceHelpers, EtfStats {
constructor(address _directory) ImDirectory(_directory) {}
/// @notice Current NAV per ETF token in WETH (1e18).
function getEtfWethPrice(uint256 etfId) external view returns (uint256) {
(Etf memory etf, address vaultAddress) = getEtfFactory().getEtfByIdAndVault(etfId);
return _getEtfWethPrice(etfId, etf, vaultAddress);
}
/// @notice Current NAV per ETF token in USD (1e18).
/// @dev Uses the internal _convertWethToUsd helper for conversion.
function getEtfUsdPrice(uint256 etfId) external view returns (uint256) {
(Etf memory etf, address vaultAddress) = getEtfFactory().getEtfByIdAndVault(etfId);
uint256 etfWethPrice18 = _getEtfWethPrice(etfId, etf, vaultAddress);
return _convertWethToUsd(etfWethPrice18);
}
function getEtfPrice(uint256 etfId) external view returns (uint256 wethPrice18, uint256 usdPrice18) {
(Etf memory etf, address vaultAddress) = getEtfFactory().getEtfByIdAndVault(etfId);
wethPrice18 = _getEtfWethPrice(etfId, etf, vaultAddress);
usdPrice18 = _convertWethToUsd(wethPrice18);
}
function snapshotVault(uint256 etfId) external view returns (Snapshot[] memory s, uint256 etfTvlWeth) {
(Etf memory etf, address vaultAddress) = getEtfFactory().getEtfByIdAndVault(etfId);
return _snapshotVault(etf, vaultAddress);
}
function snapshotVaultUnchecked(Etf memory etf, address vaultAddress) external view returns (Snapshot[] memory s, uint256 etfTvlWeth) {
return _snapshotVault(etf, vaultAddress);
}
function snapshotVaultWithUsd(uint256 etfId) external view returns (SnapshotWithUsd[] memory s, uint256 etfTvlWeth, uint256 etfTvlUsd) {
(Etf memory etf, address vaultAddress) = getEtfFactory().getEtfByIdAndVault(etfId);
return _snapshotVaultWithUsd(etf, vaultAddress);
}
function getEtfStats(uint256 etfId) external view returns (Stats memory s) {
(Etf memory etf) = getEtfFactory().getEtfById(etfId);
return _getStats(etf);
}
}"
},
"hopium/etf/interface/iEtfOracle.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import "hopium/etf/types/etf.sol";
import "hopium/etf/types/snapshot.sol";
interface IEtfOracle {
function getEtfWethPrice(uint256 etfId) external view returns (uint256);
function getEtfPrice(uint256 etfId) external view returns (uint256 wethPrice18, uint256 usdPrice18);
function snapshotVaultUnchecked(Etf memory etf, address vaultAddress) external view returns (Snapshot[] memory s, uint256 etfTvlWeth);
}"
},
"hopium/common/interface-ext/iErc20Metadata.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import "hopium/common/interface-ext/iErc20.sol";
interface IERC20Metadata is IERC20 {
function decimals() external view returns (uint8);
}"
},
"hopium/common/lib/full-math.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
library FullMath {
/// @dev Full-precision multiply-divide: floor(a * b / denominator)
/// Reverts if denominator == 0 or the result overflows uint256.
/// @notice Based on Uniswap v3’s FullMath.mulDiv().
function mulDiv(
uint256 a,
uint256 b,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = a * b
uint256 prod0; // Least-significant 256 bits
uint256 prod1; // Most-significant 256 bits
assembly {
let mm := mulmod(a, b, not(0))
prod0 := mul(a, b)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// No overflow: do simple division
if (prod1 == 0) return prod0 / denominator;
require(denominator > prod1, "mulDiv overflow");
///////////////////////////////////////////////
// Make division exact (subtract remainder)
///////////////////////////////////////////////
uint256 remainder;
assembly {
remainder := mulmod(a, b, denominator)
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
///////////////////////////////////////////////
// Factor powers of two out of denominator
///////////////////////////////////////////////
uint256 twos = denominator & (~denominator + 1);
assembly {
denominator := div(denominator, twos)
prod0 := div(prod0, twos)
twos := add(div(sub(0, twos), twos), 1)
}
// Combine high and low products
prod0 |= prod1 * twos;
///////////////////////////////////////////////
// Compute modular inverse of denominator mod 2²⁵⁶
///////////////////////////////////////////////
uint256 inv = (3 * denominator) ^ 2;
inv *= 2 - denominator * inv; // inverse mod 2⁸
inv *= 2 - denominator * inv; // mod 2¹⁶
inv *= 2 - denominator * inv; // mod 2³²
inv *= 2 - denominator * inv; // mod 2⁶⁴
inv *= 2 - denominator * inv; // mod 2¹²⁸
inv *= 2 - denominator * inv; // mod 2²⁵⁶
///////////////////////////////////////////////
// Multiply by modular inverse to finish division
///////////////////////////////////////////////
result = prod0 * inv;
return result;
}
}
}"
},
"hopium/common/types/bips.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
uint256 constant HUNDRED_PERCENT_BIPS = 10_000;
"
},
"hopium/uniswap/interface/imUniswapOracle.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import "hopium/common/interface/imDirectory.sol";
import "hopium/uniswap/interface/iUniswapOracle.sol";
abstract contract ImUniswapOracle is ImDirectory {
function getUniswapOracle() internal view virtual returns (IUniswapOracle) {
return IUniswapOracle(fetchFromDirectory("uniswap-oracle"));
}
modifier onlyUniswapOracle() {
require(msg.sender == fetchFromDirectory("uniswap-oracle"), "msg.sender is not uniswap-oracle");
_;
}
}"
},
"hopium/etf/interface/imEtfFactory.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import "hopium/common/interface/imDirectory.sol";
import "hopium/etf/types/etf.sol";
interface IEtfFactory {
function updateEtfVolume(uint256 etfId, uint256 ethAmount) external;
//read
//data
function getEtfById(uint256 etfId) external view returns (Etf memory);
function getEtfTokenAddress(uint256 etfId) external view returns (address);
function getEtfVaultAddress(uint256 etfId) external view returns (address);
function getEtfByIdAndAddresses(uint256 etfId) external view returns (Etf memory etf, address tokenAddress, address vaultAddress);
function getEtfByIdAndVault(uint256 etfId) external view returns (Etf memory etf, address vaultAddress);
//events
function emitVaultBalanceEvent(uint256 etfId) external;
function emitPlatformFeeTransferredEvent(uint256 etfId, uint256 ethAmount) external;
}
abstract contract ImEtfFactory is ImDirectory {
function getEtfFactory() internal view virtual returns (IEtfFactory) {
return IEtfFactory(fetchFromDirectory("etf-factory"));
}
modifier onlyEtfFactory() {
require(msg.sender == fetchFromDirectory("etf-factory"), "msg.sender is not etf factory");
_;
}
}"
},
"hopium/etf/types/etf.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
struct Asset {
address tokenAddress;
uint16 weightBips;
}
struct Etf {
string name;
string ticker;
Asset[] assets;
}
"
},
"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);
}
}
"
},
"hopium/uniswap/interface/iUniswapOracle.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
interface IUniswapOracle {
function getTokenWethPrice(address tokenAddress) external view returns (uint256 price18);
function getTokenUsdPrice(address tokenAddress) external view returns (uint256 price18);
function getWethUsdPrice() external view returns (uint256 price18);
function getTokenLiquidityWeth(address) external view returns (uint256);
function getTokenLiquidityUsd(address) external view returns (uint256);
function getTokenMarketCapWeth(address) external view returns (uint256);
function getTokenMarketCapUsd(address) external view returns (uint256);
function getTokenWethPriceByPool(address tokenAddress, address poolAddress, bool isV3Pool) external view returns (uint256);
function emitPoolChangedEventOnWethUsdPool() external;
}
"
},
"hopium/etf/types/snapshot.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
struct Snapshot {
address tokenAddress;
uint8 tokenDecimals;
uint16 currentWeight;
uint256 tokenRawBalance; // vault raw balance
uint256 tokenPriceWeth18; // WETH per 1 token (1e18)
uint256 tokenValueWeth18; // raw * price / 10^dec
}
struct SnapshotWithUsd {
address tokenAddress;
uint8 tokenDecimals;
uint16 currentWeight;
uint256 tokenRawBalance; // vault raw balance
uint256 tokenPriceWeth18; // WETH per 1 token (1e18)
uint256 tokenPriceUsd18;
uint256 tokenValueWeth18; // raw * price / 10^dec
uint256 tokenValueUsd18;
}"
},
"hopium/common/interface-ext/iErc20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"remappings": []
}
}}
Submitted on: 2025-10-27 13:15:05
Comments
Log in to comment.
No comments yet.