From f3a8d92887ab9ebf019bf59a5857cb53006ac436 Mon Sep 17 00:00:00 2001 From: kexley <87971154+kexleyBeefy@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:45:20 +0000 Subject: [PATCH] Update ETH Reward Pool (#240) * Update Reward Pool * Move `TooManyRewards` error * Swapper oracle (#242) * Add Beefy Oracle * Add Beefy Swapper * FeeBatchV4 & improve reward pool * Add mooBIFI strategy * Add scripts for adding routes and price feeds * Add Oracle Tests * Add Swapper tests * Add more Swapper tests * Remove fee batch ownership of reward pool * Reward pool adjustments --- contracts/BIFI/infra/BeefyFeeBatchV4.sol | 331 ++++ .../BIFI/infra/BeefyOracle/BeefyOracle.sol | 138 ++ .../BeefyOracle/BeefyOracleChainlink.sol | 36 + .../infra/BeefyOracle/BeefyOracleErrors.sol | 25 + .../infra/BeefyOracle/BeefyOracleHelper.sol | 44 + .../infra/BeefyOracle/BeefyOracleSolidly.sol | 65 + .../BeefyOracle/BeefyOracleUniswapV2.sol | 149 ++ .../BeefyOracle/BeefyOracleUniswapV3.sol | 75 + contracts/BIFI/infra/BeefyRewardPool.sol | 434 +++++ contracts/BIFI/infra/BeefyRewardPool.txt | 119 -- contracts/BIFI/infra/BeefySwapper.sol | 320 +++ .../interfaces/beefy/IBeefyRewardPool.sol | 15 + .../BIFI/interfaces/beefy/IBeefySwapper.sol | 24 + .../BIFI/interfaces/common/ISolidlyPair.sol | 1 + .../BIFI/interfaces/common/IUniswapV2Pair.sol | 3 + .../BIFI/interfaces/common/IUniswapV3Pool.sol | 12 + .../BIFI/interfaces/common/IWrappedNative.sol | 1 - .../BIFI/interfaces/oracle/IBeefyOracle.sol | 10 + .../BIFI/interfaces/oracle/IChainlink.sol | 8 + .../BIFI/interfaces/oracle/ISubOracle.sol | 8 + .../BIFI/strategies/Beefy/StrategyBeefy.sol | 325 ++++ contracts/BIFI/utils/TickMath.sol | 205 ++ .../BIFI/utils/UniswapV3OracleLibrary.sol | 81 + data/abi/BalancerVault.json | 1179 +++++++++++ data/abi/BeefyOracle.json | 300 +++ data/abi/UniswapV2Factory.json | 193 ++ data/abi/UniswapV3Factory.json | 236 +++ data/abi/UniswapV3Router.json | 565 ++++++ data/abi/VelodromeFactory.json | 730 +++++++ data/abi/VelodromeRouter.json | 1717 +++++++++++++++++ forge/test/oracle/Oracle.t.sol | 248 +++ forge/test/swapper/Swapper.t.sol | 234 +++ foundry.toml | 3 +- package.json | 6 +- scripts/manage/setOracle.js | 162 ++ scripts/manage/setSwapInfo.js | 219 +++ 36 files changed, 8098 insertions(+), 123 deletions(-) create mode 100644 contracts/BIFI/infra/BeefyFeeBatchV4.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracleErrors.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracleHelper.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracleSolidly.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV2.sol create mode 100644 contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV3.sol create mode 100644 contracts/BIFI/infra/BeefyRewardPool.sol delete mode 100644 contracts/BIFI/infra/BeefyRewardPool.txt create mode 100644 contracts/BIFI/infra/BeefySwapper.sol create mode 100644 contracts/BIFI/interfaces/beefy/IBeefyRewardPool.sol create mode 100644 contracts/BIFI/interfaces/beefy/IBeefySwapper.sol create mode 100644 contracts/BIFI/interfaces/common/IUniswapV3Pool.sol create mode 100644 contracts/BIFI/interfaces/oracle/IBeefyOracle.sol create mode 100644 contracts/BIFI/interfaces/oracle/IChainlink.sol create mode 100644 contracts/BIFI/interfaces/oracle/ISubOracle.sol create mode 100644 contracts/BIFI/strategies/Beefy/StrategyBeefy.sol create mode 100644 contracts/BIFI/utils/TickMath.sol create mode 100644 contracts/BIFI/utils/UniswapV3OracleLibrary.sol create mode 100644 data/abi/BalancerVault.json create mode 100644 data/abi/BeefyOracle.json create mode 100644 data/abi/UniswapV2Factory.json create mode 100644 data/abi/UniswapV3Factory.json create mode 100644 data/abi/UniswapV3Router.json create mode 100644 data/abi/VelodromeFactory.json create mode 100644 data/abi/VelodromeRouter.json create mode 100644 forge/test/oracle/Oracle.t.sol create mode 100644 forge/test/swapper/Swapper.t.sol create mode 100644 scripts/manage/setOracle.js create mode 100644 scripts/manage/setSwapInfo.js diff --git a/contracts/BIFI/infra/BeefyFeeBatchV4.sol b/contracts/BIFI/infra/BeefyFeeBatchV4.sol new file mode 100644 index 00000000..d8b163f6 --- /dev/null +++ b/contracts/BIFI/infra/BeefyFeeBatchV4.sol @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { SafeERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; + +import { IBeefySwapper } from "../interfaces/beefy/IBeefySwapper.sol"; +import { IBeefyRewardPool } from "../interfaces/beefy/IBeefyRewardPool.sol"; +import { IWrappedNative } from "../interfaces/common/IWrappedNative.sol"; + +/// @title Beefy fee batch +/// @author kexley, Beefy +/// @notice All Beefy fees will flow through to the treasury and the reward pool +/// @dev Wrapped ETH will build up on this contract and will be swapped via the Beefy Swapper to +/// the pre-specified tokens and distributed to the treasury and reward pool +contract BeefyFeeBatchV4 is OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + + /// @dev Bundled token information + /// @param tokens Token addresses to swap to + /// @param index Location of a token in the tokens array + /// @param allocPoint Allocation points for this token + /// @param totalAllocPoint Total amount of allocation points assigned to tokens in the array + struct TokenInfo { + address[] tokens; + mapping(address => uint256) index; + mapping(address => uint256) allocPoint; + uint256 totalAllocPoint; + } + + /// @notice Native token (WETH) + IERC20Upgradeable public native; + + /// @notice Treasury address + address public treasury; + + /// @notice Reward pool address + address public rewardPool; + + /// @notice Swapper address to swap all tokens at + address public swapper; + + /// @notice Vault harvester + address public harvester; + + /// @notice Treasury fee of the total native received on the contract (1 = 0.1%) + uint256 public treasuryFee; + + /// @notice Denominator constant + uint256 constant public DIVISOR = 1000; + + /// @notice Duration of reward distributions + uint256 public duration; + + /// @notice Minimum operating gas level on the harvester + uint256 public harvesterMax; + + /// @notice Whether to send gas to the harvester + bool public sendHarvesterGas; + + /// @notice Tokens to be sent to the treasury + TokenInfo public treasuryTokens; + + /// @notice Tokens to be sent to the reward pool + TokenInfo public rewardTokens; + + /// @notice Fees have been harvested + /// @param totalHarvested Total fee amount that has been processed + /// @param timestamp Timestamp of the harvest + event Harvest(uint256 totalHarvested, uint256 timestamp); + /// @notice Harvester has been sent gas + /// @param gas Amount of gas that has been sent + event SendHarvesterGas(uint256 gas); + /// @notice Treasury fee that has been sent + /// @param token Token that has been sent + /// @param amount Amount of the token sent + event DistributeTreasuryFee(address indexed token, uint256 amount); + /// @notice Reward pool has been notified + /// @param token Token used as a reward + /// @param amount Amount of the token used + /// @param duration Duration of the distribution + event NotifyRewardPool(address indexed token, uint256 amount, uint256 duration); + /// @notice Reward pool set + /// @param rewardPool New reward pool address + event SetRewardPool(address rewardPool); + /// @notice Treasury set + /// @param treasury New treasury address + event SetTreasury(address treasury); + /// @notice Whether to send gas to harvester has been set + /// @param send Whether to send gas to harvester + event SetSendHarvesterGas(bool send); + /// @notice Harvester set + /// @param harvester New harvester address + /// @param harvesterMax Minimum operating gas level for the harvester + event SetHarvester(address harvester, uint256 harvesterMax); + /// @notice Swapper set + /// @param swapper New swapper address + event SetSwapper(address swapper); + /// @notice Treasury fee set + /// @param fee New fee split for the treasury + event SetTreasuryFee(uint256 fee); + /// @notice Reward pool duration set + /// @param duration New duration of the reward distribution + event SetDuration(uint256 duration); + /// @notice Set the whitelist status of a manager for the reward pool + /// @param manager Address of the manager + /// @param whitelisted Status of the manager on the whitelist + event SetWhitelistOfRewardPool(address manager, bool whitelisted); + /// @notice Remove a reward from the reward pool distribution + /// @param reward Address of the reward to remove + /// @param recipient Address to send the reward to + event RemoveRewardFromRewardPool(address reward, address recipient); + /// @notice Rescue an unsupported token from the reward pool + /// @param token Address of the token to remove + /// @param recipient Address to send the token to + event RescueTokensFromRewardPool(address token, address recipient); + /// @notice Transfer ownership of the reward pool to a new owner + /// @param owner New owner of the reward pool + event TransferOwnershipOfRewardPool(address owner); + /// @notice Rescue an unsupported token + /// @param token Address of the token + /// @param recipient Address to send the token to + event RescueTokens(address token, address recipient); + + /// @notice Initialize the contract, callable only once + /// @param _native WETH address + /// @param _rewardPool Reward pool address + /// @param _treasury Treasury address + /// @param _swapper Swapper address + /// @param _treasuryFee Treasury fee split + function initialize( + address _native, + address _rewardPool, + address _treasury, + address _swapper, + uint256 _treasuryFee + ) external initializer { + __Ownable_init(); + + native = IERC20Upgradeable(_native); + treasury = _treasury; + rewardPool = _rewardPool; + treasuryFee = _treasuryFee; + swapper = _swapper; + native.forceApprove(swapper, type(uint).max); + duration = 7 days; + } + + /// @notice Distribute the fees to the harvester, treasury and reward pool + function harvest() external { + uint256 totalFees = native.balanceOf(address(this)); + + if (sendHarvesterGas) _sendHarvesterGas(); + _distributeTreasuryFee(); + _notifyRewardPool(); + + emit Harvest(totalFees - native.balanceOf(address(this)), block.timestamp); + } + + /// @dev Unwrap the required amount of native and send to the harvester + function _sendHarvesterGas() private { + uint256 nativeBal = native.balanceOf(address(this)); + + uint256 harvesterBal = harvester.balance + native.balanceOf(harvester); + if (harvesterBal < harvesterMax) { + uint256 gas = harvesterMax - harvesterBal; + if (gas > nativeBal) { + gas = nativeBal; + } + IWrappedNative(address(native)).withdraw(gas); + (bool sent, ) = harvester.call{value: gas}(""); + require(sent, "Failed to send Ether"); + + emit SendHarvesterGas(gas); + } + } + + /// @dev Swap to required treasury tokens and send the treasury fees onto the treasury + function _distributeTreasuryFee() private { + uint256 treasuryFeeAmount = native.balanceOf(address(this)) * treasuryFee / DIVISOR; + + for (uint i; i < treasuryTokens.tokens.length; ++i) { + address token = treasuryTokens.tokens[i]; + uint256 amount = treasuryFeeAmount + * treasuryTokens.allocPoint[token] + / treasuryTokens.totalAllocPoint; + + if (amount == 0) continue; + if (token != address(native)) { + amount = IBeefySwapper(swapper).swap(address(native), token, amount); + if (amount == 0) continue; + } + + IERC20Upgradeable(token).safeTransfer(treasury, amount); + emit DistributeTreasuryFee(token, amount); + } + } + + /// @dev Swap to required reward tokens and notify the reward pool + function _notifyRewardPool() private { + uint256 rewardPoolAmount = native.balanceOf(address(this)); + + for (uint i; i < rewardTokens.tokens.length; ++i) { + address token = rewardTokens.tokens[i]; + uint256 amount = rewardPoolAmount + * rewardTokens.allocPoint[token] + / rewardTokens.totalAllocPoint; + + if (amount == 0) continue; + if (token != address(native)) { + amount = IBeefySwapper(swapper).swap(address(native), token, amount); + if (amount == 0) continue; + } + + IBeefyRewardPool(rewardPool).notifyRewardAmount(token, amount, duration); + emit NotifyRewardPool(token, amount, duration); + } + } + + /* ----------------------------------- VARIABLE SETTERS ----------------------------------- */ + + /// @notice Adjust which tokens and how much the harvest should swap the treasury fee to + /// @param _token Address of the token to send to the treasury + /// @param _allocPoint How much to swap into the particular token from the treasury fee + function setTreasuryAllocPoint(address _token, uint256 _allocPoint) external onlyOwner { + if (treasuryTokens.allocPoint[_token] > 0 && _allocPoint == 0) { + address endToken = treasuryTokens.tokens[treasuryTokens.tokens.length - 1]; + treasuryTokens.index[endToken] = treasuryTokens.index[_token]; + treasuryTokens.tokens[treasuryTokens.index[endToken]] = endToken; + treasuryTokens.tokens.pop(); + } else if (treasuryTokens.allocPoint[_token] == 0 && _allocPoint > 0) { + treasuryTokens.index[_token] = treasuryTokens.tokens.length; + treasuryTokens.tokens.push(_token); + } + + treasuryTokens.totalAllocPoint -= treasuryTokens.allocPoint[_token]; + treasuryTokens.totalAllocPoint += _allocPoint; + treasuryTokens.allocPoint[_token] = _allocPoint; + } + + /// @notice Adjust which tokens and how much the harvest should swap the reward pool fee to + /// @param _token Address of the token to send to the reward pool + /// @param _allocPoint How much to swap into the particular token from the reward pool fee + function setRewardAllocPoint(address _token, uint256 _allocPoint) external onlyOwner { + if (rewardTokens.allocPoint[_token] > 0 && _allocPoint == 0) { + address endToken = rewardTokens.tokens[rewardTokens.tokens.length - 1]; + rewardTokens.index[endToken] = rewardTokens.index[_token]; + rewardTokens.tokens[rewardTokens.index[endToken]] = endToken; + rewardTokens.tokens.pop(); + } else if (rewardTokens.allocPoint[_token] == 0 && _allocPoint > 0) { + rewardTokens.index[_token] = rewardTokens.tokens.length; + rewardTokens.tokens.push(_token); + IERC20Upgradeable(_token).forceApprove(rewardPool, type(uint).max); + } + + rewardTokens.totalAllocPoint -= rewardTokens.allocPoint[_token]; + rewardTokens.totalAllocPoint += _allocPoint; + rewardTokens.allocPoint[_token] = _allocPoint; + } + + /// @notice Set the reward pool + /// @param _rewardPool New reward pool address + function setRewardPool(address _rewardPool) external onlyOwner { + rewardPool = _rewardPool; + emit SetRewardPool(_rewardPool); + } + + /// @notice Set the treasury + /// @param _treasury New treasury address + function setTreasury(address _treasury) external onlyOwner { + treasury = _treasury; + emit SetTreasury(_treasury); + } + + /// @notice Set whether the harvester should be sent gas + /// @param _sendGas Whether the harvester should be sent gas + function setSendHarvesterGas(bool _sendGas) external onlyOwner { + sendHarvesterGas = _sendGas; + emit SetSendHarvesterGas(_sendGas); + } + + /// @notice Set the harvester and the minimum operating gas level of the harvester + /// @param _harvester New harvester address + /// @param _harvesterMax New minimum operating gas level of the harvester + function setHarvesterConfig(address _harvester, uint256 _harvesterMax) external onlyOwner { + harvester = _harvester; + harvesterMax = _harvesterMax; + emit SetHarvester(_harvester, _harvesterMax); + } + + /// @notice Set the swapper + /// @param _swapper New swapper address + function setSwapper(address _swapper) external onlyOwner { + native.approve(swapper, 0); + swapper = _swapper; + native.forceApprove(swapper, type(uint).max); + emit SetSwapper(_swapper); + } + + /// @notice Set the treasury fee + /// @param _treasuryFee New treasury fee split + function setTreasuryFee(uint256 _treasuryFee) external onlyOwner { + if (_treasuryFee > DIVISOR) _treasuryFee = DIVISOR; + treasuryFee = _treasuryFee; + emit SetTreasuryFee(_treasuryFee); + } + + /// @notice Set the duration of the reward distribution + /// @param _duration New duration of the reward distribution + function setDuration(uint256 _duration) external onlyOwner { + duration = _duration; + emit SetDuration(_duration); + } + + /* ------------------------------------- SWEEP TOKENS ------------------------------------- */ + + /// @notice Rescue an unsupported token + /// @param _token Address of the token + /// @param _recipient Address to send the token to + function rescueTokens(address _token, address _recipient) external onlyOwner { + require(_token != address(native), "!safe"); + + uint256 amount = IERC20Upgradeable(_token).balanceOf(address(this)); + IERC20Upgradeable(_token).safeTransfer(_recipient, amount); + emit RescueTokens(_token, _recipient); + } + + /// @notice Support unwrapped native + receive() external payable {} +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol new file mode 100644 index 00000000..a7756f67 --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { ISubOracle } from "../../interfaces/oracle/ISubOracle.sol"; + +/// @title Beefy Oracle +/// @author Beefy, @kexley +/// @notice On-chain oracle using various sources +contract BeefyOracle is OwnableUpgradeable { + + /// @dev Struct for the latest price of a token with the timestamp + /// @param price Stored price + /// @param timestamp Last update timestamp + struct LatestPrice { + uint256 price; + uint256 timestamp; + } + + /// @dev Struct for delegating the price calculation to a library using stored data + /// @param oracle Address of the library for a particular oracle type + /// @param data Stored data for calculating the price of a specific token using the library + struct SubOracle { + address oracle; + bytes data; + } + + /// @notice Latest price of a token with a timestamp + mapping(address => LatestPrice) public latestPrice; + + /// @notice Oracle library address and payload for delegating the price calculation of a token + mapping(address => SubOracle) public subOracle; + + /// @notice Length of time in seconds before a price goes stale + uint256 public staleness; + + /// @notice Price of a token has been updated + /// @param token Token address + /// @param price New price + /// @param timestamp Timestamp of price fetch + event PriceUpdated(address indexed token, uint256 price, uint256 timestamp); + + /// @notice New oracle has been set + /// @param token Token address + /// @param oracle Library address for price fetch + /// @param data Data to pass to library to calculate the price for that token + event SetOracle(address indexed token, address oracle, bytes data); + + /// @notice New staleness has been set + /// @param staleness Length of time a price stays fresh for + event SetStaleness(uint256 staleness); + + /// @notice Initialize the contract + /// @dev Ownership is transferred to msg.sender + function initialize() external initializer { + __Ownable_init(); + } + + /// @notice Fetch the most recent stored price for a token + /// @param _token Address of the token being fetched + /// @return price Price of the token + function getPrice(address _token) external view returns (uint256 price) { + price = latestPrice[_token].price; + } + + /// @notice Fetch an updated price for a token + /// @param _token Address of the token being fetched + /// @return price Updated price of the token + /// @return success Price update was success or not + function getFreshPrice(address _token) external returns (uint256 price, bool success) { + (price, success) = _getFreshPrice(_token); + } + + /// @dev If the price is stale then calculate a new price by delegating to the sub oracle + /// @param _token Address of the token being fetched + /// @return price Updated price of the token + /// @return success Price update was success or not + function _getFreshPrice(address _token) private returns (uint256 price, bool success) { + if (latestPrice[_token].timestamp + staleness > block.timestamp) { + price = latestPrice[_token].price; + success = true; + } else { + (price, success) = ISubOracle(subOracle[_token].oracle).getPrice(subOracle[_token].data); + if (success) { + latestPrice[_token] = LatestPrice({price: price, timestamp: block.timestamp}); + emit PriceUpdated(_token, price, block.timestamp); + } + } + } + + /* ----------------------------------- OWNER FUNCTIONS ----------------------------------- */ + + /// @notice Owner function to set a sub oracle and data for a token + /// @dev The payload will be validated against the library + /// @param _token Address of the token being fetched + /// @param _oracle Address of the library used to calculate the price + /// @param _data Payload specific to the token that will be used by the library + function setOracle(address _token, address _oracle, bytes calldata _data) external onlyOwner { + _setOracle(_token, _oracle, _data); + } + + /// @notice Owner function to set a sub oracle and data for an array of tokens + /// @dev The payloads will be validated against the libraries + /// @param _tokens Addresses of the tokens being fetched + /// @param _oracles Addresses of the libraries used to calculate the price + /// @param _datas Payloads specific to the tokens that will be used by the libraries + function setOracles( + address[] calldata _tokens, + address[] calldata _oracles, + bytes[] calldata _datas + ) external onlyOwner { + uint256 tokenLength = _tokens.length; + for (uint i; i < tokenLength;) { + _setOracle(_tokens[i], _oracles[i], _datas[i]); + unchecked { ++i; } + } + } + + /// @dev Set the sub oracle and data for a token, it also validates that the data is correct + /// @param _token Address of the token being fetched + /// @param _oracle Address of the library used to calculate the price + /// @param _data Payload specific to the token that will be used by the library + function _setOracle(address _token, address _oracle, bytes calldata _data) private { + ISubOracle(_oracle).validateData(_data); + subOracle[_token] = SubOracle({oracle: _oracle, data: _data}); + _getFreshPrice(_token); + emit SetOracle(_token, _oracle, _data); + } + + /// @notice Owner function to set the staleness + /// @param _staleness Length of time in seconds before a price becomes stale + function setStaleness(uint256 _staleness) external onlyOwner { + staleness = _staleness; + emit SetStaleness(_staleness); + } +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol new file mode 100644 index 00000000..ef7d27b0 --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { IChainlink } from "../../interfaces/oracle/IChainlink.sol"; +import { BeefyOracleHelper, BeefyOracleErrors } from "./BeefyOracleHelper.sol"; + +/// @title Beefy Oracle using Chainlink +/// @author Beefy, @kexley +/// @notice On-chain oracle using Chainlink +library BeefyOracleChainlink { + + /// @notice Fetch price from the Chainlink feed and scale to 18 decimals + /// @param _data Payload from the central oracle with the address of the Chainlink feed + /// @return price Retrieved price from the Chainlink feed + /// @return success Successful price fetch or not + function getPrice(bytes calldata _data) external view returns (uint256 price, bool success) { + address chainlink = abi.decode(_data, (address)); + try IChainlink(chainlink).decimals() returns (uint8 decimals) { + try IChainlink(chainlink).latestAnswer() returns (int256 latestAnswer) { + price = BeefyOracleHelper.scaleAmount(uint256(latestAnswer), decimals); + success = true; + } catch {} + } catch {} + } + + /// @notice Data validation for new oracle data being added to central oracle + /// @param _data Encoded Chainlink feed address + function validateData(bytes calldata _data) external view { + address chainlink = abi.decode(_data, (address)); + try IChainlink(chainlink).decimals() returns (uint8) { + try IChainlink(chainlink).latestAnswer() returns (int256) { + } catch { revert BeefyOracleErrors.NoAnswer(); } + } catch { revert BeefyOracleErrors.NoAnswer(); } + } +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracleErrors.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracleErrors.sol new file mode 100644 index 00000000..569c08c8 --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracleErrors.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +/// @title Beefy Oracle Errors +/// @author Beefy, @kexley +/// @notice Error list for Beefy Oracles +contract BeefyOracleErrors { + + /// @dev No response from the Chainlink feed + error NoAnswer(); + + /// @dev No price for base token + /// @param token Base token + error NoBasePrice(address token); + + /// @dev Token is not present in the pair + /// @param token Input token + /// @param pair Pair token + error TokenNotInPair(address token, address pair); + + /// @dev Array length is not correct + error ArrayLength(); + +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracleHelper.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracleHelper.sol new file mode 100644 index 00000000..a8f8c0ef --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracleHelper.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; + +import { IBeefyOracle } from "../../interfaces/oracle/IBeefyOracle.sol"; +import { BeefyOracleErrors } from "./BeefyOracleErrors.sol"; + +/// @title Beefy Oracle Helper +/// @author Beefy, @kexley +/// @notice Helper functions for Beefy oracles +library BeefyOracleHelper { + + /// @dev Calculate the price of the output token in 18 decimals given the base token price + /// and the amount out received from swapping 1 unit of the base token + /// @param _oracle Central Beefy oracle which stores the base token price + /// @param _token Address of token to calculate the price of + /// @param _baseToken Address of the base token used in the price chain + /// @param _amountOut Amount received from swapping 1 unit of base token into the target token + /// @return price Price of the target token in 18 decimals + function priceFromBaseToken( + address _oracle, + address _token, + address _baseToken, + uint256 _amountOut + ) internal returns (uint256 price) { + (uint256 basePrice,) = IBeefyOracle(_oracle).getFreshPrice(_baseToken); + uint8 decimals = IERC20MetadataUpgradeable(_token).decimals(); + _amountOut = scaleAmount(_amountOut, decimals); + price = basePrice * 1 ether / _amountOut; + } + + /// @dev Scale an input amount to 18 decimals + /// @param _amount Amount to be scaled up or down + /// @param _decimals Decimals that the amount is already in + /// @return scaledAmount New scaled amount in 18 decimals + function scaleAmount( + uint256 _amount, + uint8 _decimals + ) internal pure returns (uint256 scaledAmount) { + scaledAmount = _decimals == 18 ? _amount : _amount * 10 ** 18 / 10 ** _decimals; + } +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracleSolidly.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracleSolidly.sol new file mode 100644 index 00000000..ed82c2a8 --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracleSolidly.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; + +import { ISolidlyPair} from "../../interfaces/common/ISolidlyPair.sol"; +import { BeefyOracleHelper, IBeefyOracle, BeefyOracleErrors } from "./BeefyOracleHelper.sol"; + +/// @title Beefy Oracle for Solidly +/// @author Beefy, @kexley +/// @notice On-chain oracle using Solidly +contract BeefyOracleSolidly { + + /// @notice Fetch price from the Solidly pairs using the TWAP observations + /// @param _data Payload from the central oracle with the addresses of the token route, pool + /// route and TWAP periods counted in 30 minute increments + /// @return price Retrieved price from the chained quotes + /// @return success Successful price fetch or not + function getPrice(bytes calldata _data) external returns (uint256 price, bool success) { + (address[] memory tokens, address[] memory pools, uint256[] memory twapPeriods) = + abi.decode(_data, (address[], address[], uint256[])); + + uint256 amount = 10 ** IERC20MetadataUpgradeable(tokens[0]).decimals(); + for (uint i; i < pools.length; i++) { + amount = ISolidlyPair(pools[i]).quote(tokens[i], amount, twapPeriods[i]); + } + + price = BeefyOracleHelper.priceFromBaseToken( + msg.sender, tokens[tokens.length - 1], tokens[0], amount + ); + if (price != 0) success = true; + } + + /// @notice Data validation for new oracle data being added to central oracle + /// @param _data Encoded addresses of the token route, pool route and TWAP periods + function validateData(bytes calldata _data) external view { + (address[] memory tokens, address[] memory pools, uint256[] memory twapPeriods) = + abi.decode(_data, (address[], address[], uint256[])); + + if (tokens.length != pools.length + 1 || tokens.length != twapPeriods.length + 1) { + revert BeefyOracleErrors.ArrayLength(); + } + + uint256 basePrice = IBeefyOracle(msg.sender).getPrice(tokens[0]); + if (basePrice == 0) revert BeefyOracleErrors.NoBasePrice(tokens[0]); + + uint256 poolLength = pools.length; + for (uint i; i < poolLength;) { + address fromToken = tokens[i]; + address toToken = tokens[i + 1]; + address pool = pools[i]; + address token0 = ISolidlyPair(pool).token0(); + address token1 = ISolidlyPair(pool).token1(); + + if (fromToken != token0 && fromToken != token1) { + revert BeefyOracleErrors.TokenNotInPair(fromToken, pool); + } + if (toToken != token0 && toToken != token1) { + revert BeefyOracleErrors.TokenNotInPair(toToken, pool); + } + unchecked { ++i; } + } + } +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV2.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV2.sol new file mode 100644 index 00000000..568a2fb2 --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV2.sol @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; + +import { IUniswapV2Pair } from "../../interfaces/common/IUniswapV2Pair.sol"; +import { BeefyOracleHelper, IBeefyOracle, BeefyOracleErrors } from "./BeefyOracleHelper.sol"; + +/// @title Beefy Oracle for UniswapV2 +/// @author Beefy, @kexley +/// @notice On-chain oracle using UniswapV2 +/// @dev Observations are stored here as UniswapV2 pairs do not store historical observations +contract BeefyOracleUniswapV2 { + + /// @dev Struct of stored price averages and the most recent observation of a pair + /// @param priceAverage0 Average price of token0 + /// @param priceAverage1 Average price of token1 + /// @param observation Cumulative prices of token0 and token1 + struct Price { + uint256 priceAverage0; + uint256 priceAverage1; + Observation observation; + } + + /// @dev Struct of the stored latest observation of a pair + /// @param price0 Cumulative price of token0 + /// @param price1 Cumulative price of token1 + /// @param timestamp Timestamp of the observation + struct Observation { + uint256 price0; + uint256 price1; + uint256 timestamp; + } + + /// @notice Stored last average prices of tokens in a pair + mapping(address => Price) public prices; + + /// @notice Pair has been updated with average prices + /// @param pair Pair address + /// @param priceAverage0 Average price of token0 + /// @param priceAverage1 Average price of token1 + event PairUpdated(address indexed pair, uint256 priceAverage0, uint256 priceAverage1); + + /// @notice Fetch price from the UniswapV2 pairs using the TWAP observations + /// @param _data Payload from the central oracle with the addresses of the token route, pairs + /// route and TWAP periods in seconds + /// @return price Retrieved price from the chained quotes + /// @return success Successful price fetch or not + function getPrice(bytes calldata _data) external returns (uint256 price, bool success) { + (address[] memory tokens, address[] memory pairs, uint256[] memory twapPeriods) = + abi.decode(_data, (address[], address[], uint256[])); + + uint256 amount = 10 ** IERC20MetadataUpgradeable(tokens[0]).decimals(); + uint256 pairLength = pairs.length; + for (uint i; i < pairLength;) { + address pair = pairs[i]; + _updatePair(pair, twapPeriods[i]); + amount = _getAmountOut(pair, tokens[i], amount); + unchecked { ++i; } + } + + price = BeefyOracleHelper.priceFromBaseToken( + msg.sender, tokens[tokens.length - 1], tokens[0], amount + ); + if (price != 0) success = true; + } + + /// @dev Update the stored price averages and observation for a UniswapV2 pair if outside the TWAP + /// period or tracking a new pair. Initial average prices should not be trusted + /// @param _pair UniswapV2 pair to update + /// @param _twapPeriod TWAP period minimum in seconds + function _updatePair(address _pair, uint256 _twapPeriod) private { + Observation memory observation = prices[_pair].observation; + uint256 timeElapsed = block.timestamp - observation.timestamp; + + if (timeElapsed > _twapPeriod) { + (uint112 reserve0, uint112 reserve1, uint256 lastUpdate) = IUniswapV2Pair(_pair).getReserves(); + uint256 price0 = IUniswapV2Pair(_pair).price0CumulativeLast(); + uint256 price1 = IUniswapV2Pair(_pair).price1CumulativeLast(); + + if (block.timestamp > lastUpdate) { + uint256 unsyncTime = block.timestamp - lastUpdate; + price0 += (2**112 * uint256(reserve1) / reserve0) * unsyncTime; + price1 += (2**112 * uint256(reserve0) / reserve1) * unsyncTime; + } + + uint256 priceAverage0; + uint256 priceAverage1; + if (prices[_pair].observation.timestamp > 0) { + priceAverage0 = (price0 - observation.price0) * 1 ether / (timeElapsed * 2**112); + priceAverage1 = (price1 - observation.price1) * 1 ether / (timeElapsed * 2**112); + } else { + priceAverage0 = uint256(reserve1) * 1 ether / reserve0; + priceAverage1 = uint256(reserve0) * 1 ether / reserve1; + } + + prices[_pair] = Price(priceAverage0, priceAverage1, Observation(price0, price1, lastUpdate)); + emit PairUpdated(_pair, priceAverage0, priceAverage1); + } + } + + /// @dev Use the stored price average to get the amount out + /// @param _pair UniswapV2 pair + /// @param _tokenIn Address of the token being swapped into the pair + /// @param _amountIn Amount of the token being swapped in + /// @return amountOut Amount of the output token being received from the swap + function _getAmountOut( + address _pair, + address _tokenIn, + uint256 _amountIn + ) private view returns (uint256 amountOut) { + uint256 priceAverage = IUniswapV2Pair(_pair).token0() == _tokenIn + ? prices[_pair].priceAverage0 + : prices[_pair].priceAverage1; + amountOut = priceAverage * _amountIn / 1 ether; + } + + /// @notice Data validation for new oracle data being added to central oracle + /// @param _data Encoded addresses of the token route, pair route and TWAP periods + function validateData(bytes calldata _data) external view { + (address[] memory tokens, address[] memory pairs, uint256[] memory twapPeriods) = + abi.decode(_data, (address[], address[], uint256[])); + + if (tokens.length != pairs.length + 1 || tokens.length != twapPeriods.length + 1) { + revert BeefyOracleErrors.ArrayLength(); + } + + uint256 basePrice = IBeefyOracle(msg.sender).getPrice(tokens[0]); + if (basePrice == 0) revert BeefyOracleErrors.NoBasePrice(tokens[0]); + + uint256 pairLength = pairs.length; + for (uint i; i < pairLength;) { + address fromToken = tokens[i]; + address toToken = tokens[i + 1]; + address pair = pairs[i]; + address token0 = IUniswapV2Pair(pair).token0(); + address token1 = IUniswapV2Pair(pair).token1(); + + if (fromToken != token0 && fromToken != token1) { + revert BeefyOracleErrors.TokenNotInPair(fromToken, pair); + } + if (toToken != token0 && toToken != token1) { + revert BeefyOracleErrors.TokenNotInPair(toToken, pair); + } + unchecked { ++i; } + } + } +} diff --git a/contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV3.sol b/contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV3.sol new file mode 100644 index 00000000..756a0301 --- /dev/null +++ b/contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV3.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; + +import { UniswapV3OracleLibrary, IUniswapV3Pool } from "../../utils/UniswapV3OracleLibrary.sol"; +import { BeefyOracleHelper, IBeefyOracle, BeefyOracleErrors } from "./BeefyOracleHelper.sol"; + +/// @title Beefy Oracle for UniswapV3 +/// @author Beefy, @kexley +/// @notice On-chain oracle using UniswapV3 +contract BeefyOracleUniswapV3 { + + /// @notice Fetch price from the UniswapV3 pools using the TWAP observations + /// @param _data Payload from the central oracle with the addresses of the token route, pool + /// route and TWAP periods in seconds + /// @return price Retrieved price from the chained quotes + /// @return success Successful price fetch or not + function getPrice(bytes calldata _data) external returns (uint256 price, bool success) { + (address[] memory tokens, address[] memory pools, uint256[] memory twapPeriods) = + abi.decode(_data, (address[], address[], uint256[])); + + int24[] memory ticks = new int24[](pools.length); + for (uint i; i < pools.length; i++) { + (ticks[i],) = UniswapV3OracleLibrary.consult(pools[i], uint32(twapPeriods[i])); + } + + int256 chainedTick = UniswapV3OracleLibrary.getChainedPrice(tokens, ticks); + + // Do not let the conversion overflow + if (chainedTick > type(int24).max) return (0, false); + + uint256 amountOut = UniswapV3OracleLibrary.getQuoteAtTick( + int24(chainedTick), + 10 ** IERC20MetadataUpgradeable(tokens[0]).decimals() + ); + + price = BeefyOracleHelper.priceFromBaseToken( + msg.sender, tokens[tokens.length - 1], tokens[0], amountOut + ); + if (price != 0) success = true; + } + + /// @notice Data validation for new oracle data being added to central oracle + /// @param _data Encoded addresses of the token route, pool route and TWAP periods + function validateData(bytes calldata _data) external view { + (address[] memory tokens, address[] memory pools, uint256[] memory twapPeriods) = + abi.decode(_data, (address[], address[], uint256[])); + + if (tokens.length != pools.length + 1 || tokens.length != twapPeriods.length + 1) { + revert BeefyOracleErrors.ArrayLength(); + } + + uint256 basePrice = IBeefyOracle(msg.sender).getPrice(tokens[0]); + if (basePrice == 0) revert BeefyOracleErrors.NoBasePrice(tokens[0]); + + uint256 poolLength = pools.length; + for (uint i; i < poolLength;) { + address fromToken = tokens[i]; + address toToken = tokens[i + 1]; + address pool = pools[i]; + address token0 = IUniswapV3Pool(pool).token0(); + address token1 = IUniswapV3Pool(pool).token1(); + + if (fromToken != token0 && fromToken != token1) { + revert BeefyOracleErrors.TokenNotInPair(fromToken, pool); + } + if (toToken != token0 && toToken != token1) { + revert BeefyOracleErrors.TokenNotInPair(toToken, pool); + } + unchecked { ++i; } + } + } +} diff --git a/contracts/BIFI/infra/BeefyRewardPool.sol b/contracts/BIFI/infra/BeefyRewardPool.sol new file mode 100644 index 00000000..f32d6d80 --- /dev/null +++ b/contracts/BIFI/infra/BeefyRewardPool.sol @@ -0,0 +1,434 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { SafeERC20Upgradeable, IERC20Upgradeable, IERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; + +/// @title Reward pool for BIFI +/// @author kexley, Beefy +/// @notice Multi-reward staking contract for BIFI +/// @dev Multiple rewards can be added to this contract by the owner. A receipt token is issued for +/// staking and is used for withdrawing the staked BIFI. +contract BeefyRewardPool is ERC20Upgradeable, OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20Upgradeable; + + /// @dev Information for a particular reward + /// @param periodFinish End timestamp of reward distribution + /// @param duration Distribution length of time in seconds + /// @param lastUpdateTime Latest timestamp of an update + /// @param rate Distribution speed in wei per second + /// @param rewardPerTokenStored Stored reward value per staked token in 18 decimals + /// @param userRewardPerTokenPaid Stored reward value per staked token in 18 decimals at the + /// last time a user was paid the reward + /// @param earned Value of reward still owed to the user + struct RewardInfo { + uint256 periodFinish; + uint256 duration; + uint256 lastUpdateTime; + uint256 rate; + uint256 rewardPerTokenStored; + mapping(address => uint256) userRewardPerTokenPaid; + mapping(address => uint256) earned; + } + + /// @notice BIFI token address + IERC20Upgradeable public stakedToken; + + /// @notice Array of reward addresses + address[] public rewards; + + /// @notice Whitelist of manager addresses + mapping(address => bool) public whitelisted; + + /// @dev Limit to the number of rewards an owner can add + uint256 private rewardMax; + + /// @dev Location of a reward in the reward array + mapping(address => uint256) private _index; + + /// @dev Each reward address has a new unique identifier each time it is initialized. This is + /// to prevent old mappings from being reused when removing and re-adding a reward. + mapping(address => bytes32) private _id; + + /// @dev Each identifier relates to reward information + mapping(bytes32 => RewardInfo) private _rewardInfo; + + /// @notice User has staked an amount + event Staked(address indexed user, uint256 amount); + /// @notice User has withdrawn an amount + event Withdrawn(address indexed user, uint256 amount); + /// @notice A reward has been paid to the user + event RewardPaid(address indexed user, address indexed reward, uint256 amount); + /// @notice A new reward has been added to be distributed + event AddReward(address reward); + /// @notice More of an existing reward has been added to be distributed + event NotifyReward(address indexed reward, uint256 amount, uint256 duration); + /// @notice A reward has been removed from distribution and sent to the recipient + event RemoveReward(address reward, address recipient); + /// @notice The owner has removed tokens that are not supported by this contract + event RescueTokens(address token, address recipient); + /// @notice An address has been added to or removed from the whitelist + event SetWhitelist(address manager, bool whitelist); + + /// @notice Caller is not a manager + error NotManager(address caller); + /// @notice The staked token cannot be added as a reward + error StakedTokenIsNotAReward(); + /// @notice The duration is too short to be set + error ShortDuration(uint256 duration); + /// @notice There are already too many rewards + error TooManyRewards(); + /// @notice The reward has not been found in the array + error RewardNotFound(address reward); + /// @notice The owner cannot withdraw the staked token + error WithdrawingStakedToken(); + /// @notice the owner cannot withdraw an existing reward without first removing it from the array + error WithdrawingRewardToken(address reward); + + /// @dev Triggers reward updates on every user interaction + /// @param _user Address of the user making an interaction + modifier update(address _user) { + _update(_user); + _; + } + + /// @dev Only a manager can call these modified functions + modifier onlyManager { + if (!whitelisted[msg.sender]) revert NotManager(msg.sender); + _; + } + + /* ---------------------------------- EXTERNAL FUNCTIONS ---------------------------------- */ + + /// @notice Initialize the contract, callable only once + /// @param _stakedToken BIFI token address + function initialize(address _stakedToken) external initializer { + __ERC20_init("Beefy Reward Pool", "rBIFI"); + __Ownable_init(); + stakedToken = IERC20Upgradeable(_stakedToken); + rewardMax = 100; + } + + /// @notice Stake BIFI tokens + /// @dev An equal number of receipt tokens will be minted to the caller + /// @param _amount Amount of BIFI to stake + function stake(uint256 _amount) external update(msg.sender) { + _stake(msg.sender, _amount); + } + + /// @notice Stake BIFI tokens with a permit + /// @dev An equal number of receipt tokens will be minted to the caller + /// @param _user User to stake for + /// @param _amount Amount of BIFI to stake + /// @param _deadline Timestamp of the deadline after which the permit is invalid + /// @param _v Part of a signature + /// @param _r Part of a signature + /// @param _s Part of a signature + function stakeWithPermit( + address _user, + uint256 _amount, + uint256 _deadline, + uint8 _v, + bytes32 _r, + bytes32 _s + ) external update(_user) { + IERC20PermitUpgradeable(address(stakedToken)).permit( + _user, address(this), _amount, _deadline, _v, _r, _s + ); + _stake(_user, _amount); + } + + /// @notice Withdraw BIFI tokens + /// @dev Burns an equal number of receipt tokens from the caller + /// @param _amount Amount of BIFI to withdraw + function withdraw(uint256 _amount) external update(msg.sender) { + _withdraw(_amount); + } + + /// @notice Withdraw all of the caller's BIFI tokens and claim rewards + /// @dev Burns all receipt tokens owned by the caller + function exit() external update(msg.sender) { + _withdraw(balanceOf(msg.sender)); + _getReward(); + } + + /// @notice Claim all the caller's earned rewards + function getReward() external update(msg.sender) { + _getReward(); + } + + /// @notice View the amount of rewards earned by the user + /// @param _user User to view the earned rewards for + /// @return rewardTokens Address array of the rewards + /// @return earnedAmounts Amounts of the user's earned rewards + function earned(address _user) external view returns ( + address[] memory rewardTokens, + uint256[] memory earnedAmounts + ) { + uint256 rewardLength = rewards.length; + uint256[] memory amounts = new uint256[](rewardLength); + for (uint i; i < rewardLength;) { + amounts[i] = _earned(_user, rewards[i]); + unchecked { ++i; } + } + earnedAmounts = amounts; + rewardTokens = rewards; + } + + /// @notice View the amount of a single reward earned by the user + /// @param _user User to view the earned reward for + /// @param _reward Reward to calculate the earned amount for + /// @return earnedAmount Amount of the user's earned reward + function earned(address _user, address _reward) external view returns (uint256 earnedAmount) { + earnedAmount = _earned(_user, _reward); + } + + /// @notice View the reward information + /// @dev The active reward information is automatically selected from the id mapping + /// @param _rewardId Index of the reward in the array to get the information for + /// @return reward Address of the reward + /// @return periodFinish End timestamp of reward distribution + /// @return duration Distribution length of time in seconds + /// @return lastUpdateTime Latest timestamp of an update + /// @return rate Distribution speed in wei per second + function rewardInfo(uint256 _rewardId) external view returns ( + address reward, + uint256 periodFinish, + uint256 duration, + uint256 lastUpdateTime, + uint256 rate + ) { + reward = rewards[_rewardId]; + RewardInfo storage info = _getRewardInfo(reward); + (periodFinish, duration, lastUpdateTime, rate) = + (info.periodFinish, info.duration, info.lastUpdateTime, info.rate); + } + + /* ------------------------------- ERC20 OVERRIDE FUNCTIONS ------------------------------- */ + + /// @notice Update rewards for both source and recipient and then transfer receipt tokens to + /// the recipient address + /// @dev Overrides the ERC20 implementation to add the reward update + /// @param _to Recipient address of the token transfer + /// @param _value Amount to transfer + /// @return success Transfer was successful or not + function transfer(address _to, uint256 _value) public override returns (bool success) { + _update(msg.sender); + _update(_to); + return super.transfer(_to, _value); + } + + /// @notice Update rewards for both source and recipient and then transfer receipt tokens from + /// the source address to the recipient address + /// @dev Overrides the ERC20 implementation to add the reward update + /// @param _from Source address of the token transfer + /// @param _to Recipient address of the token transfer + /// @param _value Amount to transfer + /// @return success Transfer was successful or not + function transferFrom( + address _from, + address _to, + uint256 _value + ) public override returns (bool success) { + _update(_from); + _update(_to); + return super.transferFrom(_from, _to, _value); + } + + /* ----------------------------------- OWNER FUNCTIONS ------------------------------------ */ + + /// @notice Manager function to start a reward distribution + /// @dev Must approve this contract to spend the reward amount before calling this function. + /// New rewards will be assigned a id using their address and the block timestamp. + /// @param _reward Address of the reward + /// @param _amount Amount of reward + /// @param _duration Duration of the reward distribution in seconds + function notifyRewardAmount( + address _reward, + uint256 _amount, + uint256 _duration + ) external onlyManager update(address(0)) { + if (_reward == address(stakedToken)) revert StakedTokenIsNotAReward(); + if (_duration < 1 hours) revert ShortDuration(_duration); + + if (!_rewardExists(_reward)) { + _id[_reward] = keccak256(abi.encodePacked(_reward, block.timestamp)); + uint256 rewardLength = rewards.length; + if (rewards.length + 1 > rewardMax) revert TooManyRewards(); + _index[_reward] = rewardLength; + rewards.push(_reward); + emit AddReward(_reward); + } + + IERC20Upgradeable(_reward).safeTransferFrom(msg.sender, address(this), _amount); + + RewardInfo storage rewardData = _getRewardInfo(_reward); + uint256 leftover; + + if (block.timestamp < rewardData.periodFinish) { + uint256 remaining = rewardData.periodFinish - block.timestamp; + leftover = remaining * rewardData.rate; + } + + rewardData.rate = (_amount + leftover) / _duration; + rewardData.lastUpdateTime = block.timestamp; + rewardData.periodFinish = block.timestamp + _duration; + rewardData.duration = _duration; + + emit NotifyReward(_reward, _amount, _duration); + } + + /// @notice Owner function to remove a reward from this contract + /// @dev All unclaimed earnings are ignored. Re-adding the reward will have a new set of + /// reward information so any unclaimed earnings cannot be recovered + /// @param _reward Address of the reward to be removed + /// @param _recipient Address of the recipient that the removed reward was sent to + function removeReward(address _reward, address _recipient) external onlyOwner { + if (!_rewardExists(_reward)) revert RewardNotFound(_reward); + + uint256 replacedIndex = _index[_reward]; + address endToken = rewards[rewards.length - 1]; + rewards[replacedIndex] = endToken; + _index[endToken] = replacedIndex; + rewards.pop(); + + uint256 rewardBal = IERC20Upgradeable(_reward).balanceOf(address(this)); + IERC20Upgradeable(_reward).safeTransfer(_recipient, rewardBal); + + emit RemoveReward(_reward, _recipient); + } + + /// @notice Owner function to remove unsupported tokens sent to this contract + /// @param _token Address of the token to be removed + /// @param _recipient Address of the recipient that the removed token was sent to + function rescueTokens(address _token, address _recipient) external onlyOwner { + if (_token == address(stakedToken)) revert WithdrawingStakedToken(); + if (_rewardExists(_token)) revert WithdrawingRewardToken(_token); + + uint256 amount = IERC20Upgradeable(_token).balanceOf(address(this)); + IERC20Upgradeable(_token).safeTransfer(_recipient, amount); + emit RescueTokens(_token, _recipient); + } + + /// @notice Owner function to add addresses to the whitelist + /// @param _manager Address able to call manager functions + /// @param _whitelisted Whether to add or remove from whitelist + function setWhitelist(address _manager, bool _whitelisted) external onlyOwner { + whitelisted[_manager] = _whitelisted; + emit SetWhitelist(_manager, _whitelisted); + } + + /* ---------------------------------- INTERNAL FUNCTIONS ---------------------------------- */ + + /// @dev Update the rewards and earnings for a user + /// @param _user Address to update the earnings for + function _update(address _user) private { + uint256 rewardLength = rewards.length; + for (uint i; i < rewardLength;) { + address reward = rewards[i]; + RewardInfo storage rewardData = _getRewardInfo(reward); + rewardData.rewardPerTokenStored = _rewardPerToken(reward); + rewardData.lastUpdateTime = _lastTimeRewardApplicable(rewardData.periodFinish); + if (_user != address(0)) { + rewardData.earned[_user] = _earned(_user, reward); + rewardData.userRewardPerTokenPaid[_user] = rewardData.rewardPerTokenStored; + } + unchecked { ++i; } + } + } + + /// @dev Stake BIFI tokens and mint the caller receipt tokens + /// @param _user Address of the user to stake for + /// @param _amount Amount of BIFI to stake + function _stake(address _user, uint256 _amount) private { + _mint(_user, _amount); + stakedToken.safeTransferFrom(_user, address(this), _amount); + emit Staked(msg.sender, _amount); + } + + /// @dev Withdraw BIFI tokens and burn an equal number of receipt tokens from the caller + /// @param _amount Amount of BIFI to withdraw + function _withdraw(uint256 _amount) private { + _burn(msg.sender, _amount); + stakedToken.safeTransfer(msg.sender, _amount); + emit Withdrawn(msg.sender, _amount); + } + + /// @dev Claim all the caller's earned rewards + function _getReward() private { + uint256 rewardLength = rewards.length; + for (uint i; i < rewardLength;) { + address reward = rewards[i]; + uint256 rewardEarned = _earned(msg.sender, reward); + if (rewardEarned > 0) { + _getRewardInfo(reward).earned[msg.sender] = 0; + _rewardTransfer(reward, msg.sender, rewardEarned); + emit RewardPaid(msg.sender, reward, rewardEarned); + } + unchecked { ++i; } + } + } + + /// @dev Return either the period finish or the current timestamp, whichever is earliest + /// @param _periodFinish End timestamp of the reward distribution + /// @return timestamp Earliest timestamp out of the period finish or block timestamp + function _lastTimeRewardApplicable(uint256 _periodFinish) private view returns (uint256 timestamp) { + timestamp = block.timestamp > _periodFinish ? _periodFinish : block.timestamp; + } + + /// @dev Calculate the reward amount per BIFI token + /// @param _reward Address of the reward + /// @return rewardPerToken Reward amount per BIFI token + function _rewardPerToken(address _reward) private view returns (uint256 rewardPerToken) { + RewardInfo storage rewardData = _getRewardInfo(_reward); + if (totalSupply() == 0) { + rewardPerToken = rewardData.rewardPerTokenStored; + } else { + rewardPerToken = rewardData.rewardPerTokenStored + ( + (_lastTimeRewardApplicable(rewardData.periodFinish) - rewardData.lastUpdateTime) + * rewardData.rate + * 1e18 + / totalSupply() + ); + } + } + + /// @dev Calculate the reward amount earned by the user + /// @param _user Address of the user + /// @param _reward Address of the reward + /// @return earnedAmount Amount of reward earned by the user + function _earned(address _user, address _reward) private view returns (uint256 earnedAmount) { + RewardInfo storage rewardData = _getRewardInfo(_reward); + earnedAmount = rewardData.earned[_user] + ( + balanceOf(_user) * + (_rewardPerToken(_reward) - rewardData.userRewardPerTokenPaid[_user]) + / 1e18 + ); + } + + /// @dev Return the most current reward information for a reward + /// @param _reward Address of the reward + /// @return info Reward information for the reward + function _getRewardInfo(address _reward) private view returns(RewardInfo storage info) { + info = _rewardInfo[_id[_reward]]; + } + + /// @dev Check if a reward exists in the reward array already + /// @param _reward Address of the reward + /// @return exists Returns true if token is in the array + function _rewardExists(address _reward) private view returns (bool exists) { + if (rewards.length > 0) exists = _reward == rewards[_index[_reward]]; + } + + /// @dev Transfer at most the balance of the reward on this contract to avoid errors + /// @param _reward Address of the reward + /// @param _recipient Address of the recipient of the reward + /// @param _amount Amount of the reward to be sent to the recipient + function _rewardTransfer(address _reward, address _recipient, uint256 _amount) private { + uint256 rewardBal = IERC20Upgradeable(_reward).balanceOf(address(this)); + if (_amount > rewardBal) _amount = rewardBal; + if (_amount > 0) IERC20Upgradeable(_reward).safeTransfer(_recipient, _amount); + } +} diff --git a/contracts/BIFI/infra/BeefyRewardPool.txt b/contracts/BIFI/infra/BeefyRewardPool.txt deleted file mode 100644 index 7d358654..00000000 --- a/contracts/BIFI/infra/BeefyRewardPool.txt +++ /dev/null @@ -1,119 +0,0 @@ -pragma solidity ^0.5.0; - -import "@openzeppelin-2/contracts/math/Math.sol"; -import "@openzeppelin-2/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin-2/contracts/ownership/Ownable.sol"; - -import "../utils/LPTokenWrapper.sol"; - -contract BeefyRewardPool is LPTokenWrapper, Ownable { - IERC20 public rewardToken; - uint256 public constant DURATION = 1 days; - - uint256 public periodFinish = 0; - uint256 public rewardRate = 0; - uint256 public lastUpdateTime; - uint256 public rewardPerTokenStored; - mapping(address => uint256) public userRewardPerTokenPaid; - mapping(address => uint256) public rewards; - - event RewardAdded(uint256 reward); - event Staked(address indexed user, uint256 amount); - event Withdrawn(address indexed user, uint256 amount); - event RewardPaid(address indexed user, uint256 reward); - - constructor(address _stakedToken, address _rewardToken) - public - LPTokenWrapper(_stakedToken) - { - rewardToken = IERC20(_rewardToken); - } - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(); - lastUpdateTime = lastTimeRewardApplicable(); - if (account != address(0)) { - rewards[account] = earned(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - } - _; - } - - function lastTimeRewardApplicable() public view returns (uint256) { - return Math.min(block.timestamp, periodFinish); - } - - function rewardPerToken() public view returns (uint256) { - if (totalSupply() == 0) { - return rewardPerTokenStored; - } - return - rewardPerTokenStored.add( - lastTimeRewardApplicable() - .sub(lastUpdateTime) - .mul(rewardRate) - .mul(1e18) - .div(totalSupply()) - ); - } - - function earned(address account) public view returns (uint256) { - return - balanceOf(account) - .mul(rewardPerToken().sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - // stake visibility is public as overriding LPTokenWrapper's stake() function - function stake(uint256 amount) public updateReward(msg.sender) { - require(amount > 0, "Cannot stake 0"); - super.stake(amount); - emit Staked(msg.sender, amount); - } - - function withdraw(uint256 amount) public updateReward(msg.sender) { - require(amount > 0, "Cannot withdraw 0"); - super.withdraw(amount); - emit Withdrawn(msg.sender, amount); - } - - function exit() external { - withdraw(balanceOf(msg.sender)); - getReward(); - } - - function getReward() public updateReward(msg.sender) { - uint256 reward = earned(msg.sender); - if (reward > 0) { - rewards[msg.sender] = 0; - rewardToken.safeTransfer(msg.sender, reward); - emit RewardPaid(msg.sender, reward); - } - } - - function notifyRewardAmount(uint256 reward) - external - onlyOwner - updateReward(address(0)) - { - if (block.timestamp >= periodFinish) { - rewardRate = reward.div(DURATION); - } else { - uint256 remaining = periodFinish.sub(block.timestamp); - uint256 leftover = remaining.mul(rewardRate); - rewardRate = reward.add(leftover).div(DURATION); - } - lastUpdateTime = block.timestamp; - periodFinish = block.timestamp.add(DURATION); - emit RewardAdded(reward); - } - - function inCaseTokensGetStuck(address _token) external onlyOwner { - require(_token != address(stakedToken), "!staked"); - require(_token != address(rewardToken), "!reward"); - - uint256 amount = IERC20(_token).balanceOf(address(this)); - IERC20(_token).safeTransfer(msg.sender, amount); - } -} \ No newline at end of file diff --git a/contracts/BIFI/infra/BeefySwapper.sol b/contracts/BIFI/infra/BeefySwapper.sol new file mode 100644 index 00000000..219011a0 --- /dev/null +++ b/contracts/BIFI/infra/BeefySwapper.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; +import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; + +import { IBeefyOracle } from "../interfaces/oracle/IBeefyOracle.sol"; +import { BytesLib } from "../utils/BytesLib.sol"; + +/// @title Beefy Swapper +/// @author Beefy, @kexley +/// @notice Centralized swapper +contract BeefySwapper is OwnableUpgradeable { + using SafeERC20Upgradeable for IERC20MetadataUpgradeable; + using BytesLib for bytes; + + /// @dev Price update failed for a token + /// @param token Address of token that failed the price update + error PriceFailed(address token); + + /// @dev No swap data has been set by the owner + /// @param fromToken Token to swap from + /// @param toToken Token to swap to + error NoSwapData(address fromToken, address toToken); + + /// @dev Swap call failed + /// @param router Target address of the failed swap call + /// @param data Payload of the failed call + error SwapFailed(address router, bytes data); + + /// @dev Not enough output was returned from the swap + /// @param amountOut Amount returned by the swap + /// @param minAmountOut Minimum amount required from the swap + error SlippageExceeded(uint256 amountOut, uint256 minAmountOut); + + /// @dev Stored data for a swap + /// @param router Target address that will handle the swap + /// @param data Payload of a template swap between the two tokens + /// @param amountIndex Location in the data byte string where the amount should be overwritten + /// @param minIndex Location in the data byte string where the min amount to swap should be + /// overwritten + /// @param minAmountSign Represents the sign of the min amount to be included in the swap, any + /// negative value will encode a negative min amount (required for Balancer) + struct SwapInfo { + address router; + bytes data; + uint256 amountIndex; + uint256 minIndex; + int8 minAmountSign; + } + + /// @notice Stored swap info for a token + mapping(address => mapping(address => SwapInfo)) public swapInfo; + + /// @notice Oracle used to calculate the minimum output of a swap + IBeefyOracle public oracle; + + /// @notice Minimum acceptable percentage slippage output in 18 decimals + uint256 public slippage; + + /// @notice Swap between two tokens + /// @param caller Address of the caller of the swap + /// @param fromToken Address of the source token + /// @param toToken Address of the destination token + /// @param amountIn Amount of source token inputted to the swap + /// @param amountOut Amount of destination token outputted from the swap + event Swap( + address indexed caller, + address indexed fromToken, + address indexed toToken, + uint256 amountIn, + uint256 amountOut + ); + + /// @notice Set new swap info for the route between two tokens + /// @param fromToken Address of the source token + /// @param toToken Address of the destination token + /// @param swapInfo Struct of stored swap information for the pair of tokens + event SetSwapInfo(address indexed fromToken, address indexed toToken, SwapInfo swapInfo); + + /// @notice Set a new oracle + /// @param oracle New oracle address + event SetOracle(address oracle); + + /// @notice Set a new slippage + /// @param slippage New slippage amount + event SetSlippage(uint256 slippage); + + /// @notice Initialize the contract + /// @dev Ownership is transferred to msg.sender + /// @param _oracle Oracle to find prices for tokens + /// @param _slippage Acceptable slippage for any swap + function initialize(address _oracle, uint256 _slippage) external initializer { + __Ownable_init(); + oracle = IBeefyOracle(_oracle); + slippage = _slippage; + } + + /// @notice Swap between two tokens with slippage calculated using the oracle + /// @dev Caller must have already approved this contract to spend the _fromToken. After the + /// swap the _toToken token is sent directly to the caller + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @return amountOut Amount of _toToken returned to the caller + function swap( + address _fromToken, + address _toToken, + uint256 _amountIn + ) external returns (uint256 amountOut) { + uint256 minAmountOut = _getAmountOut(_fromToken, _toToken, _amountIn); + amountOut = _swap(_fromToken, _toToken, _amountIn, minAmountOut); + } + + /// @notice Swap between two tokens with slippage provided by the caller + /// @dev Caller must have already approved this contract to spend the _fromToken. After the + /// swap the _toToken token is sent directly to the caller + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @param _minAmountOut Minimum amount of _toToken that is acceptable to be returned to caller + /// @return amountOut Amount of _toToken returned to the caller + function swap( + address _fromToken, + address _toToken, + uint256 _amountIn, + uint256 _minAmountOut + ) external returns (uint256 amountOut) { + amountOut = _swap(_fromToken, _toToken, _amountIn, _minAmountOut); + } + + /// @notice Get the amount out from a simulated swap with slippage and non-fresh prices + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @return amountOut Amount of _toTokens returned from the swap + function getAmountOut( + address _fromToken, + address _toToken, + uint256 _amountIn + ) external view returns (uint256 amountOut) { + (uint256 fromPrice, uint256 toPrice) = + (oracle.getPrice(_fromToken), oracle.getPrice(_toToken)); + uint8 decimals0 = IERC20MetadataUpgradeable(_fromToken).decimals(); + uint8 decimals1 = IERC20MetadataUpgradeable(_toToken).decimals(); + amountOut = _calculateAmountOut(_amountIn, fromPrice, toPrice, decimals0, decimals1); + } + + /// @dev Use the oracle to get prices for both _fromToken and _toToken and calculate the + /// estimated output reduced by the slippage + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @return amountOut Amount of _toToken returned by the swap + function _getAmountOut( + address _fromToken, + address _toToken, + uint256 _amountIn + ) private returns (uint256 amountOut) { + (uint256 fromPrice, uint256 toPrice) = _getFreshPrice(_fromToken, _toToken); + uint8 decimals0 = IERC20MetadataUpgradeable(_fromToken).decimals(); + uint8 decimals1 = IERC20MetadataUpgradeable(_toToken).decimals(); + uint256 slippedAmountIn = _amountIn * slippage / 1 ether; + amountOut = _calculateAmountOut(slippedAmountIn, fromPrice, toPrice, decimals0, decimals1); + } + + /// @dev _fromToken is pulled into this contract from the caller, swap is executed according to + /// the stored data, resulting _toTokens are sent to the caller + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @param _minAmountOut Minimum amount of _toToken that is acceptable to be returned to caller + /// @return amountOut Amount of _toToken returned to the caller + function _swap( + address _fromToken, + address _toToken, + uint256 _amountIn, + uint256 _minAmountOut + ) private returns (uint256 amountOut) { + IERC20MetadataUpgradeable(_fromToken).safeTransferFrom(msg.sender, address(this), _amountIn); + _executeSwap(_fromToken, _toToken, _amountIn, _minAmountOut); + amountOut = IERC20MetadataUpgradeable(_toToken).balanceOf(address(this)); + if (amountOut < _minAmountOut) revert SlippageExceeded(amountOut, _minAmountOut); + IERC20MetadataUpgradeable(_toToken).safeTransfer(msg.sender, amountOut); + emit Swap(msg.sender, _fromToken, _toToken, _amountIn, amountOut); + } + + /// @dev Fetch the stored swap info for the route between the two tokens, insert the encoded + /// balance and minimum output to the payload and call the stored router with the data + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _amountIn Amount of _fromToken to use in the swap + /// @param _minAmountOut Minimum amount of _toToken that is acceptable to be returned to caller + function _executeSwap( + address _fromToken, + address _toToken, + uint256 _amountIn, + uint256 _minAmountOut + ) private { + SwapInfo memory swapData = swapInfo[_fromToken][_toToken]; + address router = swapData.router; + if (router == address(0)) revert NoSwapData(_fromToken, _toToken); + bytes memory data = swapData.data; + + data = _insertData(data, swapData.amountIndex, abi.encode(_amountIn)); + + bytes memory minAmountData = swapData.minAmountSign >= 0 + ? abi.encode(_minAmountOut) + : abi.encode(-int256(_minAmountOut)); + + data = _insertData(data, swapData.minIndex, minAmountData); + + IERC20MetadataUpgradeable(_fromToken).forceApprove(router, type(uint256).max); + (bool success,) = router.call(data); + if (!success) revert SwapFailed(router, data); + } + + /// @dev Helper function to insert data to an in-memory bytes string + /// @param _data Template swap payload with blank spaces to overwrite + /// @param _index Start location in the data byte string where the _newData should overwrite + /// @param _newData New data that is to be inserted + /// @return data The resulting string from the insertion + function _insertData( + bytes memory _data, + uint256 _index, + bytes memory _newData + ) private pure returns (bytes memory data) { + data = bytes.concat( + bytes.concat( + _data.slice(0, _index), + _newData + ), + _data.slice(_index + 32, _data.length - (_index + 32)) + ); + } + + /// @dev Fetch fresh prices from the oracle + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @return fromPrice Price of token to swap from + /// @return toPrice Price of token to swap to + function _getFreshPrice( + address _fromToken, + address _toToken + ) private returns (uint256 fromPrice, uint256 toPrice) { + bool success; + (fromPrice, success) = oracle.getFreshPrice(_fromToken); + if (!success) revert PriceFailed(_fromToken); + (toPrice, success) = oracle.getFreshPrice(_toToken); + if (!success) revert PriceFailed(_toToken); + } + + /// @dev Calculate the amount out given the prices and the decimals of the tokens involved + /// @param _amountIn Amount of _fromToken to use in the swap + /// @param _price0 Price of the _fromToken + /// @param _price1 Price of the _toToken + /// @param _decimals0 Decimals of the _fromToken + /// @param _decimals1 Decimals of the _toToken + function _calculateAmountOut( + uint256 _amountIn, + uint256 _price0, + uint256 _price1, + uint8 _decimals0, + uint8 _decimals1 + ) private pure returns (uint256 amountOut) { + amountOut = _amountIn * (_price0 * 10 ** _decimals1) / (_price1 * 10 ** _decimals0); + } + + /* ----------------------------------- OWNER FUNCTIONS ----------------------------------- */ + + /// @notice Owner function to set the stored swap info for the route between two tokens + /// @dev No validation checks + /// @param _fromToken Token to swap from + /// @param _toToken Token to swap to + /// @param _swapInfo Swap info to store + function setSwapInfo( + address _fromToken, + address _toToken, + SwapInfo calldata _swapInfo + ) external onlyOwner { + swapInfo[_fromToken][_toToken] = _swapInfo; + emit SetSwapInfo(_fromToken, _toToken, _swapInfo); + } + + /// @notice Owner function to set multiple stored swap info for the routes between two tokens + /// @dev No validation checks + /// @param _fromTokens Tokens to swap from + /// @param _toTokens Tokens to swap to + /// @param _swapInfos Swap infos to store + function setSwapInfos( + address[] calldata _fromTokens, + address[] calldata _toTokens, + SwapInfo[] calldata _swapInfos + ) external onlyOwner { + uint256 tokenLength = _fromTokens.length; + for (uint i; i < tokenLength;) { + swapInfo[_fromTokens[i]][_toTokens[i]] = _swapInfos[i]; + emit SetSwapInfo(_fromTokens[i], _toTokens[i], _swapInfos[i]); + unchecked { ++i; } + } + } + + /// @notice Owner function to set the oracle used to calculate the minimum outputs + /// @dev No validation checks + /// @param _oracle Address of the new oracle + function setOracle(address _oracle) external onlyOwner { + oracle = IBeefyOracle(_oracle); + emit SetOracle(_oracle); + } + + /// @notice Owner function to set the slippage + /// @param _slippage Acceptable slippage level + function setSlippage(uint256 _slippage) external onlyOwner { + if (_slippage > 1 ether) _slippage = 1 ether; + slippage = _slippage; + emit SetSlippage(_slippage); + } +} diff --git a/contracts/BIFI/interfaces/beefy/IBeefyRewardPool.sol b/contracts/BIFI/interfaces/beefy/IBeefyRewardPool.sol new file mode 100644 index 00000000..6f9368ad --- /dev/null +++ b/contracts/BIFI/interfaces/beefy/IBeefyRewardPool.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +interface IBeefyRewardPool { + function stake(uint256 amount) external; + function withdraw(uint256 amount) external; + function getReward() external; + function earned(address user, address reward) external view returns (uint256); + function notifyRewardAmount(address reward, uint256 amount, uint256 duration) external; + function removeReward(address reward, address recipient) external; + function rescueTokens(address token, address recipient) external; + function setWhitelist(address manager, bool whitelisted) external; + function transferOwnership(address owner) external; +} diff --git a/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol b/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol new file mode 100644 index 00000000..8ee254de --- /dev/null +++ b/contracts/BIFI/interfaces/beefy/IBeefySwapper.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +interface IBeefySwapper { + function swap( + address fromToken, + address toToken, + uint256 amountIn + ) external returns (uint256 amountOut); + + function swap( + address fromToken, + address toToken, + uint256 amountIn, + uint256 minAmountOut + ) external returns (uint256 amountOut); + + function getAmountOut( + address _fromToken, + address _toToken, + uint256 _amountIn + ) external view returns (uint256 amountOut); +} diff --git a/contracts/BIFI/interfaces/common/ISolidlyPair.sol b/contracts/BIFI/interfaces/common/ISolidlyPair.sol index a1ba3431..537401cd 100644 --- a/contracts/BIFI/interfaces/common/ISolidlyPair.sol +++ b/contracts/BIFI/interfaces/common/ISolidlyPair.sol @@ -10,4 +10,5 @@ interface ISolidlyPair { function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function stable() external view returns (bool); function getAmountOut(uint256 amountIn, address tokenIn) external view returns (uint256); + function quote(address tokenIn, uint256 amountIn, uint256 granularity) external returns (uint256 amountOut); } \ No newline at end of file diff --git a/contracts/BIFI/interfaces/common/IUniswapV2Pair.sol b/contracts/BIFI/interfaces/common/IUniswapV2Pair.sol index db6dd5e4..5dc7ee22 100644 --- a/contracts/BIFI/interfaces/common/IUniswapV2Pair.sol +++ b/contracts/BIFI/interfaces/common/IUniswapV2Pair.sol @@ -10,4 +10,7 @@ interface IUniswapV2Pair { function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); function totalSupply() external view returns (uint256); function kLast() external view returns (uint256); + function price0CumulativeLast() external view returns (uint256); + function price1CumulativeLast() external view returns (uint256); + function sync() external; } \ No newline at end of file diff --git a/contracts/BIFI/interfaces/common/IUniswapV3Pool.sol b/contracts/BIFI/interfaces/common/IUniswapV3Pool.sol new file mode 100644 index 00000000..00251990 --- /dev/null +++ b/contracts/BIFI/interfaces/common/IUniswapV3Pool.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IUniswapV3Pool { + function token0() external view returns (address); + function token1() external view returns (address); + function observe(uint32[] calldata secondsAgos) + external + view + returns (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s); +} \ No newline at end of file diff --git a/contracts/BIFI/interfaces/common/IWrappedNative.sol b/contracts/BIFI/interfaces/common/IWrappedNative.sol index 33a66ce7..a286d33e 100644 --- a/contracts/BIFI/interfaces/common/IWrappedNative.sol +++ b/contracts/BIFI/interfaces/common/IWrappedNative.sol @@ -4,6 +4,5 @@ pragma solidity >=0.6.0 <0.9.0; interface IWrappedNative { function deposit() external payable; - function withdraw(uint256 wad) external; } diff --git a/contracts/BIFI/interfaces/oracle/IBeefyOracle.sol b/contracts/BIFI/interfaces/oracle/IBeefyOracle.sol new file mode 100644 index 00000000..b0f5265e --- /dev/null +++ b/contracts/BIFI/interfaces/oracle/IBeefyOracle.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IBeefyOracle { + function getPrice(address token) external view returns (uint256 price); + function getPrice(address[] calldata tokens) external view returns (uint256[] memory prices); + function getFreshPrice(address token) external returns (uint256 price, bool success); + function getFreshPrice(address[] calldata tokens) external returns (uint256[] memory prices, bool[] memory successes); +} diff --git a/contracts/BIFI/interfaces/oracle/IChainlink.sol b/contracts/BIFI/interfaces/oracle/IChainlink.sol new file mode 100644 index 00000000..e80ad947 --- /dev/null +++ b/contracts/BIFI/interfaces/oracle/IChainlink.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface IChainlink { + function decimals() external view returns (uint8); + function latestAnswer() external view returns (int256); +} \ No newline at end of file diff --git a/contracts/BIFI/interfaces/oracle/ISubOracle.sol b/contracts/BIFI/interfaces/oracle/ISubOracle.sol new file mode 100644 index 00000000..2f8555c9 --- /dev/null +++ b/contracts/BIFI/interfaces/oracle/ISubOracle.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +interface ISubOracle { + function getPrice(bytes calldata data) external returns (uint256 price, bool success); + function validateData(bytes calldata data) external view; +} \ No newline at end of file diff --git a/contracts/BIFI/strategies/Beefy/StrategyBeefy.sol b/contracts/BIFI/strategies/Beefy/StrategyBeefy.sol new file mode 100644 index 00000000..ee8d4012 --- /dev/null +++ b/contracts/BIFI/strategies/Beefy/StrategyBeefy.sol @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import { SafeERC20Upgradeable, IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; + +import { IBeefySwapper } from "../../interfaces/beefy/IBeefySwapper.sol"; +import { IBeefyRewardPool } from "../../interfaces/beefy/IBeefyRewardPool.sol"; +import { StratFeeManagerInitializable, IFeeConfig } from "../Common/StratFeeManagerInitializable.sol"; + +/// @title Strategy staking in the BIFI reward pool +/// @author kexley, Beefy +/// @notice Strategy managing the rewards from the BIFI reward pool +contract StrategyBeefy is StratFeeManagerInitializable { + using SafeERC20Upgradeable for IERC20Upgradeable; + + /// @notice BIFI token address + address public want; + + /// @notice WETH token address + address public native; + + /// @notice Reward token array + address[] public rewards; + + /// @dev Location of a reward in the token array + mapping(address => uint256) index; + + /// @notice Reward pool for BIFI rewards + address public rewardPool; + + /// @notice Whether to harvest on deposits + bool public harvestOnDeposit; + + /// @notice Timestamp of last harvest + uint256 public lastHarvest; + + /// @notice Total profit locked on the strategy + uint256 public totalLocked; + + /// @notice Length of time in seconds to linearly unlock the profit from a harvest + uint256 public duration; + + /// @dev Reward entered is a protected token + error RewardNotAllowed(address reward); + /// @dev Reward is already in the array + error RewardAlreadySet(address reward); + /// @dev Reward is not found in the array + error RewardNotFound(address reward); + + /// @notice Strategy has been harvested + /// @param harvester Caller of the harvest + /// @param wantHarvested Amount of want harvested in this tx + /// @param tvl Total amount of deposits at the time of harvest + event StratHarvest(address indexed harvester, uint256 wantHarvested, uint256 tvl); + /// @notice Want tokens have been deposited into the underlying platform + /// @param tvl Total amount of deposits at the time of deposit + event Deposit(uint256 tvl); + /// @notice Want tokens have been withdrawn by a user + /// @param tvl Total amount of deposits at the time of withdrawal + event Withdraw(uint256 tvl); + /// @notice Fees were charged + /// @param callFees Amount of native sent to the caller as a harvest reward + /// @param beefyFees Amount of native sent to the beefy fee recipient + /// @param strategistFees Amount of native sent to the strategist + event ChargedFees(uint256 callFees, uint256 beefyFees, uint256 strategistFees); + /// @notice Duration of the locked profit degradation has been set + /// @param duration Duration of the locked profit degradation + event SetDuration(uint256 duration); + /// @notice A new reward has been added to the array + /// @param reward New reward + event SetReward(address reward); + /// @notice A reward has been removed from the array + /// @param reward Reward that has been removed + event RemoveReward(address reward); + + /// @notice Initialize the contract, callable only once + /// @param _want BIFI address + /// @param _native WETH address + /// @param _rewardPool Reward pool address + /// @param _commonAddresses The typical addresses required by a strategy (see StratManager) + function initialize( + address _want, + address _native, + address _rewardPool, + CommonAddresses calldata _commonAddresses + ) external initializer { + __StratFeeManager_init(_commonAddresses); + want = _want; + native = _native; + rewardPool = _rewardPool; + duration = 3 days; + + _giveAllowances(); + } + + /// @notice Deposit all available want on this contract into the underlying platform + function deposit() public whenNotPaused { + uint256 wantBal = IERC20Upgradeable(want).balanceOf(address(this)); + + if (wantBal > 0) { + IBeefyRewardPool(rewardPool).stake(wantBal); + emit Deposit(balanceOf()); + } + } + + /// @notice Withdraw some amount of want back to the vault + /// @param _amount Some amount to withdraw back to vault + function withdraw(uint256 _amount) external { + require(msg.sender == vault, "!vault"); + + uint256 wantBal = IERC20Upgradeable(want).balanceOf(address(this)); + + if (wantBal < _amount) { + IBeefyRewardPool(rewardPool).withdraw(_amount - wantBal); + wantBal = IERC20Upgradeable(want).balanceOf(address(this)); + } + + if (wantBal > _amount) { + wantBal = _amount; + } + + IERC20Upgradeable(want).safeTransfer(vault, wantBal); + emit Withdraw(balanceOf()); + } + + /// @notice Hook called by the vault before shares are calculated on a deposit + function beforeDeposit() external override { + if (harvestOnDeposit) { + require(msg.sender == vault, "!vault"); + _harvest(tx.origin); + } + } + + /// @notice Harvest rewards and collect a call fee reward + function harvest() external { + _harvest(tx.origin); + } + + /// @notice Harvest rewards and send the call fee reward to a specified recipient + /// @param _callFeeRecipient Recipient of the call fee reward + function harvest(address _callFeeRecipient) external { + _harvest(_callFeeRecipient); + } + + /// @dev Harvest rewards, charge fees and compound back into more want + /// @param _callFeeRecipient Recipient of the call fee reward + function _harvest(address _callFeeRecipient) internal whenNotPaused { + IBeefyRewardPool(rewardPool).getReward(); + _swapToNative(); + if (IERC20Upgradeable(native).balanceOf(address(this)) > 0) { + _chargeFees(_callFeeRecipient); + _swapToWant(); + uint256 wantHarvested = balanceOfWant(); + totalLocked = wantHarvested + lockedProfit(); + deposit(); + + lastHarvest = block.timestamp; + emit StratHarvest(msg.sender, wantHarvested, balanceOf()); + } + } + + /// @dev Swap any extra rewards into native + function _swapToNative() internal { + for (uint i; i < rewards.length; ++i) { + address reward = rewards[i]; + uint256 rewardBal = IERC20Upgradeable(reward).balanceOf(address(this)); + if (rewardBal > 0) IBeefySwapper(unirouter).swap(reward, native, rewardBal); + } + } + + /// @dev Charge performance fees and send to recipients + /// @param _callFeeRecipient Recipient of the call fee reward + function _chargeFees(address _callFeeRecipient) internal { + IFeeConfig.FeeCategory memory fees = getFees(); + uint256 nativeBal = IERC20Upgradeable(native).balanceOf(address(this)) * fees.total / DIVISOR; + + uint256 callFeeAmount = nativeBal * fees.call / DIVISOR; + IERC20Upgradeable(native).safeTransfer(_callFeeRecipient, callFeeAmount); + + uint256 beefyFeeAmount = nativeBal * fees.beefy / DIVISOR; + IERC20Upgradeable(native).safeTransfer(beefyFeeRecipient, beefyFeeAmount); + + uint256 strategistFeeAmount = nativeBal * fees.strategist / DIVISOR; + IERC20Upgradeable(native).safeTransfer(strategist, strategistFeeAmount); + + emit ChargedFees(callFeeAmount, beefyFeeAmount, strategistFeeAmount); + } + + /// @dev Swap all native into want + function _swapToWant() internal { + uint256 nativeBal = IERC20Upgradeable(native).balanceOf(address(this)); + if (nativeBal > 0) IBeefySwapper(unirouter).swap(native, want, nativeBal); + } + + /// @notice Total want controlled by the strategy in the underlying platform and this contract + /// @return balance Total want controlled by the strategy + function balanceOf() public view returns (uint256 balance) { + balance = balanceOfWant() + balanceOfPool() - lockedProfit(); + } + + /// @notice Amount of want held on this contract + /// @return balanceHeld Amount of want held + function balanceOfWant() public view returns (uint256 balanceHeld) { + balanceHeld = IERC20Upgradeable(want).balanceOf(address(this)); + } + + /// @notice Amount of want controlled by the strategy in the underlying platform + /// @return balanceInvested Amount of want in the underlying platform + function balanceOfPool() public view returns (uint256 balanceInvested) { + balanceInvested = IERC20Upgradeable(rewardPool).balanceOf(address(this)); + } + + /// @notice Amount of locked profit degrading over time + /// @return left Amount of locked profit still remaining + function lockedProfit() public view returns (uint256 left) { + uint256 elapsed = block.timestamp - lastHarvest; + uint256 remaining = elapsed < duration ? duration - elapsed : 0; + left = totalLocked * remaining / duration; + } + + /// @notice Unclaimed reward amount from the underlying platform + /// @return unclaimedReward Amount of reward left unclaimed + function rewardsAvailable() public view returns (uint256 unclaimedReward) { + unclaimedReward = IBeefyRewardPool(rewardPool).earned(address(this), native); + } + + /// @notice Estimated call fee reward for calling harvest + /// @return callFee Amount of native reward a harvest caller could claim + function callReward() public view returns (uint256 callFee) { + IFeeConfig.FeeCategory memory fees = getFees(); + callFee = rewardsAvailable() * fees.total / DIVISOR * fees.call / DIVISOR; + } + + /// @notice Manager function to toggle on harvesting on deposits + /// @param _harvestOnDeposit Turn harvesting on deposit on or off + function setHarvestOnDeposit(bool _harvestOnDeposit) external onlyManager { + harvestOnDeposit = _harvestOnDeposit; + } + + /// @notice Called by the vault as part of strategy migration, all funds are sent to the vault + function retireStrat() external { + require(msg.sender == vault, "!vault"); + + IBeefyRewardPool(rewardPool).withdraw(balanceOfPool()); + + uint256 wantBal = IERC20Upgradeable(want).balanceOf(address(this)); + IERC20Upgradeable(want).transfer(vault, wantBal); + } + + /// @notice Pauses deposits and withdraws all funds from the underlying platform + function panic() public onlyManager { + pause(); + IBeefyRewardPool(rewardPool).withdraw(balanceOfPool()); + } + + /// @notice Pauses deposits but leaves funds still invested + function pause() public onlyManager { + _pause(); + + _removeAllowances(); + } + + /// @notice Unpauses deposits and reinvests any idle funds + function unpause() external onlyManager { + _unpause(); + + _giveAllowances(); + + deposit(); + } + + /// @notice Set the duration for the degradation of the locked profit + /// @param _duration Duration for the degradation of the locked profit + function setDuration(uint256 _duration) external onlyOwner { + duration = _duration; + emit SetDuration(_duration); + } + + /// @notice Add a new reward to the array + /// @param _reward New reward + function setReward(address _reward) external onlyOwner { + if (_reward == want || _reward == native || _reward == rewardPool) { + revert RewardNotAllowed(_reward); + } + if (rewards.length > 0) { + if (_reward == rewards[index[_reward]]) revert RewardAlreadySet(_reward); + } + index[_reward] = rewards.length; + rewards.push(_reward); + IERC20Upgradeable(_reward).forceApprove(unirouter, type(uint).max); + emit SetReward(_reward); + } + + /// @notice Remove a reward from the array + /// @param _reward Removed reward + function removeReward(address _reward) external onlyManager { + if (_reward != rewards[index[_reward]]) revert RewardNotFound(_reward); + address endReward = rewards[rewards.length - 1]; + uint256 replacedIndex = index[_reward]; + index[endReward] = replacedIndex; + rewards[replacedIndex] = endReward; + rewards.pop(); + IERC20Upgradeable(_reward).forceApprove(unirouter, 0); + emit RemoveReward(_reward); + } + + /// @dev Give out allowances to third party contracts + function _giveAllowances() internal { + IERC20Upgradeable(want).forceApprove(rewardPool, type(uint).max); + IERC20Upgradeable(native).forceApprove(unirouter, type(uint).max); + for (uint i; i < rewards.length; ++i) { + IERC20Upgradeable(rewards[i]).forceApprove(unirouter, type(uint).max); + } + } + + /// @dev Revoke allowances from third party contracts + function _removeAllowances() internal { + IERC20Upgradeable(want).forceApprove(rewardPool, 0); + IERC20Upgradeable(native).forceApprove(unirouter, 0); + for (uint i; i < rewards.length; ++i) { + IERC20Upgradeable(rewards[i]).forceApprove(unirouter, 0); + } + } +} diff --git a/contracts/BIFI/utils/TickMath.sol b/contracts/BIFI/utils/TickMath.sol new file mode 100644 index 00000000..ae2b5ebf --- /dev/null +++ b/contracts/BIFI/utils/TickMath.sol @@ -0,0 +1,205 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +/// @title Math library for computing sqrt prices from ticks and vice versa +/// @notice Computes sqrt price for ticks of size 1.0001, i.e. sqrt(1.0001^tick) as fixed point Q64.96 numbers. Supports +/// prices between 2**-128 and 2**128 +library TickMath { + /// @dev The minimum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**-128 + int24 internal constant MIN_TICK = -887272; + /// @dev The maximum tick that may be passed to #getSqrtRatioAtTick computed from log base 1.0001 of 2**128 + int24 internal constant MAX_TICK = -MIN_TICK; + + /// @dev The minimum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MIN_TICK) + uint160 internal constant MIN_SQRT_RATIO = 4295128739; + /// @dev The maximum value that can be returned from #getSqrtRatioAtTick. Equivalent to getSqrtRatioAtTick(MAX_TICK) + uint160 internal constant MAX_SQRT_RATIO = 1461446703485210103287273052203988822378723970342; + + /// @notice Calculates sqrt(1.0001^tick) * 2^96 + /// @dev Throws if |tick| > max tick + /// @param tick The input tick for the above formula + /// @return sqrtPriceX96 A Fixed point Q64.96 number representing the sqrt of the ratio of the two assets (token1/token0) + /// at the given tick + function getSqrtRatioAtTick(int24 tick) internal pure returns (uint160 sqrtPriceX96) { + uint256 absTick = tick < 0 ? uint256(-int256(tick)) : uint256(int256(tick)); + require(absTick <= uint256(uint24(MAX_TICK)), 'T'); + + uint256 ratio = absTick & 0x1 != 0 ? 0xfffcb933bd6fad37aa2d162d1a594001 : 0x100000000000000000000000000000000; + if (absTick & 0x2 != 0) ratio = (ratio * 0xfff97272373d413259a46990580e213a) >> 128; + if (absTick & 0x4 != 0) ratio = (ratio * 0xfff2e50f5f656932ef12357cf3c7fdcc) >> 128; + if (absTick & 0x8 != 0) ratio = (ratio * 0xffe5caca7e10e4e61c3624eaa0941cd0) >> 128; + if (absTick & 0x10 != 0) ratio = (ratio * 0xffcb9843d60f6159c9db58835c926644) >> 128; + if (absTick & 0x20 != 0) ratio = (ratio * 0xff973b41fa98c081472e6896dfb254c0) >> 128; + if (absTick & 0x40 != 0) ratio = (ratio * 0xff2ea16466c96a3843ec78b326b52861) >> 128; + if (absTick & 0x80 != 0) ratio = (ratio * 0xfe5dee046a99a2a811c461f1969c3053) >> 128; + if (absTick & 0x100 != 0) ratio = (ratio * 0xfcbe86c7900a88aedcffc83b479aa3a4) >> 128; + if (absTick & 0x200 != 0) ratio = (ratio * 0xf987a7253ac413176f2b074cf7815e54) >> 128; + if (absTick & 0x400 != 0) ratio = (ratio * 0xf3392b0822b70005940c7a398e4b70f3) >> 128; + if (absTick & 0x800 != 0) ratio = (ratio * 0xe7159475a2c29b7443b29c7fa6e889d9) >> 128; + if (absTick & 0x1000 != 0) ratio = (ratio * 0xd097f3bdfd2022b8845ad8f792aa5825) >> 128; + if (absTick & 0x2000 != 0) ratio = (ratio * 0xa9f746462d870fdf8a65dc1f90e061e5) >> 128; + if (absTick & 0x4000 != 0) ratio = (ratio * 0x70d869a156d2a1b890bb3df62baf32f7) >> 128; + if (absTick & 0x8000 != 0) ratio = (ratio * 0x31be135f97d08fd981231505542fcfa6) >> 128; + if (absTick & 0x10000 != 0) ratio = (ratio * 0x9aa508b5b7a84e1c677de54f3e99bc9) >> 128; + if (absTick & 0x20000 != 0) ratio = (ratio * 0x5d6af8dedb81196699c329225ee604) >> 128; + if (absTick & 0x40000 != 0) ratio = (ratio * 0x2216e584f5fa1ea926041bedfe98) >> 128; + if (absTick & 0x80000 != 0) ratio = (ratio * 0x48a170391f7dc42444e8fa2) >> 128; + + if (tick > 0) ratio = type(uint256).max / ratio; + + // this divides by 1<<32 rounding up to go from a Q128.128 to a Q128.96. + // we then downcast because we know the result always fits within 160 bits due to our tick input constraint + // we round up in the division so getTickAtSqrtRatio of the output price is always consistent + sqrtPriceX96 = uint160((ratio >> 32) + (ratio % (1 << 32) == 0 ? 0 : 1)); + } + + /// @notice Calculates the greatest tick value such that getRatioAtTick(tick) <= ratio + /// @dev Throws in case sqrtPriceX96 < MIN_SQRT_RATIO, as MIN_SQRT_RATIO is the lowest value getRatioAtTick may + /// ever return. + /// @param sqrtPriceX96 The sqrt ratio for which to compute the tick as a Q64.96 + /// @return tick The greatest tick for which the ratio is less than or equal to the input ratio + function getTickAtSqrtRatio(uint160 sqrtPriceX96) internal pure returns (int24 tick) { + // second inequality must be < because the price can never reach the price at the max tick + require(sqrtPriceX96 >= MIN_SQRT_RATIO && sqrtPriceX96 < MAX_SQRT_RATIO, 'R'); + uint256 ratio = uint256(sqrtPriceX96) << 32; + + uint256 r = ratio; + uint256 msb = 0; + + assembly { + let f := shl(7, gt(r, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(6, gt(r, 0xFFFFFFFFFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(5, gt(r, 0xFFFFFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(4, gt(r, 0xFFFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(3, gt(r, 0xFF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(2, gt(r, 0xF)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := shl(1, gt(r, 0x3)) + msb := or(msb, f) + r := shr(f, r) + } + assembly { + let f := gt(r, 0x1) + msb := or(msb, f) + } + + if (msb >= 128) r = ratio >> (msb - 127); + else r = ratio << (127 - msb); + + int256 log_2 = (int256(msb) - 128) << 64; + + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(63, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(62, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(61, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(60, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(59, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(58, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(57, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(56, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(55, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(54, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(53, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(52, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(51, f)) + r := shr(f, r) + } + assembly { + r := shr(127, mul(r, r)) + let f := shr(128, r) + log_2 := or(log_2, shl(50, f)) + } + + int256 log_sqrt10001 = log_2 * 255738958999603826347141; // 128.128 number + + int24 tickLow = int24((log_sqrt10001 - 3402992956809132418596140100660247210) >> 128); + int24 tickHi = int24((log_sqrt10001 + 291339464771989622907027621153398088495) >> 128); + + tick = tickLow == tickHi ? tickLow : getSqrtRatioAtTick(tickHi) <= sqrtPriceX96 ? tickHi : tickLow; + } +} diff --git a/contracts/BIFI/utils/UniswapV3OracleLibrary.sol b/contracts/BIFI/utils/UniswapV3OracleLibrary.sol new file mode 100644 index 00000000..1b0b7f44 --- /dev/null +++ b/contracts/BIFI/utils/UniswapV3OracleLibrary.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import "../interfaces/common/IUniswapV3Pool.sol"; +import "./TickMath.sol"; + +/// @title Oracle library +/// @notice Provides functions to integrate with V3 pool oracle +library UniswapV3OracleLibrary { + /// @notice Calculates time-weighted means of tick and liquidity for a given Uniswap V3 pool + /// @param pool Address of the pool that we want to observe + /// @param secondsAgo Number of seconds in the past from which to calculate the time-weighted means + /// @return arithmeticMeanTick The arithmetic mean tick from (block.timestamp - secondsAgo) to block.timestamp + /// @return harmonicMeanLiquidity The harmonic mean liquidity from (block.timestamp - secondsAgo) to block.timestamp + function consult(address pool, uint32 secondsAgo) + internal + view + returns (int24 arithmeticMeanTick, uint128 harmonicMeanLiquidity) + { + require(secondsAgo != 0, 'BP'); + + uint32[] memory secondsAgos = new uint32[](2); + secondsAgos[0] = secondsAgo; + secondsAgos[1] = 0; + + (int56[] memory tickCumulatives, uint160[] memory secondsPerLiquidityCumulativeX128s) = + IUniswapV3Pool(pool).observe(secondsAgos); + + int56 tickCumulativesDelta = tickCumulatives[1] - tickCumulatives[0]; + uint160 secondsPerLiquidityCumulativesDelta = + secondsPerLiquidityCumulativeX128s[1] - secondsPerLiquidityCumulativeX128s[0]; + + arithmeticMeanTick = int24(tickCumulativesDelta / int32(secondsAgo)); + // Always round to negative infinity + if (tickCumulativesDelta < 0 && (tickCumulativesDelta % int32(secondsAgo) != 0)) arithmeticMeanTick--; + + // We are multiplying here instead of shifting to ensure that harmonicMeanLiquidity doesn't overflow uint128 + uint192 secondsAgoX160 = uint192(secondsAgo) * type(uint160).max; + harmonicMeanLiquidity = uint128(secondsAgoX160 / (uint192(secondsPerLiquidityCumulativesDelta) << 32)); + } + + /// @notice Given a tick and a token amount, calculates the amount of token received in exchange + /// @param tick Tick value used to calculate the quote + /// @param baseAmount Amount of token to be converted + /// @return quoteAmount Amount of quoteToken received for baseAmount of baseToken + function getQuoteAtTick( + int24 tick, + uint256 baseAmount + ) internal pure returns (uint256 quoteAmount) { + uint160 sqrtRatioX96 = TickMath.getSqrtRatioAtTick(tick); + + // Calculate quoteAmount with better precision if it doesn't overflow when multiplied by itself + if (sqrtRatioX96 <= type(uint128).max) { + uint256 ratioX192 = uint256(sqrtRatioX96) * sqrtRatioX96; + quoteAmount = ratioX192 * baseAmount / (1 << 192); + } else { + uint256 ratioX128 = uint256(sqrtRatioX96) * sqrtRatioX96 / (1 << 64); + quoteAmount = ratioX128 * baseAmount / (1 << 128); + } + } + + /// @notice Returns the "synthetic" tick which represents the price of the first entry in `tokens` in terms of the last + /// @dev Useful for calculating relative prices along routes. + /// @dev There must be one tick for each pairwise set of tokens. + /// @param tokens The token contract addresses + /// @param ticks The ticks, representing the price of each token pair in `tokens` + /// @return syntheticTick The synthetic tick, representing the relative price of the outermost tokens in `tokens` + function getChainedPrice(address[] memory tokens, int24[] memory ticks) + internal + pure + returns (int256 syntheticTick) + { + require(tokens.length - 1 == ticks.length, 'DL'); + for (uint256 i = 1; i <= ticks.length; i++) { + // check the tokens for address sort order, then accumulate the + // ticks into the running synthetic tick, ensuring that intermediate tokens "cancel out" + tokens[i - 1] < tokens[i] ? syntheticTick += ticks[i - 1] : syntheticTick -= ticks[i - 1]; + } + } +} diff --git a/data/abi/BalancerVault.json b/data/abi/BalancerVault.json new file mode 100644 index 00000000..ebb30304 --- /dev/null +++ b/data/abi/BalancerVault.json @@ -0,0 +1,1179 @@ +[ + { + "inputs": [ + { + "internalType": "contract IAuthorizer", + "name": "authorizer", + "type": "address" + }, + { + "internalType": "contract IWETH", + "name": "weth", + "type": "address" + }, + { + "internalType": "uint256", + "name": "pauseWindowDuration", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodDuration", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "AuthorizerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ExternalBalanceTransfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IFlashLoanRecipient", + "name": "recipient", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "feeAmount", + "type": "uint256" + } + ], + "name": "FlashLoan", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "delta", + "type": "int256" + } + ], + "name": "InternalBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "int256[]", + "name": "deltas", + "type": "int256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "protocolFeeAmounts", + "type": "uint256[]" + } + ], + "name": "PoolBalanceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "assetManager", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "cashDelta", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "managedDelta", + "type": "int256" + } + ], + "name": "PoolBalanceManaged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "poolAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "enum IVault.PoolSpecialization", + "name": "specialization", + "type": "uint8" + } + ], + "name": "PoolRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "RelayerApprovalChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "TokensDeregistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "assetManagers", + "type": "address[]" + } + ], + "name": "TokensRegistered", + "type": "event" + }, + { + "inputs": [], + "name": "WETH", + "outputs": [ + { + "internalType": "contract IWETH", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "assetInIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetOutIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.BatchSwapStep[]", + "name": "swaps", + "type": "tuple[]" + }, + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + }, + { + "internalType": "int256[]", + "name": "limits", + "type": "int256[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "batchSwap", + "outputs": [ + { + "internalType": "int256[]", + "name": "assetDeltas", + "type": "int256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "deregisterTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.ExitPoolRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "exitPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IFlashLoanRecipient", + "name": "recipient", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "flashLoan", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "selector", + "type": "bytes4" + } + ], + "name": "getActionId", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAuthorizer", + "outputs": [ + { + "internalType": "contract IAuthorizer", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDomainSeparator", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "name": "getInternalBalance", + "outputs": [ + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "getNextNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "pauseWindowEndTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bufferPeriodEndTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "enum IVault.PoolSpecialization", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getPoolTokenInfo", + "outputs": [ + { + "internalType": "uint256", + "name": "cash", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "managed", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + }, + { + "internalType": "address", + "name": "assetManager", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + } + ], + "name": "getPoolTokens", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "balances", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "lastChangeBlock", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProtocolFeesCollector", + "outputs": [ + { + "internalType": "contract ProtocolFeesCollector", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "relayer", + "type": "address" + } + ], + "name": "hasApprovedRelayer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.JoinPoolRequest", + "name": "request", + "type": "tuple" + } + ], + "name": "joinPool", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.PoolBalanceOpKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "internalType": "struct IVault.PoolBalanceOp[]", + "name": "ops", + "type": "tuple[]" + } + ], + "name": "managePoolBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "enum IVault.UserBalanceOpKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IAsset", + "name": "asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + } + ], + "internalType": "struct IVault.UserBalanceOp[]", + "name": "ops", + "type": "tuple[]" + } + ], + "name": "manageUserBalance", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "assetInIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "assetOutIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.BatchSwapStep[]", + "name": "swaps", + "type": "tuple[]" + }, + { + "internalType": "contract IAsset[]", + "name": "assets", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + } + ], + "name": "queryBatchSwap", + "outputs": [ + { + "internalType": "int256[]", + "name": "", + "type": "int256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "enum IVault.PoolSpecialization", + "name": "specialization", + "type": "uint8" + } + ], + "name": "registerPool", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "assetManagers", + "type": "address[]" + } + ], + "name": "registerTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "setAuthorizer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "setPaused", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "relayer", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setRelayerApproval", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "poolId", + "type": "bytes32" + }, + { + "internalType": "enum IVault.SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "contract IAsset", + "name": "assetIn", + "type": "address" + }, + { + "internalType": "contract IAsset", + "name": "assetOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IVault.SingleSwap", + "name": "singleSwap", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bool", + "name": "fromInternalBalance", + "type": "bool" + }, + { + "internalType": "address payable", + "name": "recipient", + "type": "address" + }, + { + "internalType": "bool", + "name": "toInternalBalance", + "type": "bool" + } + ], + "internalType": "struct IVault.FundManagement", + "name": "funds", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCalculated", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/data/abi/BeefyOracle.json b/data/abi/BeefyOracle.json new file mode 100644 index 00000000..964612d1 --- /dev/null +++ b/data/abi/BeefyOracle.json @@ -0,0 +1,300 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "PriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "SetOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "staleness", + "type": "uint256" + } + ], + "name": "SetStaleness", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + } + ], + "name": "getFreshPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "success", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + } + ], + "name": "getPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "latestPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_oracle", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "setOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_tokens", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "_oracles", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "_datas", + "type": "bytes[]" + } + ], + "name": "setOracles", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_staleness", + "type": "uint256" + } + ], + "name": "setStaleness", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "staleness", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "subOracle", + "outputs": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] \ No newline at end of file diff --git a/data/abi/UniswapV2Factory.json b/data/abi/UniswapV2Factory.json new file mode 100644 index 00000000..5b7a497b --- /dev/null +++ b/data/abi/UniswapV2Factory.json @@ -0,0 +1,193 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_feeToSetter", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "pair", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "PairCreated", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allPairs", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "allPairsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "createPair", + "outputs": [ + { + "internalType": "address", + "name": "pair", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "feeTo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "feeToSetter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "getPair", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_feeTo", + "type": "address" + } + ], + "name": "setFeeTo", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_feeToSetter", + "type": "address" + } + ], + "name": "setFeeToSetter", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ] \ No newline at end of file diff --git a/data/abi/UniswapV3Factory.json b/data/abi/UniswapV3Factory.json new file mode 100644 index 00000000..67af8b6b --- /dev/null +++ b/data/abi/UniswapV3Factory.json @@ -0,0 +1,236 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": true, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "FeeAmountEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "enableFeeAmount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "name": "feeAmountTickSpacing", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint24", + "name": "", + "type": "uint24" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "parameters", + "outputs": [ + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] \ No newline at end of file diff --git a/data/abi/UniswapV3Router.json b/data/abi/UniswapV3Router.json new file mode 100644 index 00000000..18b0521b --- /dev/null +++ b/data/abi/UniswapV3Router.json @@ -0,0 +1,565 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_WETH9", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "WETH9", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + } + ], + "internalType": "struct ISwapRouter.ExactInputParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinimum", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct ISwapRouter.ExactInputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactInputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes", + "name": "path", + "type": "bytes" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMaximum", + "type": "uint256" + } + ], + "internalType": "struct ISwapRouter.ExactOutputParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactOutput", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMaximum", + "type": "uint256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct ISwapRouter.ExactOutputSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "exactOutputSingle", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "refundETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitAllowed", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expiry", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitAllowedIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "selfPermitIfNecessary", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "sweepToken", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feeBips", + "type": "uint256" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "sweepTokenWithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "int256", + "name": "amount0Delta", + "type": "int256" + }, + { + "internalType": "int256", + "name": "amount1Delta", + "type": "int256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "uniswapV3SwapCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + } + ], + "name": "unwrapWETH9", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountMinimum", + "type": "uint256" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "feeBips", + "type": "uint256" + }, + { + "internalType": "address", + "name": "feeRecipient", + "type": "address" + } + ], + "name": "unwrapWETH9WithFee", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/data/abi/VelodromeFactory.json b/data/abi/VelodromeFactory.json new file mode 100644 index 00000000..9490d71b --- /dev/null +++ b/data/abi/VelodromeFactory.json @@ -0,0 +1,730 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_implementation", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "FeeInvalid", + "type": "error" + }, + { + "inputs": [], + "name": "FeeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPool", + "type": "error" + }, + { + "inputs": [], + "name": "NotFeeManager", + "type": "error" + }, + { + "inputs": [], + "name": "NotPauser", + "type": "error" + }, + { + "inputs": [], + "name": "NotSinkConverter", + "type": "error" + }, + { + "inputs": [], + "name": "NotVoter", + "type": "error" + }, + { + "inputs": [], + "name": "PoolAlreadyExists", + "type": "error" + }, + { + "inputs": [], + "name": "SameAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroFee", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "PoolCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "SetCustomFee", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "feeManager", + "type": "address" + } + ], + "name": "SetFeeManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "state", + "type": "bool" + } + ], + "name": "SetPauseState", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pauser", + "type": "address" + } + ], + "name": "SetPauser", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "voter", + "type": "address" + } + ], + "name": "SetVoter", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_FEE", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ZERO_FEE_INDICATOR", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allPools", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allPoolsLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "createPair", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "createPool", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "customFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "feeManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "bool", + "name": "_stable", + "type": "bool" + } + ], + "name": "getFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "getPair", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + } + ], + "name": "getPool", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPair", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPool", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pauser", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "name": "setCustomFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "_fee", + "type": "uint256" + } + ], + "name": "setFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_feeManager", + "type": "address" + } + ], + "name": "setFeeManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "_state", + "type": "bool" + } + ], + "name": "setPauseState", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_pauser", + "type": "address" + } + ], + "name": "setPauser", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_sinkConverter", + "type": "address" + }, + { + "internalType": "address", + "name": "_velo", + "type": "address" + }, + { + "internalType": "address", + "name": "_veloV2", + "type": "address" + } + ], + "name": "setSinkConverter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_voter", + "type": "address" + } + ], + "name": "setVoter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sinkConverter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "stableFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "velo", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "veloV2", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "volatileFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "voter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ] \ No newline at end of file diff --git a/data/abi/VelodromeRouter.json b/data/abi/VelodromeRouter.json new file mode 100644 index 00000000..b2b03fe8 --- /dev/null +++ b/data/abi/VelodromeRouter.json @@ -0,0 +1,1717 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_forwarder", + "type": "address" + }, + { + "internalType": "address", + "name": "_factoryRegistry", + "type": "address" + }, + { + "internalType": "address", + "name": "_v1Factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "address", + "name": "_voter", + "type": "address" + }, + { + "internalType": "address", + "name": "_weth", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "ConversionFromV2ToV1VeloProhibited", + "type": "error" + }, + { + "inputs": [], + "name": "ETHTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "Expired", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAmountA", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAmountADesired", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAmountAOptimal", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAmountB", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientAmountBDesired", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientLiquidity", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientOutputAmount", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAmountInForETHDeposit", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPath", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRouteA", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRouteB", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTokenInForETHDeposit", + "type": "error" + }, + { + "inputs": [], + "name": "OnlyWETH", + "type": "error" + }, + { + "inputs": [], + "name": "PoolDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "PoolFactoryDoesNotExist", + "type": "error" + }, + { + "inputs": [], + "name": "SameAddresses", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ETHER", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "UNSAFE_swapExactTokensForTokens", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountADesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBDesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "addLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "amountA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountB", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountTokenDesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountTokenMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountETHMin", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "addLiquidityETH", + "outputs": [ + { + "internalType": "uint256", + "name": "amountToken", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountETH", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "defaultFactory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "factoryRegistry", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountInA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInB", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesA", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesB", + "type": "tuple[]" + } + ], + "name": "generateZapInParams", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOutMinA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinB", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesA", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesB", + "type": "tuple[]" + } + ], + "name": "generateZapOutParams", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOutMinA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinB", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + } + ], + "name": "getAmountsOut", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint256", + "name": "reserveA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveB", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "forwarder", + "type": "address" + } + ], + "name": "isTrustedForwarder", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "pairFor", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "poolFor", + "outputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountADesired", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBDesired", + "type": "uint256" + } + ], + "name": "quoteAddLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "amountA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountB", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "name": "quoteRemoveLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "amountA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountB", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "address", + "name": "_factory", + "type": "address" + } + ], + "name": "quoteStableLiquidityRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "ratio", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "removeLiquidity", + "outputs": [ + { + "internalType": "uint256", + "name": "amountA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountB", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountTokenMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountETHMin", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "removeLiquidityETH", + "outputs": [ + { + "internalType": "uint256", + "name": "amountToken", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountETH", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountTokenMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountETHMin", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "removeLiquidityETHSupportingFeeOnTransferTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "amountETH", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + } + ], + "name": "sortTokens", + "outputs": [ + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactETHForTokens", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactETHForTokensSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactTokensForETH", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactTokensForETHSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactTokensForTokens", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routes", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "name": "swapExactTokensForTokensSupportingFeeOnTransferTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "v1Factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "voter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "weth", + "outputs": [ + { + "internalType": "contract IWETH", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountInA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInB", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountOutMinA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinB", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + } + ], + "internalType": "struct IRouter.Zap", + "name": "zapInPool", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesA", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesB", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stake", + "type": "bool" + } + ], + "name": "zapIn", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "address", + "name": "tokenA", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenB", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountOutMinA", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMinB", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountAMin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountBMin", + "type": "uint256" + } + ], + "internalType": "struct IRouter.Zap", + "name": "zapOutPool", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesA", + "type": "tuple[]" + }, + { + "components": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "stable", + "type": "bool" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "internalType": "struct IRouter.Route[]", + "name": "routesB", + "type": "tuple[]" + } + ], + "name": "zapOut", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] \ No newline at end of file diff --git a/forge/test/oracle/Oracle.t.sol b/forge/test/oracle/Oracle.t.sol new file mode 100644 index 00000000..7a82116c --- /dev/null +++ b/forge/test/oracle/Oracle.t.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import "forge-std/Test.sol"; + +// Interfaces +import { IERC20 } from "@openzeppelin-4/contracts/token/ERC20/IERC20.sol"; +import { IUniswapV2Pair } from "../../../contracts/BIFI/interfaces/common/IUniswapV2Pair.sol"; +import { IUniswapRouterETH } from "../../../contracts/BIFI/interfaces/common/IUniswapRouterETH.sol"; +import { BeefyOracle } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol"; +import { BeefyOracleChainlink } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol"; +import { BeefyOracleUniswapV3 } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV3.sol"; +import { BeefyOracleUniswapV2 } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleUniswapV2.sol"; +import { BeefyOracleSolidly } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleSolidly.sol"; +import { BeefyOracleErrors } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleErrors.sol"; + +contract Oracle is Test { + + BeefyOracle public oracle; + + address eth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; + address usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + address matic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address usdr = 0x40379a439D4F6795B6fc9aa5687dB461677A2dBa; + + address ethFeedAddress = 0xF9680D99D6C9589e2a93a78A04A279e509205945; + address usdcFeedAddress = 0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7; + address maticFeedAddress = 0xAB594600376Ec9fD91F8e885dADF0CE036862dE0; + + address usdcEthUniswapV3Pool = 0x45dDa9cb7c25131DF268515131f647d726f50608; + address ethMaticUniswapV3Pool = 0x86f1d8390222A3691C28938eC7404A1661E618e0; + + address usdcEthUniswapV2Pool = 0x853Ee4b2A13f8a742d64C8F088bE7bA2131f670d; + + address usdcUsdrSolidlyPool = 0xD17cb0f162f133e339C0BbFc18c36c357E681D6b; + address usdrEthSolidlyPool = 0x74c64d1976157E7Aaeeed46EF04705F4424b27eC; + + address quickRouter = 0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff; + + bytes ethFeed = abi.encode(ethFeedAddress); + bytes usdcFeed = abi.encode(usdcFeedAddress); + bytes maticFeed = abi.encode(maticFeedAddress); + + function setUp() public { + oracle = new BeefyOracle(); + oracle.initialize(); + } + + function testSetChainlinkOracle() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(eth, chainlinkOracle, ethFeed); + + (uint256 price,) = oracle.getFreshPrice(eth); + console.log("ETH Price:", price); + assertGt(price, 0, "ETH price not fetched"); + } + + function testSetUniswapV3Oracle() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, eth); + address[] memory pools = new address[](1); + pools[0] = usdcEthUniswapV3Pool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 7200; + + bytes memory usdcEthUniswapV3Feed = abi.encode(tokens, pools, twaps); + + address uniswapV3Oracle = deployCode("BeefyOracleUniswapV3.sol"); + oracle.setOracle(eth, uniswapV3Oracle, usdcEthUniswapV3Feed); + + (uint256 price,) = oracle.getFreshPrice(eth); + console.log("ETH Price:", price); + assertGt(price, 0, "ETH price not fetched"); + } + + function testSetUniswapV2Oracle() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, eth); + address[] memory pairs = new address[](1); + pairs[0] = usdcEthUniswapV2Pool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 60; + + bytes memory usdcEthUniswapV2Feed = abi.encode(tokens, pairs, twaps); + + IUniswapV2Pair(usdcEthUniswapV2Pool).sync(); + + address uniswapV2Oracle = deployCode("BeefyOracleUniswapV2.sol"); + oracle.setOracle(eth, uniswapV2Oracle, usdcEthUniswapV2Feed); + uint256 startPrice = oracle.getPrice(eth); + console.log("ETH start price:", startPrice); + skip(30); + + address alice = makeAddr("alice"); + deal(usdc, alice, 100000 * 10 ** 6); + vm.startPrank(alice); + IERC20(usdc).approve(quickRouter, 100000 * 10 ** 6); + IUniswapRouterETH(quickRouter).swapExactTokensForTokens( + 100000 * 10 ** 6, 0, tokens, alice, block.timestamp + ); + + skip(31); + + (uint256 endPrice,) = oracle.getFreshPrice(eth); + console.log("ETH end price:", endPrice); + assertGt(endPrice, startPrice, "ETH price has not increased"); + } + + function testSetSolidlyOracle() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, usdr); + address[] memory pools = new address[](1); + pools[0] = usdcUsdrSolidlyPool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 4; + + bytes memory usdcUsdrSolidlyFeed = abi.encode(tokens, pools, twaps); + + (tokens[0], tokens[1]) = (usdr, eth); + pools[0] = usdrEthSolidlyPool; + + bytes memory usdrEthSolidlyFeed = abi.encode(tokens, pools, twaps); + + address solidlyOracle = deployCode("BeefyOracleSolidly.sol"); + oracle.setOracle(usdr, solidlyOracle, usdcUsdrSolidlyFeed); + oracle.setOracle(eth, solidlyOracle, usdrEthSolidlyFeed); + + (uint256 usdrPrice,) = oracle.getFreshPrice(usdr); + console.log("USDR price:", usdrPrice); + assertGt(usdrPrice, 0, "USDR price not fetched"); + + (uint256 ethPrice,) = oracle.getFreshPrice(eth); + console.log("ETH price:", ethPrice); + assertGt(ethPrice, 0, "ETH price not fetched"); + } + + function testSetMultipleOracles() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (eth, usdc); + address[] memory oracles = new address[](2); + (oracles[0], oracles[1]) = (chainlinkOracle, chainlinkOracle); + bytes[] memory feeds = new bytes[](2); + (feeds[0], feeds[1]) = (ethFeed, usdcFeed); + + oracle.setOracles(tokens, oracles, feeds); + + (uint256 ethPrice,) = oracle.getFreshPrice(eth); + console.log("ETH price:", ethPrice); + assertGt(ethPrice, 0, "ETH price not fetched"); + (uint256 usdcPrice,) = oracle.getFreshPrice(usdc); + console.log("USDC price:", usdcPrice); + assertGt(usdcPrice, 0, "USDC price not fetched"); + } + + function testSetOracleNoBasePrice() external { + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, eth); + address[] memory pools = new address[](1); + pools[0] = usdcEthUniswapV3Pool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 7200; + + bytes memory usdcEthUniswapV3Feed = abi.encode(tokens, pools, twaps); + + address uniswapV3Oracle = deployCode("BeefyOracleUniswapV3.sol"); + vm.expectRevert(abi.encodeWithSelector(BeefyOracleErrors.NoBasePrice.selector, usdc)); + oracle.setOracle(eth, uniswapV3Oracle, usdcEthUniswapV3Feed); + } + + function testSetOracleNoAnswer() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + bytes memory emptyFeed = abi.encode(address(0)); + vm.expectRevert(); + oracle.setOracle(eth, chainlinkOracle, emptyFeed); + } + + function testSetOracleTokenNotInPair() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, matic); + address[] memory pools = new address[](1); + pools[0] = usdcEthUniswapV3Pool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 7200; + + bytes memory usdcMaticUniswapV3Feed = abi.encode(tokens, pools, twaps); + + address uniswapV3Oracle = deployCode("BeefyOracleUniswapV3.sol"); + vm.expectRevert(); + oracle.setOracle(matic, uniswapV3Oracle, usdcMaticUniswapV3Feed); + } + + function testSetChainedUniswapV3Oracles() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + + address[] memory tokens = new address[](3); + (tokens[0], tokens[1], tokens[2]) = (usdc, eth, matic); + address[] memory pools = new address[](2); + (pools[0], pools[1]) = (usdcEthUniswapV3Pool, ethMaticUniswapV3Pool); + uint256[] memory twaps = new uint256[](2); + (twaps[0], twaps[1]) = (7200, 7200); + + bytes memory usdcMaticUniswapV3Feed = abi.encode(tokens, pools, twaps); + + address uniswapV3Oracle = deployCode("BeefyOracleUniswapV3.sol"); + oracle.setOracle(matic, uniswapV3Oracle, usdcMaticUniswapV3Feed); + + (uint256 price,) = oracle.getFreshPrice(matic); + console.log("MATIC price:", price); + assertGt(price, 0, "MATIC price not fetched"); + } + + function testOverwriteOracle() external { + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + oracle.setOracle(eth, chainlinkOracle, ethFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, eth); + address[] memory pools = new address[](1); + pools[0] = usdcEthUniswapV3Pool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 7200; + + bytes memory usdcEthUniswapV3Feed = abi.encode(tokens, pools, twaps); + + address uniswapV3Oracle = deployCode("BeefyOracleUniswapV3.sol"); + oracle.setOracle(eth, uniswapV3Oracle, usdcEthUniswapV3Feed); + (uint256 price,) = oracle.getFreshPrice(eth); + console.log("ETH price:", price); + assertGt(price, 0, "ETH price not fetched"); + } +} diff --git a/forge/test/swapper/Swapper.t.sol b/forge/test/swapper/Swapper.t.sol new file mode 100644 index 00000000..b6565ac1 --- /dev/null +++ b/forge/test/swapper/Swapper.t.sol @@ -0,0 +1,234 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import "forge-std/Test.sol"; + +import { UniswapV3Utils, IUniswapRouterV3WithDeadline } from "../../../contracts/BIFI/utils/UniswapV3Utils.sol"; +import { IUniswapRouterETH } from "../../../contracts/BIFI/interfaces/common/IUniswapRouterETH.sol"; +import { ISolidlyRouter } from "../../../contracts/BIFI/interfaces/common/ISolidlyRouter.sol"; +import { IBalancerVault } from "../../../contracts/BIFI/interfaces/beethovenx/IBalancerVault.sol"; + +// Interfaces +import { IERC20 } from "@openzeppelin-4/contracts/token/ERC20/IERC20.sol"; +import { BeefySwapper } from "../../../contracts/BIFI/infra/BeefySwapper.sol"; +import { BeefyOracle } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracle.sol"; +import { BeefyOracleChainlink } from "../../../contracts/BIFI/infra/BeefyOracle/BeefyOracleChainlink.sol"; + +contract Swapper is Test { + + BeefySwapper public swapper; + BeefyOracle public oracle; + + address eth = 0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619; + address usdc = 0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174; + address matic = 0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270; + address usdr = 0x40379a439D4F6795B6fc9aa5687dB461677A2dBa; + + address ethFeedAddress = 0xF9680D99D6C9589e2a93a78A04A279e509205945; + address usdcFeedAddress = 0xfE4A8cc5b5B2366C1B58Bea3858e81843581b2F7; + address maticFeedAddress = 0xAB594600376Ec9fD91F8e885dADF0CE036862dE0; + + address usdcUsdrSolidlyPool = 0xD17cb0f162f133e339C0BbFc18c36c357E681D6b; + + bytes32 usdcEthBalancerPool = 0x03cd191f589d12b0582a99808cf19851e468e6b500010000000000000000000a; + + address uniswapV3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564; + address uniswapV2Router = 0xa5E0829CaCEd8fFDD4De3c43696c57F7D7A678ff; + address solidlyRouter = 0x06374F57991CDc836E5A318569A910FE6456D230; + address balancerRouter = 0xBA12222222228d8Ba445958a75a0704d566BF2C8; + + bytes ethFeed = abi.encode(ethFeedAddress); + bytes usdcFeed = abi.encode(usdcFeedAddress); + bytes maticFeed = abi.encode(maticFeedAddress); + + address alice; + + function setUp() public { + oracle = new BeefyOracle(); + oracle.initialize(); + + swapper = new BeefySwapper(); + swapper.initialize(address(oracle), 0.99 ether); + + address chainlinkOracle = deployCode("BeefyOracleChainlink.sol"); + oracle.setOracle(eth, chainlinkOracle, ethFeed); + oracle.setOracle(usdc, chainlinkOracle, usdcFeed); + oracle.setOracle(matic, chainlinkOracle, maticFeed); + + address[] memory tokens = new address[](2); + (tokens[0], tokens[1]) = (usdc, usdr); + address[] memory pools = new address[](1); + pools[0] = usdcUsdrSolidlyPool; + uint256[] memory twaps = new uint256[](1); + twaps[0] = 4; + bytes memory usdcUsdrSolidlyFeed = abi.encode(tokens, pools, twaps); + address solidlyOracle = deployCode("BeefyOracleSolidly.sol"); + oracle.setOracle(usdr, solidlyOracle, usdcUsdrSolidlyFeed); + + alice = makeAddr("alice"); + deal(usdc, alice, 1_000_000 * 10 ** 6); + vm.prank(alice); + IERC20(usdc).approve(address(swapper), type(uint256).max); + } + + function testSetSwapInfo() external { + address[] memory route = new address[](2); + (route[0], route[1]) = (usdc, eth); + uint24[] memory fees = new uint24[](1); + fees[0] = 500; + + bytes memory data = abi.encodeWithSelector( + IUniswapRouterV3WithDeadline.exactInput.selector, + IUniswapRouterV3WithDeadline.ExactInputParams( + UniswapV3Utils.routeToPath(route, fees), + address(swapper), + type(uint256).max, + 0, + 0 + ) + ); + uint256 amountIndex = 132; + uint256 minIndex = 164; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + BeefySwapper.SwapInfo(uniswapV3Router, data, amountIndex, minIndex, minSign) + ); + + vm.prank(alice); + uint256 ethReceived = swapper.swap(usdc, eth, 1000 * 10 ** 6); + console.log("ETH received:", ethReceived); + assertGt(ethReceived, 0, "No ETH received from swap"); + } + + function testSetLongUniswapV3() external { + address[] memory route = new address[](3); + (route[0], route[1], route[2]) = (usdc, eth, matic); + uint24[] memory fees = new uint24[](2); + (fees[0], fees[1]) = (500, 3000); + + bytes memory data = abi.encodeWithSelector( + IUniswapRouterV3WithDeadline.exactInput.selector, + IUniswapRouterV3WithDeadline.ExactInputParams( + UniswapV3Utils.routeToPath(route, fees), + address(swapper), + type(uint256).max, + 0, + 0 + ) + ); + uint256 amountIndex = 132; + uint256 minIndex = 164; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + BeefySwapper.SwapInfo(uniswapV3Router, data, amountIndex, minIndex, minSign) + ); + + vm.prank(alice); + uint256 maticReceived = swapper.swap(usdc, matic, 1000 * 10 ** 6); + console.log("MATIC received:", maticReceived); + assertGt(maticReceived, 0, "No MATIC received from swap"); + } + + function testSetSwapInfoUniswapV2() external { + address[] memory route = new address[](2); + (route[0], route[1]) = (usdc, eth); + + bytes memory data = abi.encodeWithSelector( + IUniswapRouterETH.swapExactTokensForTokens.selector, + 0, + 0, + route, + address(swapper), + type(uint256).max + ); + uint256 amountIndex = 4; + uint256 minIndex = 36; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + BeefySwapper.SwapInfo(uniswapV2Router, data, amountIndex, minIndex, minSign) + ); + + vm.prank(alice); + uint256 ethReceived = swapper.swap(usdc, eth, 1000 * 10 ** 6); + console.log("ETH received:", ethReceived); + assertGt(ethReceived, 0, "No ETH received from swap"); + } + + function testSetSwapInfoSolidly() external { + address[] memory route = new address[](2); + (route[0], route[1]) = (usdc, usdr); + + ISolidlyRouter.Routes[] memory path = new ISolidlyRouter.Routes[](1); + path[0] = ISolidlyRouter.Routes(route[0], route[1], true); + + bytes memory data = abi.encodeWithSignature( + "swapExactTokensForTokens(uint256,uint256,(address,address,bool)[],address,uint256)", + 0, + 0, + path, + address(swapper), + type(uint256).max + ); + uint256 amountIndex = 4; + uint256 minIndex = 36; + int8 minSign = 0; + + swapper.setSwapInfo( + route[0], + route[route.length - 1], + BeefySwapper.SwapInfo(solidlyRouter, data, amountIndex, minIndex, minSign) + ); + + vm.prank(alice); + uint256 usdrReceived = swapper.swap(usdc, usdr, 1000 * 10 ** 6); + console.log("USDR received:", usdrReceived); + assertGt(usdrReceived, 0, "No USDR received from swap"); + } + + function testSetSwapInfoBalancer() external { + uint8 swapKind = 0; + IBalancerVault.BatchSwapStep[] memory swapSteps = new IBalancerVault.BatchSwapStep[](1); + swapSteps[0] = IBalancerVault.BatchSwapStep(usdcEthBalancerPool, 0, 1, 0, bytes("")); + address[] memory assets = new address[](2); + (assets[0], assets[1]) = (usdc, eth); + IBalancerVault.FundManagement memory funds = IBalancerVault.FundManagement( + address(swapper), false, payable(address(swapper)), false + ); + int256[] memory limits = new int256[](2); + (limits[0], limits[1]) = (type(int256).max, 0); + + bytes memory data = abi.encodeWithSelector( + IBalancerVault.batchSwap.selector, + swapKind, + swapSteps, + assets, + funds, + limits, + type(uint256).max + ); + uint256 amountIndex = 452; + uint256 minIndex = 708; + int8 minSign = -1; + + swapper.setSwapInfo( + assets[0], + assets[assets.length - 1], + BeefySwapper.SwapInfo(balancerRouter, data, amountIndex, minIndex, minSign) + ); + + vm.prank(alice); + uint256 ethReceived = swapper.swap(usdc, eth, 1000 * 10 ** 6); + console.log("ETH received:", ethReceived); + assertGt(ethReceived, 0, "No ETH received from swap"); + } +} diff --git a/foundry.toml b/foundry.toml index 76e8b92c..3dd92de6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,7 +17,7 @@ remappings = [ evm_version = 'shanghai' # gas_reports = ['*'] ## Sets the concrete solc version to use, this overrides the `auto_detect_solc` value -solc_version = '0.8.15' +solc_version = '0.8.19' # auto_detect_solc = true # offline = false optimizer = true @@ -36,6 +36,7 @@ verbosity = 2 # block_coinbase = '0x0000000000000000000000000000000000000000' # block_timestamp = 0 # block_difficulty = 0 +fs_permissions = [{ access = "read", path = "./"}] # These^ are all the available config options # Docs at https://github.com/gakonst/foundry/tree/master/config diff --git a/package.json b/package.json index c83e33cb..bccfb04b 100644 --- a/package.json +++ b/package.json @@ -50,13 +50,15 @@ "deploy:autoHarvester": "hardhat run scripts/infra/deployAutoHarvester.ts --network", "deploy:contract": "hardhat run scripts/infra/deployContract.js --network", "zap": "hardhat run ./scripts/infra/zapViaAggregator.js --network", - "flat-hardhat": "hardhat flatten contracts/BIFI/strategies/Sushi/StrategySushiConstantProduct.sol > tmp/flattened.sol", + "flat-hardhat": "hardhat flatten contracts/BIFI/infra/BeefyFeeConfigurator.sol > tmp/flattened.sol", "flat": "bin/flatten.sh", "flat:chain": "bin/flattenChainContracts.sh", "abi-to-sol": "abi-to-sol", "installForge": ". scripts/forge/installForge.sh", - "forgeTest:vault": "forge test --force --fork-url https://rpc.ftm.tools --match-contract ProdVaultTest", + "forgeTest:vault": "forge test --force --fork-url http://localhost:8545 --match-contract ProdVaultTest", "forgeTest:wrapper": "forge test --force --fork-url https://arb1.arbitrum.io/rpc --match-contract WrapperTest", + "forgeTest:oracle": "forge test --force --fork-url https://polygon-rpc.com --match-contract Oracle", + "forgeTest:swapper": "forge test --force --fork-url https://polygon-rpc.com --match-contract Swapper", "forgeTest:aura": "forge test --force --fork-url https://rpc.ankr.com/eth --match-contract StrategyAuraGyroTest", "deploy:vaultFactory": "hardhat run scripts/deploy-vault-proxyFactory.js --network", "fork:bsc": "hardhat node --fork bsc", diff --git a/scripts/manage/setOracle.js b/scripts/manage/setOracle.js new file mode 100644 index 00000000..41476948 --- /dev/null +++ b/scripts/manage/setOracle.js @@ -0,0 +1,162 @@ +import hardhat, { ethers, web3 } from "hardhat"; +import BeefyOracleAbi from "../../data/abi/BeefyOracle.json"; +import UniswapV3FactoryAbi from "../../data/abi/UniswapV3Factory.json"; +import UniswapV2FactoryAbi from "../../data/abi/UniswapV2Factory.json"; +import VelodromeFactoryAbi from "../../data/abi/VelodromeFactory.json"; +import { addressBook } from "blockchain-addressbook"; + +const { + platforms: { beefyfinance }, + tokens: { + WMATIC: {address: WMATIC}, + USDC: { address: USDC}, + ETH: { address: ETH}, + }, +} = addressBook.polygon; + +const ethers = hardhat.ethers; + +const nullAddress = "0x0000000000000000000000000000000000000000"; +const uniswapV3Factory = "0x1F98431c8aD98523631AE4a59f267346ea31F984"; +const uniswapV2Factory = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"; +const velodromeFactory = "0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a"; + +const beefyfinanceOracle = "0x70655508b04cCB34781e557Ad4605D3fe0d98D79"; +const chainlinkOracle = "0xf89703fc237DFAaA28b3d1D4835c7540cd2D0885"; +const uniswapV3Oracle = "0x0000000000000000000000000000000000000000"; +const uniswapV2Oracle = "0x0000000000000000000000000000000000000000"; +const solidlyOracle = "0x0000000000000000000000000000000000000000"; + +const config = { + type: "chainlink", + chainlink: { + token: ETH, + feed: "0xF9680D99D6C9589e2a93a78A04A279e509205945", + }, + uniswapV3: { + path: [[USDC, WMATIC, 500]], + twaps: [7200], + factory: uniswapV3Factory, + }, + uniswapV2: { + path: [USDC, WMATIC], + twaps: [7200], + factory: uniswapV2Factory, + }, + solidly: { + path: [[USDC, WMATIC, false]], + twaps: [4], + factory: velodromeFactory, + }, +}; + +async function main() { + switch(config.type) { + case 'chainlink': + await chainlink(); + break; + case 'uniswapV3': + await uniswapV3(); + break; + case 'uniswapV2': + await uniswapV2(); + break; + case 'solidly': + await solidly(); + break; + } +}; + +async function chainlink() { + const data = ethers.utils.defaultAbiCoder.encode( + ["address"], + [config.chainlink.feed] + ); + + await setOracle(config.chainlink.token, chainlinkOracle, data); +}; + +async function uniswapV3() { + const factory = await ethers.getContractAt(UniswapV3FactoryAbi, config.uniswapV3.factory); + const tokens = []; + const pairs = []; + for (let i = 0; i < config.uniswapV3.path.length; i++) { + tokens.push(config.uniswapV3.path[i][0]); + const pair = await factory.getPool( + config.uniswapV3.path[i][0], + config.uniswapV3.path[i][1], + config.uniswapV3.path[i][2] + ); + pairs.push(pair); + } + tokens.push(config.uniswapV3.path[config.uniswapV3.path.length - 1][1]); + + const data = ethers.utils.defaultAbiCoder.encode( + ["address[]","address[]","uint256[]"], + [tokens, pairs, config.uniswapV3.twapPeriods] + ); + + await setOracle(tokens[tokens.length - 1], uniswapV3Oracle, data); +}; + +async function uniswapV2() { + const factory = await ethers.getContractAt(UniswapV2FactoryAbi, config.uniswapV2.factory); + const tokens = []; + const pairs = []; + for (let i = 0; i < config.uniswapV2.path.length - 1; i++) { + tokens.push(config.uniswapV2.path[i][0]); + const pair = await factory.getPair( + config.uniswapV2.path[i], + config.uniswapV2.path[i + 1] + ); + pairs.push(pair); + } + tokens.push(config.uniswapV2.path[config.uniswapV2.path.length - 1]); + + const data = ethers.utils.defaultAbiCoder.encode( + ["address[]","address[]","uint256[]"], + [tokens, pairs, config.uniswapV2.twapPeriods] + ); + + await setOracle(tokens[tokens.length - 1], uniswapV2Oracle, data); +}; + +async function solidly() { + const factory = await ethers.getContractAt(VelodromeFactoryAbi, config.solidly.factory); + const tokens = []; + const pairs = []; + for (let i = 0; i < config.solidly.path.length; i++) { + tokens.push(config.solidly.path[i][0]); + const pair = await factory.getPair( + config.solidly.path[i][0], + config.solidly.path[i][1], + config.solidly.path[i][2] + ); + pairs.push(pair); + } + tokens.push(config.solidly.path[config.solidly.path.length - 1][1]); + + const data = ethers.utils.defaultAbiCoder.encode( + ["address[]","address[]","uint256[]"], + [tokens, pairs, config.solidly.twapPeriods] + ); + + await setOracle(tokens[tokens.length - 1], solidlyOracle, data); +}; + +async function setOracle(token, oracle, data) { + const oracleContract = await ethers.getContractAt(BeefyOracleAbi, beefyfinanceOracle); + + let tx = await oracleContract.setOracle(token, oracle, data); + tx = await tx.wait(); + tx.status === 1 + ? console.log(`Info set for ${token} with tx: ${tx.transactionHash}`) + : console.log(`Could not set info for ${token}} with tx: ${tx.transactionHash}`) +}; + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + }); diff --git a/scripts/manage/setSwapInfo.js b/scripts/manage/setSwapInfo.js new file mode 100644 index 00000000..a983e204 --- /dev/null +++ b/scripts/manage/setSwapInfo.js @@ -0,0 +1,219 @@ +import hardhat, { ethers, web3 } from "hardhat"; +import swapperAbi from "../../artifacts/contracts/BIFI/infra/BeefySwapper.sol/BeefySwapper.json"; +import UniswapV3RouterAbi from "../../data/abi/UniswapV3Router.json"; +import BalancerVaultAbi from "../../data/abi/BalancerVault.json"; +import VelodromeRouterAbi from "../../data/abi/VelodromeRouter.json"; +import { addressBook } from "blockchain-addressbook"; + +const { + platforms: { beefyfinance }, + tokens: { + WMATIC: {address: WMATIC}, + USDC: { address: USDC}, + ETH: { address: ETH}, + }, +} = addressBook.polygon; + +const ethers = hardhat.ethers; + +const nullAddress = "0x0000000000000000000000000000000000000000"; +const uint256Max = "115792089237316195423570985008687907853269984665640564039457584007913129639935"; +const int256Max = "57896044618658097711785492504343953926634992332820282019728792003956564819967"; +const beefyfinanceSwapper = "0x3Ca6e5cf70bCf11cB8C73BBa13C55DFb91334B11"; + +const uniswapV3Router = "0xE592427A0AEce92De3Edee1F18E0157C05861564"; +const uniswapV2Router = "0x10ED43C718714eb63d5aA57B78B54704E256024E"; +const velodromeRouter = "0x0000000000000000000000000000000000000000"; +const balancerVault = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"; + +const config = { + type: "uniswapV3", + uniswapV3: { + path: [[USDC, ETH, 500]], + router: uniswapV3Router, + }, + uniswapV2: { + path: [USDC, WMATIC], + router: uniswapV2Router, + }, + balancer: { + path: [ + [USDC, WMATIC, "0x03cd191f589d12b0582a99808cf19851e468e6b500010000000000000000000a"] + ], + router: balancerVault, + }, + solidly: { + path: [[USDC, WMATIC, false, nullAddress]], + router: velodromeRouter, + }, +}; + +async function main() { + switch(config.type) { + case 'uniswapV3': + await uniswapV3(); + break; + case 'uniswapV2': + await uniswapV2(); + break; + case 'balancer': + await balancer(); + break; + case 'solidly': + await solidly(); + break; + } +}; + +async function uniswapV3() { + const router = await ethers.getContractAt(UniswapV3RouterAbi, config.uniswapV3.router); + + let path = ethers.utils.solidityPack( + ["address"], + [config.uniswapV3.path[0][0]] + ); + for (let i = 0; i < config.uniswapV3.path.length; i++) { + path = ethers.utils.solidityPack( + ["bytes", "uint24", "address"], + [path, config.uniswapV3.path[i][2], config.uniswapV3.path[i][1]] + ); + } + const exactInputParams = [ + path, + beefyfinanceSwapper, + uint256Max, + 0, + 0 + ]; + const txData = await router.populateTransaction.exactInput(exactInputParams); + const amountIndex = 132; + const minIndex = 164; + + const minAmountSign = 0; + + const swapInfo = [ + config.uniswapV3.router, + txData.data, + amountIndex, + minIndex, + minAmountSign + ]; + + await setSwapInfo( + config.uniswapV3.path[0][0], + config.uniswapV3.path[config.uniswapV3.path.length - 1][1], + swapInfo + ); +}; + +async function uniswapV2() { + const router = await ethers.getContractAt(UniswapV2RouterAbi, config.uniswapV2.router); + const txData = await router.populateTransaction.swapExactTokensForTokens( + 0, + 0, + config.uniswapV2.path, + beefyfinanceSwapper, + uint256Max + ); + const amountIndex = 4; + const minIndex = 36; + const minAmountSign = 0; + + const swapInfo = [ + config.router, + txData.data, + amountIndex, + minIndex, + minAmountSign + ]; + + /*await setSwapInfo( + config.uniswapV2.path[0], + config.uniswapV2.path[config.uniswapV2.path.length - 1], + swapInfo + );*/ +}; + +async function balancer() { + const router = await ethers.getContractAt(BalancerVaultAbi, config.balancer.router); + const swapKind = 0; + const swapSteps = []; + const assets = []; + const funds = [beefyfinanceSwapper, false, beefyfinanceSwapper, false]; + const limits = [int256Max]; + const deadline = uint256Max; + + for (let i = 0; i < config.balancer.path.length; ++i) { + swapSteps.push([config.balancer.path[i][2], i, i + 1, 0, []]) + assets.push(config.balancer.path[i][0]); + limits.push(0); + } + assets.push(config.balancer.path[config.balancer.path.length - 1][1]); + + const txData = await router.populateTransaction.batchSwap( + swapKind, + swapSteps, + assets, + funds, + limits, + deadline + ); + const amountIndex = 420 + (32 * config.balancer.path.length); + const minIndex = (txData.data.length - 66) / 2; + const minAmountSign = -1; + + const swapInfo = [ + config.router, + txData.data, + amountIndex, + minIndex, + minAmountSign + ]; + + /*await setSwapInfo( + config.uniswapV2.path[0], + config.uniswapV2.path[config.uniswapV2.path.length - 1], + swapInfo + );*/ +}; + +async function solidly() { + const router = await ethers.getContractAt(VelodromeRouterAbi, config.solidly.router); + const txData = await router.populateTransaction.swapExactTokensForTokens(3, 4, config.solidly.path, beefyfinanceSwapper, uint256Max); + const amountIndex = 4; + const minIndex = 36; + const minAmountSign = 0; + + const swapInfo = [ + config.solidly.router, + txData.data, + amountIndex, + minIndex, + minAmountSign + ]; + + console.log(txData.data); + + /*await setSwapInfo( + config.solidly.path[0][0], + config.solidly.path[config.solidly.path.length - 1][1], + swapInfo + );*/ +}; + +async function setSwapInfo(fromToken, toToken, swapInfo) { + const swapper = await ethers.getContractAt(swapperAbi.abi, beefyfinanceSwapper); + + let tx = await swapper.setSwapInfo(fromToken, toToken, swapInfo); + tx = await tx.wait(); + tx.status === 1 + ? console.log(`Info set for ${toToken} with tx: ${tx.transactionHash}`) + : console.log(`Could not set info for ${toToken}} with tx: ${tx.transactionHash}`) +}; + +main() + .then(() => process.exit(0)) + .catch(error => { + console.error(error); + process.exit(1); + });