-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1 changed file
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.24; | ||
|
||
import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol"; | ||
import {Hooks} from "v4-core/src/libraries/Hooks.sol"; | ||
import {IPoolManager} from "v4-core/src/interfaces/IPoolManager.sol"; | ||
import {PoolKey} from "v4-core/src/types/PoolKey.sol"; | ||
import {PoolId, PoolIdLibrary} from "v4-core/src/types/PoolId.sol"; | ||
import {BalanceDelta} from "v4-core/src/types/BalanceDelta.sol"; | ||
import {BeforeSwapDelta, BeforeSwapDeltaLibrary} from "v4-core/src/types/BeforeSwapDelta.sol"; | ||
import {IEntropyConsumer} from "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; | ||
import {IEntropy} from "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; | ||
import {ETFManager} from "./EtfToken.sol"; | ||
import {ERC20} from "solmate/src/tokens/ERC20.sol"; | ||
|
||
contract ETFHook is BaseHook ,ETFManager, IEntropyConsumer { | ||
IEntropy public entropy; | ||
bytes32 private latestRandomNumber; | ||
bool private isRandomNumberReady; | ||
|
||
address[2] public tokens; | ||
uint256[2] public weights; | ||
uint256 public rebalanceThreshold; | ||
uint256[2] public tokenBalances; | ||
|
||
// Oracle addresses | ||
address public chainlinkOracle; | ||
address public pythOracle; | ||
address public api3Oracle; | ||
|
||
// Events | ||
event RandomNumberReceived(bytes32 randomNumber); | ||
event OracleSelected(uint256 indexed oracleIndex); | ||
|
||
constructor( | ||
IPoolManager _poolManager, | ||
address[2] memory _tokens, | ||
uint256[2] memory _weights, | ||
uint256 _rebalanceThreshold, | ||
address entropyAddress, | ||
address _chainlinkOracle, | ||
address _pythOracle, | ||
address _api3Oracle | ||
) BaseHook(_poolManager) ETFManager("ETF Token", "ETF") { | ||
entropy = IEntropy(entropyAddress); | ||
tokens = _tokens; | ||
weights = _weights; | ||
rebalanceThreshold = _rebalanceThreshold; | ||
chainlinkOracle = _chainlinkOracle; | ||
pythOracle = _pythOracle; | ||
api3Oracle = _api3Oracle; | ||
|
||
for (uint256 i = 0; i < 2; i++) { | ||
tokenBalances[i] = 0; | ||
} | ||
} | ||
|
||
// Entropy Implementation | ||
function requestRandomNumber() internal { | ||
bytes32 userRandomNumber = keccak256(abi.encodePacked(block.timestamp, msg.sender)); | ||
address entropyProvider = entropy.getDefaultProvider(); | ||
uint256 fee = entropy.getFee(entropyProvider); | ||
|
||
entropy.requestWithCallback{value: fee}( | ||
entropyProvider, | ||
userRandomNumber | ||
); | ||
|
||
isRandomNumberReady = false; | ||
} | ||
|
||
function entropyCallback( | ||
uint64 sequenceNumber, | ||
address provider, | ||
bytes32 randomNumber | ||
) internal override { | ||
latestRandomNumber = randomNumber; | ||
isRandomNumberReady = true; | ||
emit RandomNumberReceived(randomNumber); | ||
} | ||
|
||
function getEntropy() internal view override returns (address) { | ||
return address(entropy); | ||
} | ||
|
||
function selectOracle() internal returns (address) { | ||
if (!isRandomNumberReady) { | ||
requestRandomNumber(); | ||
return chainlinkOracle; // Default to Chainlink if random number not ready | ||
} | ||
|
||
uint256 randomValue = uint256(latestRandomNumber) % 3; | ||
emit OracleSelected(randomValue); | ||
|
||
if (randomValue == 0) return chainlinkOracle; | ||
if (randomValue == 1) return pythOracle; | ||
return api3Oracle; | ||
} | ||
|
||
// Hook permissions | ||
function getHookPermissions() public pure override returns (Hooks.Permissions memory) { | ||
return Hooks.Permissions({ | ||
beforeInitialize: false, | ||
afterInitialize: false, | ||
beforeAddLiquidity: true, | ||
afterAddLiquidity: false, | ||
beforeRemoveLiquidity: false, | ||
afterRemoveLiquidity: true, | ||
beforeSwap: true, | ||
afterSwap: false, | ||
beforeDonate: false, | ||
afterDonate: false, | ||
beforeSwapReturnDelta: false, | ||
afterSwapReturnDelta: false, | ||
afterAddLiquidityReturnDelta: false, | ||
afterRemoveLiquidityReturnDelta: false | ||
}); | ||
} | ||
|
||
// Price fetching functions | ||
function getPrices() internal returns (uint256[2] memory prices) { | ||
address selectedOracle = selectOracle(); | ||
|
||
if (selectedOracle == chainlinkOracle) { | ||
return getChainlinkPrices(); | ||
} else if (selectedOracle == pythOracle) { | ||
return getPythPrices(); | ||
} else { | ||
return getAPI3Prices(); | ||
} | ||
} | ||
|
||
function getChainlinkPrices() internal view returns (uint256[2] memory prices) { | ||
// TODO: Implement Chainlink price fetching | ||
return prices; | ||
} | ||
|
||
function getPythPrices() internal view returns (uint256[2] memory prices) { | ||
// TODO: Implement Pyth price fetching | ||
return prices; | ||
} | ||
|
||
function getAPI3Prices() internal view returns (uint256[2] memory prices) { | ||
// TODO: Implement API3 price fetching | ||
return prices; | ||
} | ||
|
||
// Hook callbacks | ||
function beforeSwap(address sender, PoolKey calldata key, IPoolManager.SwapParams calldata, bytes calldata) | ||
external | ||
override | ||
returns (bytes4, BeforeSwapDelta, uint24) | ||
{ | ||
(bool needed, uint256[2] memory prices) = checkIfRebalanceNeeded(); | ||
if (needed) { | ||
rebalance(); | ||
} | ||
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0); | ||
} | ||
|
||
function beforeAddLiquidity( | ||
address, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata, | ||
bytes calldata | ||
) external override returns (bytes4) { | ||
(bool needed, uint256[2] memory prices) = checkIfRebalanceNeeded(); | ||
if (needed) { | ||
rebalance(); | ||
} | ||
// how many etf tokens to mint | ||
mintETFToken(0, prices); // TODO | ||
return BaseHook.beforeAddLiquidity.selector; | ||
} | ||
|
||
function afterRemoveLiquidity( | ||
address, | ||
PoolKey calldata key, | ||
IPoolManager.ModifyLiquidityParams calldata, | ||
bytes calldata | ||
) external override returns (bytes4) { | ||
(bool needed, uint256[2] memory prices) = checkIfRebalanceNeeded(); | ||
if (needed) { | ||
rebalance(); | ||
} | ||
burnETFToken(0, prices); // TODO | ||
return BaseHook.beforeRemoveLiquidity.selector; | ||
} | ||
|
||
// Your existing functions | ||
function checkIfRebalanceNeeded() private returns (bool needed, uint256[2] memory prices) { | ||
prices = getPrices(); | ||
|
||
uint256[2] memory tokenValues; | ||
for (uint256 i = 0; i < 2; i++) { | ||
tokenValues[i] = prices[i] * tokenBalances[i]; | ||
} | ||
|
||
uint256 totalValue = tokenValues[0] + tokenValues[1]; | ||
if (totalValue == 0) return (false, prices); | ||
|
||
uint256[2] memory currentWeights; | ||
for (uint256 i = 0; i < 2; i++) { | ||
currentWeights[i] = (tokenValues[i] * 10000) / totalValue; | ||
} | ||
|
||
for (uint256 i = 0; i < 2; i++) { | ||
if (currentWeights[i] > weights[i]) { | ||
if (currentWeights[i] - weights[i] > rebalanceThreshold) return (true, prices); | ||
} else { | ||
if (weights[i] - currentWeights[i] > rebalanceThreshold) return (true, prices); | ||
} | ||
} | ||
|
||
return (false, prices); | ||
} | ||
|
||
function rebalance() private { | ||
uint256[2] memory prices = getPrices(); | ||
|
||
uint256[2] memory tokenValues; | ||
for (uint256 i = 0; i < 2; i++) { | ||
tokenValues[i] = prices[i] * tokenBalances[i]; | ||
} | ||
|
||
uint256 totalValue = tokenValues[0] + tokenValues[1]; | ||
if (totalValue == 0) return; | ||
|
||
uint256[2] memory targetValues; | ||
for (uint256 i = 0; i < 2; i++) { | ||
targetValues[i] = (totalValue * weights[i]) / 10000; | ||
} | ||
|
||
if (tokenValues[0] > targetValues[0]) { | ||
uint256 token0ToSell = (tokenValues[0] - targetValues[0]) / prices[0]; | ||
// TODO: Implement swap logic | ||
} else { | ||
uint256 token1ToSell = (tokenValues[1] - targetValues[1]) / prices[1]; | ||
// TODO: Implement swap logic | ||
} | ||
} | ||
|
||
// 1 etf = 1 token0 + x token1 (x is calculated based on the weights and prices) | ||
function mintETFToken(uint256 etfAmount, uint256[2] memory prices) private { | ||
uint256 token0Amount = etfAmount; | ||
uint256 token1Amount = etfAmount * prices[0] / prices[1] * weights[1] / weights[0]; | ||
// transfer tokens to ETF pool contract | ||
ERC20(tokens[0]).transferFrom(msg.sender, address(this), token0Amount); | ||
ERC20(tokens[1]).transferFrom(msg.sender, address(this), token1Amount); | ||
// | ||
mint(msg.sender, etfAmount); | ||
tokenBalances[0] += token0Amount; | ||
tokenBalances[1] += token1Amount; | ||
} | ||
|
||
function burnETFToken(uint256 etfAmount, uint256[2] memory prices) private { | ||
uint256 token0Amount = etfAmount; | ||
uint256 token1Amount = etfAmount * prices[0] / prices[1] * weights[1] / weights[0]; | ||
// transfer tokens to ETF pool contract | ||
ERC20(tokens[0]).transfer(msg.sender, token0Amount); | ||
ERC20(tokens[1]).transfer(msg.sender, token1Amount); | ||
// | ||
burn(msg.sender, etfAmount); | ||
tokenBalances[0] -= token0Amount; | ||
tokenBalances[1] -= token1Amount; | ||
} | ||
} |