Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BAL Hookathon - ReBalancer Hook #113

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
375 changes: 89 additions & 286 deletions README.md

Large diffs are not rendered by default.

Binary file added images/balance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
169 changes: 169 additions & 0 deletions packages/foundry/contracts/hooks/rebalancer/MinimalRouterWithSwap.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.24;

import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";


import { MinimalRouter } from "@balancer-labs/v3-pool-hooks/contracts/MinimalRouter.sol";
import { SwapKind , VaultSwapParams } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";


import { IWETH } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";


abstract contract MinimalRouterWithSwap is MinimalRouter {

constructor(IVault vault, IWETH weth, IPermit2 permit2) MinimalRouter(vault, weth, permit2) {
// solhint-disable-previous-line no-empty-blocks
}



/**
* @notice Data for the swap hook.
* @param sender Account initiating the swap operation
* @param kind Type of swap (exact in or exact out)
* @param pool Address of the liquidity pool
* @param tokenIn Token to be swapped from
* @param tokenOut Token to be swapped to
* @param amountGiven Amount given based on kind of the swap (e.g., tokenIn for exact in)
* @param limit Maximum or minimum amount based on the kind of swap (e.g., maxAmountIn for exact out)
* @param deadline Deadline for the swap, after which it will revert
* @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH
* @param userData Additional (optional) data sent with the swap request
*/
struct SwapSingleTokenHookParams {
address sender;
SwapKind kind;
address pool;
IERC20 tokenIn;
IERC20 tokenOut;
uint256 amountGiven;
uint256 limit;
uint256 deadline;
bool wethIsEth;
bytes userData;
}


/***************************************************************************
Swaps
***************************************************************************/

function swapSingleTokenExactIn(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountIn,
uint256 minAmountOut,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable saveSender returns (uint256) {
return
abi.decode(
_vault.unlock(
abi.encodeCall(
MinimalRouterWithSwap.swapSingleTokenHook,
SwapSingleTokenHookParams({
sender: msg.sender,
kind: SwapKind.EXACT_IN,
pool: pool,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountGiven: exactAmountIn,
limit: minAmountOut,
deadline: deadline,
wethIsEth: wethIsEth,
userData: userData
})
)
),
(uint256)
);
}

function swapSingleTokenExactOut(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountOut,
uint256 maxAmountIn,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable saveSender returns (uint256) {
return
abi.decode(
_vault.unlock(
abi.encodeCall(
MinimalRouterWithSwap.swapSingleTokenHook,
SwapSingleTokenHookParams({
sender: msg.sender,
kind: SwapKind.EXACT_OUT,
pool: pool,
tokenIn: tokenIn,
tokenOut: tokenOut,
amountGiven: exactAmountOut,
limit: maxAmountIn,
deadline: deadline,
wethIsEth: wethIsEth,
userData: userData
})
)
),
(uint256)
);
}

/**
* @notice Hook for swaps.
* @dev Can only be called by the Vault. Also handles native ETH.
* @param params Swap parameters (see IRouter for struct definition)
* @return amountCalculated Token amount calculated by the pool math (e.g., amountOut for a exact in swap)
*/
function swapSingleTokenHook(
SwapSingleTokenHookParams calldata params
) external nonReentrant onlyVault returns (uint256) {
(uint256 amountCalculated, uint256 amountIn, uint256 amountOut) = _swapHook(params);

IERC20 tokenIn = params.tokenIn;

_takeTokenIn(params.sender, tokenIn, amountIn, params.wethIsEth);
_sendTokenOut(params.sender, params.tokenOut, amountOut, params.wethIsEth);

if (tokenIn == _weth) {
// Return the rest of ETH to sender
_returnEth(params.sender);
}

return amountCalculated;
}

function _swapHook(
SwapSingleTokenHookParams calldata params
) internal returns (uint256 amountCalculated, uint256 amountIn, uint256 amountOut) {
// The deadline is timestamp-based: it should not be relied upon for sub-minute accuracy.
// solhint-disable-next-line not-rely-on-time
if (block.timestamp > params.deadline) {
revert SwapDeadline();
}

(amountCalculated, amountIn, amountOut) = _vault.swap(
VaultSwapParams({
kind: params.kind,
pool: params.pool,
tokenIn: params.tokenIn,
tokenOut: params.tokenOut,
amountGivenRaw: params.amountGiven,
limitRaw: params.limit,
userData: params.userData
})
);
}


}
61 changes: 61 additions & 0 deletions packages/foundry/contracts/hooks/rebalancer/Oracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity ^0.8.24;

import {IOracle,TokenData} from "./interfaces/IOracle.sol";

contract Oracle is IOracle {
uint256 immutable public baseFee;
address public oracle;

event FeeUpdate(address indexed pool, uint256 fee);
event PositionUpdate(address indexed pool, TokenData);

error UnAuthorized();

mapping (address => uint256) public dynamicFee;

mapping(address => TokenData[]) public poolTokens;
error NotOracle();

modifier onlyOracle() {
if(msg.sender != oracle) {
revert UnAuthorized();
}
_;
}

constructor(uint256 _baseFee, address _oracle) {
baseFee = _baseFee;
oracle = _oracle;
}

function setFee(address pool, uint256 fee) external override {
dynamicFee[pool] = fee;
emit FeeUpdate(pool, fee);
}

function setPoolTokensData(
address pool,
TokenData[] memory _tokensData
) external onlyOracle {

TokenData[] storage poolRebalanceData = poolTokens[pool];

if (poolRebalanceData.length > 0) {
delete poolTokens[pool];
}

for (uint256 i = 0; i < _tokensData.length; i++) {
poolRebalanceData.push(_tokensData[i]);
}
}

function getFee(address pool) external view override returns (uint256) {
return dynamicFee[pool];
}

function getPoolTokensData(address pool) external view override returns (TokenData[] memory){
return poolTokens[pool];
}

}
Loading