-
Notifications
You must be signed in to change notification settings - Fork 3
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
create token balancer #43
Open
maksimKrukovich
wants to merge
1
commit into
main
Choose a base branch
from
feature/erc4626
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,181 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.24; | ||
|
||
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol"; | ||
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
|
||
import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; | ||
import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; | ||
|
||
import {ISaucerSwap} from "./interfaces/ISaucerSwap.sol"; | ||
|
||
import "../common/safe-HTS/SafeHTS.sol"; | ||
|
||
/** | ||
* @title Token Balancer | ||
* | ||
* The contract that helps to maintain reward token balances. | ||
*/ | ||
abstract contract TokenBalancer is AccessControl { | ||
mapping(address => uint256 allocationPercentage) internal targetPercentages; | ||
|
||
mapping(address => bytes32 priceId) public priceIds; | ||
|
||
mapping(address => address[]) public swapPaths; | ||
|
||
mapping(address => uint256) public tokenPrices; | ||
|
||
// Saucer Swap | ||
ISaucerSwap public saucerSwap; | ||
|
||
// Oracle | ||
IPyth public pyth; | ||
|
||
/** | ||
* @dev Initializes contract with passed parameters. | ||
* | ||
* @param _pyth The address of the Pyth oracle contract. | ||
* @param _saucerSwap The address of Saucer Swap contract. | ||
* @param tokens The reward tokens. | ||
* @param allocationPercentage The allocation percentages for rebalances. | ||
* @param _priceIds The Pyth price ids to fetch prices. | ||
*/ | ||
function __TokenBalancer_init( | ||
address _pyth, | ||
address _saucerSwap, | ||
address[] memory tokens, | ||
uint256[] memory allocationPercentage, | ||
bytes32[] memory _priceIds | ||
) internal { | ||
saucerSwap = ISaucerSwap(_saucerSwap); | ||
pyth = IPyth(_pyth); | ||
|
||
address whbar = saucerSwap.WHBAR(); | ||
|
||
uint256 tokensSize = tokens.length; | ||
for (uint256 i = 0; i < tokensSize; i++) { | ||
targetPercentages[tokens[i]] = allocationPercentage[i]; | ||
priceIds[tokens[i]] = _priceIds[i]; | ||
swapPaths[tokens[i]] = [whbar, tokens[i]]; | ||
tokenPrices[tokens[i]] = _getPrice(tokens[i]); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Gets token price and calculate one dollar in any token. | ||
* | ||
* @param token The token address. | ||
*/ | ||
function _getPrice(address token) public view returns (uint256 oneDollarInHbar) { | ||
PythStructs.Price memory price = pyth.getPrice(priceIds[token]); | ||
|
||
uint256 decimals = IERC20Metadata(token).decimals(); | ||
|
||
uint256 hbarPrice8Decimals = (uint(uint64(price.price)) * (18 ** decimals)) / | ||
(18 ** uint8(uint32(-1 * price.expo))); | ||
oneDollarInHbar = ((18 ** decimals) * (18 ** decimals)) / hbarPrice8Decimals; | ||
} | ||
|
||
/** | ||
* @dev Updates price. | ||
* | ||
* @param pythPriceUpdate The pyth price update. | ||
*/ | ||
function update(bytes[] calldata pythPriceUpdate) public payable { | ||
uint updateFee = pyth.getUpdateFee(pythPriceUpdate); | ||
pyth.updatePriceFeeds{value: updateFee}(pythPriceUpdate); | ||
} | ||
|
||
/** | ||
* @dev Rebalances reward balances. | ||
*/ | ||
function rebalance(address[] calldata _rewardTokens) external { | ||
uint256 rewardTokensSize = _rewardTokens.length; | ||
uint256[] memory prices; | ||
for (uint256 i = 0; i < rewardTokensSize; i++) { | ||
prices[i] = tokenPrices[_rewardTokens[i]]; | ||
} | ||
|
||
uint256[] memory swapAmounts = _rebalance(prices, _rewardTokens); | ||
|
||
_swapExtraRewardSupplyToTransitionToken(_rewardTokens); | ||
|
||
uint256 swapsCount = swapAmounts.length; | ||
for (uint256 i = 0; i < swapsCount; i++) { | ||
saucerSwap.swapExactETHForTokens( | ||
swapAmounts[i], | ||
swapPaths[_rewardTokens[i]], | ||
address(this), | ||
block.timestamp | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* @dev Swaps extra reward balance to WHBAR token for future rebalance. | ||
* | ||
*/ | ||
function _swapExtraRewardSupplyToTransitionToken(address[] calldata _rewardTokens) public { | ||
for (uint256 i = 0; i < _rewardTokens.length; i++) { | ||
address token = _rewardTokens[i]; | ||
uint256 tokenBalance = IERC20(token).balanceOf(address(this)); | ||
uint256 tokenPrice = tokenPrices[token]; | ||
uint256 totalValue = tokenBalance * tokenPrice; | ||
uint256 targetValue = (totalValue * targetPercentages[token]) / 10000; | ||
uint256 targetQuantity = targetValue / tokenPrice; | ||
|
||
if (tokenBalance > targetQuantity) { | ||
uint256 excessQuantity = tokenBalance - targetQuantity; | ||
|
||
// Approve token transfer to SaucerSwap | ||
IERC20(token).approve(address(saucerSwap), excessQuantity); | ||
|
||
// Perform the swap | ||
saucerSwap.swapExactTokensForETH( | ||
excessQuantity, | ||
0, // Accept any amount of ETH | ||
swapPaths[token], | ||
address(this), | ||
block.timestamp | ||
); | ||
} | ||
} | ||
} | ||
|
||
function _rebalance( | ||
uint256[] memory _tokenPrices, | ||
address[] calldata _rewardTokens | ||
) public view returns (uint256[] memory) { | ||
require(_tokenPrices.length == _rewardTokens.length, "Token prices array length mismatch"); | ||
|
||
uint256 totalValue; | ||
uint256[] memory tokenBalances = new uint256[](_rewardTokens.length); | ||
|
||
// Calculate total value in the contract | ||
for (uint256 i = 0; i < _rewardTokens.length; i++) { | ||
tokenBalances[i] = IERC20(_rewardTokens[i]).balanceOf(address(this)); | ||
totalValue += tokenBalances[i] * _tokenPrices[i]; | ||
} | ||
|
||
// Array to store the amounts to swap | ||
uint256[] memory swapAmounts = new uint256[](_rewardTokens.length); | ||
|
||
// Calculate target values and swap amounts | ||
for (uint256 i = 0; i < _rewardTokens.length; i++) { | ||
uint256 targetValue = (totalValue * targetPercentages[_rewardTokens[i]]) / 10000; | ||
uint256 targetQuantity = targetValue / _tokenPrices[i]; | ||
|
||
swapAmounts[i] = targetQuantity - tokenBalances[i]; | ||
} | ||
|
||
return swapAmounts; | ||
} | ||
|
||
// Utility function to update target percentages | ||
function setTargetPercentage(address token, uint256 percentage) external { | ||
require(percentage < 10000, "Percentage exceeds 100%"); | ||
require(token != address(0), "Invalid token address"); | ||
targetPercentages[token] = percentage; | ||
} | ||
} |
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
2 changes: 1 addition & 1 deletion
2
contracts/erc4626/IERC4626.sol → contracts/erc4626/interfaces/IERC4626.sol
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
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,36 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.24; | ||
|
||
/** | ||
* @title Saucer Swap | ||
*/ | ||
interface ISaucerSwap { | ||
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); | ||
|
||
function getPair(address tokenA, address tokenB) external view returns (address pair); | ||
|
||
function WHBAR() external pure returns (address); | ||
|
||
function swapExactETHForTokens( | ||
uint amountOutMin, | ||
address[] calldata path, | ||
address to, | ||
uint deadline | ||
) external payable returns (uint[] memory amounts); | ||
|
||
function swapTokensForExactTokens( | ||
uint amountOut, | ||
uint amountInMax, | ||
address[] calldata path, | ||
address to, | ||
uint deadline | ||
) external returns (uint[] memory amounts); | ||
|
||
function swapExactTokensForETH( | ||
uint amountIn, | ||
uint amountOutMin, | ||
address[] calldata path, | ||
address to, | ||
uint deadline | ||
) external returns (uint[] memory amounts); | ||
} |
File renamed without changes.
2 changes: 1 addition & 1 deletion
2
contracts/erc4626/SafeTransferLib.sol → ...cts/erc4626/libraries/SafeTransferLib.sol
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
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,81 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.24; | ||
|
||
import {IPyth} from "@pythnetwork/pyth-sdk-solidity/IPyth.sol"; | ||
import {PythStructs} from "@pythnetwork/pyth-sdk-solidity/PythStructs.sol"; | ||
|
||
contract MockPyth is IPyth { | ||
mapping(bytes32 => PythStructs.Price) public prices; | ||
|
||
// Function to set mock prices | ||
function setPrice(bytes32 id, int64 price, uint64 conf, int32 expo, uint publishTime) public { | ||
prices[id] = PythStructs.Price({price: price, conf: conf, expo: expo, publishTime: publishTime}); | ||
} | ||
|
||
function getValidTimePeriod() external pure override returns (uint validTimePeriod) { | ||
return 3600; // 1 hour for example | ||
} | ||
|
||
function getPrice(bytes32 id) external view override returns (PythStructs.Price memory price) { | ||
return prices[id]; | ||
} | ||
|
||
function getEmaPrice(bytes32 id) external view override returns (PythStructs.Price memory price) { | ||
return prices[id]; // Just returning the same price for simplicity | ||
} | ||
|
||
function getPriceUnsafe(bytes32 id) external view override returns (PythStructs.Price memory price) { | ||
return prices[id]; | ||
} | ||
|
||
function getPriceNoOlderThan(bytes32 id, uint age) external view override returns (PythStructs.Price memory price) { | ||
require(block.timestamp - prices[id].publishTime <= age, "Price is too old"); | ||
return prices[id]; | ||
} | ||
|
||
function getEmaPriceUnsafe(bytes32 id) external view override returns (PythStructs.Price memory price) { | ||
return prices[id]; | ||
} | ||
|
||
function getEmaPriceNoOlderThan( | ||
bytes32 id, | ||
uint age | ||
) external view override returns (PythStructs.Price memory price) { | ||
require(block.timestamp - prices[id].publishTime <= age, "Price is too old"); | ||
return prices[id]; | ||
} | ||
|
||
function updatePriceFeeds(bytes[] calldata updateData) external payable override { | ||
// Mock implementation | ||
} | ||
|
||
function updatePriceFeedsIfNecessary( | ||
bytes[] calldata updateData, | ||
bytes32[] calldata priceIds, | ||
uint64[] calldata publishTimes | ||
) external payable override { | ||
// Mock implementation | ||
} | ||
|
||
function getUpdateFee(bytes[] calldata updateData) external view override returns (uint feeAmount) { | ||
return 0; // No fee for mock | ||
} | ||
|
||
function parsePriceFeedUpdates( | ||
bytes[] calldata updateData, | ||
bytes32[] calldata priceIds, | ||
uint64 minPublishTime, | ||
uint64 maxPublishTime | ||
) external payable override returns (PythStructs.PriceFeed[] memory priceFeeds) { | ||
// Mock implementation | ||
} | ||
|
||
function parsePriceFeedUpdatesUnique( | ||
bytes[] calldata updateData, | ||
bytes32[] calldata priceIds, | ||
uint64 minPublishTime, | ||
uint64 maxPublishTime | ||
) external payable override returns (PythStructs.PriceFeed[] memory priceFeeds) { | ||
// Mock implementation | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe check if lists have same length ? And also limit max numbers of tokens?