Skip to content

Commit

Permalink
impl burnETFToken and mintETFToken
Browse files Browse the repository at this point in the history
  • Loading branch information
guzus committed Nov 16, 2024
1 parent b53c424 commit b8b1730
Showing 1 changed file with 267 additions and 0 deletions.
267 changes: 267 additions & 0 deletions src/EtfHook.sol
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;
}
}

0 comments on commit b8b1730

Please sign in to comment.