Contract Name:
EigenLSTRestaking
Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {EigenStrategy} from "../EigenStrategy.sol";
import {SwappingAggregator} from "../SwappingAggregator.sol";
import {IStrategyManager} from "../../interfaces/IStrategyManager.sol";
import {IDelegationManager} from "../../interfaces/IDelegationManager.sol";
import {IEigenStrategy} from "../../interfaces/IEigenStrategy.sol";
import {ILido} from "../../interfaces/ILido.sol";
import {ILidoWithdrawalQueue} from "../../interfaces/ILidoWithdrawalQueue.sol";
contract EigenLSTRestaking is EigenStrategy {
using EnumerableSet for EnumerableSet.Bytes32Set;
address public immutable tokenAddr;
address public immutable strategyManager;
address public immutable delegationManager;
address public immutable eigenStrategy;
address public immutable LidoWithdrawalQueue;
address payable public immutable SWAPPING;
address public referral;
address public eigenOperator;
bool public buyOnDex;
bool public sellOnDex;
bytes32[] public withdrawals;
mapping(bytes32 => uint256) public withdrawingShares;
EnumerableSet.Bytes32Set private withdrawalRootsSet;
event Swap(address from, address to, uint256 sent, uint256 received);
event DepositIntoStrategy(address strategy, address token, uint256 amount);
event DelegateTo(address operator);
event WithdrawalQueued(bytes32 withdrawalRoot);
event WithdrawalCompleted(
IDelegationManager.Withdrawal withdrawal,
bytes32 root
);
event SetEigenOperator(address oldOperator, address newOperator);
event SetWithdrawQueueParams(uint256 length, uint256 amount);
event SetRouter(bool buyOnDex, bool sellOnDex);
event SetReferral(address oldAddr, address newAddr);
event Invoked(address indexed targetAddress, uint256 value, bytes data);
constructor(
address payable _controller,
address _tokenAddr,
address _lidoWithdrawalQueue,
address _strategyManager,
address _delegationManager,
address _eigenStrategy,
address payable _swap,
string memory _name
) EigenStrategy(_controller, _name) {
require(
_tokenAddr != address(0) &&
_lidoWithdrawalQueue != address(0) &&
_strategyManager != address(0) &&
_delegationManager != address(0) &&
_eigenStrategy != address(0) &&
_swap != address(0),
"ZERO ADDRESS"
);
tokenAddr = _tokenAddr;
LidoWithdrawalQueue = _lidoWithdrawalQueue;
strategyManager = _strategyManager;
delegationManager = _delegationManager;
eigenStrategy = _eigenStrategy;
SWAPPING = _swap;
}
function deposit() public payable override onlyController notAtSameBlock {
require(msg.value != 0, "zero value");
latestUpdateTime = block.timestamp;
}
function withdraw(
uint256 _amount
)
public
override
onlyController
notAtSameBlock
returns (uint256 actualAmount)
{
actualAmount = _withdraw(_amount);
}
function instantWithdraw(
uint256 _amount
) public override onlyController returns (uint256 actualAmount) {
actualAmount = _withdraw(_amount);
}
function _withdraw(
uint256 _amount
) internal returns (uint256 actualAmount) {
require(_amount != 0, "zero value");
require(_amount <= address(this).balance, "not enough");
actualAmount = _amount;
TransferHelper.safeTransferETH(controller, actualAmount);
latestUpdateTime = block.timestamp;
}
function clear() public override onlyController returns (uint256 amount) {
uint256 balance = address(this).balance;
if (balance != 0) {
TransferHelper.safeTransferETH(controller, balance);
amount = balance;
}
}
function getAllValue() public override returns (uint256 value) {
value = getInvestedValue();
}
// TODO need testing
function getInvestedValue() public override returns (uint256 value) {
uint256 etherValue = address(this).balance;
uint256 tokenValue = IERC20(tokenAddr).balanceOf(address(this));
(, uint256 claimableValue, uint256 pendingValue) = checkPendingAssets();
uint256 eigenValue = getRestakingValue();
uint256 unstakingValue = getUnstakingValue();
value =
etherValue +
tokenValue +
claimableValue +
pendingValue +
eigenValue +
unstakingValue;
}
function depositIntoStrategy(
uint256 _amount
) external onlyOwner returns (uint256 shares) {
TransferHelper.safeApprove(tokenAddr, strategyManager, _amount);
shares = IStrategyManager(strategyManager).depositIntoStrategy(
eigenStrategy,
tokenAddr,
_amount
);
emit DepositIntoStrategy(eigenStrategy, tokenAddr, _amount);
}
function delegateTo(
IDelegationManager.SignatureWithExpiry
memory _approverSignatureAndExpiry,
bytes32 _approverSalt
) external onlyOwner {
IDelegationManager(delegationManager).delegateTo(
eigenOperator,
_approverSignatureAndExpiry,
_approverSalt
);
emit DelegateTo(eigenOperator);
}
function undelegate()
external
onlyOwner
returns (bytes32[] memory withdrawalRoots)
{
require(
IEigenStrategy(eigenStrategy).shares(address(this)) == 0,
"active shares"
);
withdrawalRoots = IDelegationManager(delegationManager).undelegate(
address(this)
);
}
function queueWithdrawals(
IDelegationManager.QueuedWithdrawalParams[]
calldata _queuedWithdrawalParams
) external onlyOwner returns (bytes32[] memory withdrawalRoots) {
require(
_queuedWithdrawalParams.length == 1 &&
_queuedWithdrawalParams[0].shares.length == 1 &&
_queuedWithdrawalParams[0].strategies.length == 1,
"invalid length"
);
withdrawalRoots = IDelegationManager(delegationManager)
.queueWithdrawals(_queuedWithdrawalParams);
uint256 length = withdrawalRoots.length;
require(length == 1, "invalid root length");
bytes32 root = withdrawalRoots[0];
withdrawalRootsSet.add(root);
withdrawingShares[root] = _queuedWithdrawalParams[0].shares[0];
emit WithdrawalQueued(root);
}
function completeQueuedWithdrawal(
IDelegationManager.Withdrawal calldata _withdrawal,
IERC20[] calldata _tokens,
uint256 _middlewareTimesIndex,
bool _receiveAsTokens
) external onlyOwner {
IDelegationManager(delegationManager).completeQueuedWithdrawal(
_withdrawal,
_tokens,
_middlewareTimesIndex,
_receiveAsTokens
);
bytes32 root = calculateWithdrawalRoot(_withdrawal);
withdrawingShares[root] = 0;
withdrawalRootsSet.remove(root);
emit WithdrawalCompleted(_withdrawal, root);
}
function swapToToken(
uint256 _amount
) external onlyOwner returns (uint256 tokenAmount) {
require(_amount != 0, "zero");
require(_amount <= address(this).balance, "exceed balance");
if (!buyOnDex) {
tokenAmount = ILido(tokenAddr).submit{value: _amount}(referral);
} else {
tokenAmount = SwappingAggregator(SWAPPING).swap{value: _amount}(
tokenAddr,
_amount,
false
);
}
emit Swap(address(0), tokenAddr, _amount, tokenAmount);
}
function swapToEther(
uint256 _amount
) external onlyOwner returns (uint256 etherAmount) {
IERC20 token = IERC20(tokenAddr);
require(_amount != 0, "zero");
require(_amount <= token.balanceOf(address(this)), "exceed balance");
if (!sellOnDex) {
token.approve(LidoWithdrawalQueue, _amount);
ILidoWithdrawalQueue withdrawalQueue = ILidoWithdrawalQueue(
LidoWithdrawalQueue
);
uint256 maxAmountPerRequest = withdrawalQueue
.MAX_STETH_WITHDRAWAL_AMOUNT();
uint256 minAmountPerRequest = withdrawalQueue
.MIN_STETH_WITHDRAWAL_AMOUNT();
uint256[] memory amounts;
if (_amount <= maxAmountPerRequest) {
amounts = new uint256[](1);
amounts[0] = _amount;
} else {
uint256 length = _amount / maxAmountPerRequest + 1;
uint256 remainder = _amount % maxAmountPerRequest;
if (remainder >= minAmountPerRequest) {
amounts = new uint256[](length);
amounts[length - 1] = remainder;
} else {
amounts = new uint256[](length - 1);
}
uint256 i;
for (i; i < length - 1; i++) {
amounts[i] = maxAmountPerRequest;
}
}
uint256[] memory ids = withdrawalQueue.requestWithdrawals(
amounts,
address(this)
);
require(ids.length != 0, "Lido request withdrawal error");
etherAmount = _amount;
} else {
TransferHelper.safeApprove(tokenAddr, SWAPPING, _amount);
etherAmount = SwappingAggregator(SWAPPING).swap(
tokenAddr,
_amount,
true
);
}
emit Swap(tokenAddr, address(0), _amount, etherAmount);
}
function setRouter(bool _buyOnDex, bool _sellOnDex) external onlyOwner {
buyOnDex = _buyOnDex;
sellOnDex = _sellOnDex;
emit SetRouter(_buyOnDex, _sellOnDex);
}
function setEigenOperator(address _operator) external onlyOwner {
require(_operator != address(0), "ZERO ADDR");
require(
IDelegationManager(delegationManager).isOperator(_operator),
"not operator"
);
emit SetEigenOperator(eigenOperator, _operator);
eigenOperator = _operator;
}
function setReferral(address _referral) external onlyOwner {
require(_referral != address(0), "ZERO ADDR");
emit SetReferral(referral, _referral);
referral = _referral;
}
function claimPendingAssets(uint256[] memory _ids) external onlyOwner {
uint256 length = _ids.length;
require(length != 0, "invalid length");
for (uint256 i; i < length; i++) {
if (_ids[i] == 0) continue;
ILidoWithdrawalQueue(LidoWithdrawalQueue).claimWithdrawal(_ids[i]);
}
}
function claimAllPendingAssets() external onlyOwner {
(uint256[] memory ids, , ) = checkPendingAssets();
uint256 length = ids.length;
for (uint256 i; i < length; i++) {
if (ids[i] == 0) continue;
ILidoWithdrawalQueue(LidoWithdrawalQueue).claimWithdrawal(ids[i]);
}
}
// TODO Gas consumption
function checkPendingAssets()
public
returns (
uint256[] memory ids,
uint256 totalClaimable,
uint256 totalPending
)
{
ILidoWithdrawalQueue queue = ILidoWithdrawalQueue(LidoWithdrawalQueue);
uint256[] memory allIds = queue.getWithdrawalRequests(address(this));
if (allIds.length == 0) {
return (new uint256[](0), 0, 0);
}
ids = new uint256[](allIds.length);
ILidoWithdrawalQueue.WithdrawalRequestStatus[] memory statuses = queue
.getWithdrawalStatus(allIds);
uint256 j;
uint256 length = statuses.length;
for (uint256 i; i < length; i++) {
ILidoWithdrawalQueue.WithdrawalRequestStatus
memory status = statuses[i];
if (status.isClaimed) {
continue;
}
if (status.isFinalized) {
ids[j++] = allIds[i];
totalClaimable = totalClaimable + status.amountOfStETH;
} else {
totalPending = totalPending + status.amountOfStETH;
}
}
assembly {
mstore(ids, j)
}
}
function getRestakingValue() public view returns (uint256 value) {
value = IEigenStrategy(eigenStrategy).userUnderlyingView(address(this));
}
function getUnstakingValue() public view returns (uint256 value) {
uint256 length = withdrawalRootsSet.length();
uint256 i;
for (i; i < length; i++) {
value += IEigenStrategy(eigenStrategy).sharesToUnderlyingView(
withdrawingShares[withdrawalRootsSet.at(i)]
);
}
}
function getWithdrawalRoots() public view returns (bytes32[] memory roots) {
uint256 length = withdrawalRootsSet.length();
roots = new bytes32[](length);
for (uint256 i; i < length; i++) {
roots[i] = withdrawalRootsSet.at(i);
}
}
function calculateWithdrawalRoot(
IDelegationManager.Withdrawal memory withdrawal
) public pure returns (bytes32) {
return keccak256(abi.encode(withdrawal));
}
function invoke(
address target,
bytes memory data
) external onlyOwner returns (bytes memory result) {
require(target != tokenAddr, "not permit");
bool success;
(success, result) = target.call{value: 0}(data);
if (!success) {
// solhint-disable-next-line no-inline-assembly
assembly {
returndatacopy(0, 0, returndatasize())
revert(0, returndatasize())
}
}
emit Invoked(target, 0, data);
}
receive() external payable {}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import {ISwapRouter} from "@uniswap/v3-periphery/contracts/interfaces/ISwapRouter.sol";
import {IQuoter} from "@uniswap/v3-periphery/contracts/interfaces/IQuoter.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IStableSwap} from "../interfaces/IStableSwap.sol";
import {IWETH9} from "../interfaces/IWETH9.sol";
contract SwappingAggregator is ReentrancyGuard {
uint256 internal constant MULTIPLIER = 1e18;
uint256 internal constant ONE_HUNDRED_PERCENT = 1e6;
address internal immutable QUOTER =
0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6;
address internal immutable ROUTER =
0xE592427A0AEce92De3Edee1F18E0157C05861564;
address internal immutable WETH9;
address internal immutable CURVE_ETH =
0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
mapping(address => address) public uniV3Pools; // token => pool
mapping(address => address) public curvePools;
mapping(address => uint8) public curvePoolType; // 0 - stableswap; 1 - two crypto swap
mapping(address => uint256) public slippage;
mapping(address => uint24) public fees;
address public governance;
event SetSlippage(
address indexed token,
uint256 oldSlippage,
uint256 newSlippage
);
event TransferGovernance(address oldAddr, address newAddr);
modifier onlyGovernance() {
require(governance == msg.sender, "not governace");
_;
}
enum DEX_TYPE {
UNISWAPV3,
CURVE
}
constructor(
address _wETH,
address[] memory _tokens,
address[] memory _uniPools,
address[] memory _curvePools,
uint8[] memory _curvePoolTypes,
uint256[] memory _slippages,
uint24[] memory _fees
) {
uint256 length = _tokens.length;
require(
length == _uniPools.length &&
length == _curvePools.length &&
length == _curvePoolTypes.length,
"invalid length"
);
require(_wETH != address(0), "ZERO ADDRESS");
for (uint256 i; i < length; i++) {
require(
_tokens[i] != address(0) &&
_uniPools[i] != address(0) &&
_curvePools[i] != address(0),
"ZERO ADDRESS"
);
uniV3Pools[_tokens[i]] = _uniPools[i];
curvePools[_tokens[i]] = _curvePools[i];
curvePoolType[_curvePools[i]] = _curvePoolTypes[i];
slippage[_tokens[i]] = _slippages[i];
fees[_tokens[i]] = _fees[i];
}
governance = msg.sender;
WETH9 = _wETH;
}
function swap(
address _token,
uint256 _amount,
bool _isSell
) external payable returns (uint256 amount) {
if (
uniV3Pools[_token] == address(0) && curvePools[_token] == address(0)
) {
return 0;
}
(DEX_TYPE dex, uint256 expected) = getBestRouter(
_token,
_amount,
_isSell
);
if (!_isSell) {
require(_amount == msg.value, "wrong value");
}
if (expected == 0) {
return 0;
}
if (dex == DEX_TYPE.UNISWAPV3) {
amount = swapOnUniV3(_token, _amount, _isSell);
} else {
amount = swapOnCurve(_token, _amount, _isSell);
}
}
function swapOnUniV3(
address _token,
uint256 _amount,
bool _isSell
) internal nonReentrant returns (uint256 amount) {
uint256 minReceived = calMinimumReceivedAmount(
_amount,
slippage[_token]
);
address pool = uniV3Pools[_token];
if (_isSell) {
TransferHelper.safeTransferFrom(
_token,
msg.sender,
address(this),
_amount
);
TransferHelper.safeApprove(_token, ROUTER, _amount);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: _token,
tokenOut: WETH9,
fee: fees[_token],
recipient: address(this),
deadline: block.timestamp,
amountIn: _amount,
amountOutMinimum: minReceived,
sqrtPriceLimitX96: 0
});
amount = ISwapRouter(ROUTER).exactInputSingle(params);
IWETH9(WETH9).withdraw(amount);
TransferHelper.safeTransferETH(msg.sender, address(this).balance);
} else {
IWETH9(WETH9).deposit{value: msg.value}();
TransferHelper.safeApprove(
WETH9,
ROUTER,
IWETH9(WETH9).balanceOf(address(this))
);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter
.ExactInputSingleParams({
tokenIn: WETH9,
tokenOut: _token,
fee: fees[_token],
recipient: address(this),
deadline: block.timestamp,
amountIn: _amount,
amountOutMinimum: minReceived,
sqrtPriceLimitX96: 0
});
amount = ISwapRouter(ROUTER).exactInputSingle(params);
TransferHelper.safeTransfer(
_token,
msg.sender,
IERC20(_token).balanceOf(address(this))
);
}
}
function swapOnCurve(
address _token,
uint256 _amount,
bool _isSell
) internal nonReentrant returns (uint256 amount) {
uint256 minReceived = calMinimumReceivedAmount(
_amount,
slippage[_token]
);
address pool = curvePools[_token];
(uint256 e, uint256 t, bool wrapped) = getCurveCoinIndex(_token);
uint8 poolType = curvePoolType[pool];
if (_isSell) {
TransferHelper.safeTransferFrom(
_token,
msg.sender,
address(this),
_amount
);
TransferHelper.safeApprove(_token, pool, _amount);
if (poolType == 0) {
amount = IStableSwap(pool).exchange(
int128(int256(t)),
int128(int256(e)),
_amount,
minReceived
);
} else {
amount = IStableSwap(pool).exchange(t, e, _amount, minReceived);
}
if (wrapped) {
IWETH9 weth = IWETH9(WETH9);
weth.withdraw(weth.balanceOf(address(this)));
}
TransferHelper.safeTransferETH(msg.sender, address(this).balance);
} else {
if (poolType == 0) {
if (!wrapped) {
amount = IStableSwap(pool).exchange{value: _amount}(
int128(int256(e)),
int128(int256(t)),
_amount,
minReceived
);
} else {
IWETH9 weth = IWETH9(WETH9);
weth.deposit{value: _amount}();
weth.approve(pool, _amount);
amount = IStableSwap(pool).exchange(
int128(int256(e)),
int128(int256(t)),
_amount,
minReceived
);
}
} else {
if (!wrapped) {
amount = IStableSwap(pool).exchange{value: _amount}(
e,
t,
_amount,
minReceived
);
} else {
IWETH9 weth = IWETH9(WETH9);
weth.deposit{value: _amount}();
weth.approve(pool, _amount);
amount = IStableSwap(pool).exchange(
e,
t,
_amount,
minReceived
);
}
}
TransferHelper.safeTransfer(
_token,
msg.sender,
IERC20(_token).balanceOf(address(this))
);
}
}
function getBestRouter(
address _token,
uint256 _amount,
bool _isSell
) public returns (DEX_TYPE dex, uint256 out) {
uint256 uniV3Out = getUniV3Out(_token, _amount, _isSell);
uint256 curveOut = getCurveOut(_token, _amount, _isSell);
return
uniV3Out > curveOut
? (DEX_TYPE.UNISWAPV3, uniV3Out)
: (DEX_TYPE.CURVE, curveOut);
}
function getUniV3Out(
address _token,
uint256 _amount,
bool _isSell
) public returns (uint256 out) {
if (uniV3Pools[_token] == address(0)) {
return 0;
}
if (_isSell) {
out = IQuoter(QUOTER).quoteExactInputSingle(
_token,
WETH9,
fees[_token],
_amount,
0
);
} else {
out = IQuoter(QUOTER).quoteExactInputSingle(
WETH9,
_token,
fees[_token],
_amount,
0
);
}
}
function getCurveOut(
address _token,
uint256 _amount,
bool _isSell
) public returns (uint256 out) {
address poolAddr = curvePools[_token];
if (poolAddr == address(0)) {
return 0;
}
(uint256 e, uint256 t, ) = getCurveCoinIndex(_token);
IStableSwap pool = IStableSwap(poolAddr);
uint8 poolType = curvePoolType[poolAddr];
if (_isSell) {
if (poolType == 0) {
out = pool.get_dy(
int128(int256(t)),
int128(int256(e)),
_amount
);
} else {
out = pool.get_dy(t, e, _amount);
}
} else {
if (poolType == 0) {
out = pool.get_dy(
int128(int256(e)),
int128(int256(t)),
_amount
);
} else {
out = pool.get_dy(e, t, _amount);
}
}
}
function getCurveCoinIndex(
address _token
) public view returns (uint256 i, uint256 j, bool wrapped) {
// i for Ether, j for another token
IStableSwap pool = IStableSwap(curvePools[_token]);
if (pool.coins(0) == WETH9 || pool.coins(1) == WETH9) {
wrapped = true;
}
if (pool.coins(0) == CURVE_ETH || pool.coins(0) == WETH9) {
i = 0;
j = 1;
} else {
i = 1;
j = 0;
}
}
function calMinimumReceivedAmount(
uint256 _amount,
uint256 _slippage
) internal view returns (uint256 amount) {
amount = (_amount * _slippage) / ONE_HUNDRED_PERCENT;
}
function setSlippage(
address _token,
uint256 _slippage
) external onlyGovernance {
emit SetSlippage(_token, slippage[_token], _slippage);
slippage[_token] = _slippage;
}
function setUniRouter(
address _token,
address _uniPool,
uint256 _slippage,
uint24 _fee
) external onlyGovernance {
require(_token != address(0), "ZERO ADDRESS");
uniV3Pools[_token] = _uniPool;
slippage[_token] = _slippage;
fees[_token] = _fee;
}
function setCurveRouter(
address _token,
address _curvePool,
uint8 _curvePoolType,
uint256 _slippage
) external onlyGovernance {
require(_token != address(0), "ZERO ADDRESS");
curvePools[_token] = _curvePool;
curvePoolType[_curvePool] = _curvePoolType;
slippage[_token] = _slippage;
}
function setNewGovernance(address _governance) external onlyGovernance {
emit TransferGovernance(governance, _governance);
governance = _governance;
}
receive() external payable {}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
import {Strategy} from "./Strategy.sol";
import {AssetsVault} from "../AssetsVault.sol";
contract StrategyController {
using EnumerableSet for EnumerableSet.AddressSet;
uint256 internal constant ONE_HUNDRED_PERCENT = 1e6;
address public stoneVault;
address payable public immutable assetsVault;
EnumerableSet.AddressSet private strategies;
mapping(address => uint256) public ratios;
struct StrategyDiff {
address strategy;
bool isDeposit;
uint256 amount;
}
modifier onlyVault() {
require(stoneVault == msg.sender, "not vault");
_;
}
constructor(
address payable _assetsVault,
address[] memory _strategies,
uint256[] memory _ratios
) {
require(_assetsVault != address(0), "ZERO ADDRESS");
uint256 length = _strategies.length;
for (uint256 i; i < length; i++) {
require(_strategies[i] != address(0), "ZERO ADDRESS");
}
stoneVault = msg.sender;
assetsVault = _assetsVault;
_initStrategies(_strategies, _ratios);
}
function forceWithdraw(
uint256 _amount
) external onlyVault returns (uint256 actualAmount) {
uint256 balanceBeforeRepay = address(this).balance;
if (balanceBeforeRepay >= _amount) {
_repayToVault();
actualAmount = _amount;
} else {
actualAmount =
_forceWithdraw(_amount - balanceBeforeRepay) +
balanceBeforeRepay;
}
}
function setStrategies(
address[] memory _strategies,
uint256[] memory _ratios
) external onlyVault {
_setStrategies(_strategies, _ratios);
}
function addStrategy(address _strategy) external onlyVault {
require(!strategies.contains(_strategy), "already exist");
strategies.add(_strategy);
}
function rebaseStrategies(
uint256 _in,
uint256 _out
) external payable onlyVault {
_rebase(_in, _out);
}
function destroyStrategy(address _strategy) external onlyVault {
_destoryStrategy(_strategy);
}
function _rebase(uint256 _in, uint256 _out) internal {
require(_in == 0 || _out == 0, "only deposit or withdraw");
if (_in != 0) {
AssetsVault(assetsVault).withdraw(address(this), _in);
}
uint256 total = getAllStrategyValidValue();
if (total < _out) {
total = 0;
} else {
total = total + _in - _out;
}
uint256 length = strategies.length();
StrategyDiff[] memory diffs = new StrategyDiff[](length);
uint256 head;
uint256 tail = length - 1;
for (uint i; i < length; i++) {
address strategy = strategies.at(i);
if (ratios[strategy] == 0) {
_clearStrategy(strategy, true);
continue;
}
uint256 newPosition = (total * ratios[strategy]) /
ONE_HUNDRED_PERCENT;
uint256 position = getStrategyValidValue(strategy);
if (newPosition < position) {
diffs[head] = StrategyDiff(
strategy,
false,
position - newPosition
);
head++;
} else if (newPosition > position) {
diffs[tail] = StrategyDiff(
strategy,
true,
newPosition - position
);
if (tail != 0) {
tail--;
}
}
}
length = diffs.length;
for (uint256 i; i < length; i++) {
StrategyDiff memory diff = diffs[i];
if (diff.amount == 0) {
continue;
}
if (diff.isDeposit) {
if (address(this).balance < diff.amount) {
diff.amount = address(this).balance;
}
_depositToStrategy(diff.strategy, diff.amount);
} else {
_withdrawFromStrategy(diff.strategy, diff.amount);
}
}
_repayToVault();
}
function _repayToVault() internal {
if (address(this).balance != 0) {
TransferHelper.safeTransferETH(assetsVault, address(this).balance);
}
}
function _depositToStrategy(address _strategy, uint256 _amount) internal {
Strategy(_strategy).deposit{value: _amount}();
}
function _withdrawFromStrategy(
address _strategy,
uint256 _amount
) internal {
Strategy(_strategy).withdraw(_amount);
}
function _forceWithdraw(
uint256 _amount
) internal returns (uint256 actualAmount) {
uint256 length = strategies.length();
uint256 allRatios;
for (uint i; i < length; i++) {
address strategy = strategies.at(i);
allRatios += ratios[strategy];
}
for (uint i; i < length; i++) {
address strategy = strategies.at(i);
uint256 withAmount = (_amount * ratios[strategy]) / allRatios;
if (withAmount != 0) {
actualAmount =
Strategy(strategy).instantWithdraw(withAmount) +
actualAmount;
}
}
_repayToVault();
}
function getStrategyValue(
address _strategy
) public returns (uint256 _value) {
return Strategy(_strategy).getAllValue();
}
function getStrategyValidValue(
address _strategy
) public returns (uint256 _value) {
return Strategy(_strategy).getInvestedValue();
}
function getStrategyPendingValue(
address _strategy
) public returns (uint256 _value) {
return Strategy(_strategy).getPendingValue();
}
function getAllStrategiesValue() public returns (uint256 _value) {
uint256 length = strategies.length();
for (uint i; i < length; i++) {
_value = _value + getStrategyValue(strategies.at(i));
}
}
function getAllStrategyValidValue() public returns (uint256 _value) {
uint256 length = strategies.length();
for (uint i; i < length; i++) {
_value = _value + getStrategyValidValue(strategies.at(i));
}
}
function getAllStrategyPendingValue() public returns (uint256 _value) {
uint256 length = strategies.length();
for (uint i; i < length; i++) {
_value = _value + getStrategyPendingValue(strategies.at(i));
}
}
function getStrategies()
public
view
returns (address[] memory addrs, uint256[] memory portions)
{
uint256 length = strategies.length();
addrs = new address[](length);
portions = new uint256[](length);
for (uint256 i; i < length; i++) {
address addr = strategies.at(i);
addrs[i] = addr;
portions[i] = ratios[addr];
}
}
function _initStrategies(
address[] memory _strategies,
uint256[] memory _ratios
) internal {
require(_strategies.length == _ratios.length, "invalid length");
uint256 totalRatio;
uint256 length = _strategies.length;
for (uint i; i < length; i++) {
strategies.add(_strategies[i]);
ratios[_strategies[i]] = _ratios[i];
totalRatio = totalRatio + _ratios[i];
}
require(totalRatio <= ONE_HUNDRED_PERCENT, "exceed 100%");
}
function _setStrategies(
address[] memory _strategies,
uint256[] memory _ratios
) internal {
uint256 length = _strategies.length;
require(length == _ratios.length, "invalid length");
uint256 oldLength = strategies.length();
for (uint i; i < oldLength; i++) {
ratios[strategies.at(i)] = 0;
}
uint256 totalRatio;
for (uint i; i < length; i++) {
require(
Strategy(_strategies[i]).controller() == address(this),
"controller mismatch"
);
strategies.add(_strategies[i]);
ratios[_strategies[i]] = _ratios[i];
totalRatio = totalRatio + _ratios[i];
}
require(totalRatio <= ONE_HUNDRED_PERCENT, "exceed 100%");
}
function clearStrategy(address _strategy) public onlyVault {
_clearStrategy(_strategy, false);
}
function _clearStrategy(address _strategy, bool _isRebase) internal {
Strategy(_strategy).clear();
if (!_isRebase) {
_repayToVault();
}
}
function _destoryStrategy(address _strategy) internal {
require(_couldDestroyStrategy(_strategy), "still active");
strategies.remove(_strategy);
_repayToVault();
}
function _couldDestroyStrategy(
address _strategy
) internal returns (bool status) {
return
ratios[_strategy] == 0 && Strategy(_strategy).getAllValue() < 1e4;
}
function setNewVault(address _vault) external onlyVault {
stoneVault = _vault;
}
receive() external payable {}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {StrategyController} from "../strategies/StrategyController.sol";
abstract contract Strategy {
address payable public immutable controller;
address public governance;
uint256 public latestUpdateTime;
uint256 public bufferTime = 12;
string public name;
modifier onlyGovernance() {
require(governance == msg.sender, "not governace");
_;
}
modifier notAtSameBlock() {
require(
latestUpdateTime + bufferTime <= block.timestamp,
"at the same block"
);
_;
}
event TransferGovernance(address oldOwner, address newOwner);
constructor(address payable _controller, string memory _name) {
require(_controller != address(0), "ZERO ADDRESS");
governance = msg.sender;
controller = _controller;
name = _name;
}
modifier onlyController() {
require(controller == msg.sender, "not controller");
_;
}
function deposit() public payable virtual onlyController notAtSameBlock {}
function withdraw(
uint256 _amount
)
public
virtual
onlyController
notAtSameBlock
returns (uint256 actualAmount)
{}
function instantWithdraw(
uint256 _amount
)
public
virtual
onlyController
notAtSameBlock
returns (uint256 actualAmount)
{}
function clear() public virtual onlyController returns (uint256 amount) {}
function execPendingRequest(
uint256 _amount
) public virtual returns (uint256 amount) {}
function getAllValue() public virtual returns (uint256 value) {}
function getPendingValue() public virtual returns (uint256 value) {}
function getInvestedValue() public virtual returns (uint256 value) {}
function checkPendingStatus()
public
virtual
returns (uint256 pending, uint256 executable)
{}
function setGovernance(address governance_) external onlyGovernance {
emit TransferGovernance(governance, governance_);
governance = governance_;
}
function setBufferTime(uint256 _time) external onlyGovernance {
bufferTime = _time;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {StrategyController} from "../strategies/StrategyController.sol";
abstract contract EigenStrategy is Ownable2Step {
address payable public immutable controller;
uint256 public latestUpdateTime;
uint256 public bufferTime = 12;
string public name;
modifier notAtSameBlock() {
require(
latestUpdateTime + bufferTime <= block.timestamp,
"at the same block"
);
_;
}
event TransferGovernance(address oldOwner, address newOwner);
constructor(address payable _controller, string memory _name) {
require(_controller != address(0), "ZERO ADDRESS");
controller = _controller;
name = _name;
}
modifier onlyController() {
require(controller == msg.sender, "not controller");
_;
}
function deposit() public payable virtual onlyController notAtSameBlock {}
function withdraw(
uint256 _amount
)
public
virtual
onlyController
notAtSameBlock
returns (uint256 actualAmount)
{}
function instantWithdraw(
uint256 _amount
)
public
virtual
onlyController
notAtSameBlock
returns (uint256 actualAmount)
{}
function clear() public virtual onlyController returns (uint256 amount) {}
function execPendingRequest(
uint256 _amount
) public virtual returns (uint256 amount) {}
function getAllValue() public virtual returns (uint256 value) {}
function getPendingValue() public virtual returns (uint256 value) {}
function getInvestedValue() public virtual returns (uint256 value) {}
function checkPendingStatus()
public
virtual
returns (uint256 pending, uint256 executable)
{}
function setBufferTime(uint256 _time) external onlyOwner {
bufferTime = _time;
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity 0.8.21;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/// @title Interface for WETH9
interface IWETH9 is IERC20 {
/// @notice Deposit ether to get wrapped ether
function deposit() external payable;
/// @notice Withdraw wrapped ether to get ether
function withdraw(uint256) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface IStrategyManager {
function depositIntoStrategy(
address strategy,
address token,
uint256 amount
) external returns (uint256 shares);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface IStableSwap {
function lp_token() external view returns (address);
function coins(uint256 i) external view returns (address);
function get_dy(
int128 i,
int128 j,
uint256 dx
) external view returns (uint256);
function get_dy(
uint256 i,
uint256 j,
uint256 dx
) external view returns (uint256);
function fee() external view returns (uint256);
function admin_fee() external view returns (uint256);
function calc_withdraw_one_coin(
uint256 _token_amount,
int128 i
) external view returns (uint256);
function calc_token_amount(
uint256[2] memory,
bool
) external view returns (uint256);
function remove_liquidity_imbalance(
uint256[2] memory _amounts,
uint256 _max_burn_amount
) external returns (uint256);
function remove_liquidity_one_coin(
uint256 _token_amount,
int128 i,
uint256 _min_amount
) external returns (uint256);
function add_liquidity(
uint256[2] memory amounts,
uint256 min_mint_amount
) external payable returns (uint256);
function exchange(
int128 i,
int128 j,
uint256 dx,
uint256 min_dy
) external payable returns (uint256);
function exchange(
uint256 i,
uint256 j,
uint256 dx,
uint256 min_dy
) external payable returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface ILidoWithdrawalQueue {
struct WithdrawalRequestStatus {
/// @notice stETH token amount that was locked on withdrawal queue for this request
uint256 amountOfStETH;
/// @notice amount of stETH shares locked on withdrawal queue for this request
uint256 amountOfShares;
/// @notice address that can claim or transfer this request
address owner;
/// @notice timestamp of when the request was created, in seconds
uint256 timestamp;
/// @notice true, if request is finalized
bool isFinalized;
/// @notice true, if request is claimed. Request is claimable if (isFinalized && !isClaimed)
bool isClaimed;
}
function MAX_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256);
function MIN_STETH_WITHDRAWAL_AMOUNT() external view returns (uint256);
function getWithdrawalRequests(
address _owner
) external view returns (uint256[] memory requestsIds);
function getWithdrawalStatus(
uint256[] calldata _requestIds
) external view returns (WithdrawalRequestStatus[] memory statuses);
function requestWithdrawals(
uint256[] calldata _amounts,
address _owner
) external returns (uint256[] memory requestIds);
function claimWithdrawals(
uint256[] calldata _requestIds,
uint256[] calldata _hints
) external;
function claimWithdrawalsTo(
uint256[] calldata _requestIds,
uint256[] calldata _hints,
address _recipient
) external;
function claimWithdrawal(uint256 _requestId) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface ILido {
function submit(address _referral) external payable returns (uint256);
function balanceOf(address account) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
interface IEigenStrategy {
function userUnderlyingView(address user) external view returns (uint256);
function shares(address user) external view returns (uint256);
function sharesToUnderlyingView(
uint256 amountShares
) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IDelegationManager {
struct SignatureWithExpiry {
bytes signature;
uint256 expiry;
}
struct QueuedWithdrawalParams {
address[] strategies;
uint256[] shares;
address withdrawer;
}
struct Withdrawal {
address staker;
address delegatedTo;
address withdrawer;
uint256 nonce;
uint32 startBlock;
address[] strategies;
uint256[] shares;
}
event WithdrawalQueued(bytes32 withdrawalRoot, Withdrawal withdrawal);
function delegateTo(
address operator,
SignatureWithExpiry memory approverSignatureAndExpiry,
bytes32 approverSalt
) external;
function undelegate(
address staker
) external returns (bytes32[] memory withdrawalRoots);
function queueWithdrawals(
QueuedWithdrawalParams[] calldata queuedWithdrawalParams
) external returns (bytes32[] memory);
function completeQueuedWithdrawal(
Withdrawal calldata withdrawal,
IERC20[] calldata tokens,
uint256 middlewareTimesIndex,
bool receiveAsTokens
) external;
function isOperator(address operator) external view returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;
import {TransferHelper} from "@uniswap/v3-periphery/contracts/libraries/TransferHelper.sol";
contract AssetsVault {
address public stoneVault;
address public strategyController;
modifier onlyPermit() {
require(
stoneVault == msg.sender || strategyController == msg.sender,
"not permit"
);
_;
}
constructor(address _stoneVault, address _strategyController) {
require(
_stoneVault != address(0) && _strategyController != address(0),
"ZERO ADDRESS"
);
stoneVault = _stoneVault;
strategyController = _strategyController;
}
function deposit() external payable {
require(msg.value != 0, "too small");
}
function withdraw(address _to, uint256 _amount) external onlyPermit {
TransferHelper.safeTransferETH(_to, _amount);
}
function setNewVault(address _vault) external onlyPermit {
stoneVault = _vault;
}
function getBalance() external view returns (uint256 amount) {
amount = address(this).balance;
}
receive() external payable {}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.6.0;
import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
library TransferHelper {
/// @notice Transfers tokens from the targeted address to the given destination
/// @notice Errors with 'STF' if transfer fails
/// @param token The contract address of the token to be transferred
/// @param from The originating address from which the tokens will be transferred
/// @param to The destination address of the transfer
/// @param value The amount to be transferred
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
}
/// @notice Transfers tokens from msg.sender to a recipient
/// @dev Errors with ST if transfer fails
/// @param token The contract address of the token which will be transferred
/// @param to The recipient of the transfer
/// @param value The value of the transfer
function safeTransfer(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
}
/// @notice Approves the stipulated contract to spend the given allowance in the given token
/// @dev Errors with 'SA' if transfer fails
/// @param token The contract address of the token to be approved
/// @param to The target of the approval
/// @param value The amount of the given token the target will be allowed to spend
function safeApprove(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
}
/// @notice Transfers ETH to the recipient address
/// @dev Fails with `STE`
/// @param to The destination of the transfer
/// @param value The value to be transferred
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'STE');
}
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;
import '@uniswap/v3-core/contracts/interfaces/callback/IUniswapV3SwapCallback.sol';
/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
struct ExactInputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another token
/// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
/// @return amountOut The amount of the received token
function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);
struct ExactInputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountIn;
uint256 amountOutMinimum;
}
/// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
/// @return amountOut The amount of the received token
function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);
struct ExactOutputSingleParams {
address tokenIn;
address tokenOut;
uint24 fee;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
uint160 sqrtPriceLimitX96;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another token
/// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
/// @return amountIn The amount of the input token
function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);
struct ExactOutputParams {
bytes path;
address recipient;
uint256 deadline;
uint256 amountOut;
uint256 amountInMaximum;
}
/// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
/// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
/// @return amountIn The amount of the input token
function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.7.5;
pragma abicoder v2;
/// @title Quoter Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoter {
/// @notice Returns the amount out received for a given exact input swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee
/// @param amountIn The amount of the first token to swap
/// @return amountOut The amount of the last token that would be received
function quoteExactInput(bytes memory path, uint256 amountIn) external returns (uint256 amountOut);
/// @notice Returns the amount out received for a given exact input but for a swap of a single pool
/// @param tokenIn The token being swapped in
/// @param tokenOut The token being swapped out
/// @param fee The fee of the token pool to consider for the pair
/// @param amountIn The desired input amount
/// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountOut The amount of `tokenOut` that would be received
function quoteExactInputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountIn,
uint160 sqrtPriceLimitX96
) external returns (uint256 amountOut);
/// @notice Returns the amount in required for a given exact output swap without executing the swap
/// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
/// @param amountOut The amount of the last token to receive
/// @return amountIn The amount of first token required to be paid
function quoteExactOutput(bytes memory path, uint256 amountOut) external returns (uint256 amountIn);
/// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
/// @param tokenIn The token being swapped in
/// @param tokenOut The token being swapped out
/// @param fee The fee of the token pool to consider for the pair
/// @param amountOut The desired output amount
/// @param sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
/// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
function quoteExactOutputSingle(
address tokenIn,
address tokenOut,
uint24 fee,
uint256 amountOut,
uint160 sqrtPriceLimitX96
) external returns (uint256 amountIn);
}
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.5.0;
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
/// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
/// @dev In the implementation you must pay the pool tokens owed for the swap.
/// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
/// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
/// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
/// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
/// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
/// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
function uniswapV3SwapCallback(
int256 amount0Delta,
int256 amount1Delta,
bytes calldata data
) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```solidity
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)
pragma solidity ^0.8.0;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant _NOT_ENTERED = 1;
uint256 private constant _ENTERED = 2;
uint256 private _status;
constructor() {
_status = _NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be _NOT_ENTERED
require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
// Any calls to nonReentrant after this point will fail
_status = _ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = _NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == _ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.0;
import "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner");
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}